Package: hansel
hanselParametric file paths to access and build structured folder trees.It almost doesn’t haveDependencies, check how toInstallit.Github repository:https://github.com/alexsavio/hanselUsageQuick IntroImagine this folder tree:data
└── raw
    ├── 0040000
    │   └── session_1
    │       ├── anat_1
    │       └── rest_1
    ├── 0040001
    │   └── session_1
    │       ├── anat_1
    │       └── rest_1
    ├── 0040002
    │   └── session_1
    │       ├── anat_1
    │       └── rest_1
    ├── 0040003
    │   └── session_1
    │       ├── anat_1
    │       └── rest_1
    ├── 0040004
    │   └── session_1
    │       ├── anat_1
    │       └── rest_1>>>frompprintimportpprint>>>fromhanselimportCrumb# create the crumb>>>crumb=Crumb("{base_dir}/raw/{subject_id}/{session_id}/{image_type}/{image}")# set the base_dir path>>>crumb=crumb.replace(base_dir='/tmp/hansel/data')>>>print(str(crumb))/tmp/hansel/data/raw/{subject_id}/{session_id}/{image_type}/{image}# get the ids of the subjects>>>subj_ids=crumb['subject_id']>>>print(subj_ids)['0040000','0040001','0040002','0040003','0040004','0040005',...# get the paths to the subject folders, the output can be strings or crumbs,# you choose with the ``make_crumbs`` boolean argument. Default: True.>>>subj_paths=crumb.ls('subject_id',make_crumbs=True)>>>pprint(subj_paths)[Crumb("/tmp/hansel/data/raw/0040000/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/0040001/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/0040002/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/0040003/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/0040004/{session_id}/{image_type}/{image}"),...# set the image_type>>>anat_crumb=crumb.replace(image_type='anat_1')>>>print(anat_crumb)/tmp/hansel/data/raw/{subject_id}/{session_id}/anat_1/{image}# get the paths to the images inside the anat_1 folders>>>anat_paths=anat_crumb.ls('image')>>>pprint(anat_paths)[Crumb("/tmp/hansel/data/raw/0040000/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040001/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040002/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040003/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040004/session_1/anat_1/mprage.nii.gz"),...# get the ``session_id`` of each of these ``anat_paths``>>>sessions=[cr['session_id'][0]forcrinanat_paths]>>>print(sessions)['session_1','session_1','session_1','session_1','session_1',...# if you don't want the the output to be ``Crumbs`` but string paths:>>>anat_paths=anat_crumb.ls('image',make_crumbs=False)>>>pprint(anat_paths)['/tmp/hansel/data/raw/0040000/session_1/anat_1/mprage.nii.gz','/tmp/hansel/data/raw/0040001/session_1/anat_1/mprage.nii.gz','/tmp/hansel/data/raw/0040002/session_1/anat_1/mprage.nii.gz','/tmp/hansel/data/raw/0040003/session_1/anat_1/mprage.nii.gz','/tmp/hansel/data/raw/0040004/session_1/anat_1/mprage.nii.gz',...# you can also use a list of ``fnmatch`` expressions to ignore certain files patterns# using the ``ignore_list`` argument in the constructor.# For example, the files that start with '.'.>>>crumb=Crumb("{base_dir}/data/raw/{subject_id}/{session_id}/{image_type}/{image}",ignore_list=['.*'])Once you have a fully defined Crumb, you can use itspathfor operations with the corresponding file.
For that you have to convert it to string by usingstr(crumb)orcrumb.path.See more quick examples after theLong IntrocheckMore features and tricks.Long IntroI often find myself in a work related with structured folder paths, such as the
one shown above.I have tried many ways of solving these situations: loops, dictionaries,
configuration files, etc. I always end up doing a different thing for the same
problem over and over again.This week I grew tired of it and decided to make a representation of a
structured folder tree in a string and access it the most easy way.If you look at the folder structure above I have:the root directory from where it is hanging:...data/raw,many identifiers (in this case a subject identification), e.g.,0040000,session identification,session_1anda data type (in this case an image type),anat_1andrest_1.WithhanselI can represent this folder structure like this:>>>fromhanselimportCrumb>>>crumb=Crumb("{base_dir}/data/raw/{subject_id}/{session_id}/{image_type}/{image}")Let’s say we have the structure above hanging from a base directory like/home/hansel/.I can use thereplacefunction to make set thebase_dirparameter:>>>crumb=crumb.replace(base_dir='/home/hansel')>>>print(str(crumb))/home/hansel/data/raw/{subject_id}/{session_id}/{image_type}/{image}if I don’t need a copy ofcrumb, I can use the[]operator:>>>crumb['base_dir']='/tmp/hansel'>>>print(str(crumb))/tmp/hansel/data/raw/{subject_id}/{session_id}/{image_type}/{image}Now that the root path of my dataset is set, I can start querying my
crumb path.If I want to know the path to the existingsubject_idfolders:We can use thelsfunction. Its output can bestrorCrumb.
I can choose this using themake_crumbsargument (default: True):>>>subj_crumbs=crumb.ls('subject_id')>>>pprint(subj_crumbs)[Crumb("/tmp/hansel/data/raw/0040000/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/0040001/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/0040002/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/0040003/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/0040004/{session_id}/{image_type}/{image}"),...>>>subj_paths=crumb.ls('subject_id',make_crumbs=False)>>>pprint(subj_paths)['/tmp/hansel/data/raw/0040000/{session_id}/{image_type}/{image}','/tmp/hansel/data/raw/0040001/{session_id}/{image_type}/{image}','/tmp/hansel/data/raw/0040002/{session_id}/{image_type}/{image}','/tmp/hansel/data/raw/0040003/{session_id}/{image_type}/{image}','/tmp/hansel/data/raw/0040004/{session_id}/{image_type}/{image}',...If I want to know what are the existingsubject_id:>>>subj_ids=crumb.ls('subject_id',fullpath=False)>>>print(subj_ids)['0040000','0040001','0040002','0040003','0040004','0040005',...or>>>subj_ids=crumb['subject_id']>>>print(subj_ids)['0040000','0040001','0040002','0040003','0040004','0040005',...Now, if I wanted to get the path to all the images inside theanat_1folders,
I could do this:>>>anat_crumb=crumb.replace(image_type='anat_1')>>>print(anat_crumb)/tmp/hansel/data/raw/{subject_id}/{session_id}/anat_1/{image}or if I don’t need to keep a copy ofcrumb:>>>crumb['image_type']='anat_1'# get the paths to the images inside the anat_1 folders>>>anat_paths=crumb.ls('image')>>>pprint(anat_paths)[Crumb("/tmp/hansel/data/raw/0040000/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040001/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040002/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040003/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040004/session_1/anat_1/mprage.nii.gz"),...Remember that I can still access the replaced crumb arguments in each of the previous
crumbs inanat_paths.>>>subj_ids=[cr['subject_id'][0]forcrinanat_paths]>>>print(subj_ids)['0040000','0040001','0040002','0040003','0040004',...>>>files=[cr['image'][0]forcrinanat_paths]>>>print(files)['mprage.nii.gz','mprage.nii.gz','mprage.nii.gz','mprage.nii.gz',...More features and tricksThere are more possibilities such as:Creating folder treesUsemktreeandParameterGridto create a tree of folders.>>>fromhanselimportmktree>>>fromhansel.utilsimportParameterGrid>>>crumb=Crumb("/tmp/hansel/data/raw/{subject_id}/{session_id}/{image_type}/{image}")>>>session_ids=["session_{}".format(i)foriinrange(2)]>>>subject_ids=["subj_{}".format(i)foriinrange(3)]>>>values_map=dict(session_id=session_ids,subject_id=subject_ids)>>>crumbs=mktree(crumb,list(ParameterGrid(values_map)))>>>pprint(crumbs)[Crumb("/tmp/hansel/data/raw/subj_0/session_0/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/subj_1/session_0/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/subj_2/session_0/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/subj_0/session_1/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/subj_1/session_1/{image_type}/{image}"),Crumb("/tmp/hansel/data/raw/subj_2/session_1/{image_type}/{image}")]Check the feasibility of a crumb path>>>crumb=Crumb("/tmp/hansel/raw/{subject_id}/{session_id}/{image_type}/{image}")# ask if there is any subject with the image 'lollipop.png'.>>>crumb['image']='lollipop.png'>>>assertnotcrumb.exists()Check which subjects have ‘jujube.png’ and ‘toffee.png’ files>>>crumb=Crumb("/tmp/hansel/raw/{subject_id}/{session_id}/{image_type}/{image}")>>>toffee_crumb=crumb.replace(image='toffee.png')>>>jujube_crumb=crumb.replace(image='jujube.png')# using sets functionality>>>gluttons=set(toffee_crumb['subject_id']).intersection(set(jujube_crumb['subject_id'])# doctest: +SKIP>>>print(gluttons)# doctest: +SKIP['gretel','hansel']Use theintersectionfunctionUse it for comparisons on more than one crumb argument.
This can be used to compare datasets with the same structure in different folders.One argumentImagine that we have two working folders of subjects for two different projects:proj1andproj2.
If I want to check what subjects are common to both projects:>>>fromhanselimportintersection# using one argument>>>cr_proj1=Crumb("/tmp/hansel/data/proj1/{subject_id}/{session_id}/{image_type}/{image}")>>>cr_proj2=Crumb("/tmp/hansel/data/proj2/{subject_id}/{session_id}/{image_type}/{image}")# set the `on` argument in `intersection` to specify which crumb arguments to merge.>>>merged=intersection(cr_proj1,cr_proj2,on=['subject_id'])>>>pprint(merged)[(('subject_id','0040006'),),(('subject_id','0040007'),),(('subject_id','0040008'),),(('subject_id','0040009'),)]# I can pick these subject crumbs from this result using the `build_paths` function.>>>proj1_merged_paths=cr_proj1.build_paths(merged,make_crumbs=True)>>>type(proj1_merged_paths)<class'generator'>>>>pprint(list(proj1_merged_paths))[Crumb("/tmp/hansel/data/proj1/0040006/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/proj1/0040007/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/proj1/0040008/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/proj1/0040009/{session_id}/{image_type}/{image}")]>>>pprint(list(cr_proj2.build_paths(merged,make_crumbs=True)))[Crumb("/tmp/hansel/data/proj2/0040006/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/proj2/0040007/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/proj2/0040008/{session_id}/{image_type}/{image}"),Crumb("/tmp/hansel/data/proj2/0040009/{session_id}/{image_type}/{image}")]Two argumentsNow, imagine that I have different sets of{image}for these subjects.
I want to check which of those subjects have exactly the same images.
Let’s say that the subject0040000has aanatomical.nii.gzinstead ofmprage.nii.gz.>>>fromhanselimportintersection# using one argument>>>cr_proj3=Crumb("/tmp/hansel/data/proj3/{subject_id}/{session_id}/anat_1/{image}")>>>cr_proj4=Crumb("/tmp/hansel/data/proj4/{subject_id}/{session_id}/anat_1/{image}")# set the `on` argument in `intersection` to specify which crumb arguments to merge.>>>merged=intersection(cr_proj3,cr_proj4,on=['subject_id','image'])>>>pprint(merged)[(('subject_id','0040001'),('image','mprage.nii.gz')),(('subject_id','0040002'),('image','mprage.nii.gz')),(('subject_id','0040003'),('image','mprage.nii.gz')),(('subject_id','0040004'),('image','mprage.nii.gz')),...# I can pick these image crumbs from this result using the `build_paths` function.>>>pprint(list(cr_proj3.build_paths(merged,make_crumbs=True)))[Crumb("/tmp/hansel/data/proj3/0040001/{session_id}/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/proj3/0040002/{session_id}/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/proj3/0040003/{session_id}/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/proj3/0040004/{session_id}/anat_1/mprage.nii.gz"),...>>>pprint(list(cr_proj4.build_paths(merged,make_crumbs=True)))[Crumb("/tmp/hansel/data/proj4/0040001/{session_id}/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/proj4/0040002/{session_id}/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/proj4/0040003/{session_id}/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/proj4/0040004/{session_id}/anat_1/mprage.nii.gz"),...# adding 'mod' to the intersection would be:>>>common_values=intersection(cr_proj3,cr_proj4,on=['subject_id','session_id','image'])>>>pprint(common_values,width=120)[(('subject_id','0040001'),('session_id','session_1'),('image','mprage.nii.gz')),(('subject_id','0040002'),('session_id','session_1'),('image','mprage.nii.gz')),(('subject_id','0040003'),('session_id','session_1'),('image','mprage.nii.gz')),(('subject_id','0040004'),('session_id','session_1'),('image','mprage.nii.gz')),...TheunfoldandlsfunctionsUnfold the whole crumb path to get the whole file tree in a list of paths:>>>all_images=Crumb("/tmp/hansel/data/raw/{subject_id}/{session_id}/{image_type}/{image}")>>>all_images=all_images.unfold()>>>pprint(all_images)[Crumb("/tmp/hansel/data/raw/0040000/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040000/session_1/rest_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040001/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040001/session_1/rest_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040002/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040002/session_1/rest_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040003/session_1/anat_1/mprage.nii.gz"),...# and you can ask for the value of the crumb argument in each element>>>print(all_images[0]['subject_id'])['0040000']Note thatunfoldis the same as callinglsfunction without arguments.Use regular expressionsUsere.matchorfnmatchexpressions to filter the paths:The syntax for crumb arguments with a regular expression is:"{<arg_name>:<arg_regex>}"# only the session_1 folders>>>session1_cr=Crumb("/tmp/hansel/data/raw/{subject_id}/{session_id:*_1}/{image_type}/{image}")>>>session1_imgs=session1_cr.ls()>>>pprint(session1_imgs)[Crumb("/tmp/hansel/data/raw/0040000/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040000/session_1/rest_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040001/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040001/session_1/rest_1/mprage.nii.gz"),...The default is forfnmatchexpressions. If you prefer usingre.matchfor filtering,
set theregexargument to're'or're.ignorecase'in the constructor.# only the rest images from the subject ``040000``>>>s0_rest_cr=Crumb("/tmp/hansel/data/raw/{subject_id:.*00$}/{session_id}/{image_type:rest.*}/{image}",regex='re')>>>s0_rest_imgs=s0_rest_cr.ls()>>>print(s0_rest_imgs)[Crumb("/tmp/hansel/data/raw/0040000/session_1/rest_1/mprage.nii.gz")]The regular expressions can be checked with thepatternsproperty.>>>pprint(s0_rest_cr.patterns){'image_type':'rest.*','subject_id':'.*00$'}And can be also modified with theset_patternfunction.>>>s0_rest_cr.set_pattern('session_id','.*_1$')>>>pprint(s0_rest_cr.patterns){'image_type':'rest.*','session_id':'.*_1$','subject_id':'.*00$'}>>>s0_rest_cr.path'/tmp/hansel/data/raw/{subject_id:.*00$}/{session_id:.*_1$}/{image_type:rest.*}/{image}'A regular expression can be temporarily set with thelsfunction and the[]operator.>>>crumb=Crumb("/tmp/hansel/data/raw/{subject_id}/{session_id}/{image_type}/{image}")>>>mprage_crumb=crumb.ls('image:mprage.*')>>>pprint(mprage_crumb)[Crumb("/tmp/hansel/data/raw/0040000/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040000/session_1/rest_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040001/session_1/anat_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040001/session_1/rest_1/mprage.nii.gz"),Crumb("/tmp/hansel/data/raw/0040002/session_1/anat_1/mprage.nii.gz"),...>>>pprint(crumb['image:mprage.*'])['mprage.nii.gz','mprage.nii.gz','mprage.nii.gz','mprage.nii.gz','mprage.nii.gz','mprage.nii.gz',...Copy and modify folder structure withcrumb_copyCopy a folder structure from one crumb to the other. The source crumb
must be fully specified, i.e., all crumb arguments must get an existing value.
In addition the destination crumb can only have a subset of the crumb arguments
of the source crumb.>>>fromhanselimportCrumb,crumb_copy>>>src_cr=Crumb("/tmp/hansel/data/raw/{subj_id}/{sess}/{type}/{img}")>>>dst_cr=Crumb("/tmp/hansel/data/copy/{subj_id}/{sess}/{type}")>>>crumb_copy(src_cr,dst_cr)More functionalities, ideas and comments are welcome.Command Linehanselwill install a command calledcrumb.
This CLI has been made withClick,
so trycrumb -hto see more details.You can useCrumb.ls:crumbls"/data/hansel/cobre/{sid:4*100}/{session}/{img}"Copy one file tree to another file tree withcrumb copy:crumbcopy"/data/hansel/cobre/{sid}/{session}/{img}""/data/hansel/cobre2/{sid}/{img}"Link one file tree to another file tree withlink:crumblink"/data/hansel/cobre/{sid}/{session}/{img}""/data/hansel/cobre2/{sid}/{img}"Return the intersection between crumb1 and crumb2 on a given argument with theintersectfunction:crumbintersect--on"sid""/data/hansel/cobre/{sid}/{session}/{img}""/data/hansel/cobre2/{sid}/{img}"Return the differencecrumb1 - crumb2on a given argument with thedifffunction:crumbdiff--on"sid""/data/hansel/cobre/{sid}/{session}/{img}""/data/hansel/cobre2/{sid}/{img}"DependenciesPlease see the requirements.txt file. Before installing this package,
install its dependencies with:pipinstall-rrequirements.txtInstallIt works on Python 3.4, 3.5 and 2.7. For Python 2.7 installpathlib2as well.This package uses setuptools. You can install it running:pythonsetup.pyinstallIf you already have the dependencies listed in requirements.txt
installed, to install in your home directory, use:pythonsetup.pyinstall--userTo install for all users on Unix/Linux:pythonsetup.pybuildsudopythonsetup.pyinstallYou can also install it in development mode with:pythonsetup.pydevelopDevelopmentCodeGithubYou can check the latest sources with the command:gitclonehttps://www.github.com/alexsavio/hansel.gitor if you have write privileges:gitclonegit@github.com:alexsavio/hansel.gitIf you are going to create patches for this project, create a branch
for it from the master branch.We tag stable releases in the repository with the version number.TestingWe are usingpy.testto help us with the testing.Otherwise you can run the tests executing:pythonsetup.pytestorpy.testormaketestChangelogVersion 2.0.1 (25.02.2018)Add type annotations accross all code base.Fixed documentation. Now it README example code snippets are tested.Version 2.0.0 (24.02.2018)Drop support for Python 2.7. Add support for Python 3.6.Version 1.0.0 - Version 1.0.2add CLI docs to README.set inlsfunctionmake_crumbsto False iffullpathis also False.make inlsfunctionarg_nameparameter mandatory whenfullpathis False.Version 0.9.0 - 0.9.6“cli diff” and “cli intersect” have the same interface.changed how “cli intersect” prints results. Added ‘base_crumb’ option.added to CLI default ignore list for crumbs:[‘.*’].addcrumb diffto CLI.adddifferenceutility function with test.addtest__utils.pyas a WIP.move CLI to click.Annoying bug fixed.Addcrumb_copy.Now it doesn’t bother with relative paths.Fixcrumb_copyfor existing paths and linking relative paths.Addcrumb_copyCLI.Makeclicka dependency and movecrumb_copytocrumb copy.Addcrumb lsandcrumb intersectCLI.Addis_crumb_argfunction to utils.Fix error incrumb copyparameter names.Version 0.8.0 - 0.8.3Set to True the default value forcheck_existsinCrumb.lsfunction.
I don’t think anybody is interested in non-existing paths.Now it is possible to set a non-open item in a Crumb, i.e., I can replace the value for an already set crumb argument.Update README.rstCode clean-up.Replace dict to OrderedDict output invaluesmap_to_dictfunction.Add regex option withinarg_nameargument oflsand__get_item__.Version 0.7.0 - 0.7.5Refactoring of how Crumb works, now using string.Formatter.
This will help with new features due to simpler logic.Now it is not possible to change the syntax of the Crumbs,
although I guess nobody is interested in that.Fixed a few bugs from previous versions.Nowcopyfunction is not a classmethod anymore, so you can docrumb.copy()as well asCrumb.copy(crumb).patternsis not a dictionary anymore, the regexes are embedded in the_pathstring.
The propertypatternsreturns the dictionary as before.  The functionset_patternmust be used instead to set a different pattern to a given argument.Update README.rstFix README.rst because of bad syntax for PyPI.Fix bug for Python 2.7Fix the bug in .rst for PyPI.Code cleanupVersion 0.6.0 - 0.6.2Addedintersectionfunction inutils.py.Change of behaviour in__getitem__, now it returns a list of values even if is only the one replace string from_argval.General renaming of the private functions inside Crumbs, more in accordance to theopen_args/all_argsidea.Fixed a few bugs and now the generated crumbs fromunfoldandlswill have the same parameters as the original Crumb.Change the behaviour orintersectionwithlen(arg_names) == 1for compatibility withcrumb.build_pathfunction.Improve README, update with new examples usingintersection.Addpandashelper functions.Addutilsto convert from values_maps to dicts.Improve docstrings.Version 0.5.0 - 0.5.5Add Python 2.7 compatibility. Friends don’t let friends use Python 2.7!Add ‘re.ignorecase’ option for theregexargument in the constructor.Addutils.check_pathfunction.FixCrumb.splitfunction to return the not defined part of the crumb.AddCrumbs.keys()function.Renameutils.remove_duplicates()toutils.rm_dups().DeprecatingCrumbs.keys()function.RenamedCrumbs.keys()toCrumbs.open_args()and addedCrumbs.all_args().Substitute the internal logic of Crumbs to work withCrumbs.open_args(), made it a bit faster.Added CHANGES.rst to MANIFEST.inVersion 0.4.0 - 0.4.2Fill CHANGES.rst.All outputs fromCrumb.lsfunction will be sorted.Add regular expressions orfnmatchoption for crumb arguments.Changeexistsbehaviour. Now the empty crumb arguments will return False whenexist().Code clean up.Fix bugs.Fix CHANGES.rst to correct restview in PyPI.Thanks to restview:https://pypi.python.org/pypi/restview.
Use:restview--long-descriptionImprove documentation in README.Rename member_argregtopatterns, so the user can use it to manage the argument patterns.Version 0.3.0 - 0.3.1Add_argvalmember, a dict which stores crumb arguments replacements.Add tests.Removerm_dupsoption inCrumb.lsfunction.Remove conversion toPathswhenCrumbhas no crumb arguments inCrumb.ls.Fix README.Code clean up.Version 0.2.0Addignore_listparameter inCrumbconstructor.Version 0.1.0 - 0.1.1Simplify code.Increase test coverage.Addexist_checktoCrumb.lsfunction.Fix bugs.AddCrumb.unfoldfunction.Movemktreeout ofCrumbclass.