175 Commits
Author SHA1 Message Date
coissac bc4d7dc190 Actualiser README.md 2025-09-14 07:46:53 +00:00
Eric Coissac 8c995a47a0 add flex 2025-06-22 15:53:18 +02:00
coissac 02232d6453 Actualiser Dockerfile 2025-06-16 06:16:12 +00:00
coissac 9a9a968116 Actualiser Dockerfile 2025-06-14 05:14:06 +00:00
coissac feca46cbfd Actualiser Dockerfile 2025-06-14 05:12:06 +00:00
coissac 5ea47205c5 Actualiser Dockerfile 2025-06-14 05:11:12 +00:00
coissac bca9928ade Actualiser Dockerfile 2025-06-14 05:10:22 +00:00
coissac 8cafb794b0 Actualiser Dockerfile 2025-06-14 04:50:38 +00:00
coissac cb13a8d228 Actualiser Dockerfile 2025-06-14 04:27:54 +00:00
coissac 83beecb6ec Actualiser Dockerfile 2025-06-13 21:41:15 +00:00
coissac 73030ff54f Actualiser Dockerfile 2025-06-13 18:08:19 +00:00
coissac 51ff099cca Actualiser Dockerfile 2025-06-13 14:13:43 +00:00
Eric Coissac 171920398c Update pour la dockerization 2025-06-12 17:29:50 +02:00
Celine Mercier edb0106cc0 obiaddtaxids: UNITE databases not restrained to Fungi anymore, and
better check of format
2020-04-05 17:14:04 +02:00
coissac c198e7d8bf Patch obitools for not importing too recent packages 2019-04-01 14:32:05 +02:00
coissac a2db4ce5ae ignore the new virtualenv for version 1.12.13 2019-04-01 14:12:10 +02:00
coissac 0e5ae4dd68 Switch to version 1.12.13 2019-04-01 14:06:07 +02:00
coissac 0d4bb5bbd6 cleaning 2 2019-04-01 14:04:17 +02:00
coissac ee53992acc Merge branch 'master' of git.metabarcoding.org:obitools/obitools
# Conflicts:
#	requirements.txt
2019-04-01 14:00:17 +02:00
coissac 45736d1771 Version switch 2019-04-01 13:59:09 +02:00
coissac de2673174f cleaning 1 2019-04-01 13:58:16 +02:00
Celine Mercier 1c318bd34d Merge branch 'master' of git@git.metabarcoding.org:obitools/obitools.git 2019-03-26 11:36:54 +01:00
Celine Mercier e99684a47d Fixed --skip-on-error option for embl and genbank files (did not work
because python generators can never resume after raising any exception)
2019-03-26 11:36:11 +01:00
Celine Mercier f8dcd2f401 Fixed requirements.txt triggering errors with the 2 conditions for
ipython
2019-03-15 16:34:23 +01:00
Celine Mercier a43601cffd obitaxonomy: fixed a bug when trying to add a new species by name 2018-11-01 18:07:56 +01:00
coissac 71cc64977b Merge branch 'master' of git@git.metabarcoding.org:obitools/obitools.git 2018-09-19 15:52:38 +02:00
Celine Mercier 0cd2814fa7 Merge branch 'master' of git@git.metabarcoding.org:obitools/obitools.git 2018-09-19 15:39:47 +02:00
Celine Mercier 97268211ff Sphinx: replaced the deprecated 'sphinx.ext.pngmath' extension with
'sphinx.ext.imgmath' (would stop OBITools installation)
2018-09-19 15:36:51 +02:00
coissac 9962c452d8 Switch to version 1.2.11 2017-04-30 19:13:43 +02:00
coissac abf8fde9f5 Switch to version 1.2.11 2017-04-30 18:57:28 +02:00
coissac 1311cf3c63 Limit Ipython version to stay compatible with python 2.7 2017-04-27 13:49:14 +02:00
coissac 59e2665de9 close #32 2017-04-20 18:46:03 +02:00
coissac 05cc5b06c7 Switch to version 1.2.10 2017-02-01 12:01:24 +01:00
Celine Mercier 54c68bab99 Fixed a bug when parsing embl files: accession ids end with ';' in recent embl formats. The ';' was kept by the old embl parser, resulting in many possible bugs. 2017-01-23 16:56:38 +01:00
coissac 5dc1f4d325 Switch to version 1.2.9 and ask for Cython >= 2.4.0 2016-08-03 09:21:05 +02:00
coissac 8bede453ae Patch a memory bug in the progress bar printing leading to a bus error
with some C compiler and the new version of Cython (0.2.4)
2016-08-03 09:07:49 +02:00
coissac 8cbac7a75e Remove an extra log added for debuging at the beginning of the output 2016-08-03 07:59:58 +02:00
coissac 1b31d9091a swithc to version 1.2.8 2016-07-28 08:06:34 +02:00
coissac eaf50be2ca Switch to cython 0.24 2016-07-28 08:05:14 +02:00
coissac b24f993780 Remove some false error warning 2016-07-28 08:03:23 +02:00
coissac e0bf25d72c replace _BYTES call by the new .data.as_uchars to reflect the new API 2016-07-28 07:54:59 +02:00
coissac a4a854a2a8 Merge branch 'master' of git.metabarcoding.org:obitools/obitools
Conflicts:
	src/obitools/align/_lcs.ext.4.c

the symbolic link is correctly restored
2016-07-06 15:23:47 +02:00
coissac a186bf439c Small changes on distutils.ext 2016-07-06 15:17:15 +02:00
Aurélie Bonin a6daa709e9 Merge branch 'windows_version' into 'master'
Windows version

This branch allows for compiling OBITools on windows in the cygwin environment.

It asks for installing the following cygwin packages :

      - binutils
      - gcc-core
      - python 2.7

See merge request !1
2016-07-06 15:02:35 +02:00
abonin 22f98eaa4b switch to the new version 1.2.7 2016-07-06 14:50:56 +02:00
abonin 38504ab03a Final switch to the new cython interface on array.array 2016-07-06 14:46:31 +02:00
abonin 1b8a867fbf switch to the new cython interface to array.array 2016-07-06 14:29:43 +02:00
abonin 9a9bd6c60f switch to the new cython interface to array.array 2016-07-06 14:23:58 +02:00
abonin 6002fbaa98 Remove the reference to arrayarray.h 2016-07-06 14:12:56 +02:00
Aurélie Bonin e869e16d9f Adaptation for running with cygwin on windows platform 2016-07-06 11:55:32 +02:00
Aurélie Bonin 3a5ad68013 A very small fastq test file 2016-07-06 11:52:55 +02:00
coissac b8dc49ef84 Rename members of the DNA profil strucuture to potentiel avoid a compile
bug on windows cygwin
2016-06-28 15:13:30 +02:00
coissac 140f26977a Merge branch 'master' of git@git.metabarcoding.org:obitools/obitools.git 2016-06-28 15:10:39 +02:00
coissac d96cf37147 Remove a debug trace in the installer 2016-06-28 15:10:12 +02:00
Celine Mercier 20f1c1d337 Fixed a potential bug with the parsing of taxonomic paths when using
obitaxonomy after obiaddtaxids on a general UNITE database
2016-06-16 16:40:27 +02:00
coissac f32ca9e9fa Patch the obitool url on metabarcoding.org 2016-05-27 14:37:58 +02:00
coissac a06e79102d Switch to version 1.2.6 2016-05-27 12:56:14 +02:00
coissac 0e2c486178 Merge branch 'master' of git@git.metabarcoding.org:obitools/obitools.git 2016-05-27 12:52:04 +02:00
Celine Mercier 950bae882b Numbering version 1.2.5 2016-05-27 11:20:27 +02:00
Celine Mercier 683f3b5bbc Merge branch 'master' of git@git.metabarcoding.org:obitools/obitools.git 2016-05-26 16:01:03 +02:00
Celine Mercier 5490144788 Fixed a bug in obitaxonomy sometimes triggering an error when adding a
new taxon from a file. Also now checks if taxon already in taxonomy.
2016-05-26 15:58:48 +02:00
coissac 42a3f98495 for cython to version 0.23.5 the latest 0.23 version 2016-04-07 12:19:42 +02:00
coissac c395d76231 patch a bug in the sequence counting of obiselect 2016-04-06 14:46:08 +02:00
coissac b2d2346eb7 Set the Cython required version to 0.23 because of a bug in the 0.24
version
2016-04-06 14:44:17 +02:00
coissac ffb33f61a9 Switch to version 1.2.3 2016-04-04 18:49:10 +02:00
Celine Mercier adbeac9684 Fixed major bug with -f UNITE_FULL option of obiaddtaxids 2016-04-04 16:36:05 +02:00
Celine Mercier 4aac47e639 Merge branch 'master' of git.metabarcoding.org:obitools/obitools 2016-03-30 16:35:49 +02:00
Celine Mercier 2028c69b3f Fixed typo in obiaddtaxids documentation 2016-03-17 15:05:18 +01:00
coissac 2649cd268c Switch to version 1.2.2 2016-01-12 20:57:47 +01:00
coissac 46b446f0c8 Add capacity to filter on merged sequences 2016-01-12 20:53:56 +01:00
Celine Mercier 6208cbee02 Fixes #20 and several other bugs related to that function 2016-01-08 11:40:39 +01:00
coissac 1d4c3f99b2 Patch a bug related to the -E et -M options 2015-10-27 20:16:07 +01:00
coissac ba30e74dc9 Switch the version number to 1.2.1 2015-10-27 18:59:51 +01:00
coissac 0e8b97ca40 Modify the action of the -M option to fit the documentation 2015-10-27 18:58:32 +01:00
coissac a95ae5126b Change the ecotag doc according to the new -M option 2015-10-27 18:56:49 +01:00
coissac 0e2f859839 Patch bug in the -E option of ecotag and add the -M option to limit the
action of the -E option
2015-10-27 18:48:03 +01:00
coissac 23717f5833 Add a first version of the obisubset tool and tag the version 1.2.0 2015-10-20 22:02:02 +02:00
coissac fdeaf5956c Change the way ecoPCRDB are written by obitools. If the obitools is
called with several sequence files as input when the ecoPCRDB is
requested as output format, the sequences are splitted in several sdx
files
2015-10-02 16:36:25 +02:00
coissac 343d9ec6df Switch to version 1.1.22 2015-08-03 23:16:37 +02:00
coissac 7a993ec841 Patch a wrong import in the file 2015-08-03 23:14:13 +02:00
celinemercier 6170fac081 fixed a problem with the progress bar in obiselect when there is more
than one category
2015-07-20 15:24:30 +02:00
coissac 764ee27c0d Switch to the version 1.1.21 2015-07-13 15:31:20 +02:00
coissac 18d87aa1b1 Add a small work around patch for the --skip and --only options in
illuminapairedend
2015-07-11 23:38:47 +02:00
coissac 1c91c27ee3 Tag the version 1.1.20 2015-07-03 14:57:24 +02:00
coissac 22d343b46a Patch two small print bug in the new ecotag 2015-07-03 14:55:44 +02:00
coissac 2af94b9da7 Add docuentation for the new options and an option to manage the ecotag
cache size
2015-07-03 10:39:59 +02:00
coissac aa064dda57 Add two general options for limiting the analysis of a file to only a
sub-part of the data:

   - --skip <N>
   - --only <N>
   
They respectively allow to first skip the N first sequences, and to
analysize only the <N> next sequences.
2015-07-03 09:10:49 +02:00
coissac f38ccae698 Add a --minimum-circle option to ecotag and a cache on the self
alignment scores of 1000000 of pairwise scores
2015-07-02 16:14:22 +02:00
Celine Mercier b99881817a Closes #9 : adds a parser in obiaddtaxids for the UNITE 'general FASTA
release' format
2015-06-12 15:08:58 +02:00
Celine Mercier df093b3fa9 updated the UNITE parser of obiaddtaxids 2015-06-11 17:47:00 +02:00
coissac 9ba67985e9 Switch to the 1.1.18 version 2015-06-02 16:01:16 +02:00
coissac f74aeff002 Merge branch 'master' of git@git.metabarcoding.org:obitools/obitools.git 2015-06-02 15:59:57 +02:00
coissac b9fa718a2c tag the 1.1.17 version 2015-06-02 15:53:41 +02:00
coissac 51a7ecde21 Patch the installer 2015-06-02 15:52:18 +02:00
Celine Mercier 6bf6cc6612 fixing a documentation problem (issue #3) 2015-06-01 14:11:11 +02:00
Celine Mercier 191af9669a modified the obisilva script to deal with version names containing dots,
e.g. "119.1"
2015-06-01 14:07:19 +02:00
Celine Mercier 429fbbb61b switching branch OBITools-1.01 to master 2015-06-01 10:41:26 +02:00
Celine Mercier 18c3a7dc08 Fixes #3 2015-05-22 18:00:13 +02:00
coissac 83b60ab7b0 Switch to version 1.1.16 2015-04-28 13:54:32 +00:00
coissac 8f61723141 Patch a bug in the getTaxid function 2015-04-28 13:52:19 +00:00
coissac 21ac43ca12 2015-04-16 13:59:12 +00:00
coissac 6be099b885 2015-04-16 13:06:56 +00:00
coissac 37d24febd2 2015-04-16 13:05:21 +00:00
coissac ee1063ed28 2015-04-16 12:42:46 +00:00
coissac 6bd5407a13 2015-04-16 12:42:11 +00:00
coissac dd5fbb0246 2015-04-16 12:41:43 +00:00
coissac 3a82469281 Use setuptools instead of distutils as much as possible 2015-04-16 09:32:07 +00:00
coissac edfa796c83 Resolve dependency with cython by making a temporary install if needed 2015-04-16 08:52:30 +00:00
coissac 704dbd50c8 Switch to version 1.1.6 2015-04-15 16:10:57 +00:00
coissac bd32b9f4e6 Switch to version 1.1.6 2015-04-15 16:10:18 +00:00
coissac 4547dd490b Add a local copy of setuptools 2015-04-15 16:09:25 +00:00
coissac 48dc9497d4 Try to solve dependencies with sphinx at the first install 2015-04-15 16:08:36 +00:00
coissac 2614654b41 Change the install documentation 2015-03-21 19:28:45 +00:00
coissac 6bfec50f25 Debug the reading of gziped files, change the doc of obipr2 and obisilva 2015-03-21 17:58:45 +00:00
coissac 28dad843f1 2015-03-14 15:55:19 +00:00
coissac 230be1c08b Add dependency to ipython 2015-03-10 15:36:17 +00:00
coissac 1ca1fa0b03 Switch to the version 1.1.2
Add the exports of the doc in html epub and man pages
Modify the obitool script to setup the path to export/bin
2015-03-10 15:11:40 +00:00
coissac 9fa9d23c15 Switch to the version 1.1.2
Add the exports of the doc in html epub and man pages
Modify the obitool script to setup the path to export/bin
2015-03-10 15:10:53 +00:00
Céline Mercier bb4170dfdd in obitaxonomy, fixed a typo, and a problem when adding taxa from a file with an associated path 2015-03-06 16:20:01 +00:00
coissac 9893dba6f6 2015-03-05 18:40:29 +00:00
coissac 173dde2857 New realease of obitools including a first version of obipr2 and obisilva 2015-03-05 17:04:05 +00:00
coissac 49e3eb3c77 Switch to the version 1.1 of obitools 2015-03-02 13:41:31 +00:00
Céline Mercier f40c2f5245 in obitaxonomy, fixed a problem when adding taxa from file with an associated path 2015-02-23 14:02:31 +00:00
coissac c0eb996dd3 Create a new major version 2015-02-20 19:31:31 +00:00
Frédéric Boyer f596d3a4fb [ADD] order in location 2014-10-21 10:32:44 +00:00
Frédéric Boyer 4de712702d Bug sur merged_KEY lorsque la merged_KEY existe deja dans les sequences : la somme n'etait pas effectuee correctement 2014-09-30 09:21:32 +00:00
Frédéric Boyer 1dad3bf086 -r option fix 2014-05-15 14:41:16 +00:00
Frédéric Boyer b25091e8b0 Bug à l'affichage au chargement de la taxonomie 2014-04-30 15:04:23 +00:00
Frédéric Boyer a664ea5d77 assertion failed with progress bar when empty file 2014-04-25 08:51:17 +00:00
coissac a9a86d1067 remove memory leak 2014-04-23 14:00:48 +00:00
coissac 5a6c4d231c 2014-04-23 12:35:54 +00:00
Frédéric Boyer 62923e1cb8 [MOD] Erreur de lecture des fichiers (standard, pas les options) 2014-04-22 22:39:23 +00:00
Frédéric Boyer d5ce87f7f8 [MOD] Corrected a bug in initialization of 'word4table' that 'coredump' on linux 2014-03-10 18:19:55 +00:00
coissac 7c55ad6d32 Patch to fit the version 0.20 of cython 2014-02-11 16:25:06 +00:00
coissac eac5bc958d Patch to fit the version 0.20 of cython 2014-02-11 16:22:28 +00:00
Frédéric Boyer 847284267e 2014-01-31 16:51:20 +00:00
Frédéric Boyer 105cb4c8be 2014-01-31 16:45:18 +00:00
Frédéric Boyer acad67c67f 2014-01-31 16:26:44 +00:00
Frédéric Boyer 4daec682ff 2014-01-31 16:18:31 +00:00
Frédéric Boyer b09f7b4ef4 2014-01-31 10:39:48 +00:00
Frédéric Boyer 8b00463558 2014-01-31 10:32:21 +00:00
coissac bd75660e3d 2014-01-31 09:29:36 +00:00
Frédéric Boyer 2d432e1d51 doc 2014-01-30 16:20:36 +00:00
coissac 2f6c3c05db Change the name of the tags forward_primer and reverse_primer used in ecoPCR file conversion to forward_match and reverse_match to conform with names used in ngsfilter 2014-01-30 10:39:01 +00:00
Céline Mercier 4e22703ddb fixed spelling mistake 2013-12-10 13:59:51 +00:00
coissac 0675ea6770 Patch a bug related to the -n option of obitab 2013-12-10 08:11:07 +00:00
Frédéric Boyer f3d52f1007 [DOC] 2013-12-04 14:54:09 +00:00
Frédéric Boyer 9ae4a1b4c4 [MOD] direct_tag replaced by forward_tag 2013-12-04 13:41:31 +00:00
Frédéric Boyer a938c9bdc2 2013-12-04 11:02:02 +00:00
Frédéric Boyer 514cd02210 [MOD] nqual and junction removed 2013-12-04 11:00:27 +00:00
Frédéric Boyer f2f8d8805c 2013-12-04 10:18:00 +00:00
Céline Mercier f355cc7a9f 2013-11-20 15:42:07 +00:00
Frédéric Boyer 608db8b1c0 [MOD] removed a print 2013-11-15 21:53:40 +00:00
coissac 47e6b4108c New version of obiclean with clustering options 2013-11-14 15:03:11 +00:00
Frédéric Boyer 5917031d61 [MOD] now handle dictionaries, in such case, sequences can then be found in several files named after the keys of the dictionary 2013-11-05 14:14:24 +00:00
coissac 411c5134ae Path 2 error in the option helps 2013-10-29 14:02:24 +00:00
Céline Mercier 22217920a5 fixed obiaddtaxids bug 2013-10-24 14:32:27 +00:00
Céline Mercier 621b499cb8 fixed obitaxonomy bug 2013-10-24 13:32:44 +00:00
Céline Mercier 2e18284dc0 fixed obitaxonomy bug 2013-10-24 13:29:20 +00:00
Céline Mercier fc5d914093 obiaddtaxids now handle homonymes 2013-10-24 12:55:46 +00:00
Céline Mercier 7a7ee58a51 taxonomy databases now handle homonymes 2013-10-24 12:15:00 +00:00
Céline Mercier 82d92f8497 commented )D option in obitaxonomy because it doesn't work 2013-10-18 12:03:49 +00:00
Céline Mercier 47920a4687 obitaxonomy now checks if taxon already exists under the specific ancestor + changed -A option: if genus already existing it MUST be under restricting ancestor + fixed minor bug 2013-10-16 13:53:16 +00:00
coissac 12c080d7f4 rename --rank option to --seq-rank option 2013-09-19 01:58:11 +00:00
Céline Mercier 7b3886974d better handling of options in obiaddtaxids 2013-09-10 09:57:34 +00:00
Céline Mercier 0686029075 -w option in obitaxonomy 2013-09-04 12:07:16 +00:00
Céline Mercier ca36a4fbfe fixed bug 2013-09-04 10:28:56 +00:00
Céline Mercier cbf007b5a9 -w option in obitaxonomy 2013-09-04 10:27:02 +00:00
Céline Mercier 7e8835295a figuring out a bug 2013-09-04 10:11:10 +00:00
Céline Mercier e3aaa53be1 removed a print 2013-09-04 10:00:20 +00:00
Céline Mercier fbdf0db3c5 changed the UNITE parser in obiaddtaxids to remove empty fields ('-') 2013-09-04 09:46:26 +00:00
Frédéric Boyer 3b10f97587 [MOD] updated doc 2013-08-01 15:11:38 +00:00
coissac ff287fc31d Patch variance estimator for returning 0 for singleton 2013-06-22 08:31:52 +00:00
coissac 3cea0f40fe patch a bug in obisample on the sampling with replacement method 2013-06-18 08:42:48 +00:00
coissac 7bf04063f8 add doc to the manifest 2013-05-20 10:12:31 +00:00
371 changed files with 301973 additions and 39102 deletions
+24
View File
@@ -0,0 +1,24 @@
/OBITools-1.1.19/
/OBITools-1.1.20/
/OBITools-1.1.21/
/OBITools-1.1.22/
/OBITools-1.2.0/
/obitools.test/
/build/
/dist/
/OBITools-1.2.2/
/OBITools-1.2.3/
/OBITools-1.2.4/
/OBITools-1.2.5/
/OBITools-1.2.6/
/OBITools-1.2.7/
/OBITools-1.2.8/
/OBITools-1.2.9/
/OBITools-1.2.10/
/.DS_Store
/OBITools-1.2.11/
/OBITools-1.2.12/
/obitools.test.old/
/coucou
/toto.fasta
/OBITools-1.2.13/
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>OBITools</name>
<name>OBITools-1.1</name>
<comment></comment>
<projects>
</projects>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/OBITools-1.0/src</path>
<path>/OBITools-1.1/src</path>
<path>/${PROJECT_DIR_NAME}/distutils.ext</path>
</pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
+102
View File
@@ -0,0 +1,102 @@
FROM python:2.7.17-alpine AS builder
RUN apk add --no-cache \
bash flex build-base curl openssl-dev bzip2-dev readline-dev sqlite-dev zlib-dev xz-dev tk-dev libffi-dev make ncurses-dev git
RUN pip install Cython==0.29
RUN mkdir -p /app
WORKDIR /app
COPY src .
RUN gcc -o littlebigman littlebigman.c
RUN python setup.py build_ext --inplace
RUN python setup.py install
RUN ./install.sh
WORKDIR /app
RUN git clone https://forge.metabarcoding.org/obitools/ecopcr.git
WORKDIR /app/ecopcr/src
RUN make clean && make \
&& cp ecoPCR ecofind ecogrep /usr/local/bin
WORKDIR /app
RUN git clone https://forge.metabarcoding.org/obitools/ecoprimers.git
WORKDIR /app/ecoprimers/src
RUN make clean && make \
&& cp ecoPrimers /usr/local/bin \
&& cp ../tools/ecoPCRFormat.py /usr/local/bin/ecoPCRFormat \
&& chmod +x /usr/local/bin/ecoPCRFormat
WORKDIR /app
RUN git clone --recurse-submodules https://forge.metabarcoding.org/obitools/sumatra.git
WORKDIR /app/sumatra
RUN make clean && make \
&& cp sumatra /usr/local/bin
WORKDIR /app
RUN git clone --recurse-submodules https://forge.metabarcoding.org/obitools/sumaclust.git
WORKDIR /app/sumaclust
RUN make clean && make \
&& cp sumaclust /usr/local/bin
RUN find /usr/local/bin /usr/local/python2/bin /usr/local/python2/lib -type f -exec file {} \; | \
grep -E 'ELF (32-bit|64-bit) (executable|shared object)' | \
cut -d: -f1 | \
xargs -r strip --strip-unneeded || true
RUN pip install "virtualenv<20" && \
virtualenv --system-site-packages /obitools-doc
ENV PATH="/obitools-doc/bin:${PATH}"
RUN pip install "sphinx==1.8.5"
COPY doc /obitools-doc/doc
RUN echo "/doc" > /obitools-doc/doc/sphinx/build_dir.txt
ENV PYTHONPATH="/app"
WORKDIR /obitools-doc/doc/sphinx
RUN make html
RUN cp /app/docserver.sh /usr/local/bin/docserver
RUN chmod +x /usr/local/bin/docserver
#--- Image finale minimaliste ---
FROM python:2.7.17-alpine
LABEL org.opencontainers.image.title="OBITools2 (legacy)"
LABEL org.opencontainers.image.description="Legacy OBITools2 environment for compatibility with older pipelines and benchmarks. Includes OBITools2, ecoPCR, ecoPrimers, sumatra, and sumaclust. Python 2-based software stack. Full documentation embedded in the image."
LABEL org.opencontainers.image.version="latest"
LABEL org.opencontainers.image.authors="Eric Coissac <eric.coissac@metabarcoding.org>"
LABEL org.opencontainers.image.url="https://registry.metabarcoding.org/image/obitools%2Fobitools2"
LABEL org.opencontainers.image.documentation="https://metabarcoding.org/en/obitools/obitools2"
LABEL org.opencontainers.image.source="https://forge.metabarcoding.org/obitools/obitools"
# Dépendances runtime seulement (exemple)
RUN apk add --no-cache \
openssl \
zlib \
libgomp
# Copier Python compilé et libs depuis le builder
COPY --from=builder /usr/local/lib/python2.7/site-packages /usr/local/lib/python2.7/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY --from=builder /obitools-doc/doc/sphinx/build/html /doc
RUN rm -rf /usr/local/share /usr/local/include /usr/local/lib/pkgconfig /usr/local/lib/python2.7/test && \
find /usr/local/bin /usr/local/python2/bin /usr/local/python2/lib -type f -exec file {} \; | \
grep -E 'ELF (32-bit|64-bit) (executable|shared object)' | \
cut -d: -f1 | \
xargs -r strip --strip-unneeded || true
RUN mkdir -p /data
ENV LD_LIBRARY_PATH=/usr/local/lib
ENV PATH="/usr/local/bin:${PATH}"
VOLUME ["/data"]
WORKDIR /data
CMD ["ash"]
+2 -1
View File
@@ -1,7 +1,8 @@
include setup.py
recursive-include distutils.ext *.py *.c
recursive-include distutils.ext *.py *.c *.pem
recursive-include src *.pyx *.pxd *.c *.h *.cfiles
recursive-include doc/sphinx/source *.txt *.rst *.py
recursive-include doc/sphinx/sphinxext *.py
include doc/sphinx/make.bat
include doc/sphinx/Makefile
include README.txt
+11
View File
@@ -0,0 +1,11 @@
# Legacy OBITools status
Legacy OBITools is 20 years old. It was developed using Python 2, a language that is no longer supported. Therefore, maintaining this code is impossible. It has now been superseded by OBITools4, the latest version in the OBITools lineage.
In order to enable users to continue using Legacy OBITools, a Docker image has been created and made available on the [metabarcoding.org registry](https://registry.metabarcoding.org). While we discourage using this old version for new projects, being able to run it can still be useful for reproducibility purposes.
You can find more information about using this Docker image on the [relevant page](https://metabarcoding.org/en/obitools/obitools2) of the [new metabarcoding website](https://metabarcoding.org).
All the best,
Eric Coissac
+2
View File
@@ -0,0 +1,2 @@
*.pyc
/.DS_Store
+1
View File
@@ -0,0 +1 @@
/*.pyc
@@ -0,0 +1 @@
/*.pyc
+11 -1
View File
@@ -7,6 +7,7 @@ Created on 20 oct. 2012
from distutils.command.build import build as ori_build
from obidistutils.serenity.checksystem import is_mac_system
from distutils import log
class build(ori_build):
@@ -27,11 +28,20 @@ class build(ori_build):
def has_pidname(self):
return is_mac_system()
def has_doc(self):
return True
sub_commands = [('littlebigman', has_littlebigman),
('pidname',has_pidname),
('build_ctools', has_ctools),
('build_files', has_files),
('build_cexe', has_executables)] \
+ ori_build.sub_commands
+ ori_build.sub_commands + \
[('build_sphinx',has_doc)]
def run(self):
log.info('\n\nRunning obidistutils build process\n\n')
ori_build.run(self)
@@ -4,11 +4,8 @@ Created on 20 oct. 2012
@author: coissac
'''
import os
from obidistutils.command.build_ctools import build_ctools
from distutils.sysconfig import customize_compiler
from distutils.errors import DistutilsSetupError
from distutils import log
class build_cexe(build_ctools):
@@ -4,26 +4,35 @@ Created on 13 fevr. 2014
@author: coissac
'''
from distutils import log
import sys
from obidistutils.serenity import is_serenity
import os
from obidistutils.serenity.checkpackage import install_requirements
from obidistutils.serenity.rerun import rerun_with_anothe_python
try:
from Cython.Distutils import build_ext as ori_build_ext # @UnresolvedImport
except ImportError:
from distutils.command.build_ext import build_ext as ori_build_ext
if not is_serenity() and install_requirements():
log.info("Restarting installation with all dependencies ok")
rerun_with_anothe_python(os.path.realpath(sys.executable))
from distutils.errors import DistutilsSetupError
import os
class build_ext(ori_build_ext):
def modifyDocScripts(self):
print >>open("doc/sphinx/build_dir.txt","w"),self.build_lib
def initialize_options(self):
ori_build_ext.initialize_options(self)
ori_build_ext.initialize_options(self) # @UndefinedVariable
self.littlebigman = None
self.built_files = None
def finalize_options(self):
ori_build_ext.finalize_options(self)
ori_build_ext.finalize_options(self) # @UndefinedVariable
self.set_undefined_options('littlebigman',
('littlebigman', 'littlebigman'))
@@ -83,8 +92,21 @@ class build_ext(ori_build_ext):
def run(self):
self.modifyDocScripts()
ori_build_ext.run(self)
ori_build_ext.run(self) # @UndefinedVariable
# from obidistutils.serenity.getcython import get_a_cython_module
# import imp
# import os.path
#
# log.info("No cython installed, try to install a temporary cython")
# cython = get_a_cython_module()
# sys.path.insert(0,os.path.dirname(os.path.dirname(cython.__file__)))
# f, filename, description=imp.find_module('Distutils',[os.path.dirname(cython.__file__)])
# submodule = imp.load_module('Cython.Distutils', f, filename, description)
# ori_build_ext = submodule.build_ext
#
@@ -0,0 +1,41 @@
'''
Created on 10 mars 2015
@author: coissac
'''
from distutils import log
from obidistutils.serenity.checkpackage import install_requirements
from obidistutils.serenity.rerun import rerun_with_anothe_python
from obidistutils.serenity import is_serenity
import os
import sys
try:
from sphinx.setup_command import BuildDoc as ori_build_sphinx
class build_sphinx(ori_build_sphinx):
'''
Build Sphinx documentation in html, epub and man formats
'''
description = __doc__
def run(self):
self.builder='html'
self.finalize_options()
ori_build_sphinx.run(self)
self.builder='epub'
self.finalize_options()
ori_build_sphinx.run(self)
self.builder='man'
self.finalize_options()
ori_build_sphinx.run(self)
except ImportError:
if not is_serenity() and install_requirements():
log.info("Restarting installation with all dependencies ok")
rerun_with_anothe_python(os.path.realpath(sys.executable))
+13 -2
View File
@@ -4,11 +4,22 @@ Created on 6 oct. 2014
@author: coissac
'''
from distutils.command.install import install as install_ori
try:
from setuptools.command.install import install as install_ori
has_setuptools=True
except ImportError:
from distutils.command.install import install as install_ori
has_setuptools=False
from distutils import log
class install(install_ori):
def __init__(self,dist):
install_ori.__init__(self, dist)
self.sub_commands.insert(0, ('build',lambda self: True))
self.sub_commands.append(('install_sphinx',lambda self: self.distribution.serenity))
def run(self):
log.info('\n\nRunning obidistutils install process\n\n')
install_ori.run(self)
@@ -3,8 +3,13 @@ Created on 20 oct. 2012
@author: coissac
'''
try:
from setuptools.command.install_scripts import install_scripts as ori_install_scripts
has_setuptools = True
except ImportError:
from distutils.command.install_scripts import install_scripts as ori_install_scripts
has_setuptools = False
from distutils.command.install_scripts import install_scripts as ori_install_scripts
import os.path
from distutils import log
@@ -13,7 +18,8 @@ class install_scripts(ori_install_scripts):
def initialize_options(self):
ori_install_scripts.initialize_options(self)
self.deprecated_scripts = None
self.public_dir = None
def revove_dot_py(self):
for filename in self.get_outputs():
pyfile = "%s.py" % filename
@@ -28,8 +34,23 @@ class install_scripts(ori_install_scripts):
except:
log.info('Unix command %s is not present in build dir' % command)
def remove_deprecated_script(self):
def install_public_link(self):
self.mkpath(self.public_dir)
for file in self.get_outputs():
if self.dry_run:
log.info("changing mode of %s", file)
else:
log.info("exporting file %s -> %s", file,os.path.join(self.public_dir,
os.path.split(file)[1]
))
dest = os.path.join(self.public_dir,
os.path.split(file)[1]
)
if os.path.exists(dest):
os.unlink(dest)
os.symlink(file,dest)
def remove_deprecated_script(self):
if self.deprecated_scripts is not None:
for f in self.deprecated_scripts:
try:
@@ -46,8 +67,13 @@ class install_scripts(ori_install_scripts):
def run(self):
self.remove_deprecated_script()
ori_install_scripts.run(self)
if self.distribution.serenity:
self.public_dir=os.path.join(self.install_dir,"../export/bin")
self.public_dir=os.path.abspath(self.public_dir)
self.install_public_link()
self.revove_dot_py()
@@ -0,0 +1,46 @@
'''
Created on 10 mars 2015
@author: coissac
'''
from distutils.core import Command
import os.path
import glob
class install_sphinx(Command):
'''
Install the sphinx documentation
'''
description = "Install the sphinx documentation in serenity mode"
boolean_options = ['force', 'skip-build']
def initialize_options (self):
self.install_doc = None
self.build_dir = None
def finalize_options (self):
self.set_undefined_options('build_sphinx', ('build_dir', 'build_dir'))
self.set_undefined_options('install',
('install_scripts', 'install_doc'))
def run (self):
if self.distribution.serenity:
self.install_doc = os.path.join(self.install_doc,"../export/share")
self.install_doc=os.path.abspath(self.install_doc)
self.mkpath(self.install_doc)
self.mkpath(os.path.join(self.install_doc,'html'))
outfiles = self.copy_tree(os.path.join(self.build_dir,'html'),
os.path.join(self.install_doc,'html'))
self.mkpath(os.path.join(self.install_doc,'man','man1'))
outfiles = self.copy_tree(os.path.join(self.build_dir,'man'),
os.path.join(self.install_doc,'man','man1'))
for epub in glob.glob(os.path.join(self.build_dir,'epub/*.epub')):
self.copy_file(os.path.join(epub),
os.path.join(self.install_doc,os.path.split(epub)[1]))
@@ -8,7 +8,7 @@ import os
from obidistutils.command.build_exe import build_exe
from obidistutils.serenity.checksystem import is_mac_system
from distutils import log
class pidname(build_exe):
@@ -44,7 +44,9 @@ class pidname(build_exe):
def run(self):
if is_mac_system():
log.info("Building pidname...")
build_exe.run(self)
log.info("Done")
self.pidname=True
else:
self.pidname=False
@@ -0,0 +1,46 @@
'''
Created on 10 mars 2015
@author: coissac
'''
import os.path
try:
from setuptools.command.sdist import sdist as orig_sdist
except ImportError:
from distutils.command.sdist import sdist as orig_sdist
from distutils import dir_util
class sdist(orig_sdist):
def make_distribution(self):
"""Create the source distribution(s). First, we create the release
tree with 'make_release_tree()'; then, we create all required
archive files (according to 'self.formats') from the release tree.
Finally, we clean up by blowing away the release tree (unless
'self.keep_temp' is true). The list of archive files created is
stored so it can be retrieved later by 'get_archive_files()'.
"""
# Don't warn about missing meta-data here -- should be (and is!)
# done elsewhere.
base_dir = self.distribution.get_fullname()
base_name = os.path.join(self.dist_dir,base_dir)
self.make_release_tree(os.path.join('tmp',base_dir), self.filelist.files)
archive_files = [] # remember names of files we create
# tar archive must be created last to avoid overwrite and remove
if 'tar' in self.formats:
self.formats.append(self.formats.pop(self.formats.index('tar')))
for fmt in self.formats:
file = self.make_archive(base_name, fmt, root_dir='tmp',base_dir=base_dir,
owner=self.owner, group=self.group)
archive_files.append(file)
self.distribution.dist_files.append(('sdist', '', file))
self.archive_files = archive_files
if not self.keep_temp:
dir_util.remove_tree(os.path.join('tmp',base_dir), dry_run=self.dry_run)
+19 -5
View File
@@ -8,10 +8,12 @@ from os import path
import os.path
import glob
import sys
from obidistutils.command.sdist import sdist
try:
from setuptools import setup as ori_setup
from setuptools.command.egg_info import egg_info
has_setuptools = True
except ImportError:
from distutils.core import setup as ori_setup
@@ -19,15 +21,16 @@ except ImportError:
from distutils.extension import Extension
from obidistutils.command.build import build
from obidistutils.command.littlebigman import littlebigman
from obidistutils.command.build_cexe import build_cexe
from obidistutils.command.build_sphinx import build_sphinx
from obidistutils.command import build_ext
from obidistutils.command.build_ctools import build_ctools
from obidistutils.command.build_files import build_files
from obidistutils.command.build_scripts import build_scripts
from obidistutils.command.install_scripts import install_scripts
from obidistutils.command.install_sphinx import install_sphinx
from obidistutils.command.install import install
from obidistutils.command.pidname import pidname
@@ -122,8 +125,15 @@ COMMANDS = {'build':build,
'build_cexe':build_cexe,
'build_ext': build_ext,
'build_scripts':build_scripts,
'build_sphinx':build_sphinx,
'install_scripts':install_scripts,
'install':install}
'install_sphinx':install_sphinx,
'install':install,
'sdist':sdist}
if has_setuptools:
COMMANDS['egg_info']=egg_info
CTOOLS =[]
CEXES =[]
@@ -133,11 +143,11 @@ def setup(**attrs):
if has_setuptools:
try:
requirements = open('requirements.txt').readlines()
requirements = [x.strip() for x in requirements]
requirements = [x for x in requirements if x[0]!='-']
if 'install_requires' not in attrs:
attrs['install_requires']=requirements
else:
@@ -145,6 +155,7 @@ def setup(**attrs):
except IOError:
pass
if 'distclass' not in attrs:
attrs['distclass']=Distribution
@@ -177,6 +188,9 @@ def setup(**attrs):
if 'sse' not in attrs:
attrs['sse']=None
if 'serenity' not in attrs:
attrs['serenity']=False
EXTENTION=findCython(SRC)
@@ -187,5 +201,5 @@ def setup(**attrs):
if 'ext_modules' not in attrs:
attrs['ext_modules'] = EXTENTION
ori_setup(**attrs)
+6 -2
View File
@@ -4,8 +4,10 @@ Created on 20 oct. 2012
@author: coissac
'''
from distutils.dist import Distribution as ori_Distribution
try:
from setuptools.dist import Distribution as ori_Distribution
except ImportError:
from distutils.dist import Distribution as ori_Distribution
class Distribution(ori_Distribution):
@@ -17,6 +19,7 @@ class Distribution(ori_Distribution):
self.deprecated_scripts = None
self.zip_safe=False
self.sse = None
self.serenity=attrs['serenity']
ori_Distribution.__init__(self, attrs)
@@ -29,6 +32,7 @@ class Distribution(ori_Distribution):
"By default the name is PACKAGE-VERSION"
))
def has_executables(self):
return self.executables is not None and self.executables
@@ -0,0 +1 @@
/*.pyc
@@ -42,7 +42,7 @@ from obidistutils.serenity.util import save_argv
from obidistutils.serenity.snake import snake
from obidistutils.serenity.globals import PIP_MINVERSION
from obidistutils.serenity.globals import PIP_MINVERSION, local_serenity
def serenity_snake(envname,package,version,minversion=PIP_MINVERSION):
@@ -63,7 +63,9 @@ def serenity_snake(envname,package,version,minversion=PIP_MINVERSION):
log.info("%s will be installed with python : %s" % (package,virtualpython))
install_requirements()
if install_requirements():
log.info("Restarting installation with all dependencies ok")
rerun_with_anothe_python(virtualpython)
log.set_threshold(old)
@@ -71,6 +73,9 @@ def serenity_assert(version,minversion=PIP_MINVERSION):
check_requirements()
def is_serenity():
from obidistutils.serenity.globals import local_serenity
return local_serenity and local_serenity[0]
def serenity_mode(package,version):
@@ -100,12 +105,13 @@ def serenity_mode(package,version):
sys.argv = [sys.argv[0]] + unknown
if args.serenity:
local_serenity.append(True)
serenity_snake(args.virtual,package,version)
else:
pass
# serenity_assert(package,version)
local_serenity.append(False)
log.set_threshold(old)
return args.serenity
@@ -80,7 +80,7 @@ def install_requirements(skip_virtualenv=True,pip=None):
if pip is None:
pip = get_a_pip_module()
install_something=False
try:
requirements = open('requirements.txt').readlines()
requirements = [x.strip() for x in requirements]
@@ -93,9 +93,12 @@ def install_requirements(skip_virtualenv=True,pip=None):
if not ok:
log.info(" Installing requirement : %s" % x)
pip_install_package(x,pip=pip)
install_something=True
except IOError:
pass
return install_something
def check_requirements(skip_virtualenv=True,pip=None):
@@ -56,41 +56,13 @@ def get_a_pip_module(minversion=PIP_MINVERSION):
tmpdir = get_serenity_dir()
if not local_pip:
# if not is_pip_installed(minversion):
# try:
# if 'http_proxy' in os.environ and 'https_proxy' not in os.environ:
# os.environ['https_proxy']=os.environ['http_proxy']
# pipinstallscript = urllib2.urlopen('https://bootstrap.pypa.io/get-pip.py')
# except:
# raise DistutilsError,"Pip (>=%s) is not install on your system and I cannot install it" % PIP_MINVERSION
#
# script = pipinstallscript.read()
# getpip_py = os.path.join(tmpdir, "get-pip.py")
# with open(getpip_py, "wb") as fp:
# log.info("Downloading temporary pip...")
# fp.write(script)
# log.info(" done.")
#
# getpip = imp.load_source("getpip",getpip_py)
# ZIPFILE=getpip.ZIPFILE
#
# pip_zip = os.path.join(tmpdir, "pip.zip")
# with open(pip_zip, "wb") as fp:
# log.info("Installing temporary pip...")
# fp.write(base64.decodestring(ZIPFILE))
# log.info(" done.")
#
# # Add the zipfile to sys.path so that we can import it
# sys.path.insert(0,pip_zip)
# zi = zipimport.zipimporter(pip_zip)
# pip = zi.load_module("pip")
# pip = importlib.import_module('obidistutils.serenity.pip')
# else:
serenity = importlib.import_module('obidistutils.serenity')
sys.path.insert(0, os.path.dirname(serenity.__file__))
pip = importlib.import_module('pip')
local_pip.append(pip)
else:
pip = local_pip[-1]
# Prepare the CERT certificat for https download
@@ -5,10 +5,12 @@ Created on 2 oct. 2014
'''
from distutils import util
from distutils import log
def is_mac_system():
platform = util.get_platform().split('-')[0]
if platform=='macosx':
log.info('You are running on a Mac platform')
return platform=='macosx'
def is_windows_system():
@@ -0,0 +1,72 @@
'''
Created on 2 oct. 2014
@author: coissac
'''
import imp
import importlib
from distutils.errors import DistutilsError
from distutils.version import StrictVersion
from distutils import log
from obidistutils.serenity.globals import local_cython # @UnusedImport
from obidistutils.serenity.checkpip import get_a_pip_module
from obidistutils.serenity.checkpackage import get_package_requirement
from obidistutils.serenity.checkpackage import parse_package_requirement
from obidistutils.serenity.checkpackage import is_installed
from obidistutils.serenity.checkpackage import pip_install_package
from obidistutils.serenity.util import get_serenity_dir
def get_a_cython_module(pip=None):
global local_cython
if not local_cython:
if pip is None:
pip = get_a_pip_module()
cython_req = get_package_requirement('Cython',pip)
if cython_req is None:
cython_req='Cython'
requirement_project,requirement_relation,minversion = parse_package_requirement(cython_req) # @UnusedVariable
if cython_req is None or not is_installed(cython_req, pip):
tmpdir = get_serenity_dir()
ok = pip_install_package(cython_req,directory=tmpdir,pip=pip)
log.debug('temp install dir : %s' % tmpdir)
if ok!=0:
raise DistutilsError, "I cannot install a cython package"
f, filename, description = imp.find_module('Cython', [tmpdir])
cythonmodule = imp.load_module('Cython', f, filename, description)
if minversion is not None:
assert StrictVersion(cythonmodule.__version__) >= minversion, \
"Unable to find suitable version of cython get %s instead of %s" % (cythonmodule.__version__,
minversion)
else:
cythonmodule = importlib.import_module('Cython')
local_cython.append(cythonmodule)
return local_cython[0]
@@ -11,3 +11,5 @@ saved_args=[]
tmpdir=[]
local_pip=[]
local_virtualenv=[]
local_cython=[]
local_serenity=[]
Binary file not shown.
@@ -0,0 +1 @@
/*.pyc
@@ -106,7 +106,7 @@ def create_main_parser():
parser = ConfigOptionParser(**parser_kw)
parser.disable_interspersed_args()
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
parser.version = 'pip %s from %s (python %s)' % (
__version__, pip_pkg_dir, sys.version[:3])
@@ -0,0 +1 @@
/*.pyc
+8 -3
View File
@@ -16,7 +16,7 @@ from obidistutils.serenity.checkpython import is_good_python27, \
lookfor_good_python27
def rerun_with_anothe_python(path):
def rerun_with_anothe_python(path, fork=False):
if saved_args:
args = saved_args
@@ -32,8 +32,13 @@ def rerun_with_anothe_python(path):
sys.stderr.flush()
sys.stdout.flush()
os.execv(path,list(args))
if fork:
log.info('Forking a new install process')
os.system(' '.join(list(args)))
log.info('External process ended')
sys.exit(0)
else:
os.execv(path,list(args))
def enforce_good_python():
if is_good_python27():
@@ -18,7 +18,8 @@ from obidistutils.serenity.globals import PIP_MINVERSION, \
from obidistutils.serenity.checkpip import get_a_pip_module
from obidistutils.serenity.checkpackage import get_package_requirement
from obidistutils.serenity.checkpackage import get_package_requirement,\
install_requirements
from obidistutils.serenity.checkpackage import parse_package_requirement
from obidistutils.serenity.checkpackage import is_installed
from obidistutils.serenity.checkpackage import pip_install_package
@@ -61,7 +62,7 @@ def get_a_virtualenv_module(pip=None):
if minversion is not None:
assert StrictVersion(vitualenvmodule.__version__) >= minversion, \
"Unable to find suitable version of pip get %s instead of %s" % (vitualenvmodule.__version__,
"Unable to find suitable version of virtualenv get %s instead of %s" % (vitualenvmodule.__version__,
minversion)
else:
@@ -112,7 +113,7 @@ def serenity_virtualenv(envname,package,version,minversion=PIP_MINVERSION,pip=No
else:
ok = False
#
# Creates a new virtualenv
+5
View File
@@ -56,6 +56,11 @@ json:
@echo
@echo "Build finished; now you can process the JSON files."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
+18 -2
View File
@@ -12,6 +12,7 @@
# serve to show the default.
import sys, os
import glob
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -37,7 +38,7 @@ import obitools.version
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo',
'sphinx.ext.coverage', 'sphinx.ext.viewcode',
'sphinx.ext.graphviz', 'sphinx.ext.inheritance_diagram',
'sphinx.ext.pngmath',
'sphinx.ext.imgmath',
# 'matplotlib.sphinxext.mathmpl',
# 'matplotlib.sphinxext.only_directives',
# 'matplotlib.sphinxext.plot_directive',
@@ -61,7 +62,7 @@ master_doc = 'index'
# General information about the project.
project = u'OBITools'
copyright = u'2009 - 2013, OBITool Development Team'
copyright = u'2009 - 2015, OBITool Development Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -244,3 +245,18 @@ PygmentsBridge.latex_formatter = CustomLatexFormatter
epub_publisher=u"metabarcoding.org"
epub_author=u"OBITools Development Team"
#--options for the man format -------------------------------
man_pages = []
for f in glob.glob('scripts/*.rst'):
man_pages.append((
f[:-4], # source file (no extension)
os.path.split(f[:-4])[1], # output file (under output dir)
'description of %s' % os.path.split(f[:-4])[1], # description
'The OBITools Development Team - LECA', # author
1, # section
))
+2
View File
@@ -5,5 +5,7 @@ File format conversions
:maxdepth: 2
scripts/obiconvert
scripts/obipr2
scripts/obisilva
scripts/obitaxonomy
scripts/obitab
+3 -3
View File
@@ -3,12 +3,12 @@ Sequence sampling and filtering
.. toctree::
:maxdepth: 2
scripts/obiextract
scripts/obigrep
scripts/obihead
scripts/obisample
scripts/obiselect
scripts/obisplit
scripts/obisubset
scripts/obitail
@@ -3,6 +3,24 @@ Options to specify input format
.. program:: obitools
Restrict the analysis to a sub-part of the input file
.....................................................
.. cmdoption:: --skip <N>
The N first sequence records of the file are discarded from the analysis and
not reported to the output file
.. cmdoption:: --only <N>
Only the N next sequence records of the file are analyzed. The following sequences
in the file are neither analyzed, neither reported to the output file.
This option can be used conjointly with the `--skip` option.
Sequence annotated format
.........................
+48 -25
View File
@@ -1,55 +1,79 @@
.. automodule:: ecotag
:py:mod:`ecotag` specific options
---------------------------------
.. cmdoption:: -R <FILENAME>, --ref-database=<FILENAME>
.. cmdoption:: -R <FILENAME>, --ref-database=<FILENAME>
<FILENAME> is the fasta file containing the reference sequences
.. cmdoption:: -m FLOAT, --minimum-identity=FLOAT
When sequence identity is less than FLOAT, the taxonomic
assignment for the sequence record is not indicated in ``ecotag``'s
output. FLOAT is included in a [0,1] interval.
(This option doesn't seem to work).
When the best match with the reference database present an identity
level below FLOAT, the taxonomic assignment for the sequence record
is not computed. The sequence record is nevertheless included in the
output file. FLOAT is included in a [0,1] interval.
.. cmdoption:: --minimum-circle=FLOAT
minimum identity considered for the assignment circle.
FLOAT is included in a [0,1] interval.
.. cmdoption:: -x RANK, --explain=RANK
.. cmdoption:: -u, --uniq
When this option is specified, the program first dereplicates the sequence
records to work on unique sequences only. This option greatly improves
When this option is specified, the program first dereplicates the sequence
records to work on unique sequences only. This option greatly improves
the program's speed, especially for highly redundant datasets.
.. cmdoption:: --sort=<KEY>
The output is sorted based on the values of the relevant attribute.
.. cmdoption:: -r, --reverse
The output is sorted in reverse order (should be used with the --sort option).
(Works even if the --sort option is not set, but could not find on what
(Works even if the --sort option is not set, but could not find on what
the output is sorted).
.. cmdoption:: -E FLOAT, --errors=FLOAT
FLOAT is the fraction of reference sequences that will
be ignored when looking for the most recent common ancestor. This
option is useful when a non-negligible proportion of reference sequences
is expected to be assigned to the wrong taxon, for example because of
FLOAT is the fraction of reference sequences that will
be ignored when looking for the lowest common ancestor. This
option is useful when a non-negligible proportion of reference sequences
is expected to be assigned to the wrong taxon, for example because of
taxonomic misidentification. FLOAT is included in a [0,1] interval.
.. cmdoption:: -M INTEGER, --min-matches=FLOAT
Define the minimum congruent assignation. If this minimum is reached and
the -E option is activated, the lowest common ancestor algorithm tolarated
that some sequences do not provide the same taxonomic annotation (see the
-E option).
.. cmdoption:: --cache-size=INTEGER
A cache for computed similarities is maintained by `ecotag`. the default
size for this cache is 1,000,000 of scores. This option allows to change
the cache size.
.. include:: ../optionsSet/taxonomyDB.txt
.. include:: ../optionsSet/inputformat.txt
.. include:: ../optionsSet/outputformat.txt
.. include:: ../optionsSet/defaultoptions.txt
:py:mod:`ecotag` added sequence attributes
------------------------------------------
.. hlist::
:columns: 3
- :doc:`best_identity <../attributes/best_identity>`
- :doc:`best_match <../attributes/best_match>`
- :doc:`family <../attributes/family>`
@@ -65,4 +89,3 @@
- :doc:`species_list <../attributes/species_list>`
- :doc:`species_name <../attributes/species_name>`
- :doc:`taxid <../attributes/taxid>`
+61
View File
@@ -0,0 +1,61 @@
.. automodule:: obiextract
:py:mod:`obiextract` specific options
-------------------------------------
.. cmdoption:: -s <KEY>, --sample=<KEY>
Attribute containing sample descriptions. By default the attribute
name used for describing samples is set to ``merged_sample``.
.. cmdoption:: -e <SAMPLE_NAME>, --extract=<KEY>
Attribute indicating which <SAMPLE_NAME> have to be extracted.
Several ``-p`` options can be added for specifying several samples.
If you want to extract a large number of samples, please refer to the ``-E``
option described below
.. TIP:: The ``<KEY>`` can be simply the key of an attribute, or a *Python* expression
similarly to the ``-p`` option of :py:mod:`obigrep`.
*Example:*
.. code-block:: bash
> obiextract -e sampleA -e sampleB allseqs.fasta > samplesAB.fasta
This command extracts from the ``allseqs.fasta`` file data related to samples ``A`` and ``B``.
.. cmdoption:: -E <FILENAME>, --extract-list=<FILENAME>
Allows for indicating a file name where a list of sample is stored. The file must be a simple
text file with a sample name per line.
*Example:*
.. code-block:: bash
> obiextract -E subset.txt allseqs.fasta > subset_samples.fasta
This command extracts from the ``allseqs.fasta`` file data related to samples listed in the ``subset.txt`` file.
.. include:: ../optionsSet/inputformat.txt
.. include:: ../optionsSet/outputformat.txt
.. include:: ../optionsSet/defaultoptions.txt
:py:mod:`obiextract` modified sequence attributes
-------------------------------------------------
- :doc:`count <../attributes/count>`
:py:mod:`obiextract` used sequence attribute
--------------------------------------------
- :doc:`count <../attributes/count>`
+22
View File
@@ -0,0 +1,22 @@
.. automodule:: obipr2
:py:mod:`obipr2` specific options
-------------------------------------
.. cmdoption:: --local=<DIRNAME>
Specify you have already downloaded a copy of the PR2 database located at the following URL
`<http://5.196.17.195/pr2/download/entire_database>`_
*Example:*
.. code-block:: bash
> obipr2 --local=PR2Dir
This format **PR2** database pre-downloaded in the `PR2Dir` directory.
.. include:: ../optionsSet/defaultoptions.txt
+71
View File
@@ -0,0 +1,71 @@
.. automodule:: obisilva
:py:mod:`obisilva` specific options
-------------------------------------
.. cmdoption:: -s , --ssu
Specify that you are interested in the **SSU** database.
*Example:*
.. code-block:: bash
> obisilva --ssu --parc
This download and format into an ecoPCR database the latest version of the **SSUParc** database of **Silva**.
.. cmdoption:: -l, --lsu
Specify that you are interested in the **LSU** database.
*Example:*
.. code-block:: bash
> obisilva --ssu --parc
This download and format into an ecoPCR database the latest version of the **LSUParc** database of **Silva**.
.. cmdoption:: -p , --parc
Specify that you are interested in the **Parc** (complete) version of the **Silva** database.
.. cmdoption:: -r , --ref
Specify that you are interested in the **Reference** (cleaned to keep only high quality sequences)
version of the **Silva** database.
.. cmdoption:: -n , --nr
Specify that you are interested in the **Non redundant** version of the **Silva** database.
just a version of the to closely related sequence is kept in this version of the database
.. warning::
Non redundant version of **Silva** exists only for the SSU sequences
in its **Reference** and **Truncated** version
.. cmdoption:: -t , --trunc
Specify that you are interested in the **Truncated** (limited to the rDNA element without flanked regions)
version of the **Silva** database.
.. cmdoption:: --local=<DIRNAME>
Specify you have already downloaded a copy of the **Silva** database located at the following URL
`<http://www.arb-**Silva**.de/no_cache/download/archive/current/Exports/>`_
*Example:*
.. code-block:: bash
> obisilva --ssu --parc --local=**Silva**Dir
This format the **SSUParc** version of the **Silva** database pre-downloaded in the `**Silva**Dir` directory.
.. include:: ../optionsSet/defaultoptions.txt
+81
View File
@@ -0,0 +1,81 @@
.. automodule:: obisubset
:py:mod:`obisubset` specific options
------------------------------------
.. cmdoption:: -s <TAGNAME>, --sample=<TAGNAME>,
The option ``-s`` allows to specify the tag containing sample descriptions,
the default value is set to *merged_sample*.
*Example:*
.. code-block:: bash
> obiuniq -m sample seq1.fasta > seq2.fasta
> obisubset -s merged_sample -n sample1 seq2.fasta > seq3.fasta
After the dereplication of the sequences using the
in the new attribute ``merged_sample``.
.. cmdoption:: -o <TAGNAME>, --other-tag=<TAGNAME>,
Another tag to clean according to the sample subset
*Example:*
.. code-block:: bash
> obisubset -s merged_sample -o -n sample1 seq2.fasta > seq3.fasta
.. cmdoption:: -l <FILENAME>, --sample-list=<FILENAME>,
File containing the samples names (one sample id per line).
*Example:*
.. code-block:: bash
> obisubset -s merged_sample -o -l ids.txt seq2.fasta > seq3.fasta
.. cmdoption:: -p <REGEX>, --sample-pattern=<REGEX>,
A regular expression pattern matching the sample ids to extract.
*Example:*
.. code-block:: bash
> obisubset -s merged_sample -o -p "negative_.*" seq2.fasta > seq3.fasta
.. cmdoption:: -n <SAMPLEIDS>, --sample-name=<SAMPLEIDS>,
A sample id to extract
*Example:*
.. code-block:: bash
> obisubset -s merged_sample -o -n sample1 seq2.fasta > seq3.fasta
.. include:: ../optionsSet/inputformat.txt
.. include:: ../optionsSet/outputformat.txt
.. include:: ../optionsSet/defaultoptions.txt
:py:mod:`obisubset` modifies sequence attributes
------------------------------------------------
.. hlist::
:columns: 3
- :doc:`count <../attributes/count>`
- :doc:`merged_* <../attributes/merged_star>`
:py:mod:`obisubset` used sequence attribute
-------------------------------------------
- :doc:`count <../attributes/taxid>`
- :doc:`merged_* <../attributes/merged_star>`
+3 -143
View File
@@ -20,7 +20,7 @@ Ecology Resources*, submitted.
:latex:`\vspace{\fill}`
Installing the ``OBITools``
---------------------------
===========================
Availability of the ``OBITools``
................................
@@ -35,150 +35,10 @@ and all the sources can be downloaded from our subversion server
Prerequisites
.............
To install the ``OBITools``, you need that these softwares are installed on your
system:
* Python 2.7 (installed by default on most ``Unix`` systems, available from
`the Python website <http://www.python.org/>`_)
* ``gcc`` (installed by default on most ``Unix`` systems, available from the
GNU sites dedicated to `GCC <https://www.gnu.org/software/gcc/>`_ and
`GMake <https://www.gnu.org/software/make/>`_)
On a linux system
^^^^^^^^^^^^^^^^^
You have to take care that the Python-dev packages are installed.
On MacOSX
^^^^^^^^^
The C compiler and all the other compilation tools are included in the `XCode <https://itunes.apple.com/fr/app/xcode/id497799835?mt=12>`_
application not installed by default. The Python included in the system is not
suitable for running the ``OBITools``. You have to install a complete distribution
of Python that you can download as a `MacOSX package from the Python website <https://www.python.org/downloads/>`_.
User level installation
.......................
There is two main ways for installing the ``OBITools``. The easiest one is to install them
using the :download:`get-obitools.py <../../../get_obitools/get-obitools.py>` script.
Once downloaded, move the file :download:`get-obitools.py <../../../get_obitools/get-obitools.py>` in the directory where you want to install
the ``OBITools``. From a Unix terminal you must now run the command :
.. code-block:: bash
> python get-obitools.py
The script will create a new directory at the place you are running it in which all the
``OBITools`` will be installed. No system privilege are required, and you system will not
be altered in any way by the obitools installation.
The newly created directory is named OBITools-VERSION where version is substituted by the
latest version number available.
Inside the newly created directory all the ``OBITools`` are installed. Close to this directory
there is a shell script named ``obitools``. Running this script activate the ``OBITools``
by reconfiguring your Unix environment.
.. code-block:: bash
> ./obitools
Once activated you can desactivate the ``OBITools`` byt typing the command ``exit``.
.. code-block:: bash
> exit
OBITools are no more activated, Bye...
======================================
System level installation
.........................
As all the Python software deposited on `PyPI <https://pypi.python.org/pypi/obitools>`_, ``OBITools`` can be
installed using the `pip` (`https://pypi.python.org/pypi/pip <https://pypi.python.org/pypi/pip>`_) command.
We recommand to use `pip` over `easy_install` as python package manager.
If you have not ``pip`` installed on your computer, please visite the
following page: `http://pip.readthedocs.org/en/latest/installing.html <http://pip.readthedocs.org/en/latest/installing.html>`_ .
Remember that Python 2.7, and a C compiler have to be installed before the ``OBITools``.
To install ``OBITools`` you have first to install dependencies
.. code-block:: bash
> pip install -U virtualenv
> pip install -U sphinx
> pip install -U cython
You can now install ``OBITools`` themselves.
.. code-block:: bash
> pip install -U obitools
Perhaps that on your system, the system administrator privilege are required to realise
these installations. If you are yourself administrator of your computer run the above commands
with the ``sudo`` command in front.
.. code-block:: bash
> sudo pip install -U virtualenv
> sudo pip install -U sphinx
> sudo pip install -U cython
> sudo pip install -U obitools
Retrieving the sources of the OBITools
......................................
If you want to compile by yourself the ``OBITools``, you will need to install the same
prerequisite:
.. code-block:: bash
> pip install -U virtualenv
> pip install -U sphinx
> pip install -U cython
moreover you need to install any subversion client (a list of clients is available from `Wikipedia <http://en.wikipedia.org/wiki/Comparison_of_Subversion_clients>`_)
Then you can download the
.. code-block:: bash
> svn co http://www.grenoble.prabi.fr/public-svn/OBISofts/OBITools/branches/OBITools-1.00/ OBITools
This command will create a new directory called ``OBITools``.
Compiling and installing the ``OBITools``
.........................................
From the directory where you retrieved the sources, execute the following commands:
.. code-block:: bash
> cd OBITools
> python setup.py build
> python setup.py install
Once installed, you can test your installation by running the commands of the
:doc:`tutorials <./tutorials>`.
Introduction
------------
============
DNA metabarcoding is an emerging approach for biodiversity studies (Taberlet et
al. 2012). Originally mainly developed by microbiologists (e.g. Sogin et al.
@@ -303,7 +163,7 @@ plant taxonomic and growth form diversity. Molecular Ecology, 21, 3647-3655.
Basic concepts of the ``OBITools``
----------------------------------
==================================
Once installed, the ``OBITools`` enrich the Unix command line interface with a set
of new commands dedicated to NGS data processing. Most of them have a name
+1
View File
@@ -0,0 +1 @@
*.pyc
+44989 -37213
View File
File diff suppressed because it is too large Load Diff
+277
View File
@@ -0,0 +1,277 @@
#!/usr/bin/env python
import os
import optparse
import sys
import re
from pip.exceptions import InstallationError, CommandError, PipError
from pip.log import logger
from pip.util import get_installed_distributions, get_prog
from pip.vcs import git, mercurial, subversion, bazaar # noqa
from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
from pip.commands import commands, get_summaries, get_similar_commands
# This fixes a peculiarity when importing via __import__ - as we are
# initialising the pip module, "from pip import cmdoptions" is recursive
# and appears not to work properly in that situation.
import pip.cmdoptions
cmdoptions = pip.cmdoptions
# The version as used in the setup.py and the docs conf.py
__version__ = "1.5.6"
def autocomplete():
"""Command and option completion for the main option parser (and options)
and its subcommands (and options).
Enable by sourcing one of the completion shell scripts (bash or zsh).
"""
# Don't complete if user hasn't sourced bash_completion file.
if 'PIP_AUTO_COMPLETE' not in os.environ:
return
cwords = os.environ['COMP_WORDS'].split()[1:]
cword = int(os.environ['COMP_CWORD'])
try:
current = cwords[cword - 1]
except IndexError:
current = ''
subcommands = [cmd for cmd, summary in get_summaries()]
options = []
# subcommand
try:
subcommand_name = [w for w in cwords if w in subcommands][0]
except IndexError:
subcommand_name = None
parser = create_main_parser()
# subcommand options
if subcommand_name:
# special case: 'help' subcommand has no options
if subcommand_name == 'help':
sys.exit(1)
# special case: list locally installed dists for uninstall command
if subcommand_name == 'uninstall' and not current.startswith('-'):
installed = []
lc = current.lower()
for dist in get_installed_distributions(local_only=True):
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
installed.append(dist.key)
# if there are no dists installed, fall back to option completion
if installed:
for dist in installed:
print(dist)
sys.exit(1)
subcommand = commands[subcommand_name]()
options += [(opt.get_opt_string(), opt.nargs)
for opt in subcommand.parser.option_list_all
if opt.help != optparse.SUPPRESS_HELP]
# filter out previously specified options from available options
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
options = [(x, v) for (x, v) in options if x not in prev_opts]
# filter options by current input
options = [(k, v) for k, v in options if k.startswith(current)]
for option in options:
opt_label = option[0]
# append '=' to options which require args
if option[1]:
opt_label += '='
print(opt_label)
else:
# show main parser options only when necessary
if current.startswith('-') or current.startswith('--'):
opts = [i.option_list for i in parser.option_groups]
opts.append(parser.option_list)
opts = (o for it in opts for o in it)
subcommands += [i.get_opt_string() for i in opts
if i.help != optparse.SUPPRESS_HELP]
print(' '.join([x for x in subcommands if x.startswith(current)]))
sys.exit(1)
def create_main_parser():
parser_kw = {
'usage': '\n%prog <command> [options]',
'add_help_option': False,
'formatter': UpdatingDefaultsHelpFormatter(),
'name': 'global',
'prog': get_prog(),
}
parser = ConfigOptionParser(**parser_kw)
parser.disable_interspersed_args()
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
parser.version = 'pip %s from %s (python %s)' % (
__version__, pip_pkg_dir, sys.version[:3])
# add the general options
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
parser.add_option_group(gen_opts)
parser.main = True # so the help formatter knows
# create command listing for description
command_summaries = get_summaries()
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
parser.description = '\n'.join(description)
return parser
def parseopts(args):
parser = create_main_parser()
# Note: parser calls disable_interspersed_args(), so the result of this call
# is to split the initial args into the general options before the
# subcommand and everything else.
# For example:
# args: ['--timeout=5', 'install', '--user', 'INITools']
# general_options: ['--timeout==5']
# args_else: ['install', '--user', 'INITools']
general_options, args_else = parser.parse_args(args)
# --version
if general_options.version:
sys.stdout.write(parser.version)
sys.stdout.write(os.linesep)
sys.exit()
# pip || pip help -> print_help()
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
parser.print_help()
sys.exit()
# the subcommand name
cmd_name = args_else[0].lower()
#all the args without the subcommand
cmd_args = args[:]
cmd_args.remove(args_else[0].lower())
if cmd_name not in commands:
guess = get_similar_commands(cmd_name)
msg = ['unknown command "%s"' % cmd_name]
if guess:
msg.append('maybe you meant "%s"' % guess)
raise CommandError(' - '.join(msg))
return cmd_name, cmd_args
def main(initial_args=None):
if initial_args is None:
initial_args = sys.argv[1:]
autocomplete()
try:
cmd_name, cmd_args = parseopts(initial_args)
except PipError:
e = sys.exc_info()[1]
sys.stderr.write("ERROR: %s" % e)
sys.stderr.write(os.linesep)
sys.exit(1)
command = commands[cmd_name]()
return command.main(cmd_args)
def bootstrap():
"""
Bootstrapping function to be called from install-pip.py script.
"""
pkgs = ['pip']
try:
import setuptools
except ImportError:
pkgs.append('setuptools')
return main(['install', '--upgrade'] + pkgs + sys.argv[1:])
############################################################
## Writing freeze files
class FrozenRequirement(object):
def __init__(self, name, req, editable, comments=()):
self.name = name
self.req = req
self.editable = editable
self.comments = comments
_rev_re = re.compile(r'-r(\d+)$')
_date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
@classmethod
def from_dist(cls, dist, dependency_links, find_tags=False):
location = os.path.normcase(os.path.abspath(dist.location))
comments = []
from pip.vcs import vcs, get_src_requirement
if vcs.get_backend_name(location):
editable = True
try:
req = get_src_requirement(dist, location, find_tags)
except InstallationError:
ex = sys.exc_info()[1]
logger.warn("Error when trying to get requirement for VCS system %s, falling back to uneditable format" % ex)
req = None
if req is None:
logger.warn('Could not determine repository location of %s' % location)
comments.append('## !! Could not determine repository location')
req = dist.as_requirement()
editable = False
else:
editable = False
req = dist.as_requirement()
specs = req.specs
assert len(specs) == 1 and specs[0][0] == '=='
version = specs[0][1]
ver_match = cls._rev_re.search(version)
date_match = cls._date_re.search(version)
if ver_match or date_match:
svn_backend = vcs.get_backend('svn')
if svn_backend:
svn_location = svn_backend(
).get_location(dist, dependency_links)
if not svn_location:
logger.warn(
'Warning: cannot find svn location for %s' % req)
comments.append('## FIXME: could not find svn URL in dependency_links for this package:')
else:
comments.append('# Installing as editable to satisfy requirement %s:' % req)
if ver_match:
rev = ver_match.group(1)
else:
rev = '{%s}' % date_match.group(1)
editable = True
req = '%s@%s#egg=%s' % (svn_location, rev, cls.egg_name(dist))
return cls(dist.project_name, req, editable, comments)
@staticmethod
def egg_name(dist):
name = dist.egg_name()
match = re.search(r'-py\d\.\d$', name)
if match:
name = name[:match.start()]
return name
def __str__(self):
req = self.req
if self.editable:
req = '-e %s' % req
return '\n'.join(list(self.comments) + [str(req)]) + '\n'
if __name__ == '__main__':
exit = main()
if exit:
sys.exit(exit)
+7
View File
@@ -0,0 +1,7 @@
import sys
from .runner import run
if __name__ == '__main__':
exit = run()
if exit:
sys.exit(exit)
@@ -0,0 +1,8 @@
"""
pip._vendor is for vendoring dependencies of pip to prevent needing pip to
depend on something external.
Files inside of pip._vendor should be considered immutable and should only be
updated to versions from upstream.
"""
from __future__ import absolute_import
@@ -0,0 +1,16 @@
try:
import ast
from pip._vendor._markerlib.markers import default_environment, compile, interpret
except ImportError:
if 'ast' in globals():
raise
def default_environment():
return {}
def compile(marker):
def marker_fn(environment=None, override=None):
# 'empty markers are True' heuristic won't install extra deps.
return not marker.strip()
marker_fn.__doc__ = marker
return marker_fn
def interpret(marker, environment=None, override=None):
return compile(marker)()
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
"""Interpret PEP 345 environment markers.
EXPR [in|==|!=|not in] EXPR [or|and] ...
where EXPR belongs to any of those:
python_version = '%s.%s' % (sys.version_info[0], sys.version_info[1])
python_full_version = sys.version.split()[0]
os.name = os.name
sys.platform = sys.platform
platform.version = platform.version()
platform.machine = platform.machine()
platform.python_implementation = platform.python_implementation()
a free string, like '2.6', or 'win32'
"""
__all__ = ['default_environment', 'compile', 'interpret']
import ast
import os
import platform
import sys
import weakref
_builtin_compile = compile
try:
from platform import python_implementation
except ImportError:
if os.name == "java":
# Jython 2.5 has ast module, but not platform.python_implementation() function.
def python_implementation():
return "Jython"
else:
raise
# restricted set of variables
_VARS = {'sys.platform': sys.platform,
'python_version': '%s.%s' % sys.version_info[:2],
# FIXME parsing sys.platform is not reliable, but there is no other
# way to get e.g. 2.7.2+, and the PEP is defined with sys.version
'python_full_version': sys.version.split(' ', 1)[0],
'os.name': os.name,
'platform.version': platform.version(),
'platform.machine': platform.machine(),
'platform.python_implementation': python_implementation(),
'extra': None # wheel extension
}
for var in list(_VARS.keys()):
if '.' in var:
_VARS[var.replace('.', '_')] = _VARS[var]
def default_environment():
"""Return copy of default PEP 385 globals dictionary."""
return dict(_VARS)
class ASTWhitelist(ast.NodeTransformer):
def __init__(self, statement):
self.statement = statement # for error messages
ALLOWED = (ast.Compare, ast.BoolOp, ast.Attribute, ast.Name, ast.Load, ast.Str)
# Bool operations
ALLOWED += (ast.And, ast.Or)
# Comparison operations
ALLOWED += (ast.Eq, ast.Gt, ast.GtE, ast.In, ast.Is, ast.IsNot, ast.Lt, ast.LtE, ast.NotEq, ast.NotIn)
def visit(self, node):
"""Ensure statement only contains allowed nodes."""
if not isinstance(node, self.ALLOWED):
raise SyntaxError('Not allowed in environment markers.\n%s\n%s' %
(self.statement,
(' ' * node.col_offset) + '^'))
return ast.NodeTransformer.visit(self, node)
def visit_Attribute(self, node):
"""Flatten one level of attribute access."""
new_node = ast.Name("%s.%s" % (node.value.id, node.attr), node.ctx)
return ast.copy_location(new_node, node)
def parse_marker(marker):
tree = ast.parse(marker, mode='eval')
new_tree = ASTWhitelist(marker).generic_visit(tree)
return new_tree
def compile_marker(parsed_marker):
return _builtin_compile(parsed_marker, '<environment marker>', 'eval',
dont_inherit=True)
_cache = weakref.WeakValueDictionary()
def compile(marker):
"""Return compiled marker as a function accepting an environment dict."""
try:
return _cache[marker]
except KeyError:
pass
if not marker.strip():
def marker_fn(environment=None, override=None):
""""""
return True
else:
compiled_marker = compile_marker(parse_marker(marker))
def marker_fn(environment=None, override=None):
"""override updates environment"""
if override is None:
override = {}
if environment is None:
environment = default_environment()
environment.update(override)
return eval(compiled_marker, environment)
marker_fn.__doc__ = marker
_cache[marker] = marker_fn
return _cache[marker]
def interpret(marker, environment=None):
return compile(marker)(environment)
@@ -0,0 +1,7 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
from .initialise import init, deinit, reinit
from .ansi import Fore, Back, Style
from .ansitowin32 import AnsiToWin32
__version__ = '0.3.1'
@@ -0,0 +1,50 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
'''
This module generates ANSI character codes to printing colors to terminals.
See: http://en.wikipedia.org/wiki/ANSI_escape_code
'''
CSI = '\033['
def code_to_chars(code):
return CSI + str(code) + 'm'
class AnsiCodes(object):
def __init__(self, codes):
for name in dir(codes):
if not name.startswith('_'):
value = getattr(codes, name)
setattr(self, name, code_to_chars(value))
class AnsiFore:
BLACK = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
MAGENTA = 35
CYAN = 36
WHITE = 37
RESET = 39
class AnsiBack:
BLACK = 40
RED = 41
GREEN = 42
YELLOW = 43
BLUE = 44
MAGENTA = 45
CYAN = 46
WHITE = 47
RESET = 49
class AnsiStyle:
BRIGHT = 1
DIM = 2
NORMAL = 22
RESET_ALL = 0
Fore = AnsiCodes( AnsiFore )
Back = AnsiCodes( AnsiBack )
Style = AnsiCodes( AnsiStyle )
@@ -0,0 +1,190 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
import re
import sys
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
from .winterm import WinTerm, WinColor, WinStyle
from .win32 import windll
winterm = None
if windll is not None:
winterm = WinTerm()
def is_a_tty(stream):
return hasattr(stream, 'isatty') and stream.isatty()
class StreamWrapper(object):
'''
Wraps a stream (such as stdout), acting as a transparent proxy for all
attribute access apart from method 'write()', which is delegated to our
Converter instance.
'''
def __init__(self, wrapped, converter):
# double-underscore everything to prevent clashes with names of
# attributes on the wrapped stream object.
self.__wrapped = wrapped
self.__convertor = converter
def __getattr__(self, name):
return getattr(self.__wrapped, name)
def write(self, text):
self.__convertor.write(text)
class AnsiToWin32(object):
'''
Implements a 'write()' method which, on Windows, will strip ANSI character
sequences from the text, and if outputting to a tty, will convert them into
win32 function calls.
'''
ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
# The wrapped stream (normally sys.stdout or sys.stderr)
self.wrapped = wrapped
# should we reset colors to defaults after every .write()
self.autoreset = autoreset
# create the proxy wrapping our output stream
self.stream = StreamWrapper(wrapped, self)
on_windows = sys.platform.startswith('win')
# should we strip ANSI sequences from our output?
if strip is None:
strip = on_windows
self.strip = strip
# should we should convert ANSI sequences into win32 calls?
if convert is None:
convert = on_windows and is_a_tty(wrapped)
self.convert = convert
# dict of ansi codes to win32 functions and parameters
self.win32_calls = self.get_win32_calls()
# are we wrapping stderr?
self.on_stderr = self.wrapped is sys.stderr
def should_wrap(self):
'''
True if this class is actually needed. If false, then the output
stream will not be affected, nor will win32 calls be issued, so
wrapping stdout is not actually required. This will generally be
False on non-Windows platforms, unless optional functionality like
autoreset has been requested using kwargs to init()
'''
return self.convert or self.strip or self.autoreset
def get_win32_calls(self):
if self.convert and winterm:
return {
AnsiStyle.RESET_ALL: (winterm.reset_all, ),
AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
AnsiFore.RED: (winterm.fore, WinColor.RED),
AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
AnsiFore.RESET: (winterm.fore, ),
AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
AnsiBack.RED: (winterm.back, WinColor.RED),
AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
AnsiBack.WHITE: (winterm.back, WinColor.GREY),
AnsiBack.RESET: (winterm.back, ),
}
def write(self, text):
if self.strip or self.convert:
self.write_and_convert(text)
else:
self.wrapped.write(text)
self.wrapped.flush()
if self.autoreset:
self.reset_all()
def reset_all(self):
if self.convert:
self.call_win32('m', (0,))
elif not self.wrapped.closed and is_a_tty(self.wrapped):
self.wrapped.write(Style.RESET_ALL)
def write_and_convert(self, text):
'''
Write the given text to our wrapped stream, stripping any ANSI
sequences from the text, and optionally converting them into win32
calls.
'''
cursor = 0
for match in self.ANSI_RE.finditer(text):
start, end = match.span()
self.write_plain_text(text, cursor, start)
self.convert_ansi(*match.groups())
cursor = end
self.write_plain_text(text, cursor, len(text))
def write_plain_text(self, text, start, end):
if start < end:
self.wrapped.write(text[start:end])
self.wrapped.flush()
def convert_ansi(self, paramstring, command):
if self.convert:
params = self.extract_params(paramstring)
self.call_win32(command, params)
def extract_params(self, paramstring):
def split(paramstring):
for p in paramstring.split(';'):
if p != '':
yield int(p)
return tuple(split(paramstring))
def call_win32(self, command, params):
if params == []:
params = [0]
if command == 'm':
for param in params:
if param in self.win32_calls:
func_args = self.win32_calls[param]
func = func_args[0]
args = func_args[1:]
kwargs = dict(on_stderr=self.on_stderr)
func(*args, **kwargs)
elif command in ('H', 'f'): # set cursor position
func = winterm.set_cursor_position
func(params, on_stderr=self.on_stderr)
elif command in ('J'):
func = winterm.erase_data
func(params, on_stderr=self.on_stderr)
elif command == 'A':
if params == () or params == None:
num_rows = 1
else:
num_rows = params[0]
func = winterm.cursor_up
func(num_rows, on_stderr=self.on_stderr)
@@ -0,0 +1,56 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
import atexit
import sys
from .ansitowin32 import AnsiToWin32
orig_stdout = sys.stdout
orig_stderr = sys.stderr
wrapped_stdout = sys.stdout
wrapped_stderr = sys.stderr
atexit_done = False
def reset_all():
AnsiToWin32(orig_stdout).reset_all()
def init(autoreset=False, convert=None, strip=None, wrap=True):
if not wrap and any([autoreset, convert, strip]):
raise ValueError('wrap=False conflicts with any other arg=True')
global wrapped_stdout, wrapped_stderr
sys.stdout = wrapped_stdout = \
wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
sys.stderr = wrapped_stderr = \
wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
global atexit_done
if not atexit_done:
atexit.register(reset_all)
atexit_done = True
def deinit():
sys.stdout = orig_stdout
sys.stderr = orig_stderr
def reinit():
sys.stdout = wrapped_stdout
sys.stderr = wrapped_stdout
def wrap_stream(stream, convert, strip, autoreset, wrap):
if wrap:
wrapper = AnsiToWin32(stream,
convert=convert, strip=strip, autoreset=autoreset)
if wrapper.should_wrap():
stream = wrapper.stream
return stream
@@ -0,0 +1,137 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
# from winbase.h
STDOUT = -11
STDERR = -12
import ctypes
from ctypes import LibraryLoader
try:
windll = LibraryLoader(ctypes.WinDLL)
from ctypes import wintypes
except (AttributeError, ImportError):
windll = None
SetConsoleTextAttribute = lambda *_: None
else:
from ctypes import (
byref, Structure, c_char, c_short, c_uint32, c_ushort, POINTER
)
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
"""struct in wincon.h."""
_fields_ = [
("dwSize", wintypes._COORD),
("dwCursorPosition", wintypes._COORD),
("wAttributes", wintypes.WORD),
("srWindow", wintypes.SMALL_RECT),
("dwMaximumWindowSize", wintypes._COORD),
]
def __str__(self):
return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
self.dwSize.Y, self.dwSize.X
, self.dwCursorPosition.Y, self.dwCursorPosition.X
, self.wAttributes
, self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
, self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
)
_GetStdHandle = windll.kernel32.GetStdHandle
_GetStdHandle.argtypes = [
wintypes.DWORD,
]
_GetStdHandle.restype = wintypes.HANDLE
_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
_GetConsoleScreenBufferInfo.argtypes = [
wintypes.HANDLE,
POINTER(CONSOLE_SCREEN_BUFFER_INFO),
]
_GetConsoleScreenBufferInfo.restype = wintypes.BOOL
_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
_SetConsoleTextAttribute.argtypes = [
wintypes.HANDLE,
wintypes.WORD,
]
_SetConsoleTextAttribute.restype = wintypes.BOOL
_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
_SetConsoleCursorPosition.argtypes = [
wintypes.HANDLE,
wintypes._COORD,
]
_SetConsoleCursorPosition.restype = wintypes.BOOL
_FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
_FillConsoleOutputCharacterA.argtypes = [
wintypes.HANDLE,
c_char,
wintypes.DWORD,
wintypes._COORD,
POINTER(wintypes.DWORD),
]
_FillConsoleOutputCharacterA.restype = wintypes.BOOL
_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
_FillConsoleOutputAttribute.argtypes = [
wintypes.HANDLE,
wintypes.WORD,
wintypes.DWORD,
wintypes._COORD,
POINTER(wintypes.DWORD),
]
_FillConsoleOutputAttribute.restype = wintypes.BOOL
handles = {
STDOUT: _GetStdHandle(STDOUT),
STDERR: _GetStdHandle(STDERR),
}
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
handle = handles[stream_id]
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = _GetConsoleScreenBufferInfo(
handle, byref(csbi))
return csbi
def SetConsoleTextAttribute(stream_id, attrs):
handle = handles[stream_id]
return _SetConsoleTextAttribute(handle, attrs)
def SetConsoleCursorPosition(stream_id, position):
position = wintypes._COORD(*position)
# If the position is out of range, do nothing.
if position.Y <= 0 or position.X <= 0:
return
# Adjust for Windows' SetConsoleCursorPosition:
# 1. being 0-based, while ANSI is 1-based.
# 2. expecting (x,y), while ANSI uses (y,x).
adjusted_position = wintypes._COORD(position.Y - 1, position.X - 1)
# Adjust for viewport's scroll position
sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
adjusted_position.Y += sr.Top
adjusted_position.X += sr.Left
# Resume normal processing
handle = handles[stream_id]
return _SetConsoleCursorPosition(handle, adjusted_position)
def FillConsoleOutputCharacter(stream_id, char, length, start):
handle = handles[stream_id]
char = c_char(char)
length = wintypes.DWORD(length)
num_written = wintypes.DWORD(0)
# Note that this is hard-coded for ANSI (vs wide) bytes.
success = _FillConsoleOutputCharacterA(
handle, char, length, start, byref(num_written))
return num_written.value
def FillConsoleOutputAttribute(stream_id, attr, length, start):
''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
handle = handles[stream_id]
attribute = wintypes.WORD(attr)
length = wintypes.DWORD(length)
num_written = wintypes.DWORD(0)
# Note that this is hard-coded for ANSI (vs wide) bytes.
return _FillConsoleOutputAttribute(
handle, attribute, length, start, byref(num_written))
@@ -0,0 +1,120 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
from . import win32
# from wincon.h
class WinColor(object):
BLACK = 0
BLUE = 1
GREEN = 2
CYAN = 3
RED = 4
MAGENTA = 5
YELLOW = 6
GREY = 7
# from wincon.h
class WinStyle(object):
NORMAL = 0x00 # dim text, dim background
BRIGHT = 0x08 # bright text, dim background
class WinTerm(object):
def __init__(self):
self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
self.set_attrs(self._default)
self._default_fore = self._fore
self._default_back = self._back
self._default_style = self._style
def get_attrs(self):
return self._fore + self._back * 16 + self._style
def set_attrs(self, value):
self._fore = value & 7
self._back = (value >> 4) & 7
self._style = value & WinStyle.BRIGHT
def reset_all(self, on_stderr=None):
self.set_attrs(self._default)
self.set_console(attrs=self._default)
def fore(self, fore=None, on_stderr=False):
if fore is None:
fore = self._default_fore
self._fore = fore
self.set_console(on_stderr=on_stderr)
def back(self, back=None, on_stderr=False):
if back is None:
back = self._default_back
self._back = back
self.set_console(on_stderr=on_stderr)
def style(self, style=None, on_stderr=False):
if style is None:
style = self._default_style
self._style = style
self.set_console(on_stderr=on_stderr)
def set_console(self, attrs=None, on_stderr=False):
if attrs is None:
attrs = self.get_attrs()
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
win32.SetConsoleTextAttribute(handle, attrs)
def get_position(self, handle):
position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
# Because Windows coordinates are 0-based,
# and win32.SetConsoleCursorPosition expects 1-based.
position.X += 1
position.Y += 1
return position
def set_cursor_position(self, position=None, on_stderr=False):
if position is None:
#I'm not currently tracking the position, so there is no default.
#position = self.get_position()
return
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
win32.SetConsoleCursorPosition(handle, position)
def cursor_up(self, num_rows=0, on_stderr=False):
if num_rows == 0:
return
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
position = self.get_position(handle)
adjusted_position = (position.Y - num_rows, position.X)
self.set_cursor_position(adjusted_position, on_stderr)
def erase_data(self, mode=0, on_stderr=False):
# 0 (or None) should clear from the cursor to the end of the screen.
# 1 should clear from the cursor to the beginning of the screen.
# 2 should clear the entire screen. (And maybe move cursor to (1,1)?)
#
# At the moment, I only support mode 2. From looking at the API, it
# should be possible to calculate a different number of bytes to clear,
# and to do so relative to the cursor position.
if mode[0] not in (2,):
return
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
# here's where we'll home the cursor
coord_screen = win32.COORD(0,0)
csbi = win32.GetConsoleScreenBufferInfo(handle)
# get the number of character cells in the current buffer
dw_con_size = csbi.dwSize.X * csbi.dwSize.Y
# fill the entire screen with blanks
win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen)
# now set the buffer's attributes accordingly
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen );
# put the cursor at (0, 0)
win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y))
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2014 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
import logging
__version__ = '0.1.8'
class DistlibException(Exception):
pass
try:
from logging import NullHandler
except ImportError: # pragma: no cover
class NullHandler(logging.Handler):
def handle(self, record): pass
def emit(self, record): pass
def createLock(self): self.lock = None
logger = logging.getLogger(__name__)
logger.addHandler(NullHandler())
@@ -0,0 +1,6 @@
"""Modules copied from Python 3 standard libraries, for internal use only.
Individual classes and functions are found in d2._backport.misc. Intended
usage is to always import things missing from 3.1 from that module: the
built-in/stdlib objects will be used if found.
"""
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""Backports for individual classes and functions."""
import os
import sys
__all__ = ['cache_from_source', 'callable', 'fsencode']
try:
from imp import cache_from_source
except ImportError:
def cache_from_source(py_file, debug=__debug__):
ext = debug and 'c' or 'o'
return py_file + ext
try:
callable = callable
except NameError:
from collections import Callable
def callable(obj):
return isinstance(obj, Callable)
try:
fsencode = os.fsencode
except AttributeError:
def fsencode(filename):
if isinstance(filename, bytes):
return filename
elif isinstance(filename, str):
return filename.encode(sys.getfilesystemencoding())
else:
raise TypeError("expect bytes or str, not %s" %
type(filename).__name__)
@@ -0,0 +1,761 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""Utility functions for copying and archiving files and directory trees.
XXX The functions here don't copy the resource fork or other metadata on Mac.
"""
import os
import sys
import stat
from os.path import abspath
import fnmatch
import collections
import errno
from . import tarfile
try:
import bz2
_BZ2_SUPPORTED = True
except ImportError:
_BZ2_SUPPORTED = False
try:
from pwd import getpwnam
except ImportError:
getpwnam = None
try:
from grp import getgrnam
except ImportError:
getgrnam = None
__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
"copytree", "move", "rmtree", "Error", "SpecialFileError",
"ExecError", "make_archive", "get_archive_formats",
"register_archive_format", "unregister_archive_format",
"get_unpack_formats", "register_unpack_format",
"unregister_unpack_format", "unpack_archive", "ignore_patterns"]
class Error(EnvironmentError):
pass
class SpecialFileError(EnvironmentError):
"""Raised when trying to do a kind of operation (e.g. copying) which is
not supported on a special file (e.g. a named pipe)"""
class ExecError(EnvironmentError):
"""Raised when a command could not be executed"""
class ReadError(EnvironmentError):
"""Raised when an archive cannot be read"""
class RegistryError(Exception):
"""Raised when a registery operation with the archiving
and unpacking registeries fails"""
try:
WindowsError
except NameError:
WindowsError = None
def copyfileobj(fsrc, fdst, length=16*1024):
"""copy data from file-like object fsrc to file-like object fdst"""
while 1:
buf = fsrc.read(length)
if not buf:
break
fdst.write(buf)
def _samefile(src, dst):
# Macintosh, Unix.
if hasattr(os.path, 'samefile'):
try:
return os.path.samefile(src, dst)
except OSError:
return False
# All other platforms: check for same pathname.
return (os.path.normcase(os.path.abspath(src)) ==
os.path.normcase(os.path.abspath(dst)))
def copyfile(src, dst):
"""Copy data from src to dst"""
if _samefile(src, dst):
raise Error("`%s` and `%s` are the same file" % (src, dst))
for fn in [src, dst]:
try:
st = os.stat(fn)
except OSError:
# File most likely does not exist
pass
else:
# XXX What about other special files? (sockets, devices...)
if stat.S_ISFIFO(st.st_mode):
raise SpecialFileError("`%s` is a named pipe" % fn)
with open(src, 'rb') as fsrc:
with open(dst, 'wb') as fdst:
copyfileobj(fsrc, fdst)
def copymode(src, dst):
"""Copy mode bits from src to dst"""
if hasattr(os, 'chmod'):
st = os.stat(src)
mode = stat.S_IMODE(st.st_mode)
os.chmod(dst, mode)
def copystat(src, dst):
"""Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
st = os.stat(src)
mode = stat.S_IMODE(st.st_mode)
if hasattr(os, 'utime'):
os.utime(dst, (st.st_atime, st.st_mtime))
if hasattr(os, 'chmod'):
os.chmod(dst, mode)
if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
try:
os.chflags(dst, st.st_flags)
except OSError as why:
if (not hasattr(errno, 'EOPNOTSUPP') or
why.errno != errno.EOPNOTSUPP):
raise
def copy(src, dst):
"""Copy data and mode bits ("cp src dst").
The destination may be a directory.
"""
if os.path.isdir(dst):
dst = os.path.join(dst, os.path.basename(src))
copyfile(src, dst)
copymode(src, dst)
def copy2(src, dst):
"""Copy data and all stat info ("cp -p src dst").
The destination may be a directory.
"""
if os.path.isdir(dst):
dst = os.path.join(dst, os.path.basename(src))
copyfile(src, dst)
copystat(src, dst)
def ignore_patterns(*patterns):
"""Function that can be used as copytree() ignore parameter.
Patterns is a sequence of glob-style patterns
that are used to exclude files"""
def _ignore_patterns(path, names):
ignored_names = []
for pattern in patterns:
ignored_names.extend(fnmatch.filter(names, pattern))
return set(ignored_names)
return _ignore_patterns
def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
ignore_dangling_symlinks=False):
"""Recursively copy a directory tree.
The destination directory must not already exist.
If exception(s) occur, an Error is raised with a list of reasons.
If the optional symlinks flag is true, symbolic links in the
source tree result in symbolic links in the destination tree; if
it is false, the contents of the files pointed to by symbolic
links are copied. If the file pointed by the symlink doesn't
exist, an exception will be added in the list of errors raised in
an Error exception at the end of the copy process.
You can set the optional ignore_dangling_symlinks flag to true if you
want to silence this exception. Notice that this has no effect on
platforms that don't support os.symlink.
The optional ignore argument is a callable. If given, it
is called with the `src` parameter, which is the directory
being visited by copytree(), and `names` which is the list of
`src` contents, as returned by os.listdir():
callable(src, names) -> ignored_names
Since copytree() is called recursively, the callable will be
called once for each directory that is copied. It returns a
list of names relative to the `src` directory that should
not be copied.
The optional copy_function argument is a callable that will be used
to copy each file. It will be called with the source path and the
destination path as arguments. By default, copy2() is used, but any
function that supports the same signature (like copy()) can be used.
"""
names = os.listdir(src)
if ignore is not None:
ignored_names = ignore(src, names)
else:
ignored_names = set()
os.makedirs(dst)
errors = []
for name in names:
if name in ignored_names:
continue
srcname = os.path.join(src, name)
dstname = os.path.join(dst, name)
try:
if os.path.islink(srcname):
linkto = os.readlink(srcname)
if symlinks:
os.symlink(linkto, dstname)
else:
# ignore dangling symlink if the flag is on
if not os.path.exists(linkto) and ignore_dangling_symlinks:
continue
# otherwise let the copy occurs. copy2 will raise an error
copy_function(srcname, dstname)
elif os.path.isdir(srcname):
copytree(srcname, dstname, symlinks, ignore, copy_function)
else:
# Will raise a SpecialFileError for unsupported file types
copy_function(srcname, dstname)
# catch the Error from the recursive copytree so that we can
# continue with other files
except Error as err:
errors.extend(err.args[0])
except EnvironmentError as why:
errors.append((srcname, dstname, str(why)))
try:
copystat(src, dst)
except OSError as why:
if WindowsError is not None and isinstance(why, WindowsError):
# Copying file access times may fail on Windows
pass
else:
errors.extend((src, dst, str(why)))
if errors:
raise Error(errors)
def rmtree(path, ignore_errors=False, onerror=None):
"""Recursively delete a directory tree.
If ignore_errors is set, errors are ignored; otherwise, if onerror
is set, it is called to handle the error with arguments (func,
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
path is the argument to that function that caused it to fail; and
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
is false and onerror is None, an exception is raised.
"""
if ignore_errors:
def onerror(*args):
pass
elif onerror is None:
def onerror(*args):
raise
try:
if os.path.islink(path):
# symlinks to directories are forbidden, see bug #1669
raise OSError("Cannot call rmtree on a symbolic link")
except OSError:
onerror(os.path.islink, path, sys.exc_info())
# can't continue even if onerror hook returns
return
names = []
try:
names = os.listdir(path)
except os.error:
onerror(os.listdir, path, sys.exc_info())
for name in names:
fullname = os.path.join(path, name)
try:
mode = os.lstat(fullname).st_mode
except os.error:
mode = 0
if stat.S_ISDIR(mode):
rmtree(fullname, ignore_errors, onerror)
else:
try:
os.remove(fullname)
except os.error:
onerror(os.remove, fullname, sys.exc_info())
try:
os.rmdir(path)
except os.error:
onerror(os.rmdir, path, sys.exc_info())
def _basename(path):
# A basename() variant which first strips the trailing slash, if present.
# Thus we always get the last component of the path, even for directories.
return os.path.basename(path.rstrip(os.path.sep))
def move(src, dst):
"""Recursively move a file or directory to another location. This is
similar to the Unix "mv" command.
If the destination is a directory or a symlink to a directory, the source
is moved inside the directory. The destination path must not already
exist.
If the destination already exists but is not a directory, it may be
overwritten depending on os.rename() semantics.
If the destination is on our current filesystem, then rename() is used.
Otherwise, src is copied to the destination and then removed.
A lot more could be done here... A look at a mv.c shows a lot of
the issues this implementation glosses over.
"""
real_dst = dst
if os.path.isdir(dst):
if _samefile(src, dst):
# We might be on a case insensitive filesystem,
# perform the rename anyway.
os.rename(src, dst)
return
real_dst = os.path.join(dst, _basename(src))
if os.path.exists(real_dst):
raise Error("Destination path '%s' already exists" % real_dst)
try:
os.rename(src, real_dst)
except OSError:
if os.path.isdir(src):
if _destinsrc(src, dst):
raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
copytree(src, real_dst, symlinks=True)
rmtree(src)
else:
copy2(src, real_dst)
os.unlink(src)
def _destinsrc(src, dst):
src = abspath(src)
dst = abspath(dst)
if not src.endswith(os.path.sep):
src += os.path.sep
if not dst.endswith(os.path.sep):
dst += os.path.sep
return dst.startswith(src)
def _get_gid(name):
"""Returns a gid, given a group name."""
if getgrnam is None or name is None:
return None
try:
result = getgrnam(name)
except KeyError:
result = None
if result is not None:
return result[2]
return None
def _get_uid(name):
"""Returns an uid, given a user name."""
if getpwnam is None or name is None:
return None
try:
result = getpwnam(name)
except KeyError:
result = None
if result is not None:
return result[2]
return None
def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
owner=None, group=None, logger=None):
"""Create a (possibly compressed) tar file from all the files under
'base_dir'.
'compress' must be "gzip" (the default), "bzip2", or None.
'owner' and 'group' can be used to define an owner and a group for the
archive that is being built. If not provided, the current owner and group
will be used.
The output tar file will be named 'base_name' + ".tar", possibly plus
the appropriate compression extension (".gz", or ".bz2").
Returns the output filename.
"""
tar_compression = {'gzip': 'gz', None: ''}
compress_ext = {'gzip': '.gz'}
if _BZ2_SUPPORTED:
tar_compression['bzip2'] = 'bz2'
compress_ext['bzip2'] = '.bz2'
# flags for compression program, each element of list will be an argument
if compress is not None and compress not in compress_ext:
raise ValueError("bad value for 'compress', or compression format not "
"supported : {0}".format(compress))
archive_name = base_name + '.tar' + compress_ext.get(compress, '')
archive_dir = os.path.dirname(archive_name)
if not os.path.exists(archive_dir):
if logger is not None:
logger.info("creating %s", archive_dir)
if not dry_run:
os.makedirs(archive_dir)
# creating the tarball
if logger is not None:
logger.info('Creating tar archive')
uid = _get_uid(owner)
gid = _get_gid(group)
def _set_uid_gid(tarinfo):
if gid is not None:
tarinfo.gid = gid
tarinfo.gname = group
if uid is not None:
tarinfo.uid = uid
tarinfo.uname = owner
return tarinfo
if not dry_run:
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
try:
tar.add(base_dir, filter=_set_uid_gid)
finally:
tar.close()
return archive_name
def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
# XXX see if we want to keep an external call here
if verbose:
zipoptions = "-r"
else:
zipoptions = "-rq"
from distutils.errors import DistutilsExecError
from distutils.spawn import spawn
try:
spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
except DistutilsExecError:
# XXX really should distinguish between "couldn't find
# external 'zip' command" and "zip failed".
raise ExecError("unable to create zip file '%s': "
"could neither import the 'zipfile' module nor "
"find a standalone zip utility") % zip_filename
def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
"""Create a zip file from all the files under 'base_dir'.
The output zip file will be named 'base_name' + ".zip". Uses either the
"zipfile" Python module (if available) or the InfoZIP "zip" utility
(if installed and found on the default search path). If neither tool is
available, raises ExecError. Returns the name of the output zip
file.
"""
zip_filename = base_name + ".zip"
archive_dir = os.path.dirname(base_name)
if not os.path.exists(archive_dir):
if logger is not None:
logger.info("creating %s", archive_dir)
if not dry_run:
os.makedirs(archive_dir)
# If zipfile module is not available, try spawning an external 'zip'
# command.
try:
import zipfile
except ImportError:
zipfile = None
if zipfile is None:
_call_external_zip(base_dir, zip_filename, verbose, dry_run)
else:
if logger is not None:
logger.info("creating '%s' and adding '%s' to it",
zip_filename, base_dir)
if not dry_run:
zip = zipfile.ZipFile(zip_filename, "w",
compression=zipfile.ZIP_DEFLATED)
for dirpath, dirnames, filenames in os.walk(base_dir):
for name in filenames:
path = os.path.normpath(os.path.join(dirpath, name))
if os.path.isfile(path):
zip.write(path, path)
if logger is not None:
logger.info("adding '%s'", path)
zip.close()
return zip_filename
_ARCHIVE_FORMATS = {
'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
'zip': (_make_zipfile, [], "ZIP file"),
}
if _BZ2_SUPPORTED:
_ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
"bzip2'ed tar-file")
def get_archive_formats():
"""Returns a list of supported formats for archiving and unarchiving.
Each element of the returned sequence is a tuple (name, description)
"""
formats = [(name, registry[2]) for name, registry in
_ARCHIVE_FORMATS.items()]
formats.sort()
return formats
def register_archive_format(name, function, extra_args=None, description=''):
"""Registers an archive format.
name is the name of the format. function is the callable that will be
used to create archives. If provided, extra_args is a sequence of
(name, value) tuples that will be passed as arguments to the callable.
description can be provided to describe the format, and will be returned
by the get_archive_formats() function.
"""
if extra_args is None:
extra_args = []
if not isinstance(function, collections.Callable):
raise TypeError('The %s object is not callable' % function)
if not isinstance(extra_args, (tuple, list)):
raise TypeError('extra_args needs to be a sequence')
for element in extra_args:
if not isinstance(element, (tuple, list)) or len(element) !=2:
raise TypeError('extra_args elements are : (arg_name, value)')
_ARCHIVE_FORMATS[name] = (function, extra_args, description)
def unregister_archive_format(name):
del _ARCHIVE_FORMATS[name]
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
dry_run=0, owner=None, group=None, logger=None):
"""Create an archive file (eg. zip or tar).
'base_name' is the name of the file to create, minus any format-specific
extension; 'format' is the archive format: one of "zip", "tar", "bztar"
or "gztar".
'root_dir' is a directory that will be the root directory of the
archive; ie. we typically chdir into 'root_dir' before creating the
archive. 'base_dir' is the directory where we start archiving from;
ie. 'base_dir' will be the common prefix of all files and
directories in the archive. 'root_dir' and 'base_dir' both default
to the current directory. Returns the name of the archive file.
'owner' and 'group' are used when creating a tar archive. By default,
uses the current owner and group.
"""
save_cwd = os.getcwd()
if root_dir is not None:
if logger is not None:
logger.debug("changing into '%s'", root_dir)
base_name = os.path.abspath(base_name)
if not dry_run:
os.chdir(root_dir)
if base_dir is None:
base_dir = os.curdir
kwargs = {'dry_run': dry_run, 'logger': logger}
try:
format_info = _ARCHIVE_FORMATS[format]
except KeyError:
raise ValueError("unknown archive format '%s'" % format)
func = format_info[0]
for arg, val in format_info[1]:
kwargs[arg] = val
if format != 'zip':
kwargs['owner'] = owner
kwargs['group'] = group
try:
filename = func(base_name, base_dir, **kwargs)
finally:
if root_dir is not None:
if logger is not None:
logger.debug("changing back to '%s'", save_cwd)
os.chdir(save_cwd)
return filename
def get_unpack_formats():
"""Returns a list of supported formats for unpacking.
Each element of the returned sequence is a tuple
(name, extensions, description)
"""
formats = [(name, info[0], info[3]) for name, info in
_UNPACK_FORMATS.items()]
formats.sort()
return formats
def _check_unpack_options(extensions, function, extra_args):
"""Checks what gets registered as an unpacker."""
# first make sure no other unpacker is registered for this extension
existing_extensions = {}
for name, info in _UNPACK_FORMATS.items():
for ext in info[0]:
existing_extensions[ext] = name
for extension in extensions:
if extension in existing_extensions:
msg = '%s is already registered for "%s"'
raise RegistryError(msg % (extension,
existing_extensions[extension]))
if not isinstance(function, collections.Callable):
raise TypeError('The registered function must be a callable')
def register_unpack_format(name, extensions, function, extra_args=None,
description=''):
"""Registers an unpack format.
`name` is the name of the format. `extensions` is a list of extensions
corresponding to the format.
`function` is the callable that will be
used to unpack archives. The callable will receive archives to unpack.
If it's unable to handle an archive, it needs to raise a ReadError
exception.
If provided, `extra_args` is a sequence of
(name, value) tuples that will be passed as arguments to the callable.
description can be provided to describe the format, and will be returned
by the get_unpack_formats() function.
"""
if extra_args is None:
extra_args = []
_check_unpack_options(extensions, function, extra_args)
_UNPACK_FORMATS[name] = extensions, function, extra_args, description
def unregister_unpack_format(name):
"""Removes the pack format from the registery."""
del _UNPACK_FORMATS[name]
def _ensure_directory(path):
"""Ensure that the parent directory of `path` exists"""
dirname = os.path.dirname(path)
if not os.path.isdir(dirname):
os.makedirs(dirname)
def _unpack_zipfile(filename, extract_dir):
"""Unpack zip `filename` to `extract_dir`
"""
try:
import zipfile
except ImportError:
raise ReadError('zlib not supported, cannot unpack this archive.')
if not zipfile.is_zipfile(filename):
raise ReadError("%s is not a zip file" % filename)
zip = zipfile.ZipFile(filename)
try:
for info in zip.infolist():
name = info.filename
# don't extract absolute paths or ones with .. in them
if name.startswith('/') or '..' in name:
continue
target = os.path.join(extract_dir, *name.split('/'))
if not target:
continue
_ensure_directory(target)
if not name.endswith('/'):
# file
data = zip.read(info.filename)
f = open(target, 'wb')
try:
f.write(data)
finally:
f.close()
del data
finally:
zip.close()
def _unpack_tarfile(filename, extract_dir):
"""Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
"""
try:
tarobj = tarfile.open(filename)
except tarfile.TarError:
raise ReadError(
"%s is not a compressed or uncompressed tar file" % filename)
try:
tarobj.extractall(extract_dir)
finally:
tarobj.close()
_UNPACK_FORMATS = {
'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"),
'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
'zip': (['.zip'], _unpack_zipfile, [], "ZIP file")
}
if _BZ2_SUPPORTED:
_UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [],
"bzip2'ed tar-file")
def _find_unpack_format(filename):
for name, info in _UNPACK_FORMATS.items():
for extension in info[0]:
if filename.endswith(extension):
return name
return None
def unpack_archive(filename, extract_dir=None, format=None):
"""Unpack an archive.
`filename` is the name of the archive.
`extract_dir` is the name of the target directory, where the archive
is unpacked. If not provided, the current working directory is used.
`format` is the archive format: one of "zip", "tar", or "gztar". Or any
other registered format. If not provided, unpack_archive will use the
filename extension and see if an unpacker was registered for that
extension.
In case none is found, a ValueError is raised.
"""
if extract_dir is None:
extract_dir = os.getcwd()
if format is not None:
try:
format_info = _UNPACK_FORMATS[format]
except KeyError:
raise ValueError("Unknown unpack format '{0}'".format(format))
func = format_info[1]
func(filename, extract_dir, **dict(format_info[2]))
else:
# we need to look at the registered unpackers supported extensions
format = _find_unpack_format(filename)
if format is None:
raise ReadError("Unknown archive format '{0}'".format(filename))
func = _UNPACK_FORMATS[format][1]
kwargs = dict(_UNPACK_FORMATS[format][2])
func(filename, extract_dir, **kwargs)
@@ -0,0 +1,84 @@
[posix_prefix]
# Configuration directories. Some of these come straight out of the
# configure script. They are for implementing the other variables, not to
# be used directly in [resource_locations].
confdir = /etc
datadir = /usr/share
libdir = /usr/lib
statedir = /var
# User resource directory
local = ~/.local/{distribution.name}
stdlib = {base}/lib/python{py_version_short}
platstdlib = {platbase}/lib/python{py_version_short}
purelib = {base}/lib/python{py_version_short}/site-packages
platlib = {platbase}/lib/python{py_version_short}/site-packages
include = {base}/include/python{py_version_short}{abiflags}
platinclude = {platbase}/include/python{py_version_short}{abiflags}
data = {base}
[posix_home]
stdlib = {base}/lib/python
platstdlib = {base}/lib/python
purelib = {base}/lib/python
platlib = {base}/lib/python
include = {base}/include/python
platinclude = {base}/include/python
scripts = {base}/bin
data = {base}
[nt]
stdlib = {base}/Lib
platstdlib = {base}/Lib
purelib = {base}/Lib/site-packages
platlib = {base}/Lib/site-packages
include = {base}/Include
platinclude = {base}/Include
scripts = {base}/Scripts
data = {base}
[os2]
stdlib = {base}/Lib
platstdlib = {base}/Lib
purelib = {base}/Lib/site-packages
platlib = {base}/Lib/site-packages
include = {base}/Include
platinclude = {base}/Include
scripts = {base}/Scripts
data = {base}
[os2_home]
stdlib = {userbase}/lib/python{py_version_short}
platstdlib = {userbase}/lib/python{py_version_short}
purelib = {userbase}/lib/python{py_version_short}/site-packages
platlib = {userbase}/lib/python{py_version_short}/site-packages
include = {userbase}/include/python{py_version_short}
scripts = {userbase}/bin
data = {userbase}
[nt_user]
stdlib = {userbase}/Python{py_version_nodot}
platstdlib = {userbase}/Python{py_version_nodot}
purelib = {userbase}/Python{py_version_nodot}/site-packages
platlib = {userbase}/Python{py_version_nodot}/site-packages
include = {userbase}/Python{py_version_nodot}/Include
scripts = {userbase}/Scripts
data = {userbase}
[posix_user]
stdlib = {userbase}/lib/python{py_version_short}
platstdlib = {userbase}/lib/python{py_version_short}
purelib = {userbase}/lib/python{py_version_short}/site-packages
platlib = {userbase}/lib/python{py_version_short}/site-packages
include = {userbase}/include/python{py_version_short}
scripts = {userbase}/bin
data = {userbase}
[osx_framework_user]
stdlib = {userbase}/lib/python
platstdlib = {userbase}/lib/python
purelib = {userbase}/lib/python/site-packages
platlib = {userbase}/lib/python/site-packages
include = {userbase}/include
scripts = {userbase}/bin
data = {userbase}
@@ -0,0 +1,788 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""Access to Python's configuration information."""
import codecs
import os
import re
import sys
from os.path import pardir, realpath
try:
import configparser
except ImportError:
import ConfigParser as configparser
__all__ = [
'get_config_h_filename',
'get_config_var',
'get_config_vars',
'get_makefile_filename',
'get_path',
'get_path_names',
'get_paths',
'get_platform',
'get_python_version',
'get_scheme_names',
'parse_config_h',
]
def _safe_realpath(path):
try:
return realpath(path)
except OSError:
return path
if sys.executable:
_PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
else:
# sys.executable can be empty if argv[0] has been changed and Python is
# unable to retrieve the real program name
_PROJECT_BASE = _safe_realpath(os.getcwd())
if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower():
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir))
# PC/VS7.1
if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
# PC/AMD64
if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
def is_python_build():
for fn in ("Setup.dist", "Setup.local"):
if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
return True
return False
_PYTHON_BUILD = is_python_build()
_cfg_read = False
def _ensure_cfg_read():
global _cfg_read
if not _cfg_read:
from ..resources import finder
backport_package = __name__.rsplit('.', 1)[0]
_finder = finder(backport_package)
_cfgfile = _finder.find('sysconfig.cfg')
assert _cfgfile, 'sysconfig.cfg exists'
with _cfgfile.as_stream() as s:
_SCHEMES.readfp(s)
if _PYTHON_BUILD:
for scheme in ('posix_prefix', 'posix_home'):
_SCHEMES.set(scheme, 'include', '{srcdir}/Include')
_SCHEMES.set(scheme, 'platinclude', '{projectbase}/.')
_cfg_read = True
_SCHEMES = configparser.RawConfigParser()
_VAR_REPL = re.compile(r'\{([^{]*?)\}')
def _expand_globals(config):
_ensure_cfg_read()
if config.has_section('globals'):
globals = config.items('globals')
else:
globals = tuple()
sections = config.sections()
for section in sections:
if section == 'globals':
continue
for option, value in globals:
if config.has_option(section, option):
continue
config.set(section, option, value)
config.remove_section('globals')
# now expanding local variables defined in the cfg file
#
for section in config.sections():
variables = dict(config.items(section))
def _replacer(matchobj):
name = matchobj.group(1)
if name in variables:
return variables[name]
return matchobj.group(0)
for option, value in config.items(section):
config.set(section, option, _VAR_REPL.sub(_replacer, value))
#_expand_globals(_SCHEMES)
# FIXME don't rely on sys.version here, its format is an implementation detail
# of CPython, use sys.version_info or sys.hexversion
_PY_VERSION = sys.version.split()[0]
_PY_VERSION_SHORT = sys.version[:3]
_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[2]
_PREFIX = os.path.normpath(sys.prefix)
_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
_CONFIG_VARS = None
_USER_BASE = None
def _subst_vars(path, local_vars):
"""In the string `path`, replace tokens like {some.thing} with the
corresponding value from the map `local_vars`.
If there is no corresponding value, leave the token unchanged.
"""
def _replacer(matchobj):
name = matchobj.group(1)
if name in local_vars:
return local_vars[name]
elif name in os.environ:
return os.environ[name]
return matchobj.group(0)
return _VAR_REPL.sub(_replacer, path)
def _extend_dict(target_dict, other_dict):
target_keys = target_dict.keys()
for key, value in other_dict.items():
if key in target_keys:
continue
target_dict[key] = value
def _expand_vars(scheme, vars):
res = {}
if vars is None:
vars = {}
_extend_dict(vars, get_config_vars())
for key, value in _SCHEMES.items(scheme):
if os.name in ('posix', 'nt'):
value = os.path.expanduser(value)
res[key] = os.path.normpath(_subst_vars(value, vars))
return res
def format_value(value, vars):
def _replacer(matchobj):
name = matchobj.group(1)
if name in vars:
return vars[name]
return matchobj.group(0)
return _VAR_REPL.sub(_replacer, value)
def _get_default_scheme():
if os.name == 'posix':
# the default scheme for posix is posix_prefix
return 'posix_prefix'
return os.name
def _getuserbase():
env_base = os.environ.get("PYTHONUSERBASE", None)
def joinuser(*args):
return os.path.expanduser(os.path.join(*args))
# what about 'os2emx', 'riscos' ?
if os.name == "nt":
base = os.environ.get("APPDATA") or "~"
if env_base:
return env_base
else:
return joinuser(base, "Python")
if sys.platform == "darwin":
framework = get_config_var("PYTHONFRAMEWORK")
if framework:
if env_base:
return env_base
else:
return joinuser("~", "Library", framework, "%d.%d" %
sys.version_info[:2])
if env_base:
return env_base
else:
return joinuser("~", ".local")
def _parse_makefile(filename, vars=None):
"""Parse a Makefile-style file.
A dictionary containing name/value pairs is returned. If an
optional dictionary is passed in as the second argument, it is
used instead of a new dictionary.
"""
# Regexes needed for parsing Makefile (and similar syntaxes,
# like old-style Setup files).
_variable_rx = re.compile("([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
if vars is None:
vars = {}
done = {}
notdone = {}
with codecs.open(filename, encoding='utf-8', errors="surrogateescape") as f:
lines = f.readlines()
for line in lines:
if line.startswith('#') or line.strip() == '':
continue
m = _variable_rx.match(line)
if m:
n, v = m.group(1, 2)
v = v.strip()
# `$$' is a literal `$' in make
tmpv = v.replace('$$', '')
if "$" in tmpv:
notdone[n] = v
else:
try:
v = int(v)
except ValueError:
# insert literal `$'
done[n] = v.replace('$$', '$')
else:
done[n] = v
# do variable interpolation here
variables = list(notdone.keys())
# Variables with a 'PY_' prefix in the makefile. These need to
# be made available without that prefix through sysconfig.
# Special care is needed to ensure that variable expansion works, even
# if the expansion uses the name without a prefix.
renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
while len(variables) > 0:
for name in tuple(variables):
value = notdone[name]
m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
if m is not None:
n = m.group(1)
found = True
if n in done:
item = str(done[n])
elif n in notdone:
# get it on a subsequent round
found = False
elif n in os.environ:
# do it like make: fall back to environment
item = os.environ[n]
elif n in renamed_variables:
if (name.startswith('PY_') and
name[3:] in renamed_variables):
item = ""
elif 'PY_' + n in notdone:
found = False
else:
item = str(done['PY_' + n])
else:
done[n] = item = ""
if found:
after = value[m.end():]
value = value[:m.start()] + item + after
if "$" in after:
notdone[name] = value
else:
try:
value = int(value)
except ValueError:
done[name] = value.strip()
else:
done[name] = value
variables.remove(name)
if (name.startswith('PY_') and
name[3:] in renamed_variables):
name = name[3:]
if name not in done:
done[name] = value
else:
# bogus variable reference (e.g. "prefix=$/opt/python");
# just drop it since we can't deal
done[name] = value
variables.remove(name)
# strip spurious spaces
for k, v in done.items():
if isinstance(v, str):
done[k] = v.strip()
# save the results in the global dictionary
vars.update(done)
return vars
def get_makefile_filename():
"""Return the path of the Makefile."""
if _PYTHON_BUILD:
return os.path.join(_PROJECT_BASE, "Makefile")
if hasattr(sys, 'abiflags'):
config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags)
else:
config_dir_name = 'config'
return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
def _init_posix(vars):
"""Initialize the module as appropriate for POSIX systems."""
# load the installed Makefile:
makefile = get_makefile_filename()
try:
_parse_makefile(makefile, vars)
except IOError as e:
msg = "invalid Python installation: unable to open %s" % makefile
if hasattr(e, "strerror"):
msg = msg + " (%s)" % e.strerror
raise IOError(msg)
# load the installed pyconfig.h:
config_h = get_config_h_filename()
try:
with open(config_h) as f:
parse_config_h(f, vars)
except IOError as e:
msg = "invalid Python installation: unable to open %s" % config_h
if hasattr(e, "strerror"):
msg = msg + " (%s)" % e.strerror
raise IOError(msg)
# On AIX, there are wrong paths to the linker scripts in the Makefile
# -- these paths are relative to the Python source, but when installed
# the scripts are in another directory.
if _PYTHON_BUILD:
vars['LDSHARED'] = vars['BLDSHARED']
def _init_non_posix(vars):
"""Initialize the module as appropriate for NT"""
# set basic install directories
vars['LIBDEST'] = get_path('stdlib')
vars['BINLIBDEST'] = get_path('platstdlib')
vars['INCLUDEPY'] = get_path('include')
vars['SO'] = '.pyd'
vars['EXE'] = '.exe'
vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
#
# public APIs
#
def parse_config_h(fp, vars=None):
"""Parse a config.h-style file.
A dictionary containing name/value pairs is returned. If an
optional dictionary is passed in as the second argument, it is
used instead of a new dictionary.
"""
if vars is None:
vars = {}
define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
while True:
line = fp.readline()
if not line:
break
m = define_rx.match(line)
if m:
n, v = m.group(1, 2)
try:
v = int(v)
except ValueError:
pass
vars[n] = v
else:
m = undef_rx.match(line)
if m:
vars[m.group(1)] = 0
return vars
def get_config_h_filename():
"""Return the path of pyconfig.h."""
if _PYTHON_BUILD:
if os.name == "nt":
inc_dir = os.path.join(_PROJECT_BASE, "PC")
else:
inc_dir = _PROJECT_BASE
else:
inc_dir = get_path('platinclude')
return os.path.join(inc_dir, 'pyconfig.h')
def get_scheme_names():
"""Return a tuple containing the schemes names."""
return tuple(sorted(_SCHEMES.sections()))
def get_path_names():
"""Return a tuple containing the paths names."""
# xxx see if we want a static list
return _SCHEMES.options('posix_prefix')
def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
"""Return a mapping containing an install scheme.
``scheme`` is the install scheme name. If not provided, it will
return the default scheme for the current platform.
"""
_ensure_cfg_read()
if expand:
return _expand_vars(scheme, vars)
else:
return dict(_SCHEMES.items(scheme))
def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
"""Return a path corresponding to the scheme.
``scheme`` is the install scheme name.
"""
return get_paths(scheme, vars, expand)[name]
def get_config_vars(*args):
"""With no arguments, return a dictionary of all configuration
variables relevant for the current platform.
On Unix, this means every variable defined in Python's installed Makefile;
On Windows and Mac OS it's a much smaller set.
With arguments, return a list of values that result from looking up
each argument in the configuration variable dictionary.
"""
global _CONFIG_VARS
if _CONFIG_VARS is None:
_CONFIG_VARS = {}
# Normalized versions of prefix and exec_prefix are handy to have;
# in fact, these are the standard versions used most places in the
# distutils2 module.
_CONFIG_VARS['prefix'] = _PREFIX
_CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
_CONFIG_VARS['py_version'] = _PY_VERSION
_CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
_CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
_CONFIG_VARS['base'] = _PREFIX
_CONFIG_VARS['platbase'] = _EXEC_PREFIX
_CONFIG_VARS['projectbase'] = _PROJECT_BASE
try:
_CONFIG_VARS['abiflags'] = sys.abiflags
except AttributeError:
# sys.abiflags may not be defined on all platforms.
_CONFIG_VARS['abiflags'] = ''
if os.name in ('nt', 'os2'):
_init_non_posix(_CONFIG_VARS)
if os.name == 'posix':
_init_posix(_CONFIG_VARS)
# Setting 'userbase' is done below the call to the
# init function to enable using 'get_config_var' in
# the init-function.
if sys.version >= '2.6':
_CONFIG_VARS['userbase'] = _getuserbase()
if 'srcdir' not in _CONFIG_VARS:
_CONFIG_VARS['srcdir'] = _PROJECT_BASE
else:
_CONFIG_VARS['srcdir'] = _safe_realpath(_CONFIG_VARS['srcdir'])
# Convert srcdir into an absolute path if it appears necessary.
# Normally it is relative to the build directory. However, during
# testing, for example, we might be running a non-installed python
# from a different directory.
if _PYTHON_BUILD and os.name == "posix":
base = _PROJECT_BASE
try:
cwd = os.getcwd()
except OSError:
cwd = None
if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
base != cwd):
# srcdir is relative and we are not in the same directory
# as the executable. Assume executable is in the build
# directory and make srcdir absolute.
srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
_CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
if sys.platform == 'darwin':
kernel_version = os.uname()[2] # Kernel version (8.4.3)
major_version = int(kernel_version.split('.')[0])
if major_version < 8:
# On Mac OS X before 10.4, check if -arch and -isysroot
# are in CFLAGS or LDFLAGS and remove them if they are.
# This is needed when building extensions on a 10.3 system
# using a universal build of python.
for key in ('LDFLAGS', 'BASECFLAGS',
# a number of derived variables. These need to be
# patched up as well.
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
flags = _CONFIG_VARS[key]
flags = re.sub('-arch\s+\w+\s', ' ', flags)
flags = re.sub('-isysroot [^ \t]*', ' ', flags)
_CONFIG_VARS[key] = flags
else:
# Allow the user to override the architecture flags using
# an environment variable.
# NOTE: This name was introduced by Apple in OSX 10.5 and
# is used by several scripting languages distributed with
# that OS release.
if 'ARCHFLAGS' in os.environ:
arch = os.environ['ARCHFLAGS']
for key in ('LDFLAGS', 'BASECFLAGS',
# a number of derived variables. These need to be
# patched up as well.
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
flags = _CONFIG_VARS[key]
flags = re.sub('-arch\s+\w+\s', ' ', flags)
flags = flags + ' ' + arch
_CONFIG_VARS[key] = flags
# If we're on OSX 10.5 or later and the user tries to
# compiles an extension using an SDK that is not present
# on the current machine it is better to not use an SDK
# than to fail.
#
# The major usecase for this is users using a Python.org
# binary installer on OSX 10.6: that installer uses
# the 10.4u SDK, but that SDK is not installed by default
# when you install Xcode.
#
CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
m = re.search('-isysroot\s+(\S+)', CFLAGS)
if m is not None:
sdk = m.group(1)
if not os.path.exists(sdk):
for key in ('LDFLAGS', 'BASECFLAGS',
# a number of derived variables. These need to be
# patched up as well.
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
flags = _CONFIG_VARS[key]
flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags)
_CONFIG_VARS[key] = flags
if args:
vals = []
for name in args:
vals.append(_CONFIG_VARS.get(name))
return vals
else:
return _CONFIG_VARS
def get_config_var(name):
"""Return the value of a single variable using the dictionary returned by
'get_config_vars()'.
Equivalent to get_config_vars().get(name)
"""
return get_config_vars().get(name)
def get_platform():
"""Return a string that identifies the current platform.
This is used mainly to distinguish platform-specific build directories and
platform-specific built distributions. Typically includes the OS name
and version and the architecture (as supplied by 'os.uname()'),
although the exact information included depends on the OS; eg. for IRIX
the architecture isn't particularly important (IRIX only runs on SGI
hardware), but for Linux the kernel version isn't particularly
important.
Examples of returned values:
linux-i586
linux-alpha (?)
solaris-2.6-sun4u
irix-5.3
irix64-6.2
Windows will return one of:
win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
win-ia64 (64bit Windows on Itanium)
win32 (all others - specifically, sys.platform is returned)
For other non-POSIX platforms, currently just returns 'sys.platform'.
"""
if os.name == 'nt':
# sniff sys.version for architecture.
prefix = " bit ("
i = sys.version.find(prefix)
if i == -1:
return sys.platform
j = sys.version.find(")", i)
look = sys.version[i+len(prefix):j].lower()
if look == 'amd64':
return 'win-amd64'
if look == 'itanium':
return 'win-ia64'
return sys.platform
if os.name != "posix" or not hasattr(os, 'uname'):
# XXX what about the architecture? NT is Intel or Alpha,
# Mac OS is M68k or PPC, etc.
return sys.platform
# Try to distinguish various flavours of Unix
osname, host, release, version, machine = os.uname()
# Convert the OS name to lowercase, remove '/' characters
# (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
osname = osname.lower().replace('/', '')
machine = machine.replace(' ', '_')
machine = machine.replace('/', '-')
if osname[:5] == "linux":
# At least on Linux/Intel, 'machine' is the processor --
# i386, etc.
# XXX what about Alpha, SPARC, etc?
return "%s-%s" % (osname, machine)
elif osname[:5] == "sunos":
if release[0] >= "5": # SunOS 5 == Solaris 2
osname = "solaris"
release = "%d.%s" % (int(release[0]) - 3, release[2:])
# fall through to standard osname-release-machine representation
elif osname[:4] == "irix": # could be "irix64"!
return "%s-%s" % (osname, release)
elif osname[:3] == "aix":
return "%s-%s.%s" % (osname, version, release)
elif osname[:6] == "cygwin":
osname = "cygwin"
rel_re = re.compile(r'[\d.]+')
m = rel_re.match(release)
if m:
release = m.group()
elif osname[:6] == "darwin":
#
# For our purposes, we'll assume that the system version from
# distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
# to. This makes the compatibility story a bit more sane because the
# machine is going to compile and link as if it were
# MACOSX_DEPLOYMENT_TARGET.
cfgvars = get_config_vars()
macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
if True:
# Always calculate the release of the running machine,
# needed to determine if we can build fat binaries or not.
macrelease = macver
# Get the system version. Reading this plist is a documented
# way to get the system version (see the documentation for
# the Gestalt Manager)
try:
f = open('/System/Library/CoreServices/SystemVersion.plist')
except IOError:
# We're on a plain darwin box, fall back to the default
# behaviour.
pass
else:
try:
m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
r'<string>(.*?)</string>', f.read())
finally:
f.close()
if m is not None:
macrelease = '.'.join(m.group(1).split('.')[:2])
# else: fall back to the default behaviour
if not macver:
macver = macrelease
if macver:
release = macver
osname = "macosx"
if ((macrelease + '.') >= '10.4.' and
'-arch' in get_config_vars().get('CFLAGS', '').strip()):
# The universal build will build fat binaries, but not on
# systems before 10.4
#
# Try to detect 4-way universal builds, those have machine-type
# 'universal' instead of 'fat'.
machine = 'fat'
cflags = get_config_vars().get('CFLAGS')
archs = re.findall('-arch\s+(\S+)', cflags)
archs = tuple(sorted(set(archs)))
if len(archs) == 1:
machine = archs[0]
elif archs == ('i386', 'ppc'):
machine = 'fat'
elif archs == ('i386', 'x86_64'):
machine = 'intel'
elif archs == ('i386', 'ppc', 'x86_64'):
machine = 'fat3'
elif archs == ('ppc64', 'x86_64'):
machine = 'fat64'
elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
machine = 'universal'
else:
raise ValueError(
"Don't know machine value for archs=%r" % (archs,))
elif machine == 'i386':
# On OSX the machine type returned by uname is always the
# 32-bit variant, even if the executable architecture is
# the 64-bit variant
if sys.maxsize >= 2**32:
machine = 'x86_64'
elif machine in ('PowerPC', 'Power_Macintosh'):
# Pick a sane name for the PPC architecture.
# See 'i386' case
if sys.maxsize >= 2**32:
machine = 'ppc64'
else:
machine = 'ppc'
return "%s-%s-%s" % (osname, release, machine)
def get_python_version():
return _PY_VERSION_SHORT
def _print_dict(title, data):
for index, (key, value) in enumerate(sorted(data.items())):
if index == 0:
print('%s: ' % (title))
print('\t%s = "%s"' % (key, value))
def _main():
"""Display all information sysconfig detains."""
print('Platform: "%s"' % get_platform())
print('Python version: "%s"' % get_python_version())
print('Current installation scheme: "%s"' % _get_default_scheme())
print()
_print_dict('Paths', get_paths())
print()
_print_dict('Variables', get_config_vars())
if __name__ == '__main__':
_main()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,488 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
import hashlib
import logging
import os
import shutil
import subprocess
import tempfile
try:
from threading import Thread
except ImportError:
from dummy_threading import Thread
from . import DistlibException
from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr,
urlparse, build_opener, string_types)
from .util import cached_property, zip_dir, ServerProxy
logger = logging.getLogger(__name__)
DEFAULT_INDEX = 'https://pypi.python.org/pypi'
DEFAULT_REALM = 'pypi'
class PackageIndex(object):
"""
This class represents a package index compatible with PyPI, the Python
Package Index.
"""
boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$'
def __init__(self, url=None):
"""
Initialise an instance.
:param url: The URL of the index. If not specified, the URL for PyPI is
used.
"""
self.url = url or DEFAULT_INDEX
self.read_configuration()
scheme, netloc, path, params, query, frag = urlparse(self.url)
if params or query or frag or scheme not in ('http', 'https'):
raise DistlibException('invalid repository: %s' % self.url)
self.password_handler = None
self.ssl_verifier = None
self.gpg = None
self.gpg_home = None
self.rpc_proxy = None
with open(os.devnull, 'w') as sink:
for s in ('gpg2', 'gpg'):
try:
rc = subprocess.check_call([s, '--version'], stdout=sink,
stderr=sink)
if rc == 0:
self.gpg = s
break
except OSError:
pass
def _get_pypirc_command(self):
"""
Get the distutils command for interacting with PyPI configurations.
:return: the command.
"""
from distutils.core import Distribution
from distutils.config import PyPIRCCommand
d = Distribution()
return PyPIRCCommand(d)
def read_configuration(self):
"""
Read the PyPI access configuration as supported by distutils, getting
PyPI to do the acutal work. This populates ``username``, ``password``,
``realm`` and ``url`` attributes from the configuration.
"""
# get distutils to do the work
c = self._get_pypirc_command()
c.repository = self.url
cfg = c._read_pypirc()
self.username = cfg.get('username')
self.password = cfg.get('password')
self.realm = cfg.get('realm', 'pypi')
self.url = cfg.get('repository', self.url)
def save_configuration(self):
"""
Save the PyPI access configuration. You must have set ``username`` and
``password`` attributes before calling this method.
Again, distutils is used to do the actual work.
"""
self.check_credentials()
# get distutils to do the work
c = self._get_pypirc_command()
c._store_pypirc(self.username, self.password)
def check_credentials(self):
"""
Check that ``username`` and ``password`` have been set, and raise an
exception if not.
"""
if self.username is None or self.password is None:
raise DistlibException('username and password must be set')
pm = HTTPPasswordMgr()
_, netloc, _, _, _, _ = urlparse(self.url)
pm.add_password(self.realm, netloc, self.username, self.password)
self.password_handler = HTTPBasicAuthHandler(pm)
def register(self, metadata):
"""
Register a distribution on PyPI, using the provided metadata.
:param metadata: A :class:`Metadata` instance defining at least a name
and version number for the distribution to be
registered.
:return: The HTTP response received from PyPI upon submission of the
request.
"""
self.check_credentials()
metadata.validate()
d = metadata.todict()
d[':action'] = 'verify'
request = self.encode_request(d.items(), [])
response = self.send_request(request)
d[':action'] = 'submit'
request = self.encode_request(d.items(), [])
return self.send_request(request)
def _reader(self, name, stream, outbuf):
"""
Thread runner for reading lines of from a subprocess into a buffer.
:param name: The logical name of the stream (used for logging only).
:param stream: The stream to read from. This will typically a pipe
connected to the output stream of a subprocess.
:param outbuf: The list to append the read lines to.
"""
while True:
s = stream.readline()
if not s:
break
s = s.decode('utf-8').rstrip()
outbuf.append(s)
logger.debug('%s: %s' % (name, s))
stream.close()
def get_sign_command(self, filename, signer, sign_password):
"""
Return a suitable command for signing a file.
:param filename: The pathname to the file to be signed.
:param signer: The identifier of the signer of the file.
:param sign_password: The passphrase for the signer's
private key used for signing.
:return: The signing command as a list suitable to be
passed to :class:`subprocess.Popen`.
"""
cmd = [self.gpg, '--status-fd', '2', '--no-tty']
if self.gpg_home:
cmd.extend(['--homedir', self.gpg_home])
if sign_password is not None:
cmd.extend(['--batch', '--passphrase-fd', '0'])
td = tempfile.mkdtemp()
sf = os.path.join(td, os.path.basename(filename) + '.asc')
cmd.extend(['--detach-sign', '--armor', '--local-user',
signer, '--output', sf, filename])
logger.debug('invoking: %s', ' '.join(cmd))
return cmd, sf
def run_command(self, cmd, input_data=None):
"""
Run a command in a child process , passing it any input data specified.
:param cmd: The command to run.
:param input_data: If specified, this must be a byte string containing
data to be sent to the child process.
:return: A tuple consisting of the subprocess' exit code, a list of
lines read from the subprocess' ``stdout``, and a list of
lines read from the subprocess' ``stderr``.
"""
kwargs = {
'stdout': subprocess.PIPE,
'stderr': subprocess.PIPE,
}
if input_data is not None:
kwargs['stdin'] = subprocess.PIPE
stdout = []
stderr = []
p = subprocess.Popen(cmd, **kwargs)
# We don't use communicate() here because we may need to
# get clever with interacting with the command
t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout))
t1.start()
t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr))
t2.start()
if input_data is not None:
p.stdin.write(input_data)
p.stdin.close()
p.wait()
t1.join()
t2.join()
return p.returncode, stdout, stderr
def sign_file(self, filename, signer, sign_password):
"""
Sign a file.
:param filename: The pathname to the file to be signed.
:param signer: The identifier of the signer of the file.
:param sign_password: The passphrase for the signer's
private key used for signing.
:return: The absolute pathname of the file where the signature is
stored.
"""
cmd, sig_file = self.get_sign_command(filename, signer, sign_password)
rc, stdout, stderr = self.run_command(cmd,
sign_password.encode('utf-8'))
if rc != 0:
raise DistlibException('sign command failed with error '
'code %s' % rc)
return sig_file
def upload_file(self, metadata, filename, signer=None, sign_password=None,
filetype='sdist', pyversion='source'):
"""
Upload a release file to the index.
:param metadata: A :class:`Metadata` instance defining at least a name
and version number for the file to be uploaded.
:param filename: The pathname of the file to be uploaded.
:param signer: The identifier of the signer of the file.
:param sign_password: The passphrase for the signer's
private key used for signing.
:param filetype: The type of the file being uploaded. This is the
distutils command which produced that file, e.g.
``sdist`` or ``bdist_wheel``.
:param pyversion: The version of Python which the release relates
to. For code compatible with any Python, this would
be ``source``, otherwise it would be e.g. ``3.2``.
:return: The HTTP response received from PyPI upon submission of the
request.
"""
self.check_credentials()
if not os.path.exists(filename):
raise DistlibException('not found: %s' % filename)
metadata.validate()
d = metadata.todict()
sig_file = None
if signer:
if not self.gpg:
logger.warning('no signing program available - not signed')
else:
sig_file = self.sign_file(filename, signer, sign_password)
with open(filename, 'rb') as f:
file_data = f.read()
md5_digest = hashlib.md5(file_data).hexdigest()
sha256_digest = hashlib.sha256(file_data).hexdigest()
d.update({
':action': 'file_upload',
'protcol_version': '1',
'filetype': filetype,
'pyversion': pyversion,
'md5_digest': md5_digest,
'sha256_digest': sha256_digest,
})
files = [('content', os.path.basename(filename), file_data)]
if sig_file:
with open(sig_file, 'rb') as f:
sig_data = f.read()
files.append(('gpg_signature', os.path.basename(sig_file),
sig_data))
shutil.rmtree(os.path.dirname(sig_file))
request = self.encode_request(d.items(), files)
return self.send_request(request)
def upload_documentation(self, metadata, doc_dir):
"""
Upload documentation to the index.
:param metadata: A :class:`Metadata` instance defining at least a name
and version number for the documentation to be
uploaded.
:param doc_dir: The pathname of the directory which contains the
documentation. This should be the directory that
contains the ``index.html`` for the documentation.
:return: The HTTP response received from PyPI upon submission of the
request.
"""
self.check_credentials()
if not os.path.isdir(doc_dir):
raise DistlibException('not a directory: %r' % doc_dir)
fn = os.path.join(doc_dir, 'index.html')
if not os.path.exists(fn):
raise DistlibException('not found: %r' % fn)
metadata.validate()
name, version = metadata.name, metadata.version
zip_data = zip_dir(doc_dir).getvalue()
fields = [(':action', 'doc_upload'),
('name', name), ('version', version)]
files = [('content', name, zip_data)]
request = self.encode_request(fields, files)
return self.send_request(request)
def get_verify_command(self, signature_filename, data_filename):
"""
Return a suitable command for verifying a file.
:param signature_filename: The pathname to the file containing the
signature.
:param data_filename: The pathname to the file containing the
signed data.
:return: The verifying command as a list suitable to be
passed to :class:`subprocess.Popen`.
"""
cmd = [self.gpg, '--status-fd', '2', '--no-tty']
if self.gpg_home:
cmd.extend(['--homedir', self.gpg_home])
cmd.extend(['--verify', signature_filename, data_filename])
logger.debug('invoking: %s', ' '.join(cmd))
return cmd
def verify_signature(self, signature_filename, data_filename):
"""
Verify a signature for a file.
:param signature_filename: The pathname to the file containing the
signature.
:param data_filename: The pathname to the file containing the
signed data.
:return: True if the signature was verified, else False.
"""
if not self.gpg:
raise DistlibException('verification unavailable because gpg '
'unavailable')
cmd = self.get_verify_command(signature_filename, data_filename)
rc, stdout, stderr = self.run_command(cmd)
if rc not in (0, 1):
raise DistlibException('verify command failed with error '
'code %s' % rc)
return rc == 0
def download_file(self, url, destfile, digest=None, reporthook=None):
"""
This is a convenience method for downloading a file from an URL.
Normally, this will be a file from the index, though currently
no check is made for this (i.e. a file can be downloaded from
anywhere).
The method is just like the :func:`urlretrieve` function in the
standard library, except that it allows digest computation to be
done during download and checking that the downloaded data
matched any expected value.
:param url: The URL of the file to be downloaded (assumed to be
available via an HTTP GET request).
:param destfile: The pathname where the downloaded file is to be
saved.
:param digest: If specified, this must be a (hasher, value)
tuple, where hasher is the algorithm used (e.g.
``'md5'``) and ``value`` is the expected value.
:param reporthook: The same as for :func:`urlretrieve` in the
standard library.
"""
if digest is None:
digester = None
logger.debug('No digest specified')
else:
if isinstance(digest, (list, tuple)):
hasher, digest = digest
else:
hasher = 'md5'
digester = getattr(hashlib, hasher)()
logger.debug('Digest specified: %s' % digest)
# The following code is equivalent to urlretrieve.
# We need to do it this way so that we can compute the
# digest of the file as we go.
with open(destfile, 'wb') as dfp:
# addinfourl is not a context manager on 2.x
# so we have to use try/finally
sfp = self.send_request(Request(url))
try:
headers = sfp.info()
blocksize = 8192
size = -1
read = 0
blocknum = 0
if "content-length" in headers:
size = int(headers["Content-Length"])
if reporthook:
reporthook(blocknum, blocksize, size)
while True:
block = sfp.read(blocksize)
if not block:
break
read += len(block)
dfp.write(block)
if digester:
digester.update(block)
blocknum += 1
if reporthook:
reporthook(blocknum, blocksize, size)
finally:
sfp.close()
# check that we got the whole file, if we can
if size >= 0 and read < size:
raise DistlibException(
'retrieval incomplete: got only %d out of %d bytes'
% (read, size))
# if we have a digest, it must match.
if digester:
actual = digester.hexdigest()
if digest != actual:
raise DistlibException('%s digest mismatch for %s: expected '
'%s, got %s' % (hasher, destfile,
digest, actual))
logger.debug('Digest verified: %s', digest)
def send_request(self, req):
"""
Send a standard library :class:`Request` to PyPI and return its
response.
:param req: The request to send.
:return: The HTTP response from PyPI (a standard library HTTPResponse).
"""
handlers = []
if self.password_handler:
handlers.append(self.password_handler)
if self.ssl_verifier:
handlers.append(self.ssl_verifier)
opener = build_opener(*handlers)
return opener.open(req)
def encode_request(self, fields, files):
"""
Encode fields and files for posting to an HTTP server.
:param fields: The fields to send as a list of (fieldname, value)
tuples.
:param files: The files to send as a list of (fieldname, filename,
file_bytes) tuple.
"""
# Adapted from packaging, which in turn was adapted from
# http://code.activestate.com/recipes/146306
parts = []
boundary = self.boundary
for k, values in fields:
if not isinstance(values, (list, tuple)):
values = [values]
for v in values:
parts.extend((
b'--' + boundary,
('Content-Disposition: form-data; name="%s"' %
k).encode('utf-8'),
b'',
v.encode('utf-8')))
for key, filename, value in files:
parts.extend((
b'--' + boundary,
('Content-Disposition: form-data; name="%s"; filename="%s"' %
(key, filename)).encode('utf-8'),
b'',
value))
parts.extend((b'--' + boundary + b'--', b''))
body = b'\r\n'.join(parts)
ct = b'multipart/form-data; boundary=' + boundary
headers = {
'Content-type': ct,
'Content-length': str(len(body))
}
return Request(self.url, body, headers)
def search(self, terms, operator=None):
if isinstance(terms, string_types):
terms = {'name': terms}
if self.rpc_proxy is None:
self.rpc_proxy = ServerProxy(self.url, timeout=3.0)
return self.rpc_proxy.search(terms, operator or 'and')
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,364 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2013 Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""
Class representing the list of files in a distribution.
Equivalent to distutils.filelist, but fixes some problems.
"""
import fnmatch
import logging
import os
import re
from . import DistlibException
from .compat import fsdecode
from .util import convert_path
__all__ = ['Manifest']
logger = logging.getLogger(__name__)
# a \ followed by some spaces + EOL
_COLLAPSE_PATTERN = re.compile('\\\w*\n', re.M)
_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S)
class Manifest(object):
"""A list of files built by on exploring the filesystem and filtered by
applying various patterns to what we find there.
"""
def __init__(self, base=None):
"""
Initialise an instance.
:param base: The base directory to explore under.
"""
self.base = os.path.abspath(os.path.normpath(base or os.getcwd()))
self.prefix = self.base + os.sep
self.allfiles = None
self.files = set()
#
# Public API
#
def findall(self):
"""Find all files under the base and set ``allfiles`` to the absolute
pathnames of files found.
"""
from stat import S_ISREG, S_ISDIR, S_ISLNK
self.allfiles = allfiles = []
root = self.base
stack = [root]
pop = stack.pop
push = stack.append
while stack:
root = pop()
names = os.listdir(root)
for name in names:
fullname = os.path.join(root, name)
# Avoid excess stat calls -- just one will do, thank you!
stat = os.stat(fullname)
mode = stat.st_mode
if S_ISREG(mode):
allfiles.append(fsdecode(fullname))
elif S_ISDIR(mode) and not S_ISLNK(mode):
push(fullname)
def add(self, item):
"""
Add a file to the manifest.
:param item: The pathname to add. This can be relative to the base.
"""
if not item.startswith(self.prefix):
item = os.path.join(self.base, item)
self.files.add(os.path.normpath(item))
def add_many(self, items):
"""
Add a list of files to the manifest.
:param items: The pathnames to add. These can be relative to the base.
"""
for item in items:
self.add(item)
def sorted(self, wantdirs=False):
"""
Return sorted files in directory order
"""
def add_dir(dirs, d):
dirs.add(d)
logger.debug('add_dir added %s', d)
if d != self.base:
parent, _ = os.path.split(d)
assert parent not in ('', '/')
add_dir(dirs, parent)
result = set(self.files) # make a copy!
if wantdirs:
dirs = set()
for f in result:
add_dir(dirs, os.path.dirname(f))
result |= dirs
return [os.path.join(*path_tuple) for path_tuple in
sorted(os.path.split(path) for path in result)]
def clear(self):
"""Clear all collected files."""
self.files = set()
self.allfiles = []
def process_directive(self, directive):
"""
Process a directive which either adds some files from ``allfiles`` to
``files``, or removes some files from ``files``.
:param directive: The directive to process. This should be in a format
compatible with distutils ``MANIFEST.in`` files:
http://docs.python.org/distutils/sourcedist.html#commands
"""
# Parse the line: split it up, make sure the right number of words
# is there, and return the relevant words. 'action' is always
# defined: it's the first word of the line. Which of the other
# three are defined depends on the action; it'll be either
# patterns, (dir and patterns), or (dirpattern).
action, patterns, thedir, dirpattern = self._parse_directive(directive)
# OK, now we know that the action is valid and we have the
# right number of words on the line for that action -- so we
# can proceed with minimal error-checking.
if action == 'include':
for pattern in patterns:
if not self._include_pattern(pattern, anchor=True):
logger.warning('no files found matching %r', pattern)
elif action == 'exclude':
for pattern in patterns:
if not self._exclude_pattern(pattern, anchor=True):
logger.warning('no previously-included files '
'found matching %r', pattern)
elif action == 'global-include':
for pattern in patterns:
if not self._include_pattern(pattern, anchor=False):
logger.warning('no files found matching %r '
'anywhere in distribution', pattern)
elif action == 'global-exclude':
for pattern in patterns:
if not self._exclude_pattern(pattern, anchor=False):
logger.warning('no previously-included files '
'matching %r found anywhere in '
'distribution', pattern)
elif action == 'recursive-include':
for pattern in patterns:
if not self._include_pattern(pattern, prefix=thedir):
logger.warning('no files found matching %r '
'under directory %r', pattern, thedir)
elif action == 'recursive-exclude':
for pattern in patterns:
if not self._exclude_pattern(pattern, prefix=thedir):
logger.warning('no previously-included files '
'matching %r found under directory %r',
pattern, thedir)
elif action == 'graft':
if not self._include_pattern(None, prefix=dirpattern):
logger.warning('no directories found matching %r',
dirpattern)
elif action == 'prune':
if not self._exclude_pattern(None, prefix=dirpattern):
logger.warning('no previously-included directories found '
'matching %r', dirpattern)
else: # pragma: no cover
# This should never happen, as it should be caught in
# _parse_template_line
raise DistlibException(
'invalid action %r' % action)
#
# Private API
#
def _parse_directive(self, directive):
"""
Validate a directive.
:param directive: The directive to validate.
:return: A tuple of action, patterns, thedir, dir_patterns
"""
words = directive.split()
if len(words) == 1 and words[0] not in ('include', 'exclude',
'global-include',
'global-exclude',
'recursive-include',
'recursive-exclude',
'graft', 'prune'):
# no action given, let's use the default 'include'
words.insert(0, 'include')
action = words[0]
patterns = thedir = dir_pattern = None
if action in ('include', 'exclude',
'global-include', 'global-exclude'):
if len(words) < 2:
raise DistlibException(
'%r expects <pattern1> <pattern2> ...' % action)
patterns = [convert_path(word) for word in words[1:]]
elif action in ('recursive-include', 'recursive-exclude'):
if len(words) < 3:
raise DistlibException(
'%r expects <dir> <pattern1> <pattern2> ...' % action)
thedir = convert_path(words[1])
patterns = [convert_path(word) for word in words[2:]]
elif action in ('graft', 'prune'):
if len(words) != 2:
raise DistlibException(
'%r expects a single <dir_pattern>' % action)
dir_pattern = convert_path(words[1])
else:
raise DistlibException('unknown action %r' % action)
return action, patterns, thedir, dir_pattern
def _include_pattern(self, pattern, anchor=True, prefix=None,
is_regex=False):
"""Select strings (presumably filenames) from 'self.files' that
match 'pattern', a Unix-style wildcard (glob) pattern.
Patterns are not quite the same as implemented by the 'fnmatch'
module: '*' and '?' match non-special characters, where "special"
is platform-dependent: slash on Unix; colon, slash, and backslash on
DOS/Windows; and colon on Mac OS.
If 'anchor' is true (the default), then the pattern match is more
stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
'anchor' is false, both of these will match.
If 'prefix' is supplied, then only filenames starting with 'prefix'
(itself a pattern) and ending with 'pattern', with anything in between
them, will match. 'anchor' is ignored in this case.
If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and
'pattern' is assumed to be either a string containing a regex or a
regex object -- no translation is done, the regex is just compiled
and used as-is.
Selected strings will be added to self.files.
Return True if files are found.
"""
# XXX docstring lying about what the special chars are?
found = False
pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
# delayed loading of allfiles list
if self.allfiles is None:
self.findall()
for name in self.allfiles:
if pattern_re.search(name):
self.files.add(name)
found = True
return found
def _exclude_pattern(self, pattern, anchor=True, prefix=None,
is_regex=False):
"""Remove strings (presumably filenames) from 'files' that match
'pattern'.
Other parameters are the same as for 'include_pattern()', above.
The list 'self.files' is modified in place. Return True if files are
found.
This API is public to allow e.g. exclusion of SCM subdirs, e.g. when
packaging source distributions
"""
found = False
pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
for f in list(self.files):
if pattern_re.search(f):
self.files.remove(f)
found = True
return found
def _translate_pattern(self, pattern, anchor=True, prefix=None,
is_regex=False):
"""Translate a shell-like wildcard pattern to a compiled regular
expression.
Return the compiled regex. If 'is_regex' true,
then 'pattern' is directly compiled to a regex (if it's a string)
or just returned as-is (assumes it's a regex object).
"""
if is_regex:
if isinstance(pattern, str):
return re.compile(pattern)
else:
return pattern
if pattern:
pattern_re = self._glob_to_re(pattern)
else:
pattern_re = ''
base = re.escape(os.path.join(self.base, ''))
if prefix is not None:
# ditch end of pattern character
empty_pattern = self._glob_to_re('')
prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)]
sep = os.sep
if os.sep == '\\':
sep = r'\\'
pattern_re = '^' + base + sep.join((prefix_re,
'.*' + pattern_re))
else: # no prefix -- respect anchor flag
if anchor:
pattern_re = '^' + base + pattern_re
return re.compile(pattern_re)
def _glob_to_re(self, pattern):
"""Translate a shell-like glob pattern to a regular expression.
Return a string containing the regex. Differs from
'fnmatch.translate()' in that '*' does not match "special characters"
(which are platform-specific).
"""
pattern_re = fnmatch.translate(pattern)
# '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
# IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
# and by extension they shouldn't match such "special characters" under
# any OS. So change all non-escaped dots in the RE to match any
# character except the special characters (currently: just os.sep).
sep = os.sep
if os.sep == '\\':
# we're using a regex to manipulate a regex, so we need
# to escape the backslash twice
sep = r'\\\\'
escaped = r'\1[^%s]' % sep
pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re)
return pattern_re
@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2013 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""Parser for the environment markers micro-language defined in PEP 345."""
import ast
import os
import sys
import platform
from .compat import python_implementation, string_types
from .util import in_venv
__all__ = ['interpret']
class Evaluator(object):
"""
A limited evaluator for Python expressions.
"""
operators = {
'eq': lambda x, y: x == y,
'gt': lambda x, y: x > y,
'gte': lambda x, y: x >= y,
'in': lambda x, y: x in y,
'lt': lambda x, y: x < y,
'lte': lambda x, y: x <= y,
'not': lambda x: not x,
'noteq': lambda x, y: x != y,
'notin': lambda x, y: x not in y,
}
allowed_values = {
'sys_platform': sys.platform,
'python_version': '%s.%s' % sys.version_info[:2],
# parsing sys.platform is not reliable, but there is no other
# way to get e.g. 2.7.2+, and the PEP is defined with sys.version
'python_full_version': sys.version.split(' ', 1)[0],
'os_name': os.name,
'platform_in_venv': str(in_venv()),
'platform_release': platform.release(),
'platform_version': platform.version(),
'platform_machine': platform.machine(),
'platform_python_implementation': python_implementation(),
}
def __init__(self, context=None):
"""
Initialise an instance.
:param context: If specified, names are looked up in this mapping.
"""
self.context = context or {}
self.source = None
def get_fragment(self, offset):
"""
Get the part of the source which is causing a problem.
"""
fragment_len = 10
s = '%r' % (self.source[offset:offset + fragment_len])
if offset + fragment_len < len(self.source):
s += '...'
return s
def get_handler(self, node_type):
"""
Get a handler for the specified AST node type.
"""
return getattr(self, 'do_%s' % node_type, None)
def evaluate(self, node, filename=None):
"""
Evaluate a source string or node, using ``filename`` when
displaying errors.
"""
if isinstance(node, string_types):
self.source = node
kwargs = {'mode': 'eval'}
if filename:
kwargs['filename'] = filename
try:
node = ast.parse(node, **kwargs)
except SyntaxError as e:
s = self.get_fragment(e.offset)
raise SyntaxError('syntax error %s' % s)
node_type = node.__class__.__name__.lower()
handler = self.get_handler(node_type)
if handler is None:
if self.source is None:
s = '(source not available)'
else:
s = self.get_fragment(node.col_offset)
raise SyntaxError("don't know how to evaluate %r %s" % (
node_type, s))
return handler(node)
def get_attr_key(self, node):
assert isinstance(node, ast.Attribute), 'attribute node expected'
return '%s.%s' % (node.value.id, node.attr)
def do_attribute(self, node):
if not isinstance(node.value, ast.Name):
valid = False
else:
key = self.get_attr_key(node)
valid = key in self.context or key in self.allowed_values
if not valid:
raise SyntaxError('invalid expression: %s' % key)
if key in self.context:
result = self.context[key]
else:
result = self.allowed_values[key]
return result
def do_boolop(self, node):
result = self.evaluate(node.values[0])
is_or = node.op.__class__ is ast.Or
is_and = node.op.__class__ is ast.And
assert is_or or is_and
if (is_and and result) or (is_or and not result):
for n in node.values[1:]:
result = self.evaluate(n)
if (is_or and result) or (is_and and not result):
break
return result
def do_compare(self, node):
def sanity_check(lhsnode, rhsnode):
valid = True
if isinstance(lhsnode, ast.Str) and isinstance(rhsnode, ast.Str):
valid = False
#elif (isinstance(lhsnode, ast.Attribute)
# and isinstance(rhsnode, ast.Attribute)):
# klhs = self.get_attr_key(lhsnode)
# krhs = self.get_attr_key(rhsnode)
# valid = klhs != krhs
if not valid:
s = self.get_fragment(node.col_offset)
raise SyntaxError('Invalid comparison: %s' % s)
lhsnode = node.left
lhs = self.evaluate(lhsnode)
result = True
for op, rhsnode in zip(node.ops, node.comparators):
sanity_check(lhsnode, rhsnode)
op = op.__class__.__name__.lower()
if op not in self.operators:
raise SyntaxError('unsupported operation: %r' % op)
rhs = self.evaluate(rhsnode)
result = self.operators[op](lhs, rhs)
if not result:
break
lhs = rhs
lhsnode = rhsnode
return result
def do_expression(self, node):
return self.evaluate(node.body)
def do_name(self, node):
valid = False
if node.id in self.context:
valid = True
result = self.context[node.id]
elif node.id in self.allowed_values:
valid = True
result = self.allowed_values[node.id]
if not valid:
raise SyntaxError('invalid expression: %s' % node.id)
return result
def do_str(self, node):
return node.s
def interpret(marker, execution_context=None):
"""
Interpret a marker and return a result depending on environment.
:param marker: The marker to interpret.
:type marker: str
:param execution_context: The context used for name lookup.
:type execution_context: mapping
"""
return Evaluator(execution_context).evaluate(marker.strip())
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,317 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
from __future__ import unicode_literals
import bisect
import io
import logging
import os
import pkgutil
import shutil
import sys
import types
import zipimport
from . import DistlibException
from .util import cached_property, get_cache_base, path_to_cache_dir, Cache
logger = logging.getLogger(__name__)
cache = None # created when needed
class ResourceCache(Cache):
def __init__(self, base=None):
if base is None:
# Use native string to avoid issues on 2.x: see Python #20140.
base = os.path.join(get_cache_base(), str('resource-cache'))
super(ResourceCache, self).__init__(base)
def is_stale(self, resource, path):
"""
Is the cache stale for the given resource?
:param resource: The :class:`Resource` being cached.
:param path: The path of the resource in the cache.
:return: True if the cache is stale.
"""
# Cache invalidation is a hard problem :-)
return True
def get(self, resource):
"""
Get a resource into the cache,
:param resource: A :class:`Resource` instance.
:return: The pathname of the resource in the cache.
"""
prefix, path = resource.finder.get_cache_info(resource)
if prefix is None:
result = path
else:
result = os.path.join(self.base, self.prefix_to_dir(prefix), path)
dirname = os.path.dirname(result)
if not os.path.isdir(dirname):
os.makedirs(dirname)
if not os.path.exists(result):
stale = True
else:
stale = self.is_stale(resource, path)
if stale:
# write the bytes of the resource to the cache location
with open(result, 'wb') as f:
f.write(resource.bytes)
return result
class ResourceBase(object):
def __init__(self, finder, name):
self.finder = finder
self.name = name
class Resource(ResourceBase):
"""
A class representing an in-package resource, such as a data file. This is
not normally instantiated by user code, but rather by a
:class:`ResourceFinder` which manages the resource.
"""
is_container = False # Backwards compatibility
def as_stream(self):
"""
Get the resource as a stream.
This is not a property to make it obvious that it returns a new stream
each time.
"""
return self.finder.get_stream(self)
@cached_property
def file_path(self):
global cache
if cache is None:
cache = ResourceCache()
return cache.get(self)
@cached_property
def bytes(self):
return self.finder.get_bytes(self)
@cached_property
def size(self):
return self.finder.get_size(self)
class ResourceContainer(ResourceBase):
is_container = True # Backwards compatibility
@cached_property
def resources(self):
return self.finder.get_resources(self)
class ResourceFinder(object):
"""
Resource finder for file system resources.
"""
def __init__(self, module):
self.module = module
self.loader = getattr(module, '__loader__', None)
self.base = os.path.dirname(getattr(module, '__file__', ''))
def _adjust_path(self, path):
return os.path.realpath(path)
def _make_path(self, resource_name):
parts = resource_name.split('/')
parts.insert(0, self.base)
result = os.path.join(*parts)
return self._adjust_path(result)
def _find(self, path):
return os.path.exists(path)
def get_cache_info(self, resource):
return None, resource.path
def find(self, resource_name):
path = self._make_path(resource_name)
if not self._find(path):
result = None
else:
if self._is_directory(path):
result = ResourceContainer(self, resource_name)
else:
result = Resource(self, resource_name)
result.path = path
return result
def get_stream(self, resource):
return open(resource.path, 'rb')
def get_bytes(self, resource):
with open(resource.path, 'rb') as f:
return f.read()
def get_size(self, resource):
return os.path.getsize(resource.path)
def get_resources(self, resource):
def allowed(f):
return f != '__pycache__' and not f.endswith(('.pyc', '.pyo'))
return set([f for f in os.listdir(resource.path) if allowed(f)])
def is_container(self, resource):
return self._is_directory(resource.path)
_is_directory = staticmethod(os.path.isdir)
class ZipResourceFinder(ResourceFinder):
"""
Resource finder for resources in .zip files.
"""
def __init__(self, module):
super(ZipResourceFinder, self).__init__(module)
archive = self.loader.archive
self.prefix_len = 1 + len(archive)
# PyPy doesn't have a _files attr on zipimporter, and you can't set one
if hasattr(self.loader, '_files'):
self._files = self.loader._files
else:
self._files = zipimport._zip_directory_cache[archive]
self.index = sorted(self._files)
def _adjust_path(self, path):
return path
def _find(self, path):
path = path[self.prefix_len:]
if path in self._files:
result = True
else:
if path and path[-1] != os.sep:
path = path + os.sep
i = bisect.bisect(self.index, path)
try:
result = self.index[i].startswith(path)
except IndexError:
result = False
if not result:
logger.debug('_find failed: %r %r', path, self.loader.prefix)
else:
logger.debug('_find worked: %r %r', path, self.loader.prefix)
return result
def get_cache_info(self, resource):
prefix = self.loader.archive
path = resource.path[1 + len(prefix):]
return prefix, path
def get_bytes(self, resource):
return self.loader.get_data(resource.path)
def get_stream(self, resource):
return io.BytesIO(self.get_bytes(resource))
def get_size(self, resource):
path = resource.path[self.prefix_len:]
return self._files[path][3]
def get_resources(self, resource):
path = resource.path[self.prefix_len:]
if path and path[-1] != os.sep:
path += os.sep
plen = len(path)
result = set()
i = bisect.bisect(self.index, path)
while i < len(self.index):
if not self.index[i].startswith(path):
break
s = self.index[i][plen:]
result.add(s.split(os.sep, 1)[0]) # only immediate children
i += 1
return result
def _is_directory(self, path):
path = path[self.prefix_len:]
if path and path[-1] != os.sep:
path += os.sep
i = bisect.bisect(self.index, path)
try:
result = self.index[i].startswith(path)
except IndexError:
result = False
return result
_finder_registry = {
type(None): ResourceFinder,
zipimport.zipimporter: ZipResourceFinder
}
try:
import _frozen_importlib
_finder_registry[_frozen_importlib.SourceFileLoader] = ResourceFinder
_finder_registry[_frozen_importlib.FileFinder] = ResourceFinder
except (ImportError, AttributeError):
pass
def register_finder(loader, finder_maker):
_finder_registry[type(loader)] = finder_maker
_finder_cache = {}
def finder(package):
"""
Return a resource finder for a package.
:param package: The name of the package.
:return: A :class:`ResourceFinder` instance for the package.
"""
if package in _finder_cache:
result = _finder_cache[package]
else:
if package not in sys.modules:
__import__(package)
module = sys.modules[package]
path = getattr(module, '__path__', None)
if path is None:
raise DistlibException('You cannot get a finder for a module, '
'only for a package')
loader = getattr(module, '__loader__', None)
finder_maker = _finder_registry.get(type(loader))
if finder_maker is None:
raise DistlibException('Unable to locate finder for %r' % package)
result = finder_maker(module)
_finder_cache[package] = result
return result
_dummy_module = types.ModuleType(str('__dummy__'))
def finder_for_path(path):
"""
Return a resource finder for a path, which should represent a container.
:param path: The path.
:return: A :class:`ResourceFinder` instance for the path.
"""
result = None
# calls any path hooks, gets importer into cache
pkgutil.get_importer(path)
loader = sys.path_importer_cache.get(path)
finder = _finder_registry.get(type(loader))
if finder:
module = _dummy_module
module.__file__ = os.path.join(path, '')
module.__loader__ = loader
result = finder(module)
return result
@@ -0,0 +1,323 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
from io import BytesIO
import logging
import os
import re
import struct
import sys
from .compat import sysconfig, fsencode, detect_encoding, ZipFile
from .resources import finder
from .util import (FileOperator, get_export_entry, convert_path,
get_executable, in_venv)
logger = logging.getLogger(__name__)
_DEFAULT_MANIFEST = '''
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0"
processorArchitecture="X86"
name="%s"
type="win32"/>
<!-- Identify the application security requirements. -->
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>'''.strip()
# check if Python is called on the first line with this expression
FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$')
SCRIPT_TEMPLATE = '''# -*- coding: utf-8 -*-
if __name__ == '__main__':
import sys, re
def _resolve(module, func):
__import__(module)
mod = sys.modules[module]
parts = func.split('.')
result = getattr(mod, parts.pop(0))
for p in parts:
result = getattr(result, p)
return result
try:
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
func = _resolve('%(module)s', '%(func)s')
rc = func() # None interpreted as 0
except Exception as e: # only supporting Python >= 2.6
sys.stderr.write('%%s\\n' %% e)
rc = 1
sys.exit(rc)
'''
class ScriptMaker(object):
"""
A class to copy or create scripts from source scripts or callable
specifications.
"""
script_template = SCRIPT_TEMPLATE
executable = None # for shebangs
def __init__(self, source_dir, target_dir, add_launchers=True,
dry_run=False, fileop=None):
self.source_dir = source_dir
self.target_dir = target_dir
self.add_launchers = add_launchers
self.force = False
self.clobber = False
# It only makes sense to set mode bits on POSIX.
self.set_mode = (os.name == 'posix')
self.variants = set(('', 'X.Y'))
self._fileop = fileop or FileOperator(dry_run)
def _get_alternate_executable(self, executable, options):
if options.get('gui', False) and os.name == 'nt':
dn, fn = os.path.split(executable)
fn = fn.replace('python', 'pythonw')
executable = os.path.join(dn, fn)
return executable
def _get_shebang(self, encoding, post_interp=b'', options=None):
enquote = True
if self.executable:
executable = self.executable
enquote = False # assume this will be taken care of
elif not sysconfig.is_python_build():
executable = get_executable()
elif in_venv():
executable = os.path.join(sysconfig.get_path('scripts'),
'python%s' % sysconfig.get_config_var('EXE'))
else:
executable = os.path.join(
sysconfig.get_config_var('BINDIR'),
'python%s%s' % (sysconfig.get_config_var('VERSION'),
sysconfig.get_config_var('EXE')))
if options:
executable = self._get_alternate_executable(executable, options)
# If the user didn't specify an executable, it may be necessary to
# cater for executable paths with spaces (not uncommon on Windows)
if enquote and ' ' in executable:
executable = '"%s"' % executable
executable = fsencode(executable)
shebang = b'#!' + executable + post_interp + b'\n'
# Python parser starts to read a script using UTF-8 until
# it gets a #coding:xxx cookie. The shebang has to be the
# first line of a file, the #coding:xxx cookie cannot be
# written before. So the shebang has to be decodable from
# UTF-8.
try:
shebang.decode('utf-8')
except UnicodeDecodeError:
raise ValueError(
'The shebang (%r) is not decodable from utf-8' % shebang)
# If the script is encoded to a custom encoding (use a
# #coding:xxx cookie), the shebang has to be decodable from
# the script encoding too.
if encoding != 'utf-8':
try:
shebang.decode(encoding)
except UnicodeDecodeError:
raise ValueError(
'The shebang (%r) is not decodable '
'from the script encoding (%r)' % (shebang, encoding))
return shebang
def _get_script_text(self, entry):
return self.script_template % dict(module=entry.prefix,
func=entry.suffix)
manifest = _DEFAULT_MANIFEST
def get_manifest(self, exename):
base = os.path.basename(exename)
return self.manifest % base
def _write_script(self, names, shebang, script_bytes, filenames, ext):
use_launcher = self.add_launchers and os.name == 'nt'
linesep = os.linesep.encode('utf-8')
if not use_launcher:
script_bytes = shebang + linesep + script_bytes
else:
if ext == 'py':
launcher = self._get_launcher('t')
else:
launcher = self._get_launcher('w')
stream = BytesIO()
with ZipFile(stream, 'w') as zf:
zf.writestr('__main__.py', script_bytes)
zip_data = stream.getvalue()
script_bytes = launcher + shebang + linesep + zip_data
for name in names:
outname = os.path.join(self.target_dir, name)
if use_launcher:
n, e = os.path.splitext(outname)
if e.startswith('.py'):
outname = n
outname = '%s.exe' % outname
try:
self._fileop.write_binary_file(outname, script_bytes)
except Exception:
# Failed writing an executable - it might be in use.
logger.warning('Failed to write executable - trying to '
'use .deleteme logic')
dfname = '%s.deleteme' % outname
if os.path.exists(dfname):
os.remove(dfname) # Not allowed to fail here
os.rename(outname, dfname) # nor here
self._fileop.write_binary_file(outname, script_bytes)
logger.debug('Able to replace executable using '
'.deleteme logic')
try:
os.remove(dfname)
except Exception:
pass # still in use - ignore error
else:
if os.name == 'nt' and not outname.endswith('.' + ext):
outname = '%s.%s' % (outname, ext)
if os.path.exists(outname) and not self.clobber:
logger.warning('Skipping existing file %s', outname)
continue
self._fileop.write_binary_file(outname, script_bytes)
if self.set_mode:
self._fileop.set_executable_mode([outname])
filenames.append(outname)
def _make_script(self, entry, filenames, options=None):
shebang = self._get_shebang('utf-8', options=options)
script = self._get_script_text(entry).encode('utf-8')
name = entry.name
scriptnames = set()
if '' in self.variants:
scriptnames.add(name)
if 'X' in self.variants:
scriptnames.add('%s%s' % (name, sys.version[0]))
if 'X.Y' in self.variants:
scriptnames.add('%s-%s' % (name, sys.version[:3]))
if options and options.get('gui', False):
ext = 'pyw'
else:
ext = 'py'
self._write_script(scriptnames, shebang, script, filenames, ext)
def _copy_script(self, script, filenames):
adjust = False
script = os.path.join(self.source_dir, convert_path(script))
outname = os.path.join(self.target_dir, os.path.basename(script))
if not self.force and not self._fileop.newer(script, outname):
logger.debug('not copying %s (up-to-date)', script)
return
# Always open the file, but ignore failures in dry-run mode --
# that way, we'll get accurate feedback if we can read the
# script.
try:
f = open(script, 'rb')
except IOError:
if not self.dry_run:
raise
f = None
else:
encoding, lines = detect_encoding(f.readline)
f.seek(0)
first_line = f.readline()
if not first_line:
logger.warning('%s: %s is an empty file (skipping)',
self.get_command_name(), script)
return
match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n'))
if match:
adjust = True
post_interp = match.group(1) or b''
if not adjust:
if f:
f.close()
self._fileop.copy_file(script, outname)
if self.set_mode:
self._fileop.set_executable_mode([outname])
filenames.append(outname)
else:
logger.info('copying and adjusting %s -> %s', script,
self.target_dir)
if not self._fileop.dry_run:
shebang = self._get_shebang(encoding, post_interp)
if b'pythonw' in first_line:
ext = 'pyw'
else:
ext = 'py'
n = os.path.basename(outname)
self._write_script([n], shebang, f.read(), filenames, ext)
if f:
f.close()
@property
def dry_run(self):
return self._fileop.dry_run
@dry_run.setter
def dry_run(self, value):
self._fileop.dry_run = value
if os.name == 'nt':
# Executable launcher support.
# Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/
def _get_launcher(self, kind):
if struct.calcsize('P') == 8: # 64-bit
bits = '64'
else:
bits = '32'
name = '%s%s.exe' % (kind, bits)
# Issue 31: don't hardcode an absolute package name, but
# determine it relative to the current package
distlib_package = __name__.rsplit('.', 1)[0]
result = finder(distlib_package).find(name).bytes
return result
# Public API follows
def make(self, specification, options=None):
"""
Make a script.
:param specification: The specification, which is either a valid export
entry specification (to make a script from a
callable) or a filename (to make a script by
copying from a source location).
:param options: A dictionary of options controlling script generation.
:return: A list of all absolute pathnames written to.
"""
filenames = []
entry = get_export_entry(specification)
if entry is None:
self._copy_script(specification, filenames)
else:
self._make_script(entry, filenames, options=options)
return filenames
def make_multiple(self, specifications, options=None):
"""
Take a list of specifications and make scripts from them,
:param specifications: A list of specifications.
:return: A list of all absolute pathnames written to,
"""
filenames = []
for specification in specifications:
filenames.extend(self.make(specification, options))
return filenames
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,721 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2013 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""
Implementation of a flexible versioning scheme providing support for PEP-386,
distribute-compatible and semantic versioning.
"""
import logging
import re
from .compat import string_types
__all__ = ['NormalizedVersion', 'NormalizedMatcher',
'LegacyVersion', 'LegacyMatcher',
'SemanticVersion', 'SemanticMatcher',
'UnsupportedVersionError', 'get_scheme']
logger = logging.getLogger(__name__)
class UnsupportedVersionError(ValueError):
"""This is an unsupported version."""
pass
class Version(object):
def __init__(self, s):
self._string = s = s.strip()
self._parts = parts = self.parse(s)
assert isinstance(parts, tuple)
assert len(parts) > 0
def parse(self, s):
raise NotImplementedError('please implement in a subclass')
def _check_compatible(self, other):
if type(self) != type(other):
raise TypeError('cannot compare %r and %r' % (self, other))
def __eq__(self, other):
self._check_compatible(other)
return self._parts == other._parts
def __ne__(self, other):
return not self.__eq__(other)
def __lt__(self, other):
self._check_compatible(other)
return self._parts < other._parts
def __gt__(self, other):
return not (self.__lt__(other) or self.__eq__(other))
def __le__(self, other):
return self.__lt__(other) or self.__eq__(other)
def __ge__(self, other):
return self.__gt__(other) or self.__eq__(other)
# See http://docs.python.org/reference/datamodel#object.__hash__
def __hash__(self):
return hash(self._parts)
def __repr__(self):
return "%s('%s')" % (self.__class__.__name__, self._string)
def __str__(self):
return self._string
@property
def is_prerelease(self):
raise NotImplementedError('Please implement in subclasses.')
class Matcher(object):
version_class = None
dist_re = re.compile(r"^(\w[\s\w'.-]*)(\((.*)\))?")
comp_re = re.compile(r'^(<=|>=|<|>|!=|==|~=)?\s*([^\s,]+)$')
num_re = re.compile(r'^\d+(\.\d+)*$')
# value is either a callable or the name of a method
_operators = {
'<': lambda v, c, p: v < c,
'>': lambda v, c, p: v > c,
'<=': lambda v, c, p: v == c or v < c,
'>=': lambda v, c, p: v == c or v > c,
'==': lambda v, c, p: v == c,
# by default, compatible => >=.
'~=': lambda v, c, p: v == c or v > c,
'!=': lambda v, c, p: v != c,
}
def __init__(self, s):
if self.version_class is None:
raise ValueError('Please specify a version class')
self._string = s = s.strip()
m = self.dist_re.match(s)
if not m:
raise ValueError('Not valid: %r' % s)
groups = m.groups('')
self.name = groups[0].strip()
self.key = self.name.lower() # for case-insensitive comparisons
clist = []
if groups[2]:
constraints = [c.strip() for c in groups[2].split(',')]
for c in constraints:
m = self.comp_re.match(c)
if not m:
raise ValueError('Invalid %r in %r' % (c, s))
groups = m.groups()
op = groups[0] or '~='
s = groups[1]
if s.endswith('.*'):
if op not in ('==', '!='):
raise ValueError('\'.*\' not allowed for '
'%r constraints' % op)
# Could be a partial version (e.g. for '2.*') which
# won't parse as a version, so keep it as a string
vn, prefix = s[:-2], True
if not self.num_re.match(vn):
# Just to check that vn is a valid version
self.version_class(vn)
else:
# Should parse as a version, so we can create an
# instance for the comparison
vn, prefix = self.version_class(s), False
clist.append((op, vn, prefix))
self._parts = tuple(clist)
def match(self, version):
"""
Check if the provided version matches the constraints.
:param version: The version to match against this instance.
:type version: Strring or :class:`Version` instance.
"""
if isinstance(version, string_types):
version = self.version_class(version)
for operator, constraint, prefix in self._parts:
f = self._operators.get(operator)
if isinstance(f, string_types):
f = getattr(self, f)
if not f:
msg = ('%r not implemented '
'for %s' % (operator, self.__class__.__name__))
raise NotImplementedError(msg)
if not f(version, constraint, prefix):
return False
return True
@property
def exact_version(self):
result = None
if len(self._parts) == 1 and self._parts[0][0] == '==':
result = self._parts[0][1]
return result
def _check_compatible(self, other):
if type(self) != type(other) or self.name != other.name:
raise TypeError('cannot compare %s and %s' % (self, other))
def __eq__(self, other):
self._check_compatible(other)
return self.key == other.key and self._parts == other._parts
def __ne__(self, other):
return not self.__eq__(other)
# See http://docs.python.org/reference/datamodel#object.__hash__
def __hash__(self):
return hash(self.