
    rh4                       % S r SSKJr  SSKJr  SSKrSSKrSSKrSSK	J
r
  SSK	Jr  SSKJr  SSK	Jr  SS	K	Jr  SS
KJr  \R$                  (       a  SSKJr  SSK	Jr  SSS.rS\S'   S rS!S jr S"SS.       S#S jjjrSSSSSS.             S$S jjrS r  S%SSS.     S&S jjjrS'S jrS(S jrS r S)S jr!S r"\#S :X  a  SSK	r	\	RH                  " 5         gg)*a  
The manager module handles requests across multiple corpora.  It should be the default
interface to searching corpora.

New in v3 -- previously most were static methods on corpus.corpora.Corpus, but that
seemed inappropriate since these work across corpora.
    )annotations)IterableN)common)	converter)CorpusException)environment)metadata)corpora)bundles)stream)corelocalz(dict[str, bundles.MetadataBundle | None]_metadataBundlesc                    U S:X  a  [         R                  " 5       $ U S:X  a  [         R                  " 5       $ [         R                  " U S9$ )a  
Instantiate a specific corpus based on `name`:

>>> corpus.manager.fromName('core')
<music21.corpus.corpora.CoreCorpus>

>>> corpus.manager.fromName('local')
<music21.corpus.corpora.LocalCorpus: 'local'>

>>> corpus.manager.fromName(None)
<music21.corpus.corpora.LocalCorpus: 'local'>


Note that this corpus probably does not exist on disk, but it's ready to have
paths added to it and to be stored on disk.

>>> corpus.manager.fromName('testDummy')
<music21.corpus.corpora.LocalCorpus: 'testDummy'>
r   r   name)r
   
CoreCorpusLocalCorpusr   s    P/home/james-whalen/.local/lib/python3.13/site-packages/music21/corpus/manager.pyfromNamer   2   sC    . v~!!## 
""$$""--    c              #    #    U SL aA  [         R                  " 5       v   [        5        H  n[         R                  " U5      v   M     g[         R                  " 5       R                  v   [        5        H  nUc  Sv   M  Uv   M     g7f)a  
a generator that iterates over the corpora (either as objects or as names)
for use in pan corpus searching.

This test will only show the first two, because it needs to run the same
on every system:

>>> for i, corpusObject in enumerate(corpus.manager.iterateCorpora()):
...     print(corpusObject)
...     if i == 1:
...        break
<music21.corpus.corpora.CoreCorpus>
<music21.corpus.corpora.LocalCorpus: 'local'>

We can also get names instead.
(Note that the name of the main local corpus is `"local"` not `None`)

>>> for i, corpusName in enumerate(corpus.manager.iterateCorpora(returnObjects=False)):
...     print(corpusName)
...     if i == 1:
...        break
core
local

* New in v3.
TNr   )r
   r   listLocalCorporaNamesr   r   )returnObjectscns     r   iterateCorporar   S   so     6   ""')B%%b)) *   "'''')Bz	 *s   BB	 fileExtensionsc                  Sn[        U 5      nUnU (       d  [        S5      eUR                  S5      (       d  UR                  S5      (       a'  [        R                  R                  U5      S   S-   nSnSn[        5        HG  nUR                  U UUS	9nU(       d!  U(       a  UR                  UUUS	9nU(       d  M<  U(       d  ME  Un  O   Uc,  S
n	U(       a  U	S-  n	U	SU  S3-  n	U	S-  n	U	S-  n	[        U	5      e[        U5      S:X  a  [        R                  " US   5      $ U V
s/ s H  n
[        R                  " U
5      PM     sn
$ s  sn
f )z
this parse function is called from `corpus.parse()` and does nothing differently from it.

Searches all corpora for a file that matches the name and returns it parsed.
Fz+a work name must be provided as an argumentz.xmlz	.musicxmlr   z.mxlTNr   zCould not find azn xml, musicxml, or mxlz work that met this criterion: ;z* if you are searching for a file on disk, z$use "converter" instead of "corpus".   )strr   endswithospathsplitextr   getWorkListlenpathlibPath)workNamemovementNumberr   addXMLWarningworkNameJoinedmxlWorkName	filePathscorpusObjectworkListwarningMessageps              r   getWorkr6   }   sc    M]N KKLLv&&.*A*A+*N*Ngg&&~6q9FBI&(++H,:;I , K M#//0>?M 0 OH 8 I ) +77N;H:QGGFF@@n--
9~||IaL)))23AQ333s   ' E
F)r-   numberr   forceSourceformatc                   [        U UUS9n[        U[        5      (       a  US   nOUn[        R                  " UUUUS9n[        X5        U$ )N)r,   r-   r   r   )r8   r7   r9   )r6   
isinstancelistr   parse _addCorpusFilePathToStreamObject)	r,   r-   r7   r   r8   r9   r1   filePathstreamObjects	            r   r=   r=      s^     %%I
 )T""Q<??	L %\<r   c                   [        [        R                  " 5       5      n[        U5      [        [        R
                  5      -   n[        U5      nUR                  U5      (       aE  XS nUR                  [        R
                  5      nSR                  U5      nX`R                  l
        OXR                  l
        [        U R                  [        5      (       a  U R                  R                  U l        gg)z
Adds an entry 'corpusFilePpath' to the Stream object.

TODO: this should work for non-core-corpora
TODO: this should be in the metadata object
TODO: this should set a pathlib.Path object
N/)r#   r   getCorpusFilePathr)   r%   sep
startswithsplitjoinr	   corpusFilePathr;   idint)	streamObjr?   rH   lenCFPfp2dirsEtcfp3s          r   r>   r>      s     1134N 3rvv;.F8}H>**w))BFF#hhw,/),4)),,$$ ))88	 %r   )corpusNamesr   c               :   [        U[        5      (       a  U4n[        5         [        R                  R                  5       nUc  [        [        SS95      nU H?  n[        U5      nUR                  R                  " U U4SU0UD6nUR                  U5      nMA     U$ )a  
Search all stored metadata bundles and return a list of file paths.

This function uses stored metadata and thus, on first usage, will incur a
performance penalty during metadata loading.

>>> #_DOCS_SHOW corpus.search('china')
>>> corpus.search('china', corpusNames=('core',))  #_DOCS_HIDE
<music21.metadata.bundles.MetadataBundle {1235 entries}>

>>> #_DOCS_SHOW corpus.search('china', fileExtensions=('.mid',))
>>> corpus.search('china', fileExtensions=('.mid',), corpusNames=('core',))  #_DOCS_HIDE
<music21.metadata.bundles.MetadataBundle {0 entries}>

>>> #_DOCS_SHOW corpus.search('bach', field='composer')
>>> corpus.search('bach', field='composer', corpusNames=('core',))  #_DOCS_HIDE
<music21.metadata.bundles.MetadataBundle {363 entries}>

Note the importance of good metadata -- there's almost 400 pieces by
Bach in the corpus, but many do not have correct metadata entries.

This can also be specified as:

>>> #_DOCS_SHOW corpus.search(composer='bach')
>>> corpus.search(composer='bach', corpusNames=('core',))  #_DOCS_HIDE
<music21.metadata.bundles.MetadataBundle {363 entries}>

Or, to get all the chorales (without using `corpus.chorales.Iterator`):

>>> #_DOCS_SHOW corpus.search(sourcePath='bach', numberOfParts=4)
>>> corpus.search(sourcePath='bach', numberOfParts=4, corpusNames=('core',))  #_DOCS_HIDE
<music21.metadata.bundles.MetadataBundle {368 entries}>


This function is implemented in `corpus.manager` as a method there but also directly
available in the corpus module for ease of use.

The ``corpusNames`` parameter can be used to specify which corpora to search,
for example:

>>> corpus.manager.search(
...     'bach',
...     corpusNames=('core',),
...     )
<music21.metadata.bundles.MetadataBundle {564 entries}>

>>> corpus.manager.search(
...     'bach',
...     corpusNames=('core',),
...     fileExtensions=('xml',),
...     )
<music21.metadata.bundles.MetadataBundle {412 entries}>

If ``corpusNames`` is None, all corpora known to music21 will be searched.

See usersGuide (chapter 11) for more information on searching

F)r   r   )r;   r#   readAllMetadataBundlesFromDiskr	   r   MetadataBundler<   r   r   metadataBundlesearchunion)	queryfieldrP   r   keywordsallSearchResults
corpusNamecsearchResultss	            r   rU   rU      s    H .#&&(*"$''668>>?!
Z ((//
 *
 	
 ,11-@ " r   c                    [        U 5        U R                  nU[        ;   a%  [        U   n[        R                  (       a  Uc   eU$ [        SR                  X5      5      e)a  
Return the metadata bundle for a single Corpus object

>>> cc = corpus.corpora.CoreCorpus()
>>> mdb1 = corpus.manager.getMetadataBundleByCorpus(cc)
>>> mdb1
<music21.metadata.bundles.MetadataBundle 'core': {... entries}>

This is the same as calling `metadataBundle` on the corpus itself,
but this is the routine that actually does the work. In other words,
it's the call on the object that is redundant, not this routine.

>>> mdb1 is cc.metadataBundle
True

Non-existent corpus:

>>> lc = corpus.corpora.LocalCorpus('junk')
>>> mdb1 = corpus.manager.getMetadataBundleByCorpus(lc)
>>> mdb1
<music21.metadata.bundles.MetadataBundle 'junk': {0 entries}>
z5No metadata bundle found for corpus {0} with name {1})cacheMetadataBundleFromDiskr   r   tTYPE_CHECKINGr   r9   )r2   r[   mdbs      r   getMetadataBundleByCorpusrc   H  s^    .  -""J%%z*???"?
U\\& ' 	'r   c                    U R                   nU[        ;  d
  [        U   cI  [        R                  R	                  U5      nUR                  5         UR                  5         U[        U'   gg)zE
Update a corpus' metadata bundle from its stored JSON file on disk.
N)r   r   r	   r   rS   readvalidate)r2   r[   rT   s      r   r_   r_   k  sa     ""J**
+3!))88D!'5$	 4r   c                 <    [        5        H  n [        U 5        M     g)z<
Read each corpus's metadata bundle and store it in memory.
N)r   r_   )r2   s    r   rR   rR   w  s     '(#L1 )r   c                    [         R                  " 5       nU (       d  S/nO/ nUR                  US   R                  5       5        U$ )zk
List the names of all user-defined local corpora.

The entry for None refers to the default local corpus.
NlocalCorporaSettings)r   UserSettingsextendkeys)skipNoneuserSettingsresults      r   r   r     sA     ++-L
MM,56;;=>Mr   c                 R    [         R                  R                  R                  5       $ )a  
List all available search field names:

>>> for field in corpus.manager.listSearchFields():
...     field
...
'abstract'
'accessRights'
'accompanyingMaterialWriter'
...
'composer'
'composerAlias'
'composerCorporate'
'conceptor'
'conductor'
...
'dateCreated'
'dateFirstPublished'
'dateIssued'
'dateModified'
'dateSubmitted'
'dateValid'
...
'tempoFirst'
'tempos'
'textLanguage'
'textOriginalLanguage'
'timeSignatureFirst'
'timeSignatures'
'title'
...
)r	   r   rS   listSearchFieldsr   r   r   rq   rq     s     B **;;==r   __main__)T)N)r,   str | pathlib.Pathr-   
int | Noner   Iterable[str]returnz!pathlib.Path | list[pathlib.Path])r,   rs   r-   rt   r7   rt   r   ru   r8   boolr9   
str | Nonerv   z(stream.Score | stream.Part | stream.Opus)NN)rW   rx   rX   rx   r   zIterable[str] | str)r2   corpora.Corpusrv   zbundles.MetadataBundle)r2   ry   rv   None)F)%__doc__
__future__r   collections.abcr   r*   r%   typingr`   music21r   r   music21.exceptions21r   r   r	   music21.corpusr
   ra   music21.metadatar   r   r   __annotations__r   r   r6   r=   r>   rU   rc   r_   rR   r   rq   __name__mainTestr   r   r   <module>r      sk   # $  	    0   " ??( < 8 .B'X  $24 %'	242424 "	24
 %24t  $$&  	
 "   *:9< W (*WWW
 &Wt 'F
62!>L z r   