From 0cb7a56018f7f2f47df744a068f247dd031a2580 Mon Sep 17 00:00:00 2001 From: ndparker Date: Sun, 1 Jun 2014 19:05:29 +0200 Subject: [PATCH 1/6] initial import --- .gitignore | 14 ++ LICENSE | 201 +++++++++++++++ README.rst | 73 ++++++ __init__.py | 39 +++ docs/CLASSIFIERS | 10 + docs/DESCRIPTION | 40 +++ docs/PROVIDES | 1 + docs/SUMMARY | 1 + include/cext.h | 244 ++++++++++++++++++ package.cfg | 73 ++++++ py2/__init__.py | 27 ++ py2/commands.py | 267 ++++++++++++++++++++ py2/data.py | 165 +++++++++++++ py2/dev/__init__.py | 25 ++ py2/dev/_pylint.py | 258 +++++++++++++++++++ py2/dev/analysis.py | 31 +++ py2/dev/apidoc.py | 131 ++++++++++ py2/dev/userdoc.py | 50 ++++ py2/dist.py | 51 ++++ py2/ext.py | 254 +++++++++++++++++++ py2/make/__init__.py | 28 +++ py2/make/_make.py | 338 +++++++++++++++++++++++++ py2/make/default_targets.py | 110 +++++++++ py2/make/targets.py | 324 ++++++++++++++++++++++++ py2/setup.py | 419 +++++++++++++++++++++++++++++++ py2/shell.py | 478 ++++++++++++++++++++++++++++++++++++ py2/term/__init__.py | 28 +++ py2/term/_term.py | 115 +++++++++ py2/util.py | 73 ++++++ py3/__init__.py | 27 ++ py3/commands.py | 266 ++++++++++++++++++++ py3/data.py | 165 +++++++++++++ py3/dev/__init__.py | 25 ++ py3/dev/_pylint.py | 258 +++++++++++++++++++ py3/dev/analysis.py | 31 +++ py3/dev/apidoc.py | 131 ++++++++++ py3/dev/userdoc.py | 50 ++++ py3/dist.py | 51 ++++ py3/ext.py | 253 +++++++++++++++++++ py3/make/__init__.py | 28 +++ py3/make/_make.py | 338 +++++++++++++++++++++++++ py3/make/default_targets.py | 110 +++++++++ py3/make/targets.py | 326 ++++++++++++++++++++++++ py3/setup.py | 420 +++++++++++++++++++++++++++++++ py3/shell.py | 351 ++++++++++++++++++++++++++ py3/term/__init__.py | 28 +++ py3/term/_term.py | 116 +++++++++ py3/util.py | 63 +++++ setup.py.example | 39 +++ 49 files changed, 6944 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.rst create mode 100644 __init__.py create mode 100644 docs/CLASSIFIERS create mode 100644 docs/DESCRIPTION create mode 100644 docs/PROVIDES create mode 100644 docs/SUMMARY create mode 100644 include/cext.h create mode 100644 package.cfg create mode 100644 py2/__init__.py create mode 100644 py2/commands.py create mode 100644 py2/data.py create mode 100644 py2/dev/__init__.py create mode 100644 py2/dev/_pylint.py create mode 100644 py2/dev/analysis.py create mode 100644 py2/dev/apidoc.py create mode 100644 py2/dev/userdoc.py create mode 100644 py2/dist.py create mode 100644 py2/ext.py create mode 100644 py2/make/__init__.py create mode 100644 py2/make/_make.py create mode 100644 py2/make/default_targets.py create mode 100644 py2/make/targets.py create mode 100644 py2/setup.py create mode 100644 py2/shell.py create mode 100644 py2/term/__init__.py create mode 100644 py2/term/_term.py create mode 100644 py2/util.py create mode 100644 py3/__init__.py create mode 100644 py3/commands.py create mode 100644 py3/data.py create mode 100644 py3/dev/__init__.py create mode 100644 py3/dev/_pylint.py create mode 100644 py3/dev/analysis.py create mode 100644 py3/dev/apidoc.py create mode 100644 py3/dev/userdoc.py create mode 100644 py3/dist.py create mode 100644 py3/ext.py create mode 100644 py3/make/__init__.py create mode 100644 py3/make/_make.py create mode 100644 py3/make/default_targets.py create mode 100644 py3/make/targets.py create mode 100644 py3/setup.py create mode 100644 py3/shell.py create mode 100644 py3/term/__init__.py create mode 100644 py3/term/_term.py create mode 100644 py3/util.py create mode 100644 setup.py.example diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa90895 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +Makefile +*.py[cod] +*.so +dist +build +__pycache__ +docs/_userdoc/_build/ +docs/apidoc/ +docs/userdoc/ +_website/ +*.ebuild +.sw? +.*.sw? +MANIFEST diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..efdc735 --- /dev/null +++ b/README.rst @@ -0,0 +1,73 @@ + -*- coding: utf-8 -*- +TABLE OF CONTENTS +================= + +1. Introduction +2. Copyright and License +3. System Requirements +4. Installation +5. Documentation +6. Bugs +7. Author Information + + +INTRODUCTION +============ + +The setup tools are used to simplify the development and distribution of +python projects. The stuff found in this package has been collected over the +time and finally been separated into its own package. It's a neverending +task nevertheless... + + +COPYRIGHT AND LICENSE +===================== + +Copyright 2005 - 2014 +André Malo or his licensors, as applicable. + +The whole package is distributed under the Apache License Version 2.0. +You'll find a copy in the root directory of the distribution or online +at: . + + +SYSTEM REQUIREMENTS +=================== + +You need python 2 (>= 2.2) or python 3. + + +INSTALLATION +============ + +Copy the _setup directory into your project. + + +DOCUMENTATION +============= + +There's the code documentation, generated by epydoc +(), which can be found in the +docs/apidoc/ subdirectory. + +The latest documentation is also available online at +. + + +BUGS +==== + +No bugs, of course. ;-) +But if you've found one or have an idea how to improve the setup tools, please +send a mail to . + + +AUTHOR INFORMATION +================== + +André "nd" Malo +GPG: 0x8103A37E + + +If God intended people to be naked, they would be born that way. + -- Oscar Wilde diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..77dc23c --- /dev/null +++ b/__init__.py @@ -0,0 +1,39 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================ + Package _setup +================ + +This package provides tools for main package setup. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os +import sys as _sys + +if _sys.version_info[0] == 2: + __path__ = [_os.path.join(__path__[0], 'py2')] + __author__ = __author__.decode('latin-1') +elif _sys.version_info[0] == 3: + __path__ = [_os.path.join(__path__[0], 'py3')] +else: + raise RuntimeError("Unsupported python version") +del _os, _sys + +from _setup.setup import run # pylint: disable = W0611 diff --git a/docs/CLASSIFIERS b/docs/CLASSIFIERS new file mode 100644 index 0000000..99d1048 --- /dev/null +++ b/docs/CLASSIFIERS @@ -0,0 +1,10 @@ +Development Status :: 4 - Beta +Intended Audience :: Developers +License :: OSI Approved +License :: OSI Approved :: Apache License, Version 2.0 +Operating System :: OS Independent +Programming Language :: Python +Programming Language :: Python :: 2 +Topic :: Software Development :: Libraries +Topic :: Software Development :: Libraries :: Python Modules +Topic :: Utilities diff --git a/docs/DESCRIPTION b/docs/DESCRIPTION new file mode 100644 index 0000000..9b07f02 --- /dev/null +++ b/docs/DESCRIPTION @@ -0,0 +1,40 @@ +============= + Setup Tools +============= + +The setup tools are used to simplify the development and distribution of +python projects. The stuff found in this package has been collected over the +time and finally been separated into its own package. It's a neverending +task nevertheless... + + +Copyright and License +~~~~~~~~~~~~~~~~~~~~~ + +Copyright 2005 - 2014 +André Malo or his licensors, as applicable. + +The whole package is distributed under the Apache License Version 2.0. +You'll find a copy in the root directory of the distribution or online +at: . + + +Bugs +~~~~ + +No bugs, of course. ;-) +But if you've found one or have an idea how to improve the setup tools, please +send a mail to . + + +Author Information +~~~~~~~~~~~~~~~~~~ + +André "nd" Malo +GPG: 0x8103A37E + + + If God intended people to be naked, they would be born that way. + -- Oscar Wilde + +.. vim:tw=72 syntax=rest diff --git a/docs/PROVIDES b/docs/PROVIDES new file mode 100644 index 0000000..f3d7a9d --- /dev/null +++ b/docs/PROVIDES @@ -0,0 +1 @@ +_setup (0.9) diff --git a/docs/SUMMARY b/docs/SUMMARY new file mode 100644 index 0000000..c7754fd --- /dev/null +++ b/docs/SUMMARY @@ -0,0 +1 @@ +Setup tools diff --git a/include/cext.h b/include/cext.h new file mode 100644 index 0000000..47b6f5b --- /dev/null +++ b/include/cext.h @@ -0,0 +1,244 @@ +/* + * Copyright 2006 - 2014 + * Andr\xe9 Malo or his licensors, as applicable + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * central naming stuff + */ + +#ifndef SETUP_CEXT_H +#define SETUP_CEXT_H + +#ifndef EXT_MODULE +#error EXT_MODULE must be defined outside of this file (-DEXT_MODULE=...) +#endif + +/* + * include core header files + */ +#define PY_SSIZE_T_CLEAN + +#include "Python.h" +#include "structmember.h" + +/* + * define our helper macros depending on the stuff above + */ +#define STRINGIFY(n) STRINGIFY_HELPER(n) +#define STRINGIFY_HELPER(n) #n +#define CONCATENATE(first, second) CONCATENATE_HELPER(first, second) +#define CONCATENATE_HELPER(first, second) first##second + +#define EXT_MODULE_NAME STRINGIFY(EXT_MODULE) +#ifdef EXT_PACKAGE +#define EXT_PACKAGE_NAME STRINGIFY(EXT_PACKAGE) +#define EXT_MODULE_PATH EXT_PACKAGE_NAME "." EXT_MODULE_NAME +#else +#define EXT_PACKAGE_NAME "" +#define EXT_MODULE_PATH EXT_MODULE_NAME +#endif + +#define EXT_DOCS_VAR CONCATENATE(var, CONCATENATE(EXT_MODULE, __doc__)) +#define EXT_METHODS_VAR CONCATENATE(var, CONCATENATE(EXT_MODULE, _methods)) +#define EXT_METHODS static PyMethodDef EXT_METHODS_VAR[] + +#define EXT_DEFINE_VAR CONCATENATE(var, CONCATENATE(EXT_MODULE, _module)) + +/* Py3K Support */ +#if PY_MAJOR_VERSION >= 3 + +#define EXT3 + +#ifndef PyMODINIT_FUNC +#define EXT_INIT_FUNC PyObject *CONCATENATE(PyInit_, EXT_MODULE)(void) +#else +#define EXT_INIT_FUNC PyMODINIT_FUNC CONCATENATE(PyInit_, EXT_MODULE)(void) +#endif + +#define EXT_DEFINE(name, methods, doc) \ +static struct PyModuleDef EXT_DEFINE_VAR = { \ + PyModuleDef_HEAD_INIT, \ + name, \ + doc, \ + -1, \ + methods, \ + NULL, \ + NULL, \ + NULL, \ + NULL \ +} + +#define EXT_CREATE(def) (PyModule_Create(def)) +#define EXT_INIT_ERROR(module) do {Py_XDECREF(module); return NULL;} while(0) +#define EXT_INIT_RETURN(module) return module + +#else /* end py3k */ + +#define EXT2 + +#ifndef PyMODINIT_FUNC +#define EXT_INIT_FUNC void CONCATENATE(init, EXT_MODULE)(void) +#else +#define EXT_INIT_FUNC PyMODINIT_FUNC CONCATENATE(init, EXT_MODULE)(void) +#endif + +#define EXT_DEFINE__STRUCT \ + CONCATENATE(struct, CONCATENATE(EXT_MODULE, _module)) + +struct EXT_DEFINE__STRUCT { + char *m_name; + char *m_doc; + PyMethodDef *m_methods; +}; +#define EXT_DEFINE(name, methods, doc) \ +static struct EXT_DEFINE__STRUCT EXT_DEFINE_VAR = { \ + name, \ + doc, \ + methods \ +} + +#define EXT_CREATE(def) ((def)->m_doc \ + ? Py_InitModule3((def)->m_name, (def)->m_methods, (def)->m_doc) \ + : Py_InitModule((def)->m_name, (def)->m_methods) \ +) +#define EXT_INIT_ERROR(module) return +#define EXT_INIT_RETURN(module) return + +#endif /* end py2K */ + +#define EXT_INIT_TYPE(module, type) do { \ + if (PyType_Ready(type) < 0) \ + EXT_INIT_ERROR(module); \ +} while (0) + +#define EXT_ADD_TYPE(module, name, type) do { \ + Py_INCREF(type); \ + if (PyModule_AddObject(module, name, (PyObject *)(type)) < 0) \ + EXT_INIT_ERROR(module); \ +} while (0) + +#define EXT_ADD_UNICODE(module, name, string, encoding) do { \ + if (PyModule_AddObject( \ + module, \ + name, \ + PyUnicode_Decode( \ + string, \ + sizeof(string) - 1, \ + encoding, \ + "strict" \ + )) < 0) \ + EXT_INIT_ERROR(module); \ +} while (0) + +#define EXT_ADD_STRING(module, name, string) do { \ + if (PyModule_AddStringConstant(module, name, string) < 0) \ + EXT_INIT_ERROR(module); \ +} while (0) + +#define EXT_ADD_INT(module, name, number) do { \ + if (PyModule_AddIntConstant(module, name, number) < 0) \ + EXT_INIT_ERROR(module); \ +} while (0) + + +/* PEP 353 support, implemented as of python 2.5 */ +#if PY_VERSION_HEX < 0x02050000 +typedef int Py_ssize_t; +#define PyInt_FromSsize_t(arg) PyInt_FromLong((long)arg) +#define PyInt_AsSsize_t(arg) (int)PyInt_AsLong(arg) +#define PY_SSIZE_T_MAX ((Py_ssize_t)INT_MAX) +#endif + +/* + * some helper macros (Python 2.4) + */ +#ifndef Py_VISIT +#define Py_VISIT(op) do { \ + if (op) { \ + int vret = visit((op), arg); \ + if (vret) return vret; \ + } \ +} while (0) +#endif + +#ifdef Py_CLEAR +#undef Py_CLEAR +#endif +#define Py_CLEAR(op) do { \ + if (op) { \ + PyObject *tmp__ = (PyObject *)(op); \ + (op) = NULL; \ + Py_DECREF(tmp__); \ + } \ +} while (0) + +#ifndef Py_RETURN_NONE +#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#endif + +#ifndef Py_RETURN_FALSE +#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False +#endif + +#ifndef Py_RETURN_TRUE +#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True +#endif + +/* Macros for inline documentation. (Python 2.3) */ +#ifndef PyDoc_VAR +#define PyDoc_VAR(name) static char name[] +#endif + +#ifndef PyDoc_STRVAR +#define PyDoc_STRVAR(name,str) PyDoc_VAR(name) = PyDoc_STR(str) +#endif + +#ifndef PyDoc_STR +#ifdef WITH_DOC_STRINGS +#define PyDoc_STR(str) str +#else +#define PyDoc_STR(str) "" +#endif +#endif + +/* Basestring check (basestring introduced in Python 2.3) */ +#if PY_VERSION_HEX < 0x02030000 +#define BaseString_Check(type) ( \ + PyObject_TypeCheck((type), &PyString_Type) \ + || PyObject_TypeCheck((type), &PyUnicode_Type) \ +) +#else +#define BaseString_Check(type) PyObject_TypeCheck((type), &PyBaseString_Type) +#endif + +#define GENERIC_ALLOC(type) \ + ((void *)((PyTypeObject *)type)->tp_alloc(type, (Py_ssize_t)0)) + +/* PyPy doesn't define it */ +#ifndef PyType_IS_GC +#define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC) +#endif + +#define DEFINE_GENERIC_DEALLOC(prefix) \ +static void prefix##_dealloc(void *self) \ +{ \ + if (PyType_IS_GC(((PyObject *)self)->ob_type)) \ + PyObject_GC_UnTrack(self); \ + (void)prefix##_clear(self); \ + ((PyObject *)self)->ob_type->tp_free((PyObject *)self); \ +} + +#endif /* SETUP_CEXT_H */ diff --git a/package.cfg b/package.cfg new file mode 100644 index 0000000..08f9415 --- /dev/null +++ b/package.cfg @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2005, 2006, 2007, 2008, 2009, 2010, 2011 +# André Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[package] +name = setup + +python.min = 2.2 +#python.max = 2.7 + +version.number = 0.9.2 +version.dev = false +version.revision = 3227 + +author.name = André Malo +author.email = nd@perlig.de +#maintainer.name = +#maintainer.email = +url.homepage = http://opensource.perlig.de/setup/ +url.download = http://storage.perlig.de/setup/ + + +[docs] +meta.classifiers = docs/CLASSIFIERS +meta.description = docs/DESCRIPTION +meta.summary = docs/SUMMARY +meta.provides = docs/PROVIDES +meta.license = LICENSE +meta.keywords = + setup + +apidoc.dir = docs/apidoc +apidoc.strip = 1 +#apidoc.ignore = + +#userdoc.dir = docs/userdoc +#userdoc.strip = 1 +#userdoc.ignore = +# .buildinfo + +#examples.dir = docs/examples +#examples.strip = 1 +#examples.ignore = + +#man = + +extra = +# docs/CHANGES + README + + +[manifest] +#packages.lib = . +packages.collect = _setup +#modules = + +#scripts = + +#dist = +# tests diff --git a/py2/__init__.py b/py2/__init__.py new file mode 100644 index 0000000..9582ecc --- /dev/null +++ b/py2/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================ + Package _setup +================ + +This package provides tools for main package setup. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +from _setup.setup import run # pylint: disable = W0611 diff --git a/py2/commands.py b/py2/commands.py new file mode 100644 index 0000000..a41b166 --- /dev/null +++ b/py2/commands.py @@ -0,0 +1,267 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + Command extenders +=================== + +Command extenders. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" +__test__ = False + +from distutils import fancy_getopt as _fancy_getopt +from distutils.command import build as _build +from distutils.command import build_ext as _build_ext +from distutils.command import install as _install +from distutils.command import install_data as _install_data +from distutils.command import install_lib as _install_lib +import os as _os + +from _setup.util import log + +_option_defaults = {} +_option_inherits = {} +_option_finalizers = {} +_command_mapping = { + 'install': 'Install', + 'install_data': 'InstallData', + 'install_lib': 'InstallLib', + 'build': 'Build', + 'build_ext': 'BuildExt', +} + + +def add_option(command, long_name, help_text, short_name=None, default=None, + inherit=None): + """ Add an option """ + try: + command_class = globals()[_command_mapping[command]] + except KeyError: + raise ValueError("Unknown command %r" % (command,)) + for opt in command_class.user_options: + if opt[0] == long_name: + break + else: + opt = (long_name, short_name, help_text) + command_class.user_options.append(opt) + if not long_name.endswith('='): + command_class.boolean_options.append(long_name) + attr_name = _fancy_getopt.translate_longopt(long_name) + else: + attr_name = _fancy_getopt.translate_longopt(long_name[:-1]) + if not _option_defaults.has_key(command): + _option_defaults[command] = [] + if inherit is not None: + if isinstance(inherit, (str, unicode)): + inherit = [inherit] + for i_inherit in inherit: + add_option( + i_inherit, long_name, help_text, short_name, default + ) + default = None + if not _option_inherits.has_key(command): + _option_inherits[command] = [] + for i_inherit in inherit: + for i_command, opt_name in _option_inherits[command]: + if i_command == i_inherit and opt_name == attr_name: + break + else: + _option_inherits[command].append((i_inherit, attr_name)) + _option_defaults[command].append((attr_name, default)) + + +def add_finalizer(command, key, func): + """ Add finalizer """ + if not _option_finalizers.has_key(command): + _option_finalizers[command] = {} + if not _option_finalizers[command].has_key(key): + _option_finalizers[command][key] = func + + +class Install(_install.install): + """ Extended installer to reflect the additional data options """ + user_options = _install.install.user_options + [ + ('single-version-externally-managed', None, + "Compat option. Does not a thing."), + ] + boolean_options = _install.install.boolean_options + [ + 'single-version-externally-managed' + ] + + def initialize_options(self): + """ Prepare for new options """ + _install.install.initialize_options(self) + self.single_version_externally_managed = None + if _option_defaults.has_key('install'): + for opt_name, default in _option_defaults['install']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _install.install.finalize_options(self) + if _option_inherits.has_key('install'): + for parent, opt_name in _option_inherits['install']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if _option_finalizers.has_key('install'): + for func in _option_finalizers['install'].values(): + func(self) + + +class InstallData(_install_data.install_data): + """ Extended data installer """ + user_options = _install_data.install_data.user_options + [] + boolean_options = _install_data.install_data.boolean_options + [] + + def initialize_options(self): + """ Prepare for new options """ + _install_data.install_data.initialize_options(self) + if _option_defaults.has_key('install_data'): + for opt_name, default in _option_defaults['install_data']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _install_data.install_data.finalize_options(self) + if _option_inherits.has_key('install_data'): + for parent, opt_name in _option_inherits['install_data']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if _option_finalizers.has_key('install_data'): + for func in _option_finalizers['install_data'].values(): + func(self) + + +class InstallLib(_install_lib.install_lib): + """ Extended lib installer """ + user_options = _install_lib.install_lib.user_options + [] + boolean_options = _install_lib.install_lib.boolean_options + [] + + def initialize_options(self): + """ Prepare for new options """ + _install_lib.install_lib.initialize_options(self) + if _option_defaults.has_key('install_lib'): + for opt_name, default in _option_defaults['install_lib']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _install_lib.install_lib.finalize_options(self) + if _option_inherits.has_key('install_lib'): + for parent, opt_name in _option_inherits['install_lib']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if _option_finalizers.has_key('install_lib'): + for func in _option_finalizers['install_lib'].values(): + func(self) + + +class BuildExt(_build_ext.build_ext): + """ + Extended extension builder class + + This class allows extensions to provide a ``check_prerequisites`` method + which is called before actually building it. The method takes the + `BuildExt` instance and returns whether the extension should be skipped or + not. + """ + + def initialize_options(self): + """ Prepare for new options """ + _build_ext.build_ext.initialize_options(self) + if _option_defaults.has_key('build_ext'): + for opt_name, default in _option_defaults['build_ext']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _build_ext.build_ext.finalize_options(self) + if _option_inherits.has_key('build_ext'): + for parent, opt_name in _option_inherits['build_ext']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if _option_finalizers.has_key('build_ext'): + for func in _option_finalizers['build_ext'].values(): + func(self) + + def build_extension(self, ext): + """ + Build C extension - with extended functionality + + The following features are added here: + + - ``ext.check_prerequisites`` is called before the extension is being + built. See `Extension` for details. If the method does not exist, + simply no check will be run. + - The macros ``EXT_PACKAGE`` and ``EXT_MODULE`` will be filled (or + unset) depending on the extensions name, but only if they are not + already defined. + + :Parameters: + `ext` : `Extension` + The extension to build. If it's a pure + ``distutils.core.Extension``, simply no prequisites check is + applied. + + :Return: whatever ``distutils.command.build_ext.build_ext`` returns + :Rtype: any + """ + # handle name macros + macros = dict(ext.define_macros or ()) + tup = ext.name.split('.') + if len(tup) == 1: + pkg, mod = None, tup[0] + else: + pkg, mod = '.'.join(tup[:-1]), tup[-1] + if pkg is not None and 'EXT_PACKAGE' not in macros: + ext.define_macros.append(('EXT_PACKAGE', pkg)) + if 'EXT_MODULE' not in macros: + ext.define_macros.append(('EXT_MODULE', mod)) + if pkg is None: + macros = dict(ext.undef_macros or ()) + if 'EXT_PACKAGE' not in macros: + ext.undef_macros.append('EXT_PACKAGE') + + # handle prereq checks + try: + checker = ext.check_prerequisites + except AttributeError: + pass + else: + if checker(self): + log.info("Skipping %s extension" % ext.name) + return + + return _build_ext.build_ext.build_extension(self, ext) + + +class Build(_build.build): + + def initialize_options(self): + """ Prepare for new options """ + _build.build.initialize_options(self) + if _option_defaults.has_key('build'): + for opt_name, default in _option_defaults['build']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _build.build.finalize_options(self) + if _option_inherits.has_key('build'): + for parent, opt_name in _option_inherits['build']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if _option_finalizers.has_key('build'): + for func in _option_finalizers['build'].values(): + func(self) diff --git a/py2/data.py b/py2/data.py new file mode 100644 index 0000000..9cf04e1 --- /dev/null +++ b/py2/data.py @@ -0,0 +1,165 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + Data distribution +=================== + +This module provides tools to simplify data distribution. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +from distutils import filelist as _filelist +import os as _os +import posixpath as _posixpath +import sys as _sys + +from _setup import commands as _commands + + +def splitpath(path): + """ Split a path """ + drive, path = '', _os.path.normpath(path) + try: + splitunc = _os.path.splitunc + except AttributeError: + pass + else: + drive, path = splitunc(path) + if not drive: + drive, path = _os.path.splitdrive(path) + elems = [] + try: + sep = _os.path.sep + except AttributeError: + sep = _os.path.join('1', '2')[1:-1] + while 1: + prefix, path = _os.path.split(path) + elems.append(path) + if prefix in ('', sep): + drive = _os.path.join(drive, prefix) + break + path = prefix + elems.reverse() + return drive, elems + + +def finalizer(installer): + """ Finalize install_data """ + data_files = [] + for item in installer.data_files: + if not isinstance(item, Data): + data_files.append(item) + continue + data_files.extend(item.flatten(installer)) + installer.data_files = data_files + + +class Data(object): + """ File list container """ + + def __init__(self, files, target=None, preserve=0, strip=0, + prefix=None): + """ Initialization """ + self._files = files + self._target = target + self._preserve = preserve + self._strip = strip + self._prefix = prefix + self.fixup_commands() + + def fixup_commands(self): + pass + + def from_templates(cls, *templates, **kwargs): + """ Initialize from template """ + files = _filelist.FileList() + for tpl in templates: + for line in tpl.split(';'): + files.process_template_line(line.strip()) + files.sort() + files.remove_duplicates() + result = [] + for filename in files.files: + _, elems = splitpath(filename) + if '.svn' in elems or '.git' in elems: + continue + result.append(filename) + return cls(result, **kwargs) + from_templates = classmethod(from_templates) + + def flatten(self, installer): + """ Flatten the file list to (target, file) tuples """ + # pylint: disable = W0613 + if self._prefix: + _, prefix = splitpath(self._prefix) + telems = prefix + else: + telems = [] + + tmap = {} + for fname in self._files: + (_, name), target = splitpath(fname), telems + if self._preserve: + if self._strip: + name = name[max(0, min(self._strip, len(name) - 1)):] + if len(name) > 1: + target = telems + name[:-1] + tmap.setdefault(_posixpath.join(*target), []).append(fname) + return tmap.items() + + +class Documentation(Data): + """ Documentation container """ + + def fixup_commands(self): + _commands.add_option('install_data', 'without-docs', + help_text='Do not install documentation files', + inherit='install', + ) + _commands.add_finalizer('install_data', 'documentation', finalizer) + + def flatten(self, installer): + """ Check if docs should be installed at all """ + if installer.without_docs: + return [] + return Data.flatten(self, installer) + + +class Manpages(Documentation): + """ Manpages container """ + + def dispatch(cls, files): + """ Automatically dispatch manpages to their target directories """ + mpmap = {} + for manpage in files: + normalized = _os.path.normpath(manpage) + _, ext = _os.path.splitext(normalized) + if ext.startswith(_os.path.extsep): + ext = ext[len(_os.path.extsep):] + mpmap.setdefault(ext, []).append(manpage) + return [cls(manpages, prefix=_posixpath.join( + 'share', 'man', 'man%s' % section, + )) for section, manpages in mpmap.items()] + dispatch = classmethod(dispatch) + + def flatten(self, installer): + """ Check if manpages are suitable """ + if _sys.platform == 'win32': + return [] + return Documentation.flatten(self, installer) diff --git a/py2/dev/__init__.py b/py2/dev/__init__.py new file mode 100644 index 0000000..5f6748c --- /dev/null +++ b/py2/dev/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: ascii -*- +# +# Copyright 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +==================== + Package _setup.dev +==================== + +Development tools, not distributed. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" diff --git a/py2/dev/_pylint.py b/py2/dev/_pylint.py new file mode 100644 index 0000000..4c3c085 --- /dev/null +++ b/py2/dev/_pylint.py @@ -0,0 +1,258 @@ +# -*- coding: ascii -*- +# +# Copyright 2006, 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================================= + Support for code analysis tools +================================= + +Support for code analysis tools. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import re as _re +import sys as _sys + +from _setup import term as _term +from _setup import shell as _shell + + +class NotFinished(Exception): + """ Exception used for message passing in the stream filter """ + +class NotParseable(Exception): + """ Exception used for message passing in the stream filter """ + +class SpecialMessage(Exception): + """ Exception used for message passing in the stream filter """ + + +class FilterStream(object): + """ Stream filter """ + _LINERE = _re.compile(r''' + (?P[^:]+) + : + (?P\d+) + :\s+ + \[(?P[^\],]+)(?:,\s+(?P[^\]]+))?\] + \s+ + (?P.*) + ''', _re.X) + _SIMRE = _re.compile(r'in (?P\d+) files') + _CYCRE = _re.compile(r'\((?P[^)]+)\)') + + def __init__(self, term, stream=_sys.stdout): + self.written = False + self._stream = stream + self._lastname = None + self._cycled = False + self._term = dict(term) + self._buffer = '' + + def write(self, towrite): + """ Stream write function """ + self._buffer += towrite + term = self._term + + while True: + try: + name, lineno, mid, func, desc = self._parse() + except NotFinished: + break + except SpecialMessage, e: + self._dospecial(e) + continue + except NotParseable, e: + self._print_literal(str(e.args[0])) + continue + + if name != self._lastname: + if self._lastname is not None: + self._stream.write("\n") + term['path'] = name + self._stream.write( + "%(BOLD)s>>> %(path)s%(NORMAL)s\n" % term + ) + self._lastname = name + self.written = True + + term['mid'] = mid + if mid.startswith('E') or mid.startswith('F'): + self._stream.write("%(BOLD)s%(RED)s%(mid)s%(NORMAL)s" % term) + elif mid == 'W0511': + self._stream.write( + "%(BOLD)s%(GREEN)s%(mid)s%(NORMAL)s" % term + ) + else: + self._stream.write( + "%(BOLD)s%(YELLOW)s%(mid)s%(NORMAL)s" % term + ) + + if int(lineno) != 0: + term['lineno'] = lineno + self._stream.write(" (%(lineno)s" % term) + if func: + term['func'] = func + self._stream.write( + ", %(BOLD)s%(YELLOW)s%(func)s%(NORMAL)s" % term + ) + self._stream.write(')') + + self._stream.write(": %s\n" % desc) + self._stream.flush() + + return + + def _print_literal(self, line): + """ Print literal """ + suppress = ( + line.startswith('Unable to get imported names for ') or + line.startswith("Exception exceptions.RuntimeError: 'generator " + "ignored GeneratorExit' in = 0: + lines = self._buffer[:pos + 1] + self._buffer = self._buffer[pos + 1:] + term = self._term + + self._stream.write("\n") + for name in lines.splitlines()[1:]: + name = name.rstrip()[2:] + term['path'] = name + self._stream.write( + "%(BOLD)s=== %(path)s%(NORMAL)s\n" % term + ) + self._lastname = name + + term['mid'] = e.args[0] + self._stream.write( + "%(BOLD)s%(YELLOW)s%(mid)s%(NORMAL)s" % term + ) + self._stream.write(": %s\n" % e.args[1]) + self._stream.flush() + self.written = True + + def _parse(self): + """ Parse output """ + if '\n' not in self._buffer: + raise NotFinished() + + line = self._buffer[:self._buffer.find('\n') + 1] + self._buffer = self._buffer[len(line):] + line = line.rstrip() + match = self._LINERE.match(line) + if not match: + raise NotParseable(line) + + mid = match.group('mid') + if mid in ('R0801', 'R0401'): + self._buffer = "%s\n%s" % (line, self._buffer) + raise SpecialMessage(mid, match.group('desc')) + + return match.group('name', 'lineno', 'mid', 'func', 'desc') + + +def run(config, *args): + """ Run pylint """ + try: + from pylint import lint + from pylint.reporters import text + except ImportError: + return 2 + + if config is None: + config = _shell.native('pylint.conf') + argv = ['--rcfile', config, + '--reports', 'no', + '--output-format', 'parseable', + '--include-ids', 'yes' + ] + + stream = FilterStream(_term.terminfo()) + + old_stderr = _sys.stderr + try: + # pylint: disable = E1101 + _sys.stderr = stream + from pylint import __pkginfo__ + if __pkginfo__.numversion < (0, 13): + # The lint tool is not very user friendly, so we need a hack here. + lint.REPORTER_OPT_MAP['parseable'] = \ + lambda: text.TextReporter2(stream) + reporter = text.TextReporter2(stream) + else: + reporter = text.ParseableTextReporter(stream) + lint.REPORTER_OPT_MAP['parseable'] = lambda: reporter + + for path in args: + try: + try: + lint.Run(argv + [path], reporter=reporter) + except SystemExit: + pass # don't accept the exit. strange errors happen... + + if stream.written: + print + stream.written = False + except KeyboardInterrupt: + print + raise + finally: + _sys.stderr = old_stderr + + return 0 diff --git a/py2/dev/analysis.py b/py2/dev/analysis.py new file mode 100644 index 0000000..6faa8eb --- /dev/null +++ b/py2/dev/analysis.py @@ -0,0 +1,31 @@ +# -*- coding: ascii -*- +# +# Copyright 2006, 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================================= + Support for code analysis tools +================================= + +Support for code analysis tools. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + + +def pylint(config, *args): + """ Run pylint """ + from _setup.dev import _pylint + return _pylint.run(config, *args) diff --git a/py2/dev/apidoc.py b/py2/dev/apidoc.py new file mode 100644 index 0000000..63a4669 --- /dev/null +++ b/py2/dev/apidoc.py @@ -0,0 +1,131 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================== + API doc builders +================== + +API doc builders. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os +import re as _re + +from _setup import shell as _shell +from _setup import term as _term +from _setup import util as _util + + +def _cleanup_epydoc(target): + """ + Cleanup epydoc generated files + + This removes the epydoc-footer. It changes every release because of the + timestamp. That creates bad diffs (accidently it's also invalid html). + """ + search = _re.compile(r']+width="100%%"').search + for filename in _shell.files(target, '*.html'): + fp = open(filename, 'r') + try: + html = fp.read() + finally: + fp.close() + match = search(html) + if match: + start = match.start() + end = html.find('', start) + if end >= 0: + end += len('') + 1 + html = html[:start] + html[end:] + fp = open(filename, 'w') + try: + fp.write(html) + finally: + fp.close() + + +_VERSION_SEARCH = _re.compile( + r'\bversion\s+(?P\d+)\.(?P\d+)' +).search +def epydoc(**kwargs): + """ Run epydoc """ + # pylint: disable = R0912 + prog = kwargs.get('epydoc') or 'epydoc' + if not _os.path.dirname(_os.path.normpath(prog)): + prog = _shell.frompath(prog) + if not prog: + _term.red("%(epydoc)s not found", + epydoc=kwargs.get('epydoc') or 'epydoc', + ) + return False + + version = _VERSION_SEARCH(_shell.spawn(prog, "--version", stdout=True)) + if version is not None: + try: + version = tuple(map(int, version.group('major', 'minor'))) + except (TypeError, ValueError): + version = None + if version is None: + _term.red("%(prog)s version not recognized" % locals()) + return False + + if version < (3, 0): + _term.red("%(prog)s is too old %(version)r < (3, 0)" % locals()) + return False + + env = dict(_os.environ) + + prepend = kwargs.get('prepend') + if prepend: + toprepend = _os.pathsep.join(map(str, prepend)) + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = _os.pathsep.join(( + toprepend, env['PYTHONPATH'] + )) + else: + env['PYTHONPATH'] = toprepend + + append = kwargs.get('append') + if append: + toappend = _os.pathsep.join(map(str, append)) + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = _os.pathsep.join(( + env['PYTHONPATH'], toappend + )) + else: + env['PYTHONPATH'] = toappend + + moreenv = kwargs.get('env') + if moreenv: + env.update(moreenv) + + config = kwargs.get('config') or _shell.native('docs/epydoc.conf') + + argv = [prog, '--config', config] + res = not _shell.spawn(*argv, **{'env': env}) + if res: + cfg = _util.SafeConfigParser() + cfg.read(config) + try: + target = dict(cfg.items('epydoc'))['target'] + except KeyError: + pass + else: + _cleanup_epydoc(target) + return res diff --git a/py2/dev/userdoc.py b/py2/dev/userdoc.py new file mode 100644 index 0000000..37ec7ed --- /dev/null +++ b/py2/dev/userdoc.py @@ -0,0 +1,50 @@ +# -*- coding: ascii -*- +# +# Copyright 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + User doc builders +=================== + +User doc builders. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os + +from _setup import shell as _shell +from _setup import term as _term + + +def sphinx(**kwargs): + """ Run sphinx """ + prog = _shell.frompath('sphinx-build') + if prog is None: + _term.red("sphinx-build not found") + return False + + env = dict(_os.environ) + + argv = [ + prog, '-a', + '-d', _os.path.join(kwargs['build'], 'doctrees'), + '-b', 'html', + kwargs['source'], + kwargs['target'], + ] + + return not _shell.spawn(*argv, **{'env': env}) diff --git a/py2/dist.py b/py2/dist.py new file mode 100644 index 0000000..21a6541 --- /dev/null +++ b/py2/dist.py @@ -0,0 +1,51 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================ + dist utilities +================ + +dist utilities. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import sys as _sys + +from _setup import shell as _shell + + +def run_setup(*args, **kwargs): + """ Run setup """ + if 'setup' in kwargs: + script = kwargs.get('setup') or 'setup.py' + del kwargs['setup'] + else: + script = 'setup.py' + if 'fakeroot' in kwargs: + fakeroot = kwargs['fakeroot'] + del kwargs['fakeroot'] + else: + fakeroot = None + if kwargs: + raise TypeError("Unrecognized keyword parameters") + + script = _shell.native(script) + argv = [_sys.executable, script] + list(args) + if fakeroot: + argv.insert(0, fakeroot) + return not _shell.spawn(*argv) diff --git a/py2/ext.py b/py2/ext.py new file mode 100644 index 0000000..bcc0209 --- /dev/null +++ b/py2/ext.py @@ -0,0 +1,254 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + C extension tools +=================== + +C extension tools. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" +__test__ = False + +from distutils import core as _core +from distutils import errors as _distutils_errors +import os as _os +import posixpath as _posixpath +import shutil as _shutil +import tempfile as _tempfile + +from _setup import commands as _commands +from _setup.util import log + + +def _install_finalizer(installer): + if installer.without_c_extensions: + installer.distribution.ext_modules = [] + +def _build_finalizer(builder): + if builder.without_c_extensions: + builder.extensions = [] + + +class Extension(_core.Extension): + """ + Extension with prerequisite check interface + + If your check is cacheable (during the setup run), override + `cached_check_prerequisites`, `check_prerequisites` otherwise. + + :IVariables: + `cached_check` : ``bool`` + The cached check result + """ + cached_check = None + + def __init__(self, *args, **kwargs): + """ Initialization """ + if kwargs.has_key('depends'): + self.depends = kwargs['depends'] or [] + else: + self.depends = [] + _core.Extension.__init__(self, *args, **kwargs) + + # add include path + included = _posixpath.join('_setup', 'include') + if included not in self.include_dirs: + self.include_dirs.append(included) + + # add cext.h to the dependencies + cext_h = _posixpath.join(included, 'cext.h') + if cext_h not in self.depends: + self.depends.append(cext_h) + + _commands.add_option('install_lib', 'without-c-extensions', + help_text='Don\'t install C extensions', + inherit='install', + ) + _commands.add_finalizer('install_lib', 'c-extensions', + _install_finalizer + ) + _commands.add_option('build_ext', 'without-c-extensions', + help_text='Don\'t build C extensions', + inherit=('build', 'install_lib'), + ) + _commands.add_finalizer('build_ext', 'c-extensions', _build_finalizer) + + def check_prerequisites(self, build): + """ + Check prerequisites + + The check should cover all dependencies needed for the extension to + be built and run. The method can do the following: + + - return a false value: the extension will be built + - return a true value: the extension will be skipped. This is useful + for optional extensions + - raise an exception. This is useful for mandatory extensions + + If the check result is cacheable (during the setup run), override + `cached_check_prerequisites` instead. + + :Parameters: + `build` : `BuildExt` + The extension builder + + :Return: Skip the extension? + :Rtype: ``bool`` + """ + if self.cached_check is None: + log.debug("PREREQ check for %s" % self.name) + self.cached_check = self.cached_check_prerequisites(build) + else: + log.debug("PREREQ check for %s (cached)" % self.name) + return self.cached_check + + def cached_check_prerequisites(self, build): + """ + Check prerequisites + + The check should cover all dependencies needed for the extension to + be built and run. The method can do the following: + + - return a false value: the extension will be built + - return a true value: the extension will be skipped. This is useful + for optional extensions + - raise an exception. This is useful for mandatory extensions + + If the check result is *not* cacheable (during the setup run), + override `check_prerequisites` instead. + + :Parameters: + `build` : `BuildExt` + The extension builder + + :Return: Skip the extension? + :Rtype: ``bool`` + """ + # pylint: disable = W0613 + log.debug("Nothing to check for %s!" % self.name) + return False + + +class ConfTest(object): + """ + Single conftest abstraction + + :IVariables: + `_tempdir` : ``str`` + The tempdir created for this test + + `src` : ``str`` + Name of the source file + + `target` : ``str`` + Target filename + + `compiler` : ``CCompiler`` + compiler instance + + `obj` : ``list`` + List of object filenames (``[str, ...]``) + """ + _tempdir = None + + def __init__(self, build, source): + """ + Initialization + + :Parameters: + `build` : ``distuils.command.build_ext.build_ext`` + builder instance + + `source` : ``str`` + Source of the file to compile + """ + self._tempdir = tempdir = _tempfile.mkdtemp() + src = _os.path.join(tempdir, 'conftest.c') + fp = open(src, 'w') + try: + fp.write(source) + finally: + fp.close() + self.src = src + self.compiler = compiler = build.compiler + self.target = _os.path.join(tempdir, 'conftest') + self.obj = compiler.object_filenames([src], output_dir=tempdir) + + def __del__(self): + """ Destruction """ + self.destroy() + + def destroy(self): + """ Destroy the conftest leftovers on disk """ + tempdir, self._tempdir = self._tempdir, None + if tempdir is not None: + _shutil.rmtree(tempdir) + + def compile(self, **kwargs): + """ + Compile the conftest + + :Parameters: + `kwargs` : ``dict`` + Optional keyword parameters for the compiler call + + :Return: Was the compilation successful? + :Rtype: ``bool`` + """ + kwargs['output_dir'] = self._tempdir + try: + self.compiler.compile([self.src], **kwargs) + except _distutils_errors.CompileError: + return False + return True + + def link(self, **kwargs): + r""" + Link the conftest + + Before you can link the conftest objects they need to be `compile`\d. + + :Parameters: + `kwargs` : ``dict`` + Optional keyword parameters for the linker call + + :Return: Was the linking successful? + :Rtype: ``bool`` + """ + try: + self.compiler.link_executable(self.obj, self.target, **kwargs) + except _distutils_errors.LinkError: + return False + return True + + def pipe(self, mode="r"): + r""" + Execute the conftest binary and connect to it using a pipe + + Before you can pipe to or from the conftest binary it needs to + be `link`\ed. + + :Parameters: + `mode` : ``str`` + Pipe mode - r/w + + :Return: The open pipe + :Rtype: ``file`` + """ + return _os.popen(self.compiler.executable_filename(self.target), mode) diff --git a/py2/make/__init__.py b/py2/make/__init__.py new file mode 100644 index 0000000..d3ab493 --- /dev/null +++ b/py2/make/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: ascii -*- +# +# Copyright 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +===================== + Package _setup.make +===================== + +Make tools, not distributed. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +# pylint: disable = W0611 +from _setup.make._make import main, fail, warn, fatal, Target diff --git a/py2/make/_make.py b/py2/make/_make.py new file mode 100644 index 0000000..4a01ed9 --- /dev/null +++ b/py2/make/_make.py @@ -0,0 +1,338 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================== + Simple make base +================== + +Simple make base. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import sys as _sys + +from _setup import term as _term + + +class Failure(SystemExit): + """ Failure exception """ + + +def fail(reason): + """ Fail for a reason """ + raise Failure(reason) + + +def warn(message, name=None): + """ Warn """ + _term.red("%(NAME)sWarning: %(msg)s", + NAME=name and "%s:" % name or '', msg=message + ) + + +def fatal(reason): + """ Fatal error, immediate stop """ + print >> _sys.stderr, reason + _sys.exit(1) + + +class Target(object): + """ Target base class """ + NAME = None + DEPS = None + HIDDEN = False + + ERROR = None + + def __init__(self, runner): + """ Base __init__ """ + self.runner = runner + self.init() + + def init(self): + """ Default init hook """ + pass + + def run(self): + """ Default run hook """ + pass + + def clean(self, scm=True, dist=False): + """ Default clean hook """ + pass + + +class _Runner(object): + """ Runner """ + + def __init__(self, *targetscollection): + """ Initialization """ + tdict = {} + if not targetscollection: + import __main__ + targetscollection = [__main__] + + from _setup.make import default_targets + if default_targets not in targetscollection: + targetscollection.append(default_targets) + + for targets in targetscollection: + for value in vars(targets).values(): + if isinstance(value, type) and issubclass(value, Target) and \ + value.NAME is not None: + if value.NAME in tdict: + if issubclass(value, tdict[value.NAME]): + pass # override base target + elif issubclass(tdict[value.NAME], value): + continue # found base later. ignore + else: + warn('Ambiguous target name', value.NAME) + continue + tdict[value.NAME] = value + self._tdict = tdict + self._itdict = {} + + def print_help(self): + """ Print make help """ + import textwrap as _textwrap + + targets = self.targetinfo() + keys = [] + for key, info in targets.items(): + if not info['hide']: + keys.append(key) + keys.sort() + length = max(map(len, keys)) + info = [] + for key in keys: + info.append("%s%s" % ( + (key + " " * length)[:length + 2], + _textwrap.fill( + targets[key]['desc'].strip(), + subsequent_indent=" " * (length + 2) + ), + )) + print "Available targets:\n\n" + "\n".join(info) + + def targetinfo(self): + """ Extract target information """ + result = {} + for name, cls in self._tdict.items(): + result[name] = { + 'desc': cls.__doc__ or "no description", + 'hide': cls.HIDDEN, + 'deps': cls.DEPS or (), + } + return result + + def _topleveltargets(self): + """ Find all top level targets """ + rev = {} # key is a dep of [values] + all_ = self.targetinfo() + for target, info in all_.items(): + for dep in info['deps']: + if dep not in all_: + fatal("Unknown target '%s' (dep of %s) -> exit" % ( + dep, target + )) + rev.setdefault(dep, []).append(target) + return [target for target, info in rev.items() if not info] + + def _run(self, target, seen=None): + """ Run a target """ + if target.DEPS: + self(*target.DEPS, **{'seen': seen}) + + if not target.HIDDEN: + _term.yellow(">>> %(name)s", name=target.NAME) + + try: + result = target.run() + except KeyboardInterrupt: + result, target.ERROR = False, "^C -> exit" + except Failure, e: + result, target.ERROR = False, "%s: %s" % (target.NAME, e) + except (SystemExit, MemoryError): + raise + except: + import traceback + target.ERROR = "%s errored:\n%s" % (target.NAME, ''.join( + traceback.format_exception(*_sys.exc_info()) + )) + result = False + else: + if result is None: + result = True + return result + + def _clean(self, target, scm, dist, seen=None): + """ Run a target """ + if target.DEPS: + self.run_clean( + *target.DEPS, **{'scm': scm, 'dist': dist, 'seen': seen} + ) + + try: + result = target.clean(scm, dist) + except KeyboardInterrupt: + result, target.ERROR = False, "^C -> exit" + except Failure, e: + result, target.ERROR = False, "%s: %s" % (target.NAME, e) + except (SystemExit, MemoryError): + raise + except: + import traceback + target.ERROR = "%s errored:\n%s" % (target.NAME, ''.join( + traceback.format_exception(*_sys.exc_info()) + )) + result = False + else: + if result is None: + result = True + return result + + def _make_init(self, seen): + """ Make init mapper """ + def init(target): + """ Return initialized target """ + if target not in seen: + try: + seen[target] = self._tdict[target](self) + except KeyError: + fatal("Unknown target '%s' -> exit" % target) + else: + seen[target] = None + return seen[target] + return init + + def run_clean(self, *targets, **kwargs): + """ Run targets """ + def pop(name, default=None): + """ Pop """ + if name in kwargs: + value = kwargs[name] + del kwargs[name] + if value is None: + return default + return value + else: + return default + seen = pop('seen', {}) + scm = pop('scm', True) + dist = pop('dist', False) + if kwargs: + raise TypeError('Unknown keyword parameters') + + if not targets: + top_targets = self._topleveltargets() + targets = self.targetinfo() + for item in top_targets: + del targets[item] + targets = targets.keys() + targets.sort() + top_targets.sort() + targets = top_targets + targets + + init = self._make_init(seen) + for name in targets: + target = init(name) + if target is not None: + if not self._clean(target, scm=scm, dist=dist, seen=seen): + msg = target.ERROR + if msg is None: + msg = "Clean target %s returned error -> exit" % name + fatal(msg) + + def __call__(self, *targets, **kwargs): + """ Run targets """ + if 'seen' in kwargs: + seen = kwargs['seen'] + del kwargs['seen'] + else: + seen = None + if seen is None: + seen = self._itdict + if kwargs: + raise TypeError('Unknown keyword parameters') + + init = self._make_init(seen) + for name in targets: + target = init(name) + if target is not None: + if not self._run(target, seen): + msg = target.ERROR + if msg is None: + msg = "Target %s returned error -> exit" % name + fatal(msg) + + +def main(*args, **kwargs): + """ + main(argv=None, *args, name=None) + + Main start point. This function parses the command line and executes the + targets given through `argv`. If there are no targets given, a help output + is generated. + + :Parameters: + `argv` : sequence + Command line arguments. If omitted or ``None``, they are picked from + ``sys.argv``. + + `args` : ``tuple`` + The list of modules with targets. If omitted, ``__main__`` + is imported and treated as target module. Additionally the mechanism + always adds the `_setup.make` module (this one) to the list in order + to grab some default targets. + + `name` : ``str`` + Name of the executing module. If omitted or ``None``, ``'__main__'`` + is assumed. If the final name is not ``'__main__'``, the function + returns immediately. + """ + try: + name = kwargs['name'] + except KeyError: + name = '__main__' + else: + del kwargs['name'] + if name is None: + name = '__main__' + + try: + argv = kwargs['argv'] + except KeyError: + if not args: + args = (None,) + else: + del kwargs['argv'] + args = (argv,) + args + + if kwargs: + raise TypeError("Unrecognized keyword arguments for main()") + + if name == '__main__': + argv, args = args[0], args[1:] + if argv is None: + argv = _sys.argv[1:] + + runner = _Runner(*args) + if argv: + runner(*argv) + else: + runner.print_help() diff --git a/py2/make/default_targets.py b/py2/make/default_targets.py new file mode 100644 index 0000000..b25b13e --- /dev/null +++ b/py2/make/default_targets.py @@ -0,0 +1,110 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================== + Simple make base +================== + +Simple make base. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os +import sys as _sys + +from _setup import make as _make +from _setup import shell as _shell + + +class MakefileTarget(_make.Target): + """ Create a make file """ + NAME = 'makefile' + + def run(self): + def escape(value): + """ Escape for make and shell """ + return '"%s"' % value.replace( + '\\', '\\\\').replace( + '"', '\\"').replace( + '$', '\\$$') + def decorate(line, prefix='# ', width=78, char='~', padding=' '): + """ Decorate a line """ + line = line.center(width - len(prefix)) + return '%s%s%s%s%s%s' % ( + prefix, + char * (len(line) - len(line.lstrip()) - len(padding)), + padding, + line.strip(), + padding, + char * (len(line) - len(line.rstrip()) - len(padding)), + ) + + python = escape(_sys.executable) + script = escape(_sys.argv[0]) + targets = self.runner.targetinfo() + names = [] + for name, info in targets.items(): + if not info['hide']: + names.append(name) + names.sort() + + fp = open(_shell.native('Makefile'), 'w') + print >> fp, decorate("Generated Makefile, DO NOT EDIT") + print >> fp, decorate("python %s %s" % ( + _os.path.basename(script), self.NAME + )) + print >> fp + print >> fp, "_default_:" + print >> fp, "\t@%s %s" % (python, script) + for name in names: + print >> fp, "\n" + print >> fp, "# %s" % \ + targets[name]['desc'].splitlines()[0].strip() + print >> fp, "%s:" % name + print >> fp, "\t@%s %s %s" % (python, script, escape(name)) + print >> fp + extension = self.extend(names) + if extension is not None: + print >> fp, extension + print >> fp + print >> fp, ".PHONY: _default_ %s\n\n" % ' '.join(names) + fp.close() + + def extend(self, names): + pass + + +class CleanTarget(_make.Target): + """ Clean the mess """ + NAME = 'clean' + _scm, _dist = True, False + + def run(self): + self.runner.run_clean(scm=self._scm, dist=self._dist) + + +class DistCleanTarget(CleanTarget): + """ Clean as freshly unpacked dist package """ + NAME = 'distclean' + _scm, _dist = False, True + + +class ExtraCleanTarget(CleanTarget): + """ Clean everything """ + NAME = 'extraclean' + _scm, _dist = True, True diff --git a/py2/make/targets.py b/py2/make/targets.py new file mode 100644 index 0000000..e795dc1 --- /dev/null +++ b/py2/make/targets.py @@ -0,0 +1,324 @@ +# -*- coding: ascii -*- +# +# Copyright 2007 - 2013 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================== + Standard targets +================== + +Standard targets. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os +import sys as _sys + +from _setup import dist as _dist +from _setup import make as _make +from _setup import shell as _shell +from _setup import term as _term + + +class Distribution(_make.Target): + """ Build a distribution """ + NAME = "dist" + DEPS = ["MANIFEST"] + + _dist, _ebuilds, _changes = None, None, None + + def init(self): + raise NotImplementedError() + + def run(self): + exts = self.dist_pkg() + digests = self.digest_files(exts) + self.sign_digests(digests) + self.copy_ebuilds() + self.copy_changes() + + def dist_pkg(self): + _term.green("Building package...") + _dist.run_setup("sdist", "--formats", "tar,zip", + fakeroot=_shell.frompath('fakeroot') + ) + exts = ['.zip'] + for name in _shell.files(self._dist, '*.tar', False): + exts.extend(self.compress(name)) + _shell.rm(name) + return exts + + def compress(self, filename): + """ Compress file """ + ext = _os.path.splitext(filename)[1] + exts = [] + exts.append('.'.join((ext, self.compress_gzip(filename)))) + exts.append('.'.join((ext, self.compress_bzip2(filename)))) + exts.append('.'.join((ext, self.compress_xz(filename)))) + return exts + + def compress_xz(self, filename): + outfilename = filename + '.xz' + self.compress_external(filename, outfilename, 'xz', '-c9') + return 'xz' + + def compress_bzip2(self, filename): + outfilename = filename + '.bz2' + try: + import bz2 as _bz2 + except ImportError: + self.compress_external(filename, outfilename, 'bzip2', '-c9') + else: + outfile = _bz2.BZ2File(outfilename, 'w') + self.compress_internal(filename, outfile, outfilename) + return 'bz2' + + def compress_gzip(self, filename): + outfilename = filename + '.gz' + try: + import gzip as _gzip + except ImportError: + self.compress_external(filename, outfilename, 'gzip', '-c9') + else: + outfile = _gzip.GzipFile(filename, 'wb', + fileobj=open(outfilename, 'wb') + ) + self.compress_internal(filename, outfile, outfilename) + return 'gz' + + def compress_external(self, infile, outfile, *argv): + argv = list(argv) + argv[0] = _shell.frompath(argv[0]) + if argv[0] is not None: + return not _shell.spawn(*argv, **{ + 'filepipe': True, 'stdin': infile, 'stdout': outfile, + }) + return None + + def compress_internal(self, filename, outfile, outfilename): + infile = open(filename, 'rb') + try: + try: + while 1: + chunk = infile.read(8192) + if not chunk: + break + outfile.write(chunk) + outfile.close() + except: + e = _sys.exc_info() + try: + _shell.rm(outfilename) + finally: + try: + raise e[0], e[1], e[2] + finally: + del e + finally: + infile.close() + + def digest_files(self, exts): + """ digest files """ + digests = {} + digestnames = {} + for ext in exts: + for name in _shell.files(self._dist, '*' + ext, False): + basename = _os.path.basename(name) + if basename not in digests: + digests[basename] = [] + digests[basename].extend(self.digest(name)) + digestname = basename[:-len(ext)] + if digestname not in digestnames: + digestnames[digestname] = [] + digestnames[digestname].append(basename) + + result = [] + for name, basenames in digestnames.items(): + result.append(_os.path.join(self._dist, name + '.digests')) + fp = open(result[-1], 'wb') + try: + fp.write( + '\n# The file may contain MD5, SHA1 and SHA256 digests\n' + ) + fp.write('# Check archive integrity with, e.g. md5sum -c\n') + fp.write('# Check digest file integrity with PGP\n\n') + basenames.sort() + for basename in basenames: + for digest in digests[basename]: + fp.write("%s *%s\n" % (digest, basename)) + finally: + fp.close() + return result + + def digest(self, filename): + result = [] + for method in (self.md5, self.sha1, self.sha256): + digest = method(filename) + if digest is not None: + result.append(digest) + return result + + def do_digest(self, hashfunc, name, filename): + filename = _shell.native(filename) + _term.green("%(digest)s-digesting %(name)s...", + digest=name, name=_os.path.basename(filename)) + fp = open(filename, 'rb') + sig = hashfunc() + block = fp.read(8192) + while block: + sig.update(block) + block = fp.read(8192) + fp.close() + return sig.hexdigest() + + param = {'sig': sig.hexdigest(), 'file': _os.path.basename(filename)} + fp = open("%s.%s" % (filename, name), "w") + fp.write("%(sig)s *%(file)s\n" % param) + fp.close() + + return True + + def md5(self, filename): + try: + from hashlib import md5 + except ImportError: + try: + from md5 import new as md5 + except ImportError: + _make.warn("md5 not found -> skip md5 digests", self.NAME) + return None + return self.do_digest(md5, "md5", filename) + + def sha1(self, filename): + try: + from hashlib import sha1 + except ImportError: + try: + from sha import new as sha1 + except ImportError: + _make.warn("sha1 not found -> skip sha1 digests", self.NAME) + return None + return self.do_digest(sha1, "sha1", filename) + + def sha256(self, filename): + try: + from hashlib import sha256 + except ImportError: + try: + from Crypto.Hash.SHA256 import new as sha256 + except ImportError: + _make.warn( + "sha256 not found -> skip sha256 digests", self.NAME + ) + return None + return self.do_digest(sha256, "sha256", filename) + + def copy_ebuilds(self): + if self._ebuilds is not None: + for src in _shell.files(self._ebuilds, '*.ebuild'): + _shell.cp(src, self._dist) + + def copy_changes(self): + if self._changes is not None: + _shell.cp(self._changes, self._dist) + + def sign_digests(self, digests): + for digest in digests: + self.sign(digest, detach=False) + + def sign(self, filename, detach=True): + filename = _shell.native(filename) + try: + from pyme import core, errors + from pyme.constants.sig import mode + except ImportError: + return self.sign_external(filename, detach=detach) + + _term.green("signing %(name)s...", name=_os.path.basename(filename)) + sigmode = [mode.CLEAR, mode.DETACH][bool(detach)] + fp = core.Data(file=filename) + sig = core.Data() + try: + c = core.Context() + except errors.GPGMEError: + return self.sign_external(filename, detach=detach) + c.set_armor(1) + try: + c.op_sign(fp, sig, sigmode) + except errors.GPGMEError, e: + _make.fail(str(e)) + + sig.seek(0, 0) + if detach: + open("%s.asc" % filename, "w").write(sig.read()) + else: + open(filename, "w").write(sig.read()) + + return True + + def sign_external(self, filename, detach=True): + """ Sign calling gpg """ + gpg = _shell.frompath('gpg') + if gpg is None: + _make.warn('GPG not found -> cannot sign') + return False + if detach: + _shell.spawn(gpg, + '--armor', + '--output', filename + '.asc', + '--detach-sign', + '--', + filename, + ) + else: + _shell.spawn(gpg, + '--output', filename + '.signed', + '--clearsign', + '--', + filename, + ) + _os.rename(filename + '.signed', filename) + return True + + def clean(self, scm, dist): + _term.green("Removing dist files...") + _shell.rm_rf(self._dist) + + +class Manifest(_make.Target): + """ Create manifest """ + NAME = "MANIFEST" + HIDDEN = True + DEPS = ["doc"] + + def run(self): + _term.green("Creating %(name)s...", name=self.NAME) + dest = _shell.native(self.NAME) + dest = open(dest, 'w') + for name in self.manifest_names(): + dest.write("%s\n" % name) + dest.close() + + def manifest_names(self): + import setup + for item in setup.manifest(): + yield item + + def clean(self, scm, dist): + """ Clean manifest """ + if scm: + _term.green("Removing MANIFEST") + _shell.rm(self.NAME) diff --git a/py2/setup.py b/py2/setup.py new file mode 100644 index 0000000..fd86f62 --- /dev/null +++ b/py2/setup.py @@ -0,0 +1,419 @@ +# -*- coding: ascii -*- +# +# Copyright 2007 - 2013 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + Main setup runner +=================== + +This module provides a wrapper around the distutils core setup. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import ConfigParser as _config_parser +from distutils import core as _core +import os as _os +import posixpath as _posixpath +import sys as _sys + +from _setup import commands as _commands +from _setup import data as _data +from _setup import ext as _ext +from _setup import util as _util +from _setup import shell as _shell + + +def check_python_version(impl, version_min, version_max): + """ Check python version """ + if impl == 'python': + version_info = _sys.version_info + elif impl == 'pypy': + version_info = getattr(_sys, 'pypy_version_info', None) + if not version_info: + return + elif impl == 'jython': + if not 'java' in _sys.platform.lower(): + return + version_info = _sys.version_info + else: + raise AssertionError("impl not in ('python', 'pypy', 'jython')") + + pyversion = map(int, version_info[:3]) + if version_min: + min_required = \ + map(int, '.'.join((version_min, '0.0.0')).split('.')[:3]) + if pyversion < min_required: + raise EnvironmentError("Need at least %s %s (vs. %s)" % ( + impl, version_min, '.'.join(map(str, pyversion)) + )) + if version_max: + max_required = map(int, version_max.split('.')) + max_required[-1] += 1 + if pyversion >= max_required: + raise EnvironmentError("Need at max %s %s (vs. %s)" % ( + impl, + version_max, + '.'.join(map(str, pyversion)) + )) + + +def find_description(docs): + """ + Determine the package description from DESCRIPTION + + :Parameters: + `docs` : ``dict`` + Docs config section + + :Return: Tuple of summary, description and license + (``('summary', 'description', 'license')``) + (all may be ``None``) + :Rtype: ``tuple`` + """ + summary = None + filename = docs.get('meta.summary', 'SUMMARY').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + try: + summary = fp.read().strip().splitlines()[0].rstrip() + except IndexError: + summary = '' + finally: + fp.close() + + description = None + filename = docs.get('meta.description', 'DESCRIPTION').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + description = fp.read().rstrip() + finally: + fp.close() + + if summary is None and description: + from docutils import core + summary = core.publish_parts( + source=description, + source_path=filename, + writer_name='html', + )['title'].encode('utf-8') + + return summary, description + + +def find_classifiers(docs): + """ + Determine classifiers from CLASSIFIERS + + :return: List of classifiers (``['classifier', ...]``) + :rtype: ``list`` + """ + filename = docs.get('meta.classifiers', 'CLASSIFIERS').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + content = fp.read() + finally: + fp.close() + content = [item.strip() for item in content.splitlines()] + return [item for item in content if item and not item.startswith('#')] + return [] + + +def find_provides(docs): + """ + Determine provides from PROVIDES + + :return: List of provides (``['provides', ...]``) + :rtype: ``list`` + """ + filename = docs.get('meta.provides', 'PROVIDES').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + content = fp.read() + finally: + fp.close() + content = [item.strip() for item in content.splitlines()] + return [item for item in content if item and not item.startswith('#')] + return [] + + +def find_license(docs): + """ + Determine license from LICENSE + + :return: License text + :rtype: ``str`` + """ + filename = docs.get('meta.license', 'LICENSE').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + return fp.read().rstrip() + finally: + fp.close() + return None + + +def find_packages(manifest): + """ Determine packages and subpackages """ + packages = {} + collect = manifest.get('packages.collect', '').split() + lib = manifest.get('packages.lib', '.') + try: + sep = _os.path.sep + except AttributeError: + sep = _os.path.join('1', '2')[1:-1] + for root in collect: + for dirpath, _, filenames in _shell.walk(_os.path.join(lib, root)): + if dirpath.find('.svn') >= 0 or dirpath.find('.git') >= 0: + continue + if '__init__.py' in filenames: + packages[ + _os.path.normpath(dirpath).replace(sep, '.') + ] = None + packages = packages.keys() + packages.sort() + return packages + + +def find_data(name, docs): + """ Determine data files """ + result = [] + if docs.get('extra', '').strip(): + result.append(_data.Documentation(docs['extra'].split(), + prefix='share/doc/%s' % name, + )) + if docs.get('examples.dir', '').strip(): + tpl = ['recursive-include %s *' % docs['examples.dir']] + if docs.get('examples.ignore', '').strip(): + tpl.extend(["global-exclude %s" % item + for item in docs['examples.ignore'].split() + ]) + strip = int(docs.get('examples.strip', '') or 0) + result.append(_data.Documentation.from_templates(*tpl, **{ + 'strip': strip, + 'prefix': 'share/doc/%s' % name, + 'preserve': 1, + })) + if docs.get('userdoc.dir', '').strip(): + tpl = ['recursive-include %s *' % docs['userdoc.dir']] + if docs.get('userdoc.ignore', '').strip(): + tpl.extend(["global-exclude %s" % item + for item in docs['userdoc.ignore'].split() + ]) + strip = int(docs.get('userdoc.strip', '') or 0) + result.append(_data.Documentation.from_templates(*tpl, **{ + 'strip': strip, + 'prefix': 'share/doc/%s' % name, + 'preserve': 1, + })) + if docs.get('apidoc.dir', '').strip(): + tpl = ['recursive-include %s *' % docs['apidoc.dir']] + if docs.get('apidoc.ignore', '').strip(): + tpl.extend(["global-exclude %s" % item + for item in docs['apidoc.ignore'].split() + ]) + strip = int(docs.get('apidoc.strip', '') or 0) + result.append(_data.Documentation.from_templates(*tpl, **{ + 'strip': strip, + 'prefix': 'share/doc/%s' % name, + 'preserve': 1, + })) + if docs.get('man', '').strip(): + result.extend(_data.Manpages.dispatch(docs['man'].split())) + return result + + +def make_manifest(manifest, config, docs, kwargs): + """ Create file list to pack up """ + # pylint: disable = R0912 + kwargs = kwargs.copy() + kwargs['script_args'] = ['install'] + kwargs['packages'] = list(kwargs.get('packages') or ()) + [ + '_setup', '_setup.py2', '_setup.py3', + ] + list(manifest.get('packages.extra', '').split() or ()) + _core._setup_stop_after = "commandline" + try: + dist = _core.setup(**kwargs) + finally: + _core._setup_stop_after = None + + result = ['MANIFEST', 'PKG-INFO', 'setup.py'] + list(config) + # TODO: work with default values: + for key in ('classifiers', 'description', 'summary', 'provides', + 'license'): + filename = docs.get('meta.' + key, '').strip() + if filename and _os.path.isfile(filename): + result.append(filename) + + cmd = dist.get_command_obj("build_py") + cmd.ensure_finalized() + #from pprint import pprint; pprint(("build_py", cmd.get_source_files())) + for item in cmd.get_source_files(): + result.append(_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + )) + + cmd = dist.get_command_obj("build_ext") + cmd.ensure_finalized() + #from pprint import pprint; pprint(("build_ext", cmd.get_source_files())) + for item in cmd.get_source_files(): + result.append(_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + )) + for ext in cmd.extensions: + if ext.depends: + result.extend([_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + ) for item in ext.depends]) + + cmd = dist.get_command_obj("build_clib") + cmd.ensure_finalized() + if cmd.libraries: + #import pprint; pprint.pprint(("build_clib", cmd.get_source_files())) + for item in cmd.get_source_files(): + result.append(_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + )) + for lib in cmd.libraries: + if lib[1].get('depends'): + result.extend([_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + ) for item in lib[1]['depends']]) + + cmd = dist.get_command_obj("build_scripts") + cmd.ensure_finalized() + #import pprint; pprint.pprint(("build_scripts", cmd.get_source_files())) + if cmd.get_source_files(): + for item in cmd.get_source_files(): + result.append(_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + )) + + cmd = dist.get_command_obj("install_data") + cmd.ensure_finalized() + #from pprint import pprint; pprint(("install_data", cmd.get_inputs())) + try: + strings = basestring + except NameError: + strings = (str, unicode) + + for item in cmd.get_inputs(): + if isinstance(item, strings): + result.append(item) + else: + result.extend(item[1]) + + for item in manifest.get('dist', '').split(): + result.append(item) + if _os.path.isdir(item): + for filename in _shell.files(item): + result.append(filename) + + result = dict([(item, None) for item in result]).keys() + result.sort() + return result + + +def run(config=('package.cfg',), ext=None, script_args=None, manifest_only=0): + """ Main runner """ + if ext is None: + ext = [] + + cfg = _util.SafeConfigParser() + cfg.read(config) + pkg = dict(cfg.items('package')) + python_min = pkg.get('python.min') or None + python_max = pkg.get('python.max') or None + check_python_version('python', python_min, python_max) + pypy_min = pkg.get('pypy.min') or None + pypy_max = pkg.get('pypy.max') or None + check_python_version('pypy', pypy_min, pypy_max) + jython_min = pkg.get('jython.min') or None + jython_max = pkg.get('jython.max') or None + check_python_version('jython', jython_min, jython_max) + + manifest = dict(cfg.items('manifest')) + try: + docs = dict(cfg.items('docs')) + except _config_parser.NoSectionError: + docs = {} + + summary, description = find_description(docs) + scripts = manifest.get('scripts', '').strip() or None + if scripts: + scripts = scripts.split() + modules = manifest.get('modules', '').strip() or None + if modules: + modules = modules.split() + keywords = docs.get('meta.keywords', '').strip() or None + if keywords: + keywords = keywords.split() + revision = pkg.get('version.revision', '').strip() + if revision: + revision = "-r%s" % (revision,) + + kwargs = { + 'name': pkg['name'], + 'version': "%s%s" % ( + pkg['version.number'], + ["", "-dev%s" % (revision,)][_util.humanbool( + 'version.dev', pkg.get('version.dev', 'false') + )], + ), + 'provides': find_provides(docs), + 'description': summary, + 'long_description': description, + 'classifiers': find_classifiers(docs), + 'keywords': keywords, + 'author': pkg['author.name'], + 'author_email': pkg['author.email'], + 'maintainer': pkg.get('maintainer.name'), + 'maintainer_email': pkg.get('maintainer.email'), + 'url': pkg.get('url.homepage'), + 'download_url': pkg.get('url.download'), + 'license': find_license(docs), + 'package_dir': {'': manifest.get('packages.lib', '.')}, + 'packages': find_packages(manifest), + 'py_modules': modules, + 'ext_modules': ext, + 'scripts': scripts, + 'script_args': script_args, + 'data_files': find_data(pkg['name'], docs), + 'cmdclass': { + 'build' : _commands.Build, + 'build_ext' : _commands.BuildExt, + 'install' : _commands.Install, + 'install_data': _commands.InstallData, + 'install_lib' : _commands.InstallLib, + } + } + for key in ('provides',): + if key not in _core.setup_keywords: + del kwargs[key] + + if manifest_only: + return make_manifest(manifest, config, docs, kwargs) + + # monkey-patch crappy manifest writer away. + from distutils.command import sdist + sdist.sdist.get_file_list = sdist.sdist.read_manifest + + return _core.setup(**kwargs) diff --git a/py2/shell.py b/py2/shell.py new file mode 100644 index 0000000..4eafb9c --- /dev/null +++ b/py2/shell.py @@ -0,0 +1,478 @@ +# -*- coding: ascii -*- +# +# Copyright 2007 - 2013 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================= + Shell utilities +================= + +Shell utilities. +""" +from __future__ import generators + +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import errno as _errno +import fnmatch as _fnmatch +import os as _os +import shutil as _shutil +import sys as _sys +import tempfile as _tempfile + +cwd = _os.path.dirname(_os.path.abspath(_sys.argv[0])) + +class ExitError(RuntimeError): + """ Exit error """ + def __init__(self, code): + RuntimeError.__init__(self, code) + self.code = code + self.signal = None + + +class SignalError(ExitError): + """ Signal error """ + def __init__(self, code, signal): + ExitError.__init__(self, code) + import signal as _signal + self.signal = signal + for key, val in vars(_signal).iteritems(): + if key.startswith('SIG') and not key.startswith('SIG_'): + if val == signal: + self.signalstr = key[3:] + break + else: + self.signalstr = '%04d' % signal + + +def native(path): + """ Convert slash path to native """ + path = _os.path.sep.join(path.split('/')) + return _os.path.normpath(_os.path.join(cwd, path)) + + +def cp(src, dest): + """ Copy src to dest """ + _shutil.copy2(native(src), native(dest)) + + +def cp_r(src, dest): + """ Copy -r src to dest """ + _shutil.copytree(native(src), native(dest)) + + +def rm(dest): + """ Remove a file """ + try: + _os.unlink(native(dest)) + except OSError, e: + if _errno.ENOENT != e.errno: + raise + +def rm_rf(dest): + """ Remove a tree """ + dest = native(dest) + if _os.path.exists(dest): + for path in files(dest, '*'): + _os.chmod(native(path), 0644) + _shutil.rmtree(dest) + + +try: + mkstemp = _tempfile.mkstemp +except AttributeError: + # helpers stolen from 2.4 tempfile module + try: + import fcntl as _fcntl + except ImportError: + def _set_cloexec(fd): + """ Set close-on-exec (not implemented, but not an error) """ + # pylint: disable = W0613 + pass + else: + def _set_cloexec(fd): + """ Set close-on-exec """ + try: + flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0) + except IOError: + pass + else: + # flags read successfully, modify + flags |= _fcntl.FD_CLOEXEC + _fcntl.fcntl(fd, _fcntl.F_SETFD, flags) + + _text_openflags = _os.O_RDWR | _os.O_CREAT | _os.O_EXCL + _text_openflags |= getattr(_os, 'O_NOINHERIT', 0) + _text_openflags |= getattr(_os, 'O_NOFOLLOW', 0) + + _bin_openflags = _text_openflags + _bin_openflags |= getattr(_os, 'O_BINARY', 0) + + def mkstemp(suffix="", prefix=_tempfile.gettempprefix(), dir=None, + text=False): + """ Create secure temp file """ + # pylint: disable = W0622 + if dir is None: + dir = _tempfile.gettempdir() + if text: + flags = _text_openflags + else: + flags = _bin_openflags + count = 100 + while count > 0: + j = _tempfile._counter.get_next() # pylint: disable = E1101, W0212 + fname = _os.path.join(dir, prefix + str(j) + suffix) + try: + fd = _os.open(fname, flags, 0600) + except OSError, e: + if e.errno == _errno.EEXIST: + count -= 1 + continue + raise + _set_cloexec(fd) + return fd, _os.path.abspath(fname) + raise IOError, (_errno.EEXIST, "No usable temporary file name found") + + +def _pipespawn(argv, env): + """ Pipe spawn """ + # pylint: disable = R0912 + import pickle as _pickle + fd, name = mkstemp('.py') + try: + _os.write(fd, (r""" +import os +import pickle +try: + import subprocess +except ImportError: + subprocess = None +import sys + +argv = pickle.loads(%(argv)s) +env = pickle.loads(%(env)s) +if 'X_JYTHON_WA_PATH' in env: + env['PATH'] = env['X_JYTHON_WA_PATH'] + +if subprocess is None: + pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env) + result = os.waitpid(pid, 0)[1] +else: + p = subprocess.Popen(argv, env=env) + result = p.wait() + if result < 0: + print "\n%%d 1" %% (-result) + sys.exit(2) + +if result == 0: + sys.exit(0) +signalled = getattr(os, 'WIFSIGNALED', None) +if signalled is not None: + if signalled(result): + print "\n%%d %%d" %% (os.WTERMSIG(result), result & 7) + sys.exit(2) +print "\n%%d" %% (result & 7,) +sys.exit(3) + """.strip() + "\n") % { + 'argv': repr(_pickle.dumps(argv)), + 'env': repr(_pickle.dumps(env)), + }) + fd, _ = None, _os.close(fd) + if _sys.platform == 'win32': + argv = [] + for arg in [_sys.executable, name]: + if ' ' in arg or arg.startswith('"'): + arg = '"%s"' % arg.replace('"', '\\"') + argv.append(arg) + argv = ' '.join(argv) + shell = True + close_fds = False + else: + argv = [_sys.executable, name] + shell = False + close_fds = True + + res = 0 + try: + import subprocess + except ImportError: + import popen2 as _popen2 + proc = _popen2.Popen3(argv, False) + try: + proc.tochild.close() + result = proc.fromchild.read() + finally: + res = proc.wait() + else: + if 'X_JYTHON_WA_PATH' in env: + env['PATH'] = env['X_JYTHON_WA_PATH'] + + proc = subprocess.Popen(argv, + shell=shell, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + close_fds=close_fds, + env=env, + ) + try: + proc.stdin.close() + result = proc.stdout.read() + finally: + res = proc.wait() + if res != 0: + if res == 2: + signal, code = map(int, result.splitlines()[-1].split()) + raise SignalError(code, signal) + elif res == 3: + code = int(result.splitlines()[-1].strip()) + raise ExitError(code) + raise ExitError(res) + + return result + finally: + try: + if fd is not None: + _os.close(fd) + finally: + _os.unlink(name) + + +def _filepipespawn(infile, outfile, argv, env): + """ File Pipe spawn """ + try: + import subprocess + except ImportError: + subprocess = None + import pickle as _pickle + fd, name = mkstemp('.py') + try: + _os.write(fd, (""" +import os +import pickle +import sys + +infile = pickle.loads(%(infile)s) +outfile = pickle.loads(%(outfile)s) +argv = pickle.loads(%(argv)s) +env = pickle.loads(%(env)s) + +if infile is not None: + infile = open(infile, 'rb') + os.dup2(infile.fileno(), 0) + infile.close() +if outfile is not None: + outfile = open(outfile, 'wb') + os.dup2(outfile.fileno(), 1) + outfile.close() + +pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env) +result = os.waitpid(pid, 0)[1] +sys.exit(result & 7) + """.strip() + "\n") % { + 'infile': repr(_pickle.dumps(_os.path.abspath(infile))), + 'outfile': repr(_pickle.dumps(_os.path.abspath(outfile))), + 'argv': repr(_pickle.dumps(argv)), + 'env': repr(_pickle.dumps(env)), + }) + fd, _ = None, _os.close(fd) + if _sys.platform == 'win32': + argv = [] + for arg in [_sys.executable, name]: + if ' ' in arg or arg.startswith('"'): + arg = '"%s"' % arg.replace('"', '\\"') + argv.append(arg) + argv = ' '.join(argv) + close_fds = False + shell = True + else: + argv = [_sys.executable, name] + close_fds = True + shell = False + + if subprocess is None: + pid = _os.spawnve(_os.P_NOWAIT, argv[0], argv, env) + return _os.waitpid(pid, 0)[1] + else: + p = subprocess.Popen( + argv, env=env, shell=shell, close_fds=close_fds + ) + return p.wait() + finally: + try: + if fd is not None: + _os.close(fd) + finally: + _os.unlink(name) + + +def spawn(*argv, **kwargs): + """ Spawn a process """ + try: + import subprocess + except ImportError: + subprocess = None + + if _sys.platform == 'win32': + newargv = [] + for arg in argv: + if not arg or ' ' in arg or arg.startswith('"'): + arg = '"%s"' % arg.replace('"', '\\"') + newargv.append(arg) + argv = newargv + close_fds = False + shell = True + else: + close_fds = True + shell = False + + env = kwargs.get('env') + if env is None: + env = dict(_os.environ) + if 'X_JYTHON_WA_PATH' in env: + env['PATH'] = env['X_JYTHON_WA_PATH'] + + echo = kwargs.get('echo') + if echo: + print ' '.join(argv) + filepipe = kwargs.get('filepipe') + if filepipe: + return _filepipespawn( + kwargs.get('stdin'), kwargs.get('stdout'), argv, env + ) + pipe = kwargs.get('stdout') + if pipe: + return _pipespawn(argv, env) + + if subprocess is None: + pid = _os.spawnve(_os.P_NOWAIT, argv[0], argv, env) + return _os.waitpid(pid, 0)[1] + else: + p = subprocess.Popen(argv, env=env, shell=shell, close_fds=close_fds) + return p.wait() + + +try: + walk = _os.walk +except AttributeError: + # copy from python 2.4 sources (modulo docs and comments) + def walk(top, topdown=True, onerror=None): + """ directory tree walker """ + # pylint: disable = C0103 + join, isdir, islink = _os.path.join, _os.path.isdir, _os.path.islink + listdir, error = _os.listdir, _os.error + + try: + names = listdir(top) + except error, err: + if onerror is not None: + onerror(err) + return + + dirs, nondirs = [], [] + for name in names: + if isdir(join(top, name)): + dirs.append(name) + else: + nondirs.append(name) + + if topdown: + yield top, dirs, nondirs + for name in dirs: + path = join(top, name) + if not islink(path): + for x in walk(path, topdown, onerror): + yield x + if not topdown: + yield top, dirs, nondirs + + +def files(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')): + """ Determine a filelist """ + for dirpath, dirnames, filenames in walk(native(base)): + for item in prune: + if item in dirnames: + dirnames.remove(item) + + filenames.sort() + for name in _fnmatch.filter(filenames, wildcard): + dest = _os.path.join(dirpath, name) + if dest.startswith(cwd): + dest = dest.replace(cwd, '', 1) + aslist = [] + head, tail = _os.path.split(dest) + while tail: + aslist.append(tail) + head, tail = _os.path.split(head) + aslist.reverse() + dest = '/'.join(aslist) + yield dest + + if not recursive: + break + dirnames.sort() + + +def dirs(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')): + """ Determine a filelist """ + for dirpath, dirnames, filenames in walk(native(base)): + for item in prune: + if item in dirnames: + dirnames.remove(item) + + dirnames.sort() + for name in _fnmatch.filter(dirnames, wildcard): + dest = _os.path.join(dirpath, name) + if dest.startswith(cwd): + dest = dest.replace(cwd, '', 1) + aslist = [] + head, tail = _os.path.split(dest) + while tail: + aslist.append(tail) + head, tail = _os.path.split(head) + aslist.reverse() + dest = '/'.join(aslist) + yield dest + + if not recursive: + break + + +def frompath(executable): + """ Find executable in PATH """ + # Based on distutils.spawn.find_executable. + path = _os.environ.get('PATH', '') + paths = [ + _os.path.expanduser(item) + for item in path.split(_os.pathsep) + ] + ext = _os.path.splitext(executable)[1] + exts = [''] + if _sys.platform == 'win32' or _os.name == 'os2': + eext = ['.exe', '.bat', '.py'] + if ext not in eext: + exts.extend(eext) + + for ext in exts: + if not _os.path.isfile(executable + ext): + for path in paths: + fname = _os.path.join(path, executable + ext) + if _os.path.isfile(fname): + # the file exists, we have a shot at spawn working + return fname + else: + return executable + ext + + return None diff --git a/py2/term/__init__.py b/py2/term/__init__.py new file mode 100644 index 0000000..4e50ec3 --- /dev/null +++ b/py2/term/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: ascii -*- +# +# Copyright 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +===================== + Package _setup.term +===================== + +Terminal tools, not distributed. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +# pylint: disable = W0611 +from _setup.term._term import terminfo, write, green, red, yellow, announce diff --git a/py2/term/_term.py b/py2/term/_term.py new file mode 100644 index 0000000..72b727c --- /dev/null +++ b/py2/term/_term.py @@ -0,0 +1,115 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================= + Terminal writer +================= +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import sys as _sys + + +class _INFO(dict): + """ Terminal info dict """ + + def __init__(self): + """ Initialization """ + dict.__init__(self, { + 'NORMAL': '', + 'BOLD': '', + 'ERASE': '\n', + 'RED': '', + 'YELLOW': '', + 'GREEN': '', + }) + try: + import curses as _curses + except ImportError: + # fixup if a submodule of curses failed. + if 'curses' in _sys.modules: + del _sys.modules['curses'] + else: + try: + _curses.setupterm() + except (TypeError, _curses.error): + pass + else: + def make_color(color): + """ Make color control string """ + seq = _curses.tigetstr('setaf') + if seq is not None: + # XXX may fail - need better logic + seq = seq.replace("%p1", "") % color + return seq + + self['NORMAL'] = _curses.tigetstr('sgr0') + self['BOLD'] = _curses.tigetstr('bold') + + erase = _curses.tigetstr('el1') + if erase is not None: + self['ERASE'] = erase + _curses.tigetstr('cr') + + self['RED'] = make_color(_curses.COLOR_RED) + self['YELLOW'] = make_color(_curses.COLOR_YELLOW) + self['GREEN'] = make_color(_curses.COLOR_GREEN) + + def __getitem__(self, key): + """ Deliver always """ + dict.get(self, key) or "" + + +def terminfo(): + """ Get info singleton """ + # pylint: disable = E1101, W0612 + if terminfo.info is None: + terminfo.info = _INFO() + return terminfo.info +terminfo.info = None + + +def write(fmt, **kwargs): + """ Write stuff on the terminal """ + parm = dict(terminfo()) + parm.update(kwargs) + _sys.stdout.write(fmt % parm) + _sys.stdout.flush() + + +def green(bmt, **kwargs): + """ Write something in green on screen """ + announce("%%(GREEN)s%s%%(NORMAL)s" % bmt, **kwargs) + + +def red(bmt, **kwargs): + """ Write something in red on the screen """ + announce("%%(BOLD)s%%(RED)s%s%%(NORMAL)s" % bmt, **kwargs) + + +def yellow(fmt, **kwargs): + """ Write something in yellow on the screen """ + announce("%%(BOLD)s%%(YELLOW)s%s%%(NORMAL)s" % fmt, **kwargs) + + +def announce(fmt, **kwargs): + """ Announce something """ + write(fmt, **kwargs) + _sys.stdout.write("\n") + _sys.stdout.flush() + + diff --git a/py2/util.py b/py2/util.py new file mode 100644 index 0000000..5c05fac --- /dev/null +++ b/py2/util.py @@ -0,0 +1,73 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================= + Setup utilities +================= + +Setup utilities. +""" +__author__ = u"Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +try: + from distutils import log +except ImportError: + class log(object): + def info(self, value): + print value + def debug(self, value): + pass + log = log() + +from distutils import util as _util +try: + from ConfigParser import SafeConfigParser +except ImportError: + import ConfigParser as _config_parser + class SafeConfigParser(_config_parser.ConfigParser): + """ Safe config parser """ + def _interpolate(self, section, option, rawval, vars): + return rawval + + def items(self, section): + return [(key, self.get(section, key)) + for key in self.options(section) + ] + + +def humanbool(name, value): + """ + Determine human boolean value + + :Parameters: + `name` : ``str`` + The config key (used for error message) + + `value` : ``str`` + The config value + + :Return: The boolean value + :Rtype: ``bool`` + + :Exceptions: + - `ValueError` : The value could not be recognized + """ + try: + return _util.strtobool(str(value).strip().lower() or 'no') + except ValueError: + raise ValueError("Unrecognized config value: %s = %s" % (name, value)) diff --git a/py3/__init__.py b/py3/__init__.py new file mode 100644 index 0000000..6139d51 --- /dev/null +++ b/py3/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================ + Package _setup +================ + +This package provides tools for main package setup. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +from _setup.setup import run # pylint: disable = W0611 diff --git a/py3/commands.py b/py3/commands.py new file mode 100644 index 0000000..7bfacbc --- /dev/null +++ b/py3/commands.py @@ -0,0 +1,266 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + Command extenders +=================== + +Command extenders. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" +__test__ = False + +from distutils import fancy_getopt as _fancy_getopt +from distutils import log +from distutils.command import build as _build +from distutils.command import build_ext as _build_ext +from distutils.command import install as _install +from distutils.command import install_data as _install_data +from distutils.command import install_lib as _install_lib +import os as _os + +_option_defaults = {} +_option_inherits = {} +_option_finalizers = {} +_command_mapping = { + 'install': 'Install', + 'install_data': 'InstallData', + 'install_lib': 'InstallLib', + 'build': 'Build', + 'build_ext': 'BuildExt', +} + + +def add_option(command, long_name, help_text, short_name=None, default=None, + inherit=None): + """ Add an option """ + try: + command_class = globals()[_command_mapping[command]] + except KeyError: + raise ValueError("Unknown command %r" % (command,)) + for opt in command_class.user_options: + if opt[0] == long_name: + break + else: + opt = (long_name, short_name, help_text) + command_class.user_options.append(opt) + if not long_name.endswith('='): + command_class.boolean_options.append(long_name) + attr_name = _fancy_getopt.translate_longopt(long_name) + else: + attr_name = _fancy_getopt.translate_longopt(long_name[:-1]) + if command not in _option_defaults: + _option_defaults[command] = [] + if inherit is not None: + if isinstance(inherit, str): + inherit = [inherit] + for i_inherit in inherit: + add_option( + i_inherit, long_name, help_text, short_name, default + ) + default = None + if command not in _option_inherits: + _option_inherits[command] = [] + for i_inherit in inherit: + for i_command, opt_name in _option_inherits[command]: + if i_command == i_inherit and opt_name == attr_name: + break + else: + _option_inherits[command].append((i_inherit, attr_name)) + _option_defaults[command].append((attr_name, default)) + + +def add_finalizer(command, key, func): + """ Add finalizer """ + if command not in _option_finalizers: + _option_finalizers[command] = {} + if key not in _option_finalizers[command]: + _option_finalizers[command][key] = func + + +class Install(_install.install): + """ Extended installer to reflect the additional data options """ + user_options = _install.install.user_options + [ + ('single-version-externally-managed', None, + "Compat option. Does not a thing."), + ] + boolean_options = _install.install.boolean_options + [ + 'single-version-externally-managed' + ] + + def initialize_options(self): + """ Prepare for new options """ + _install.install.initialize_options(self) + self.single_version_externally_managed = None + if 'install' in _option_defaults: + for opt_name, default in _option_defaults['install']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _install.install.finalize_options(self) + if 'install' in _option_inherits: + for parent, opt_name in _option_inherits['install']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if 'install' in _option_finalizers: + for func in list(_option_finalizers['install'].values()): + func(self) + + +class InstallData(_install_data.install_data): + """ Extended data installer """ + user_options = _install_data.install_data.user_options + [] + boolean_options = _install_data.install_data.boolean_options + [] + + def initialize_options(self): + """ Prepare for new options """ + _install_data.install_data.initialize_options(self) + if 'install_data' in _option_defaults: + for opt_name, default in _option_defaults['install_data']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _install_data.install_data.finalize_options(self) + if 'install_data' in _option_inherits: + for parent, opt_name in _option_inherits['install_data']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if 'install_data' in _option_finalizers: + for func in list(_option_finalizers['install_data'].values()): + func(self) + + +class InstallLib(_install_lib.install_lib): + """ Extended lib installer """ + user_options = _install_lib.install_lib.user_options + [] + boolean_options = _install_lib.install_lib.boolean_options + [] + + def initialize_options(self): + """ Prepare for new options """ + _install_lib.install_lib.initialize_options(self) + if 'install_lib' in _option_defaults: + for opt_name, default in _option_defaults['install_lib']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _install_lib.install_lib.finalize_options(self) + if 'install_lib' in _option_inherits: + for parent, opt_name in _option_inherits['install_lib']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if 'install_lib' in _option_finalizers: + for func in list(_option_finalizers['install_lib'].values()): + func(self) + + +class BuildExt(_build_ext.build_ext): + """ + Extended extension builder class + + This class allows extensions to provide a ``check_prerequisites`` method + which is called before actually building it. The method takes the + `BuildExt` instance and returns whether the extension should be skipped or + not. + """ + + def initialize_options(self): + """ Prepare for new options """ + _build_ext.build_ext.initialize_options(self) + if 'build_ext' in _option_defaults: + for opt_name, default in _option_defaults['build_ext']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _build_ext.build_ext.finalize_options(self) + if 'build_ext' in _option_inherits: + for parent, opt_name in _option_inherits['build_ext']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if 'build_ext' in _option_finalizers: + for func in list(_option_finalizers['build_ext'].values()): + func(self) + + def build_extension(self, ext): + """ + Build C extension - with extended functionality + + The following features are added here: + + - ``ext.check_prerequisites`` is called before the extension is being + built. See `Extension` for details. If the method does not exist, + simply no check will be run. + - The macros ``EXT_PACKAGE`` and ``EXT_MODULE`` will be filled (or + unset) depending on the extensions name, but only if they are not + already defined. + + :Parameters: + `ext` : `Extension` + The extension to build. If it's a pure + ``distutils.core.Extension``, simply no prequisites check is + applied. + + :Return: whatever ``distutils.command.build_ext.build_ext`` returns + :Rtype: any + """ + # handle name macros + macros = dict(ext.define_macros or ()) + tup = ext.name.split('.') + if len(tup) == 1: + pkg, mod = None, tup[0] + else: + pkg, mod = '.'.join(tup[:-1]), tup[-1] + if pkg is not None and 'EXT_PACKAGE' not in macros: + ext.define_macros.append(('EXT_PACKAGE', pkg)) + if 'EXT_MODULE' not in macros: + ext.define_macros.append(('EXT_MODULE', mod)) + if pkg is None: + macros = dict(ext.undef_macros or ()) + if 'EXT_PACKAGE' not in macros: + ext.undef_macros.append('EXT_PACKAGE') + + # handle prereq checks + try: + checker = ext.check_prerequisites + except AttributeError: + pass + else: + if checker(self): + log.info("Skipping %s extension" % ext.name) + return + + return _build_ext.build_ext.build_extension(self, ext) + + +class Build(_build.build): + + def initialize_options(self): + """ Prepare for new options """ + _build.build.initialize_options(self) + if 'build' in _option_defaults: + for opt_name, default in _option_defaults['build']: + setattr(self, opt_name, default) + + def finalize_options(self): + """ Finalize options """ + _build.build.finalize_options(self) + if 'build' in _option_inherits: + for parent, opt_name in _option_inherits['build']: + self.set_undefined_options(parent, (opt_name, opt_name)) + if 'build' in _option_finalizers: + for func in list(_option_finalizers['build'].values()): + func(self) diff --git a/py3/data.py b/py3/data.py new file mode 100644 index 0000000..d422173 --- /dev/null +++ b/py3/data.py @@ -0,0 +1,165 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + Data distribution +=================== + +This module provides tools to simplify data distribution. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +from distutils import filelist as _filelist +import os as _os +import posixpath as _posixpath +import sys as _sys + +from _setup import commands as _commands + + +def splitpath(path): + """ Split a path """ + drive, path = '', _os.path.normpath(path) + try: + splitunc = _os.path.splitunc + except AttributeError: + pass + else: + drive, path = splitunc(path) + if not drive: + drive, path = _os.path.splitdrive(path) + elems = [] + try: + sep = _os.path.sep + except AttributeError: + sep = _os.path.join('1', '2')[1:-1] + while 1: + prefix, path = _os.path.split(path) + elems.append(path) + if prefix in ('', sep): + drive = _os.path.join(drive, prefix) + break + path = prefix + elems.reverse() + return drive, elems + + +def finalizer(installer): + """ Finalize install_data """ + data_files = [] + for item in installer.data_files: + if not isinstance(item, Data): + data_files.append(item) + continue + data_files.extend(item.flatten(installer)) + installer.data_files = data_files + + +class Data(object): + """ File list container """ + + def __init__(self, files, target=None, preserve=0, strip=0, + prefix=None): + """ Initialization """ + self._files = files + self._target = target + self._preserve = preserve + self._strip = strip + self._prefix = prefix + self.fixup_commands() + + def fixup_commands(self): + pass + + def from_templates(cls, *templates, **kwargs): + """ Initialize from template """ + files = _filelist.FileList() + for tpl in templates: + for line in tpl.split(';'): + files.process_template_line(line.strip()) + files.sort() + files.remove_duplicates() + result = [] + for filename in files.files: + _, elems = splitpath(filename) + if '.svn' in elems or '.git' in elems: + continue + result.append(filename) + return cls(result, **kwargs) + from_templates = classmethod(from_templates) + + def flatten(self, installer): + """ Flatten the file list to (target, file) tuples """ + # pylint: disable = W0613 + if self._prefix: + _, prefix = splitpath(self._prefix) + telems = prefix + else: + telems = [] + + tmap = {} + for fname in self._files: + (_, name), target = splitpath(fname), telems + if self._preserve: + if self._strip: + name = name[max(0, min(self._strip, len(name) - 1)):] + if len(name) > 1: + target = telems + name[:-1] + tmap.setdefault(_posixpath.join(*target), []).append(fname) + return list(tmap.items()) + + +class Documentation(Data): + """ Documentation container """ + + def fixup_commands(self): + _commands.add_option('install_data', 'without-docs', + help_text='Do not install documentation files', + inherit='install', + ) + _commands.add_finalizer('install_data', 'documentation', finalizer) + + def flatten(self, installer): + """ Check if docs should be installed at all """ + if installer.without_docs: + return [] + return Data.flatten(self, installer) + + +class Manpages(Documentation): + """ Manpages container """ + + def dispatch(cls, files): + """ Automatically dispatch manpages to their target directories """ + mpmap = {} + for manpage in files: + normalized = _os.path.normpath(manpage) + _, ext = _os.path.splitext(normalized) + if ext.startswith(_os.path.extsep): + ext = ext[len(_os.path.extsep):] + mpmap.setdefault(ext, []).append(manpage) + return [cls(manpages, prefix=_posixpath.join( + 'share', 'man', 'man%s' % section, + )) for section, manpages in list(mpmap.items())] + dispatch = classmethod(dispatch) + + def flatten(self, installer): + """ Check if manpages are suitable """ + if _sys.platform == 'win32': + return [] + return Documentation.flatten(self, installer) diff --git a/py3/dev/__init__.py b/py3/dev/__init__.py new file mode 100644 index 0000000..2b95d21 --- /dev/null +++ b/py3/dev/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: ascii -*- +# +# Copyright 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +==================== + Package _setup.dev +==================== + +Development tools, not distributed. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" diff --git a/py3/dev/_pylint.py b/py3/dev/_pylint.py new file mode 100644 index 0000000..5592a7e --- /dev/null +++ b/py3/dev/_pylint.py @@ -0,0 +1,258 @@ +# -*- coding: ascii -*- +# +# Copyright 2006, 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================================= + Support for code analysis tools +================================= + +Support for code analysis tools. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import re as _re +import sys as _sys + +from _setup import term as _term +from _setup import shell as _shell + + +class NotFinished(Exception): + """ Exception used for message passing in the stream filter """ + +class NotParseable(Exception): + """ Exception used for message passing in the stream filter """ + +class SpecialMessage(Exception): + """ Exception used for message passing in the stream filter """ + + +class FilterStream(object): + """ Stream filter """ + _LINERE = _re.compile(r''' + (?P[^:]+) + : + (?P\d+) + :\s+ + \[(?P[^\],]+)(?:,\s+(?P[^\]]+))?\] + \s+ + (?P.*) + ''', _re.X) + _SIMRE = _re.compile(r'in (?P\d+) files') + _CYCRE = _re.compile(r'\((?P[^)]+)\)') + + def __init__(self, term, stream=_sys.stdout): + self.written = False + self._stream = stream + self._lastname = None + self._cycled = False + self._term = dict(term) + self._buffer = '' + + def write(self, towrite): + """ Stream write function """ + self._buffer += towrite + term = self._term + + while True: + try: + name, lineno, mid, func, desc = self._parse() + except NotFinished: + break + except SpecialMessage as e: + self._dospecial(e) + continue + except NotParseable as e: + self._print_literal(str(e.args[0])) + continue + + if name != self._lastname: + if self._lastname is not None: + self._stream.write("\n") + term['path'] = name + self._stream.write( + "%(BOLD)s>>> %(path)s%(NORMAL)s\n" % term + ) + self._lastname = name + self.written = True + + term['mid'] = mid + if mid.startswith('E') or mid.startswith('F'): + self._stream.write("%(BOLD)s%(RED)s%(mid)s%(NORMAL)s" % term) + elif mid == 'W0511': + self._stream.write( + "%(BOLD)s%(GREEN)s%(mid)s%(NORMAL)s" % term + ) + else: + self._stream.write( + "%(BOLD)s%(YELLOW)s%(mid)s%(NORMAL)s" % term + ) + + if int(lineno) != 0: + term['lineno'] = lineno + self._stream.write(" (%(lineno)s" % term) + if func: + term['func'] = func + self._stream.write( + ", %(BOLD)s%(YELLOW)s%(func)s%(NORMAL)s" % term + ) + self._stream.write(')') + + self._stream.write(": %s\n" % desc) + self._stream.flush() + + return + + def _print_literal(self, line): + """ Print literal """ + suppress = ( + line.startswith('Unable to get imported names for ') or + line.startswith("Exception exceptions.RuntimeError: 'generator " + "ignored GeneratorExit' in = 0: + lines = self._buffer[:pos + 1] + self._buffer = self._buffer[pos + 1:] + term = self._term + + self._stream.write("\n") + for name in lines.splitlines()[1:]: + name = name.rstrip()[2:] + term['path'] = name + self._stream.write( + "%(BOLD)s=== %(path)s%(NORMAL)s\n" % term + ) + self._lastname = name + + term['mid'] = e.args[0] + self._stream.write( + "%(BOLD)s%(YELLOW)s%(mid)s%(NORMAL)s" % term + ) + self._stream.write(": %s\n" % e.args[1]) + self._stream.flush() + self.written = True + + def _parse(self): + """ Parse output """ + if '\n' not in self._buffer: + raise NotFinished() + + line = self._buffer[:self._buffer.find('\n') + 1] + self._buffer = self._buffer[len(line):] + line = line.rstrip() + match = self._LINERE.match(line) + if not match: + raise NotParseable(line) + + mid = match.group('mid') + if mid in ('R0801', 'R0401'): + self._buffer = "%s\n%s" % (line, self._buffer) + raise SpecialMessage(mid, match.group('desc')) + + return match.group('name', 'lineno', 'mid', 'func', 'desc') + + +def run(config, *args): + """ Run pylint """ + try: + from pylint import lint + from pylint.reporters import text + except ImportError: + return 2 + + if config is None: + config = _shell.native('pylint.conf') + argv = ['--rcfile', config, + '--reports', 'no', + '--output-format', 'parseable', + '--include-ids', 'yes' + ] + + stream = FilterStream(_term.terminfo()) + + old_stderr = _sys.stderr + try: + # pylint: disable = E1101 + _sys.stderr = stream + from pylint import __pkginfo__ + if __pkginfo__.numversion < (0, 13): + # The lint tool is not very user friendly, so we need a hack here. + lint.REPORTER_OPT_MAP['parseable'] = \ + lambda: text.TextReporter2(stream) + reporter = text.TextReporter2(stream) + else: + reporter = text.ParseableTextReporter(stream) + lint.REPORTER_OPT_MAP['parseable'] = lambda: reporter + + for path in args: + try: + try: + lint.Run(argv + [path], reporter=reporter) + except SystemExit: + pass # don't accept the exit. strange errors happen... + + if stream.written: + print() + stream.written = False + except KeyboardInterrupt: + print() + raise + finally: + _sys.stderr = old_stderr + + return 0 diff --git a/py3/dev/analysis.py b/py3/dev/analysis.py new file mode 100644 index 0000000..f8bff20 --- /dev/null +++ b/py3/dev/analysis.py @@ -0,0 +1,31 @@ +# -*- coding: ascii -*- +# +# Copyright 2006, 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================================= + Support for code analysis tools +================================= + +Support for code analysis tools. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + + +def pylint(config, *args): + """ Run pylint """ + from _setup.dev import _pylint + return _pylint.run(config, *args) diff --git a/py3/dev/apidoc.py b/py3/dev/apidoc.py new file mode 100644 index 0000000..c1057eb --- /dev/null +++ b/py3/dev/apidoc.py @@ -0,0 +1,131 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================== + API doc builders +================== + +API doc builders. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os +import re as _re + +from _setup import shell as _shell +from _setup import term as _term +from _setup import util as _util + + +def _cleanup_epydoc(target): + """ + Cleanup epydoc generated files + + This removes the epydoc-footer. It changes every release because of the + timestamp. That creates bad diffs (accidently it's also invalid html). + """ + search = _re.compile(r']+width="100%%"').search + for filename in _shell.files(target, '*.html'): + fp = open(filename, 'r') + try: + html = fp.read() + finally: + fp.close() + match = search(html) + if match: + start = match.start() + end = html.find('', start) + if end >= 0: + end += len('') + 1 + html = html[:start] + html[end:] + fp = open(filename, 'w') + try: + fp.write(html) + finally: + fp.close() + + +_VERSION_SEARCH = _re.compile( + r'\bversion\s+(?P\d+)\.(?P\d+)' +).search +def epydoc(**kwargs): + """ Run epydoc """ + # pylint: disable = R0912 + prog = kwargs.get('epydoc') or 'epydoc' + if not _os.path.dirname(_os.path.normpath(prog)): + prog = _shell.frompath(prog) + if not prog: + _term.red("%(epydoc)s not found", + epydoc=kwargs.get('epydoc') or 'epydoc', + ) + return False + + version = _VERSION_SEARCH(_shell.spawn(prog, "--version", stdout=True)) + if version is not None: + try: + version = tuple(map(int, version.group('major', 'minor'))) + except (TypeError, ValueError): + version = None + if version is None: + _term.red("%(prog)s version not recognized" % locals()) + return False + + if version < (3, 0): + _term.red("%(prog)s is too old %(version)r < (3, 0)" % locals()) + return False + + env = dict(_os.environ) + + prepend = kwargs.get('prepend') + if prepend: + toprepend = _os.pathsep.join(map(str, prepend)) + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = _os.pathsep.join(( + toprepend, env['PYTHONPATH'] + )) + else: + env['PYTHONPATH'] = toprepend + + append = kwargs.get('append') + if append: + toappend = _os.pathsep.join(map(str, append)) + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = _os.pathsep.join(( + env['PYTHONPATH'], toappend + )) + else: + env['PYTHONPATH'] = toappend + + moreenv = kwargs.get('env') + if moreenv: + env.update(moreenv) + + config = kwargs.get('config') or _shell.native('docs/epydoc.conf') + + argv = [prog, '--config', config] + res = not _shell.spawn(*argv, **{'env': env}) + if res: + cfg = _util.SafeConfigParser() + cfg.read(config) + try: + target = dict(cfg.items('epydoc'))['target'] + except KeyError: + pass + else: + _cleanup_epydoc(target) + return res diff --git a/py3/dev/userdoc.py b/py3/dev/userdoc.py new file mode 100644 index 0000000..1130481 --- /dev/null +++ b/py3/dev/userdoc.py @@ -0,0 +1,50 @@ +# -*- coding: ascii -*- +# +# Copyright 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + User doc builders +=================== + +User doc builders. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os + +from _setup import shell as _shell +from _setup import term as _term + + +def sphinx(**kwargs): + """ Run sphinx """ + prog = _shell.frompath('sphinx-build') + if prog is None: + _term.red("sphinx-build not found") + return False + + env = dict(_os.environ) + + argv = [ + prog, '-a', + '-d', _os.path.join(kwargs['build'], 'doctrees'), + '-b', 'html', + kwargs['source'], + kwargs['target'], + ] + + return not _shell.spawn(*argv, **{'env': env}) diff --git a/py3/dist.py b/py3/dist.py new file mode 100644 index 0000000..cce21e5 --- /dev/null +++ b/py3/dist.py @@ -0,0 +1,51 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================ + dist utilities +================ + +dist utilities. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import sys as _sys + +from _setup import shell as _shell + + +def run_setup(*args, **kwargs): + """ Run setup """ + if 'setup' in kwargs: + script = kwargs.get('setup') or 'setup.py' + del kwargs['setup'] + else: + script = 'setup.py' + if 'fakeroot' in kwargs: + fakeroot = kwargs['fakeroot'] + del kwargs['fakeroot'] + else: + fakeroot = None + if kwargs: + raise TypeError("Unrecognized keyword parameters") + + script = _shell.native(script) + argv = [_sys.executable, script] + list(args) + if fakeroot: + argv.insert(0, fakeroot) + return not _shell.spawn(*argv) diff --git a/py3/ext.py b/py3/ext.py new file mode 100644 index 0000000..c54c845 --- /dev/null +++ b/py3/ext.py @@ -0,0 +1,253 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + C extension tools +=================== + +C extension tools. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" +__test__ = False + +from distutils import core as _core +from distutils import errors as _distutils_errors +from distutils import log +import os as _os +import posixpath as _posixpath +import shutil as _shutil +import tempfile as _tempfile + +from _setup import commands as _commands + +def _install_finalizer(installer): + if installer.without_c_extensions: + installer.distribution.ext_modules = [] + +def _build_finalizer(builder): + if builder.without_c_extensions: + builder.extensions = [] + + +class Extension(_core.Extension): + """ + Extension with prerequisite check interface + + If your check is cacheable (during the setup run), override + `cached_check_prerequisites`, `check_prerequisites` otherwise. + + :IVariables: + `cached_check` : ``bool`` + The cached check result + """ + cached_check = None + + def __init__(self, *args, **kwargs): + """ Initialization """ + if 'depends' in kwargs: + self.depends = kwargs['depends'] or [] + else: + self.depends = [] + _core.Extension.__init__(self, *args, **kwargs) + + # add include path + included = _posixpath.join('_setup', 'include') + if included not in self.include_dirs: + self.include_dirs.append(included) + + # add cext.h to the dependencies + cext_h = _posixpath.join(included, 'cext.h') + if cext_h not in self.depends: + self.depends.append(cext_h) + + _commands.add_option('install_lib', 'without-c-extensions', + help_text='Don\'t install C extensions', + inherit='install', + ) + _commands.add_finalizer('install_lib', 'c-extensions', + _install_finalizer + ) + _commands.add_option('build_ext', 'without-c-extensions', + help_text='Don\'t build C extensions', + inherit=('build', 'install_lib'), + ) + _commands.add_finalizer('build_ext', 'c-extensions', _build_finalizer) + + def check_prerequisites(self, build): + """ + Check prerequisites + + The check should cover all dependencies needed for the extension to + be built and run. The method can do the following: + + - return a false value: the extension will be built + - return a true value: the extension will be skipped. This is useful + for optional extensions + - raise an exception. This is useful for mandatory extensions + + If the check result is cacheable (during the setup run), override + `cached_check_prerequisites` instead. + + :Parameters: + `build` : `BuildExt` + The extension builder + + :Return: Skip the extension? + :Rtype: ``bool`` + """ + if self.cached_check is None: + log.debug("PREREQ check for %s" % self.name) + self.cached_check = self.cached_check_prerequisites(build) + else: + log.debug("PREREQ check for %s (cached)" % self.name) + return self.cached_check + + def cached_check_prerequisites(self, build): + """ + Check prerequisites + + The check should cover all dependencies needed for the extension to + be built and run. The method can do the following: + + - return a false value: the extension will be built + - return a true value: the extension will be skipped. This is useful + for optional extensions + - raise an exception. This is useful for mandatory extensions + + If the check result is *not* cacheable (during the setup run), + override `check_prerequisites` instead. + + :Parameters: + `build` : `BuildExt` + The extension builder + + :Return: Skip the extension? + :Rtype: ``bool`` + """ + # pylint: disable = W0613 + log.debug("Nothing to check for %s!" % self.name) + return False + + +class ConfTest(object): + """ + Single conftest abstraction + + :IVariables: + `_tempdir` : ``str`` + The tempdir created for this test + + `src` : ``str`` + Name of the source file + + `target` : ``str`` + Target filename + + `compiler` : ``CCompiler`` + compiler instance + + `obj` : ``list`` + List of object filenames (``[str, ...]``) + """ + _tempdir = None + + def __init__(self, build, source): + """ + Initialization + + :Parameters: + `build` : ``distuils.command.build_ext.build_ext`` + builder instance + + `source` : ``str`` + Source of the file to compile + """ + self._tempdir = tempdir = _tempfile.mkdtemp() + src = _os.path.join(tempdir, 'conftest.c') + fp = open(src, 'w') + try: + fp.write(source) + finally: + fp.close() + self.src = src + self.compiler = compiler = build.compiler + self.target = _os.path.join(tempdir, 'conftest') + self.obj = compiler.object_filenames([src], output_dir=tempdir) + + def __del__(self): + """ Destruction """ + self.destroy() + + def destroy(self): + """ Destroy the conftest leftovers on disk """ + tempdir, self._tempdir = self._tempdir, None + if tempdir is not None: + _shutil.rmtree(tempdir) + + def compile(self, **kwargs): + """ + Compile the conftest + + :Parameters: + `kwargs` : ``dict`` + Optional keyword parameters for the compiler call + + :Return: Was the compilation successful? + :Rtype: ``bool`` + """ + kwargs['output_dir'] = self._tempdir + try: + self.compiler.compile([self.src], **kwargs) + except _distutils_errors.CompileError: + return False + return True + + def link(self, **kwargs): + r""" + Link the conftest + + Before you can link the conftest objects they need to be `compile`\d. + + :Parameters: + `kwargs` : ``dict`` + Optional keyword parameters for the linker call + + :Return: Was the linking successful? + :Rtype: ``bool`` + """ + try: + self.compiler.link_executable(self.obj, self.target, **kwargs) + except _distutils_errors.LinkError: + return False + return True + + def pipe(self, mode="r"): + r""" + Execute the conftest binary and connect to it using a pipe + + Before you can pipe to or from the conftest binary it needs to + be `link`\ed. + + :Parameters: + `mode` : ``str`` + Pipe mode - r/w + + :Return: The open pipe + :Rtype: ``file`` + """ + return _os.popen(self.compiler.executable_filename(self.target), mode) diff --git a/py3/make/__init__.py b/py3/make/__init__.py new file mode 100644 index 0000000..ddb4f8e --- /dev/null +++ b/py3/make/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: ascii -*- +# +# Copyright 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +===================== + Package _setup.make +===================== + +Make tools, not distributed. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +# pylint: disable = W0611 +from _setup.make._make import main, fail, warn, fatal, Target diff --git a/py3/make/_make.py b/py3/make/_make.py new file mode 100644 index 0000000..f8eaaa7 --- /dev/null +++ b/py3/make/_make.py @@ -0,0 +1,338 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================== + Simple make base +================== + +Simple make base. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import sys as _sys + +from _setup import term as _term + + +class Failure(SystemExit): + """ Failure exception """ + + +def fail(reason): + """ Fail for a reason """ + raise Failure(reason) + + +def warn(message, name=None): + """ Warn """ + _term.red("%(NAME)sWarning: %(msg)s", + NAME=name and "%s:" % name or '', msg=message + ) + + +def fatal(reason): + """ Fatal error, immediate stop """ + print(reason, file=_sys.stderr) + _sys.exit(1) + + +class Target(object): + """ Target base class """ + NAME = None + DEPS = None + HIDDEN = False + + ERROR = None + + def __init__(self, runner): + """ Base __init__ """ + self.runner = runner + self.init() + + def init(self): + """ Default init hook """ + pass + + def run(self): + """ Default run hook """ + pass + + def clean(self, scm=True, dist=False): + """ Default clean hook """ + pass + + +class _Runner(object): + """ Runner """ + + def __init__(self, *targetscollection): + """ Initialization """ + tdict = {} + if not targetscollection: + import __main__ + targetscollection = [__main__] + + from _setup.make import default_targets + if default_targets not in targetscollection: + targetscollection.append(default_targets) + + for targets in targetscollection: + for value in list(vars(targets).values()): + if isinstance(value, type) and issubclass(value, Target) and \ + value.NAME is not None: + if value.NAME in tdict: + if issubclass(value, tdict[value.NAME]): + pass # override base target + elif issubclass(tdict[value.NAME], value): + continue # found base later. ignore + else: + warn('Ambiguous target name', value.NAME) + continue + tdict[value.NAME] = value + self._tdict = tdict + self._itdict = {} + + def print_help(self): + """ Print make help """ + import textwrap as _textwrap + + targets = self.targetinfo() + keys = [] + for key, info in list(targets.items()): + if not info['hide']: + keys.append(key) + keys.sort() + length = max(list(map(len, keys))) + info = [] + for key in keys: + info.append("%s%s" % ( + (key + " " * length)[:length + 2], + _textwrap.fill( + targets[key]['desc'].strip(), + subsequent_indent=" " * (length + 2) + ), + )) + print("Available targets:\n\n" + "\n".join(info)) + + def targetinfo(self): + """ Extract target information """ + result = {} + for name, cls in list(self._tdict.items()): + result[name] = { + 'desc': cls.__doc__ or "no description", + 'hide': cls.HIDDEN, + 'deps': cls.DEPS or (), + } + return result + + def _topleveltargets(self): + """ Find all top level targets """ + rev = {} # key is a dep of [values] + all_ = self.targetinfo() + for target, info in list(all_.items()): + for dep in info['deps']: + if dep not in all_: + fatal("Unknown target '%s' (dep of %s) -> exit" % ( + dep, target + )) + rev.setdefault(dep, []).append(target) + return [target for target, info in list(rev.items()) if not info] + + def _run(self, target, seen=None): + """ Run a target """ + if target.DEPS: + self(*target.DEPS, **{'seen': seen}) + + if not target.HIDDEN: + _term.yellow(">>> %(name)s", name=target.NAME) + + try: + result = target.run() + except KeyboardInterrupt: + result, target.ERROR = False, "^C -> exit" + except Failure as e: + result, target.ERROR = False, "%s: %s" % (target.NAME, e) + except (SystemExit, MemoryError): + raise + except: + import traceback + target.ERROR = "%s errored:\n%s" % (target.NAME, ''.join( + traceback.format_exception(*_sys.exc_info()) + )) + result = False + else: + if result is None: + result = True + return result + + def _clean(self, target, scm, dist, seen=None): + """ Run a target """ + if target.DEPS: + self.run_clean( + *target.DEPS, **{'scm': scm, 'dist': dist, 'seen': seen} + ) + + try: + result = target.clean(scm, dist) + except KeyboardInterrupt: + result, target.ERROR = False, "^C -> exit" + except Failure as e: + result, target.ERROR = False, "%s: %s" % (target.NAME, e) + except (SystemExit, MemoryError): + raise + except: + import traceback + target.ERROR = "%s errored:\n%s" % (target.NAME, ''.join( + traceback.format_exception(*_sys.exc_info()) + )) + result = False + else: + if result is None: + result = True + return result + + def _make_init(self, seen): + """ Make init mapper """ + def init(target): + """ Return initialized target """ + if target not in seen: + try: + seen[target] = self._tdict[target](self) + except KeyError: + fatal("Unknown target '%s' -> exit" % target) + else: + seen[target] = None + return seen[target] + return init + + def run_clean(self, *targets, **kwargs): + """ Run targets """ + def pop(name, default=None): + """ Pop """ + if name in kwargs: + value = kwargs[name] + del kwargs[name] + if value is None: + return default + return value + else: + return default + seen = pop('seen', {}) + scm = pop('scm', True) + dist = pop('dist', False) + if kwargs: + raise TypeError('Unknown keyword parameters') + + if not targets: + top_targets = self._topleveltargets() + targets = self.targetinfo() + for item in top_targets: + del targets[item] + targets = list(targets.keys()) + targets.sort() + top_targets.sort() + targets = top_targets + targets + + init = self._make_init(seen) + for name in targets: + target = init(name) + if target is not None: + if not self._clean(target, scm=scm, dist=dist, seen=seen): + msg = target.ERROR + if msg is None: + msg = "Clean target %s returned error -> exit" % name + fatal(msg) + + def __call__(self, *targets, **kwargs): + """ Run targets """ + if 'seen' in kwargs: + seen = kwargs['seen'] + del kwargs['seen'] + else: + seen = None + if seen is None: + seen = self._itdict + if kwargs: + raise TypeError('Unknown keyword parameters') + + init = self._make_init(seen) + for name in targets: + target = init(name) + if target is not None: + if not self._run(target, seen): + msg = target.ERROR + if msg is None: + msg = "Target %s returned error -> exit" % name + fatal(msg) + + +def main(*args, **kwargs): + """ + main(argv=None, *args, name=None) + + Main start point. This function parses the command line and executes the + targets given through `argv`. If there are no targets given, a help output + is generated. + + :Parameters: + `argv` : sequence + Command line arguments. If omitted or ``None``, they are picked from + ``sys.argv``. + + `args` : ``tuple`` + The list of modules with targets. If omitted, ``__main__`` + is imported and treated as target module. Additionally the mechanism + always adds the `_setup.make` module (this one) to the list in order + to grab some default targets. + + `name` : ``str`` + Name of the executing module. If omitted or ``None``, ``'__main__'`` + is assumed. If the final name is not ``'__main__'``, the function + returns immediately. + """ + try: + name = kwargs['name'] + except KeyError: + name = '__main__' + else: + del kwargs['name'] + if name is None: + name = '__main__' + + try: + argv = kwargs['argv'] + except KeyError: + if not args: + args = (None,) + else: + del kwargs['argv'] + args = (argv,) + args + + if kwargs: + raise TypeError("Unrecognized keyword arguments for main()") + + if name == '__main__': + argv, args = args[0], args[1:] + if argv is None: + argv = _sys.argv[1:] + + runner = _Runner(*args) + if argv: + runner(*argv) + else: + runner.print_help() diff --git a/py3/make/default_targets.py b/py3/make/default_targets.py new file mode 100644 index 0000000..16c95ee --- /dev/null +++ b/py3/make/default_targets.py @@ -0,0 +1,110 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================== + Simple make base +================== + +Simple make base. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os +import sys as _sys + +from _setup import make as _make +from _setup import shell as _shell + + +class MakefileTarget(_make.Target): + """ Create a make file """ + NAME = 'makefile' + + def run(self): + def escape(value): + """ Escape for make and shell """ + return '"%s"' % value.replace( + '\\', '\\\\').replace( + '"', '\\"').replace( + '$', '\\$$') + def decorate(line, prefix='# ', width=78, char='~', padding=' '): + """ Decorate a line """ + line = line.center(width - len(prefix)) + return '%s%s%s%s%s%s' % ( + prefix, + char * (len(line) - len(line.lstrip()) - len(padding)), + padding, + line.strip(), + padding, + char * (len(line) - len(line.rstrip()) - len(padding)), + ) + + python = escape(_sys.executable) + script = escape(_sys.argv[0]) + targets = self.runner.targetinfo() + names = [] + for name, info in list(targets.items()): + if not info['hide']: + names.append(name) + names.sort() + + fp = open(_shell.native('Makefile'), 'w') + print(decorate("Generated Makefile, DO NOT EDIT"), file=fp) + print(decorate("python %s %s" % ( + _os.path.basename(script), self.NAME + )), file=fp) + print(file=fp) + print("_default_:", file=fp) + print("\t@%s %s" % (python, script), file=fp) + for name in names: + print("\n", file=fp) + print("# %s" % \ + targets[name]['desc'].splitlines()[0].strip(), file=fp) + print("%s:" % name, file=fp) + print("\t@%s %s %s" % (python, script, escape(name)), file=fp) + print(file=fp) + extension = self.extend(names) + if extension is not None: + print(extension, file=fp) + print(file=fp) + print(".PHONY: _default_ %s\n\n" % ' '.join(names), file=fp) + fp.close() + + def extend(self, names): + pass + + +class CleanTarget(_make.Target): + """ Clean the mess """ + NAME = 'clean' + _scm, _dist = True, False + + def run(self): + self.runner.run_clean(scm=self._scm, dist=self._dist) + + +class DistCleanTarget(CleanTarget): + """ Clean as freshly unpacked dist package """ + NAME = 'distclean' + _scm, _dist = False, True + + +class ExtraCleanTarget(CleanTarget): + """ Clean everything """ + NAME = 'extraclean' + _scm, _dist = True, True diff --git a/py3/make/targets.py b/py3/make/targets.py new file mode 100644 index 0000000..eca4d02 --- /dev/null +++ b/py3/make/targets.py @@ -0,0 +1,326 @@ +# -*- coding: ascii -*- +# +# Copyright 2007 - 2013 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================== + Standard targets +================== + +Standard targets. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import os as _os +import sys as _sys + +from _setup import dist as _dist +from _setup import make as _make +from _setup import shell as _shell +from _setup import term as _term + + +class Distribution(_make.Target): + """ Build a distribution """ + NAME = "dist" + DEPS = ["MANIFEST"] + + _dist, _ebuilds, _changes = None, None, None + + def init(self): + raise NotImplementedError() + + def run(self): + exts = self.dist_pkg() + digests = self.digest_files(exts) + self.sign_digests(digests) + self.copy_ebuilds() + self.copy_changes() + + def dist_pkg(self): + _term.green("Building package...") + _dist.run_setup("sdist", "--formats", "tar,zip", + fakeroot=_shell.frompath('fakeroot') + ) + exts = ['.zip'] + for name in _shell.files(self._dist, '*.tar', False): + exts.extend(self.compress(name)) + _shell.rm(name) + return exts + + def compress(self, filename): + """ Compress file """ + ext = _os.path.splitext(filename)[1] + exts = [] + exts.append('.'.join((ext, self.compress_gzip(filename)))) + exts.append('.'.join((ext, self.compress_bzip2(filename)))) + exts.append('.'.join((ext, self.compress_xz(filename)))) + return exts + + def compress_xz(self, filename): + outfilename = filename + '.xz' + self.compress_external(filename, outfilename, 'xz', '-c9') + return 'xz' + + def compress_bzip2(self, filename): + outfilename = filename + '.bz2' + try: + import bz2 as _bz2 + except ImportError: + self.compress_external(filename, outfilename, 'bzip2', '-c9') + else: + outfile = _bz2.BZ2File(outfilename, 'w') + self.compress_internal(filename, outfile, outfilename) + return 'bz2' + + def compress_gzip(self, filename): + outfilename = filename + '.gz' + try: + import gzip as _gzip + except ImportError: + self.compress_external(filename, outfilename, 'gzip', '-c9') + else: + outfile = _gzip.GzipFile(filename, 'wb', + fileobj=open(outfilename, 'wb') + ) + self.compress_internal(filename, outfile, outfilename) + return 'gz' + + def compress_external(self, infile, outfile, *argv): + argv = list(argv) + argv[0] = _shell.frompath(argv[0]) + if argv[0] is not None: + return not _shell.spawn(*argv, **{ + 'filepipe': True, 'stdin': infile, 'stdout': outfile, + }) + return None + + def compress_internal(self, filename, outfile, outfilename): + infile = open(filename, 'rb') + try: + try: + while 1: + chunk = infile.read(8192) + if not chunk: + break + outfile.write(chunk) + outfile.close() + except: + e = _sys.exc_info() + try: + _shell.rm(outfilename) + finally: + try: + raise e[0](e[1]).with_traceback(e[2]) + finally: + del e + finally: + infile.close() + + def digest_files(self, exts): + """ digest files """ + digests = {} + digestnames = {} + for ext in exts: + for name in _shell.files(self._dist, '*' + ext, False): + basename = _os.path.basename(name) + if basename not in digests: + digests[basename] = [] + digests[basename].extend(self.digest(name)) + digestname = basename[:-len(ext)] + if digestname not in digestnames: + digestnames[digestname] = [] + digestnames[digestname].append(basename) + + result = [] + for name, basenames in digestnames.items(): + result.append(_os.path.join(self._dist, name + '.digests')) + fp = open(result[-1], 'wb') + try: + fp.write( + b'\n# The file may contain MD5, SHA1 and SHA256 digests\n' + ) + fp.write(b'# Check archive integrity with, e.g. md5sum -c\n') + fp.write(b'# Check digest file integrity with PGP\n\n') + basenames.sort() + for basename in basenames: + for digest in digests[basename]: + fp.write(( + "%s *%s\n" % (digest, basename)).encode('utf-8') + ) + finally: + fp.close() + return result + + def digest(self, filename): + result = [] + for method in (self.md5, self.sha1, self.sha256): + digest = method(filename) + if digest is not None: + result.append(digest) + return result + + def do_digest(self, hashfunc, name, filename): + filename = _shell.native(filename) + _term.green("%(digest)s-digesting %(name)s...", + digest=name, name=_os.path.basename(filename)) + fp = open(filename, 'rb') + sig = hashfunc() + block = fp.read(8192) + while block: + sig.update(block) + block = fp.read(8192) + fp.close() + return sig.hexdigest() + + param = {'sig': sig.hexdigest(), 'file': _os.path.basename(filename)} + fp = open("%s.%s" % (filename, name), "w") + fp.write("%(sig)s *%(file)s\n" % param) + fp.close() + + return True + + def md5(self, filename): + try: + from hashlib import md5 + except ImportError: + try: + from md5 import new as md5 + except ImportError: + _make.warn("md5 not found -> skip md5 digests", self.NAME) + return None + return self.do_digest(md5, "md5", filename) + + def sha1(self, filename): + try: + from hashlib import sha1 + except ImportError: + try: + from sha import new as sha1 + except ImportError: + _make.warn("sha1 not found -> skip sha1 digests", self.NAME) + return None + return self.do_digest(sha1, "sha1", filename) + + def sha256(self, filename): + try: + from hashlib import sha256 + except ImportError: + try: + from Crypto.Hash.SHA256 import new as sha256 + except ImportError: + _make.warn( + "sha256 not found -> skip sha256 digests", self.NAME + ) + return None + return self.do_digest(sha256, "sha256", filename) + + def copy_ebuilds(self): + if self._ebuilds is not None: + for src in _shell.files(self._ebuilds, '*.ebuild'): + _shell.cp(src, self._dist) + + def copy_changes(self): + if self._changes is not None: + _shell.cp(self._changes, self._dist) + + def sign_digests(self, digests): + for digest in digests: + self.sign(digest, detach=False) + + def sign(self, filename, detach=True): + filename = _shell.native(filename) + try: + from pyme import core, errors + from pyme.constants.sig import mode + except ImportError: + return self.sign_external(filename, detach=detach) + + _term.green("signing %(name)s...", name=_os.path.basename(filename)) + sigmode = [mode.CLEAR, mode.DETACH][bool(detach)] + fp = core.Data(file=filename) + sig = core.Data() + try: + c = core.Context() + except errors.GPGMEError: + return self.sign_external(filename, detach=detach) + c.set_armor(1) + try: + c.op_sign(fp, sig, sigmode) + except errors.GPGMEError as e: + _make.fail(str(e)) + + sig.seek(0, 0) + if detach: + open("%s.asc" % filename, "w").write(sig.read()) + else: + open(filename, "w").write(sig.read()) + + return True + + def sign_external(self, filename, detach=True): + """ Sign calling gpg """ + gpg = _shell.frompath('gpg') + if gpg is None: + _make.warn('GPG not found -> cannot sign') + return False + if detach: + _shell.spawn(gpg, + '--armor', + '--output', filename + '.asc', + '--detach-sign', + '--', + filename, + ) + else: + _shell.spawn(gpg, + '--output', filename + '.signed', + '--clearsign', + '--', + filename, + ) + _os.rename(filename + '.signed', filename) + return True + + def clean(self, scm, dist): + _term.green("Removing dist files...") + _shell.rm_rf(self._dist) + + +class Manifest(_make.Target): + """ Create manifest """ + NAME = "MANIFEST" + HIDDEN = True + DEPS = ["doc"] + + def run(self): + _term.green("Creating %(name)s...", name=self.NAME) + dest = _shell.native(self.NAME) + dest = open(dest, 'w') + for name in self.manifest_names(): + dest.write("%s\n" % name) + dest.close() + + def manifest_names(self): + import setup + for item in setup.manifest(): + yield item + + def clean(self, scm, dist): + """ Clean manifest """ + if scm: + _term.green("Removing MANIFEST") + _shell.rm(self.NAME) diff --git a/py3/setup.py b/py3/setup.py new file mode 100644 index 0000000..e28f000 --- /dev/null +++ b/py3/setup.py @@ -0,0 +1,420 @@ +# -*- coding: ascii -*- +# +# Copyright 2007 - 2013 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +=================== + Main setup runner +=================== + +This module provides a wrapper around the distutils core setup. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import configparser as _config_parser +from distutils import core as _core +import os as _os +import posixpath as _posixpath +import sys as _sys + +from _setup import commands as _commands +from _setup import data as _data +from _setup import ext as _ext +from _setup import util as _util +from _setup import shell as _shell + + +def check_python_version(impl, version_min, version_max): + """ Check python version """ + if impl == 'python': + version_info = _sys.version_info + elif impl == 'pypy': + version_info = getattr(_sys, 'pypy_version_info', None) + if not version_info: + return + elif impl == 'jython': + if not 'java' in _sys.platform.lower(): + return + version_info = _sys.version_info + else: + raise AssertionError("impl not in ('python', 'pypy', 'jython')") + + pyversion = list(map(int, version_info[:3])) + if version_min: + min_required = list( + map(int, '.'.join((version_min, '0.0.0')).split('.')[:3]) + ) + if pyversion < min_required: + raise EnvironmentError("Need at least %s %s (vs. %s)" % ( + impl, version_min, '.'.join(map(str, pyversion)) + )) + if version_max: + max_required = list(map(int, version_max.split('.'))) + max_required[-1] += 1 + if pyversion >= max_required: + raise EnvironmentError("Need at max %s %s (vs. %s)" % ( + impl, + version_max, + '.'.join(map(str, pyversion)) + )) + + +def find_description(docs): + """ + Determine the package description from DESCRIPTION + + :Parameters: + `docs` : ``dict`` + Docs config section + + :Return: Tuple of summary, description and license + (``('summary', 'description', 'license')``) + (all may be ``None``) + :Rtype: ``tuple`` + """ + summary = None + filename = docs.get('meta.summary', 'SUMMARY').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + try: + summary = fp.read().strip().splitlines()[0].rstrip() + except IndexError: + summary = '' + finally: + fp.close() + + description = None + filename = docs.get('meta.description', 'DESCRIPTION').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + description = fp.read().rstrip() + finally: + fp.close() + + if summary is None and description: + from docutils import core + summary = core.publish_parts( + source=description, + source_path=filename, + writer_name='html', + )['title'].encode('utf-8') + + return summary, description + + +def find_classifiers(docs): + """ + Determine classifiers from CLASSIFIERS + + :return: List of classifiers (``['classifier', ...]``) + :rtype: ``list`` + """ + filename = docs.get('meta.classifiers', 'CLASSIFIERS').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + content = fp.read() + finally: + fp.close() + content = [item.strip() for item in content.splitlines()] + return [item for item in content if item and not item.startswith('#')] + return [] + + +def find_provides(docs): + """ + Determine provides from PROVIDES + + :return: List of provides (``['provides', ...]``) + :rtype: ``list`` + """ + filename = docs.get('meta.provides', 'PROVIDES').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + content = fp.read() + finally: + fp.close() + content = [item.strip() for item in content.splitlines()] + return [item for item in content if item and not item.startswith('#')] + return [] + + +def find_license(docs): + """ + Determine license from LICENSE + + :return: License text + :rtype: ``str`` + """ + filename = docs.get('meta.license', 'LICENSE').strip() + if filename and _os.path.isfile(filename): + fp = open(filename) + try: + return fp.read().rstrip() + finally: + fp.close() + return None + + +def find_packages(manifest): + """ Determine packages and subpackages """ + packages = {} + collect = manifest.get('packages.collect', '').split() + lib = manifest.get('packages.lib', '.') + try: + sep = _os.path.sep + except AttributeError: + sep = _os.path.join('1', '2')[1:-1] + for root in collect: + for dirpath, _, filenames in _shell.walk(_os.path.join(lib, root)): + if dirpath.find('.svn') >= 0 or dirpath.find('.git') >= 0: + continue + if '__init__.py' in filenames: + packages[ + _os.path.normpath(dirpath).replace(sep, '.') + ] = None + packages = list(packages.keys()) + packages.sort() + return packages + + +def find_data(name, docs): + """ Determine data files """ + result = [] + if docs.get('extra', '').strip(): + result.append(_data.Documentation(docs['extra'].split(), + prefix='share/doc/%s' % name, + )) + if docs.get('examples.dir', '').strip(): + tpl = ['recursive-include %s *' % docs['examples.dir']] + if docs.get('examples.ignore', '').strip(): + tpl.extend(["global-exclude %s" % item + for item in docs['examples.ignore'].split() + ]) + strip = int(docs.get('examples.strip', '') or 0) + result.append(_data.Documentation.from_templates(*tpl, **{ + 'strip': strip, + 'prefix': 'share/doc/%s' % name, + 'preserve': 1, + })) + if docs.get('userdoc.dir', '').strip(): + tpl = ['recursive-include %s *' % docs['userdoc.dir']] + if docs.get('userdoc.ignore', '').strip(): + tpl.extend(["global-exclude %s" % item + for item in docs['userdoc.ignore'].split() + ]) + strip = int(docs.get('userdoc.strip', '') or 0) + result.append(_data.Documentation.from_templates(*tpl, **{ + 'strip': strip, + 'prefix': 'share/doc/%s' % name, + 'preserve': 1, + })) + if docs.get('apidoc.dir', '').strip(): + tpl = ['recursive-include %s *' % docs['apidoc.dir']] + if docs.get('apidoc.ignore', '').strip(): + tpl.extend(["global-exclude %s" % item + for item in docs['apidoc.ignore'].split() + ]) + strip = int(docs.get('apidoc.strip', '') or 0) + result.append(_data.Documentation.from_templates(*tpl, **{ + 'strip': strip, + 'prefix': 'share/doc/%s' % name, + 'preserve': 1, + })) + if docs.get('man', '').strip(): + result.extend(_data.Manpages.dispatch(docs['man'].split())) + return result + + +def make_manifest(manifest, config, docs, kwargs): + """ Create file list to pack up """ + # pylint: disable = R0912 + kwargs = kwargs.copy() + kwargs['script_args'] = ['install'] + kwargs['packages'] = list(kwargs.get('packages') or ()) + [ + '_setup', '_setup.py2', '_setup.py3', + ] + list(manifest.get('packages.extra', '').split() or ()) + _core._setup_stop_after = "commandline" + try: + dist = _core.setup(**kwargs) + finally: + _core._setup_stop_after = None + + result = ['MANIFEST', 'PKG-INFO', 'setup.py'] + list(config) + # TODO: work with default values: + for key in ('classifiers', 'description', 'summary', 'provides', + 'license'): + filename = docs.get('meta.' + key, '').strip() + if filename and _os.path.isfile(filename): + result.append(filename) + + cmd = dist.get_command_obj("build_py") + cmd.ensure_finalized() + #from pprint import pprint; pprint(("build_py", cmd.get_source_files())) + for item in cmd.get_source_files(): + result.append(_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + )) + + cmd = dist.get_command_obj("build_ext") + cmd.ensure_finalized() + #from pprint import pprint; pprint(("build_ext", cmd.get_source_files())) + for item in cmd.get_source_files(): + result.append(_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + )) + for ext in cmd.extensions: + if ext.depends: + result.extend([_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + ) for item in ext.depends]) + + cmd = dist.get_command_obj("build_clib") + cmd.ensure_finalized() + if cmd.libraries: + #import pprint; pprint.pprint(("build_clib", cmd.get_source_files())) + for item in cmd.get_source_files(): + result.append(_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + )) + for lib in cmd.libraries: + if lib[1].get('depends'): + result.extend([_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + ) for item in lib[1]['depends']]) + + cmd = dist.get_command_obj("build_scripts") + cmd.ensure_finalized() + #import pprint; pprint.pprint(("build_scripts", cmd.get_source_files())) + if cmd.get_source_files(): + for item in cmd.get_source_files(): + result.append(_posixpath.sep.join( + _os.path.normpath(item).split(_os.path.sep) + )) + + cmd = dist.get_command_obj("install_data") + cmd.ensure_finalized() + #from pprint import pprint; pprint(("install_data", cmd.get_inputs())) + try: + strings = str + except NameError: + strings = (str, str) + + for item in cmd.get_inputs(): + if isinstance(item, strings): + result.append(item) + else: + result.extend(item[1]) + + for item in manifest.get('dist', '').split(): + result.append(item) + if _os.path.isdir(item): + for filename in _shell.files(item): + result.append(filename) + + result = list(dict([(item, None) for item in result]).keys()) + result.sort() + return result + + +def run(config=('package.cfg',), ext=None, script_args=None, manifest_only=0): + """ Main runner """ + if ext is None: + ext = [] + + cfg = _util.SafeConfigParser() + cfg.read(config) + pkg = dict(cfg.items('package')) + python_min = pkg.get('python.min') or None + python_max = pkg.get('python.max') or None + check_python_version('python', python_min, python_max) + pypy_min = pkg.get('pypy.min') or None + pypy_max = pkg.get('pypy.max') or None + check_python_version('pypy', pypy_min, pypy_max) + jython_min = pkg.get('jython.min') or None + jython_max = pkg.get('jython.max') or None + check_python_version('jython', jython_min, jython_max) + + manifest = dict(cfg.items('manifest')) + try: + docs = dict(cfg.items('docs')) + except _config_parser.NoSectionError: + docs = {} + + summary, description = find_description(docs) + scripts = manifest.get('scripts', '').strip() or None + if scripts: + scripts = scripts.split() + modules = manifest.get('modules', '').strip() or None + if modules: + modules = modules.split() + keywords = docs.get('meta.keywords', '').strip() or None + if keywords: + keywords = keywords.split() + revision = pkg.get('version.revision', '').strip() + if revision: + revision = "-r%s" % (revision,) + + kwargs = { + 'name': pkg['name'], + 'version': "%s%s" % ( + pkg['version.number'], + ["", "-dev%s" % (revision,)][_util.humanbool( + 'version.dev', pkg.get('version.dev', 'false') + )], + ), + 'provides': find_provides(docs), + 'description': summary, + 'long_description': description, + 'classifiers': find_classifiers(docs), + 'keywords': keywords, + 'author': pkg['author.name'], + 'author_email': pkg['author.email'], + 'maintainer': pkg.get('maintainer.name'), + 'maintainer_email': pkg.get('maintainer.email'), + 'url': pkg.get('url.homepage'), + 'download_url': pkg.get('url.download'), + 'license': find_license(docs), + 'package_dir': {'': manifest.get('packages.lib', '.')}, + 'packages': find_packages(manifest), + 'py_modules': modules, + 'ext_modules': ext, + 'scripts': scripts, + 'script_args': script_args, + 'data_files': find_data(pkg['name'], docs), + 'cmdclass': { + 'build' : _commands.Build, + 'build_ext' : _commands.BuildExt, + 'install' : _commands.Install, + 'install_data': _commands.InstallData, + 'install_lib' : _commands.InstallLib, + } + } + for key in ('provides',): + if key not in _core.setup_keywords: + del kwargs[key] + + if manifest_only: + return make_manifest(manifest, config, docs, kwargs) + + # monkey-patch crappy manifest writer away. + from distutils.command import sdist + sdist.sdist.get_file_list = sdist.sdist.read_manifest + + return _core.setup(**kwargs) diff --git a/py3/shell.py b/py3/shell.py new file mode 100644 index 0000000..a40c85c --- /dev/null +++ b/py3/shell.py @@ -0,0 +1,351 @@ +# -*- coding: ascii -*- +# +# Copyright 2007 - 2013 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================= + Shell utilities +================= + +Shell utilities. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import errno as _errno +import fnmatch as _fnmatch +import os as _os +import shutil as _shutil +import subprocess as _subprocess +import sys as _sys +import tempfile as _tempfile + +cwd = _os.path.dirname(_os.path.abspath(_sys.argv[0])) + +class ExitError(RuntimeError): + """ Exit error """ + def __init__(self, code): + RuntimeError.__init__(self, code) + self.code = code + self.signal = None + + +class SignalError(ExitError): + """ Signal error """ + def __init__(self, code, signal): + ExitError.__init__(self, code) + import signal as _signal + self.signal = signal + for key, val in vars(_signal).items(): + if key.startswith('SIG') and not key.startswith('SIG_'): + if val == signal: + self.signalstr = key[3:] + break + else: + self.signalstr = '%04d' % signal + + +def native(path): + """ Convert slash path to native """ + path = _os.path.sep.join(path.split('/')) + return _os.path.normpath(_os.path.join(cwd, path)) + + +def cp(src, dest): + """ Copy src to dest """ + _shutil.copy2(native(src), native(dest)) + + +def cp_r(src, dest): + """ Copy -r src to dest """ + _shutil.copytree(native(src), native(dest)) + + +def rm(dest): + """ Remove a file """ + try: + _os.unlink(native(dest)) + except OSError as e: + if _errno.ENOENT != e.errno: + raise + +def rm_rf(dest): + """ Remove a tree """ + dest = native(dest) + if _os.path.exists(dest): + for path in files(dest, '*'): + _os.chmod(native(path), 0o644) + _shutil.rmtree(dest) + + +mkstemp = _tempfile.mkstemp + + +def _pipespawn(argv, env): + """ Pipe spawn """ + # pylint: disable = R0912 + import pickle as _pickle + fd, name = mkstemp('.py') + try: + _os.write(fd, ((r""" +import os +import pickle +import subprocess +import sys + +argv = pickle.loads(%(argv)s) +env = pickle.loads(%(env)s) +if 'X_JYTHON_WA_PATH' in env: + env['PATH'] = env['X_JYTHON_WA_PATH'] + +p = subprocess.Popen(argv, env=env) +result = p.wait() +if result < 0: + print("\n%%d 1" %% (-result)) + sys.exit(2) +if result == 0: + sys.exit(0) +print("\n%%d" %% (result & 7,)) +sys.exit(3) + """.strip() + "\n") % { + 'argv': repr(_pickle.dumps(argv)), + 'env': repr(_pickle.dumps(dict(env))), + }).encode('utf-8')) + fd, _ = None, _os.close(fd) + if _sys.platform == 'win32': + argv = [] + for arg in [_sys.executable, name]: + if ' ' in arg or arg.startswith('"'): + arg = '"%s"' % arg.replace('"', '\\"') + argv.append(arg) + argv = ' '.join(argv) + shell = True + close_fds = False + else: + argv = [_sys.executable, name] + shell = False + close_fds = True + + res = 0 + if 'X_JYTHON_WA_PATH' in env: + env['PATH'] = env['X_JYTHON_WA_PATH'] + + proc = _subprocess.Popen(argv, + shell=shell, + stdin=_subprocess.PIPE, + stdout=_subprocess.PIPE, + close_fds=close_fds, + env=env, + ) + try: + proc.stdin.close() + result = proc.stdout.read() + finally: + res = proc.wait() + if res != 0: + if res == 2: + signal, code = list(map(int, result.splitlines()[-1].split())) + raise SignalError(code, signal) + elif res == 3: + code = int(result.splitlines()[-1].strip()) + raise ExitError(code) + raise ExitError(res) + + return result.decode('latin-1') + finally: + try: + if fd is not None: + _os.close(fd) + finally: + _os.unlink(name) + + +def _filepipespawn(infile, outfile, argv, env): + """ File Pipe spawn """ + import pickle as _pickle + fd, name = mkstemp('.py') + try: + _os.write(fd, ((""" +import os +import pickle +import sys + +infile = pickle.loads(%(infile)s) +outfile = pickle.loads(%(outfile)s) +argv = pickle.loads(%(argv)s) +env = pickle.loads(%(env)s) + +if infile is not None: + infile = open(infile, 'rb') + os.dup2(infile.fileno(), 0) + infile.close() +if outfile is not None: + outfile = open(outfile, 'wb') + os.dup2(outfile.fileno(), 1) + outfile.close() + +pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env) +result = os.waitpid(pid, 0)[1] +sys.exit(result & 7) + """.strip() + "\n") % { + 'infile': repr(_pickle.dumps(_os.path.abspath(infile))), + 'outfile': repr(_pickle.dumps(_os.path.abspath(outfile))), + 'argv': repr(_pickle.dumps(argv)), + 'env': repr(_pickle.dumps(env)), + })) + fd, _ = None, _os.close(fd) + if _sys.platform == 'win32': + argv = [] + for arg in [_sys.executable, name]: + if ' ' in arg or arg.startswith('"'): + arg = '"%s"' % arg.replace('"', '\\"') + argv.append(arg) + argv = ' '.join(argv) + close_fds = False + shell = True + else: + argv = [_sys.executable, name] + close_fds = True + shell = False + + p = _subprocess.Popen( + argv, env=env, shell=shell, close_fds=close_fds + ) + return p.wait() + finally: + try: + if fd is not None: + _os.close(fd) + finally: + _os.unlink(name) + + +def spawn(*argv, **kwargs): + """ Spawn a process """ + if _sys.platform == 'win32': + newargv = [] + for arg in argv: + if not arg or ' ' in arg or arg.startswith('"'): + arg = '"%s"' % arg.replace('"', '\\"') + newargv.append(arg) + argv = newargv + close_fds = False + shell = True + else: + close_fds = True + shell = False + + env = kwargs.get('env') + if env is None: + env = dict(_os.environ) + if 'X_JYTHON_WA_PATH' in env: + env['PATH'] = env['X_JYTHON_WA_PATH'] + + echo = kwargs.get('echo') + if echo: + print(' '.join(argv)) + filepipe = kwargs.get('filepipe') + if filepipe: + return _filepipespawn( + kwargs.get('stdin'), kwargs.get('stdout'), argv, env + ) + pipe = kwargs.get('stdout') + if pipe: + return _pipespawn(argv, env) + + p = _subprocess.Popen(argv, env=env, shell=shell, close_fds=close_fds) + return p.wait() + + +walk = _os.walk + + +def files(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')): + """ Determine a filelist """ + for dirpath, dirnames, filenames in walk(native(base)): + for item in prune: + if item in dirnames: + dirnames.remove(item) + + filenames.sort() + for name in _fnmatch.filter(filenames, wildcard): + dest = _os.path.join(dirpath, name) + if dest.startswith(cwd): + dest = dest.replace(cwd, '', 1) + aslist = [] + head, tail = _os.path.split(dest) + while tail: + aslist.append(tail) + head, tail = _os.path.split(head) + aslist.reverse() + dest = '/'.join(aslist) + yield dest + + if not recursive: + break + dirnames.sort() + + +def dirs(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')): + """ Determine a filelist """ + for dirpath, dirnames, filenames in walk(native(base)): + for item in prune: + if item in dirnames: + dirnames.remove(item) + + dirnames.sort() + for name in _fnmatch.filter(dirnames, wildcard): + dest = _os.path.join(dirpath, name) + if dest.startswith(cwd): + dest = dest.replace(cwd, '', 1) + aslist = [] + head, tail = _os.path.split(dest) + while tail: + aslist.append(tail) + head, tail = _os.path.split(head) + aslist.reverse() + dest = '/'.join(aslist) + yield dest + + if not recursive: + break + + +def frompath(executable): + """ Find executable in PATH """ + # Based on distutils.spawn.find_executable. + path = _os.environ.get('PATH', '') + paths = [ + _os.path.expanduser(item) + for item in path.split(_os.pathsep) + ] + ext = _os.path.splitext(executable)[1] + exts = [''] + if _sys.platform == 'win32' or _os.name == 'os2': + eext = ['.exe', '.bat', '.py'] + if ext not in eext: + exts.extend(eext) + + for ext in exts: + if not _os.path.isfile(executable + ext): + for path in paths: + fname = _os.path.join(path, executable + ext) + if _os.path.isfile(fname): + # the file exists, we have a shot at spawn working + return fname + else: + return executable + ext + + return None diff --git a/py3/term/__init__.py b/py3/term/__init__.py new file mode 100644 index 0000000..5459454 --- /dev/null +++ b/py3/term/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: ascii -*- +# +# Copyright 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +===================== + Package _setup.term +===================== + +Terminal tools, not distributed. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +# pylint: disable = W0611 +from _setup.term._term import terminfo, write, green, red, yellow, announce diff --git a/py3/term/_term.py b/py3/term/_term.py new file mode 100644 index 0000000..b94f58e --- /dev/null +++ b/py3/term/_term.py @@ -0,0 +1,116 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================= + Terminal writer +================= +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +import sys as _sys + + +class _INFO(dict): + """ Terminal info dict """ + + def __init__(self): + """ Initialization """ + dict.__init__(self, { + 'NORMAL': '', + 'BOLD': '', + 'ERASE': '\n', + 'RED': '', + 'YELLOW': '', + 'GREEN': '', + }) + try: + import curses as _curses + except ImportError: + # fixup if a submodule of curses failed. + if 'curses' in _sys.modules: + del _sys.modules['curses'] + else: + try: + _curses.setupterm() + except (TypeError, _curses.error): + pass + else: + def make_color(color): + """ Make color control string """ + seq = _curses.tigetstr('setaf').decode('ascii') + if seq is not None: + # XXX may fail - need better logic + seq = seq.replace("%p1", "") % color + return seq + + self['NORMAL'] = _curses.tigetstr('sgr0').decode('ascii') + self['BOLD'] = _curses.tigetstr('bold').decode('ascii') + + erase = _curses.tigetstr('el1').decode('ascii') + if erase is not None: + self['ERASE'] = erase + \ + _curses.tigetstr('cr').decode('ascii') + + self['RED'] = make_color(_curses.COLOR_RED) + self['YELLOW'] = make_color(_curses.COLOR_YELLOW) + self['GREEN'] = make_color(_curses.COLOR_GREEN) + + def __getitem__(self, key): + """ Deliver always """ + dict.get(self, key) or "" + + +def terminfo(): + """ Get info singleton """ + # pylint: disable = E1101, W0612 + if terminfo.info is None: + terminfo.info = _INFO() + return terminfo.info +terminfo.info = None + + +def write(fmt, **kwargs): + """ Write stuff on the terminal """ + parm = dict(terminfo()) + parm.update(kwargs) + _sys.stdout.write(fmt % parm) + _sys.stdout.flush() + + +def green(bmt, **kwargs): + """ Write something in green on screen """ + announce("%%(GREEN)s%s%%(NORMAL)s" % bmt, **kwargs) + + +def red(bmt, **kwargs): + """ Write something in red on the screen """ + announce("%%(BOLD)s%%(RED)s%s%%(NORMAL)s" % bmt, **kwargs) + + +def yellow(fmt, **kwargs): + """ Write something in yellow on the screen """ + announce("%%(BOLD)s%%(YELLOW)s%s%%(NORMAL)s" % fmt, **kwargs) + + +def announce(fmt, **kwargs): + """ Announce something """ + write(fmt, **kwargs) + _sys.stdout.write("\n") + _sys.stdout.flush() + + diff --git a/py3/util.py b/py3/util.py new file mode 100644 index 0000000..a07daa8 --- /dev/null +++ b/py3/util.py @@ -0,0 +1,63 @@ +# -*- coding: ascii -*- +# +# Copyright 2007, 2008, 2009, 2010, 2011 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +================= + Setup utilities +================= + +Setup utilities. +""" +__author__ = "Andr\xe9 Malo" +__docformat__ = "restructuredtext en" + +from distutils import util as _util +try: + from configparser import SafeConfigParser +except ImportError: + import configparser as _config_parser + class SafeConfigParser(_config_parser.ConfigParser): + """ Safe config parser """ + def _interpolate(self, section, option, rawval, vars): + return rawval + + def items(self, section): + return [(key, self.get(section, key)) + for key in self.options(section) + ] + + +def humanbool(name, value): + """ + Determine human boolean value + + :Parameters: + `name` : ``str`` + The config key (used for error message) + + `value` : ``str`` + The config value + + :Return: The boolean value + :Rtype: ``bool`` + + :Exceptions: + - `ValueError` : The value could not be recognized + """ + try: + return _util.strtobool(str(value).strip().lower() or 'no') + except ValueError: + raise ValueError("Unrecognized config value: %s = %s" % (name, value)) diff --git a/setup.py.example b/setup.py.example new file mode 100644 index 0000000..7f1404b --- /dev/null +++ b/setup.py.example @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: ascii -*- +# +# Copyright 2005 - 2014 +# Andr\xe9 Malo or his licensors, as applicable +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from _setup import run + + +def setup(args=None, _manifest=0): + """ Main setup function """ + # Use this for extensions: + #from _setup.ext import Extension + + return run( + script_args=args, + manifest_only=_manifest, + ) + + +def manifest(): + """ Create List of packaged files """ + return setup((), _manifest=1) + + +if __name__ == '__main__': + setup() From 1d9bee1d774c316bc5a2b7efbeef6949d788f507 Mon Sep 17 00:00:00 2001 From: ndparker Date: Sun, 1 Jun 2014 20:35:51 +0200 Subject: [PATCH 2/6] rename package.cfg --- package.cfg => package.cfg.example | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename package.cfg => package.cfg.example (100%) diff --git a/package.cfg b/package.cfg.example similarity index 100% rename from package.cfg rename to package.cfg.example From 0f45d6642666d6f1d4aa0d23cc46022580940b44 Mon Sep 17 00:00:00 2001 From: ndparker Date: Sun, 1 Jun 2014 20:40:44 +0200 Subject: [PATCH 3/6] fix description --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index efdc735..f7f7547 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ You need python 2 (>= 2.2) or python 3. INSTALLATION ============ -Copy the _setup directory into your project. +Copy the directory into your project (as /_setup) DOCUMENTATION From c4035db756f6a2c4262ab2735ced9120afdd71b0 Mon Sep 17 00:00:00 2001 From: ndparker Date: Sun, 1 Jun 2014 20:51:39 +0200 Subject: [PATCH 4/6] change wording --- setup.py.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py.example b/setup.py.example index 7f1404b..0ba5a55 100644 --- a/setup.py.example +++ b/setup.py.example @@ -21,7 +21,7 @@ from _setup import run def setup(args=None, _manifest=0): """ Main setup function """ - # Use this for extensions: + # Use may want to use this for extensions: #from _setup.ext import Extension return run( From f54f9c6512aca0a1cb1be504af1cabf6d39d3290 Mon Sep 17 00:00:00 2001 From: ndparker Date: Sun, 5 Oct 2014 16:45:21 +0200 Subject: [PATCH 5/6] adjust for newer pylints --- py2/dev/_pylint.py | 43 ++++++++++++++++++++++++++++--------------- py3/dev/_pylint.py | 43 ++++++++++++++++++++++++++++--------------- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/py2/dev/_pylint.py b/py2/dev/_pylint.py index 4c3c085..7a38a82 100644 --- a/py2/dev/_pylint.py +++ b/py2/dev/_pylint.py @@ -1,6 +1,6 @@ # -*- coding: ascii -*- # -# Copyright 2006, 2007, 2008, 2009, 2010, 2011 +# Copyright 2006 - 2014 # Andr\xe9 Malo or his licensors, as applicable # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,9 +34,11 @@ from _setup import shell as _shell class NotFinished(Exception): """ Exception used for message passing in the stream filter """ + class NotParseable(Exception): """ Exception used for message passing in the stream filter """ + class SpecialMessage(Exception): """ Exception used for message passing in the stream filter """ @@ -121,9 +123,11 @@ class FilterStream(object): """ Print literal """ suppress = ( line.startswith('Unable to get imported names for ') or - line.startswith("Exception exceptions.RuntimeError: 'generator " + line.startswith( + "Exception exceptions.RuntimeError: 'generator " "ignored GeneratorExit' in = (1, 0, 0): + reporter = text.TextReporter(stream) + argv.extend([ + '--msg-template', + '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}' + ]) else: - reporter = text.ParseableTextReporter(stream) - lint.REPORTER_OPT_MAP['parseable'] = lambda: reporter + argv.extend([ + '--output-format', 'parseable', + '--include-ids', 'yes' + ]) + if __pkginfo__.numversion < (0, 13): + lint.REPORTER_OPT_MAP['parseable'] = \ + lambda: text.TextReporter2(stream) + reporter = text.TextReporter2(stream) + else: + reporter = text.ParseableTextReporter(stream) + lint.REPORTER_OPT_MAP['parseable'] = lambda: reporter for path in args: try: try: lint.Run(argv + [path], reporter=reporter) except SystemExit: - pass # don't accept the exit. strange errors happen... + pass # don't accept the exit. strange errors happen... if stream.written: print diff --git a/py3/dev/_pylint.py b/py3/dev/_pylint.py index 5592a7e..b0f3612 100644 --- a/py3/dev/_pylint.py +++ b/py3/dev/_pylint.py @@ -1,6 +1,6 @@ # -*- coding: ascii -*- # -# Copyright 2006, 2007, 2008, 2009, 2010, 2011 +# Copyright 2006 - 2014 # Andr\xe9 Malo or his licensors, as applicable # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,9 +34,11 @@ from _setup import shell as _shell class NotFinished(Exception): """ Exception used for message passing in the stream filter """ + class NotParseable(Exception): """ Exception used for message passing in the stream filter """ + class SpecialMessage(Exception): """ Exception used for message passing in the stream filter """ @@ -121,9 +123,11 @@ class FilterStream(object): """ Print literal """ suppress = ( line.startswith('Unable to get imported names for ') or - line.startswith("Exception exceptions.RuntimeError: 'generator " + line.startswith( + "Exception exceptions.RuntimeError: 'generator " "ignored GeneratorExit' in = (1, 0, 0): + reporter = text.TextReporter(stream) + argv.extend([ + '--msg-template', + '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}' + ]) else: - reporter = text.ParseableTextReporter(stream) - lint.REPORTER_OPT_MAP['parseable'] = lambda: reporter + argv.extend([ + '--output-format', 'parseable', + '--include-ids', 'yes' + ]) + if __pkginfo__.numversion < (0, 13): + lint.REPORTER_OPT_MAP['parseable'] = \ + lambda: text.TextReporter2(stream) + reporter = text.TextReporter2(stream) + else: + reporter = text.ParseableTextReporter(stream) + lint.REPORTER_OPT_MAP['parseable'] = lambda: reporter for path in args: try: try: lint.Run(argv + [path], reporter=reporter) except SystemExit: - pass # don't accept the exit. strange errors happen... + pass # don't accept the exit. strange errors happen... if stream.written: print() From 3f9612409b8e4d9ba23cdc5488a082b377d0be70 Mon Sep 17 00:00:00 2001 From: ndparker Date: Sat, 25 Oct 2014 17:20:01 +0200 Subject: [PATCH 6/6] remove references to pylint.conf, replace them with pylintrc --- py2/dev/_pylint.py | 2 +- py3/dev/_pylint.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/py2/dev/_pylint.py b/py2/dev/_pylint.py index 7a38a82..f5787c4 100644 --- a/py2/dev/_pylint.py +++ b/py2/dev/_pylint.py @@ -220,7 +220,7 @@ def run(config, *args): return 2 if config is None: - config = _shell.native('pylint.conf') + config = _shell.native('pylintrc') argv = [ '--rcfile', config, '--reports', 'no', diff --git a/py3/dev/_pylint.py b/py3/dev/_pylint.py index b0f3612..98a311d 100644 --- a/py3/dev/_pylint.py +++ b/py3/dev/_pylint.py @@ -220,7 +220,7 @@ def run(config, *args): return 2 if config is None: - config = _shell.native('pylint.conf') + config = _shell.native('pylintrc') argv = [ '--rcfile', config, '--reports', 'no',