summaryrefslogtreecommitdiff
path: root/passlib/_setup
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-08-17 11:51:08 -0400
committerEli Collins <elic@assurancetechnologies.com>2011-08-17 11:51:08 -0400
commitf3a121563512d51133cbacf50c96b1a3dccde31d (patch)
tree081e1a83805003ebfb8b433f1513c413040aa944 /passlib/_setup
parentd2ffaa3eecd9866918a20547d02c3a1b45dc8fdb (diff)
downloadpasslib-f3a121563512d51133cbacf50c96b1a3dccde31d.tar.gz
setup script enhancements
* added code to make builtin snapshots & releases with correct version # easier, no longer dependant on egg_info's tag_date (which didn't make PEP compatible version strings anyways). * moved passlib.setup to passlib._setup - not really publically useful anyways, and name was causing nose/unitest to get confused * added tests/*.cfg to setup & manifest
Diffstat (limited to 'passlib/_setup')
-rw-r--r--passlib/_setup/__init__.py5
-rw-r--r--passlib/_setup/cond2to3.py152
-rw-r--r--passlib/_setup/stamp.py57
3 files changed, 214 insertions, 0 deletions
diff --git a/passlib/_setup/__init__.py b/passlib/_setup/__init__.py
new file mode 100644
index 0000000..814c93a
--- /dev/null
+++ b/passlib/_setup/__init__.py
@@ -0,0 +1,5 @@
+"""passlib.setup - helpers used by passlib's setup.py script
+
+note that unlike the rest of passlib, the code in this package must
+work *unaltered* under both python 2 & 3
+"""
diff --git a/passlib/_setup/cond2to3.py b/passlib/_setup/cond2to3.py
new file mode 100644
index 0000000..12cb0a8
--- /dev/null
+++ b/passlib/_setup/cond2to3.py
@@ -0,0 +1,152 @@
+"""passlib.setup.cond2to3 - moneypatches 2to3 to provide conditional macros, ala SQLAlchemy"""
+#=========================================================
+#imports
+#=========================================================
+#core
+from lib2to3.refactor import RefactoringTool
+import re
+#site
+#local
+__all__ = [
+ "patch2to3",
+]
+
+#=========================================================
+#macro preprocessor
+#=========================================================
+py3k_start_re = re.compile(r"^(\s*)# Py3K #", re.I)
+py3k_stop_re = re.compile(r"^(\s*)# end Py3K #", re.I)
+
+py2k_start_re = re.compile(r"^(\s*)# Py2K #", re.I)
+py2k_stop_re = re.compile(r"^(\s*)# end Py2K #", re.I)
+
+bare_comment_re = re.compile(r"^(\s*)#(.*)")
+bare_re = re.compile(r"^(\s*)(.*)")
+
+def preprocess(data, name):
+ #TODO: add flag so this can also function in reverse, for 3to2
+ changed = False
+
+ lines = data.split("\n")
+ state = 0
+ #0: parsing normally, looking for start-p3k or start-py2k
+ #1: in Py3K block - removing comment chars until end-py3k
+ #2: in Py2K block - adding comment chars until end-py2k
+ idx = 0
+ indent = ''
+ while idx < len(lines):
+ line = lines[idx]
+
+ #hack to detect ''"abc" strings - using this as py25-compat way to indicate bytes.
+ #should really turn into a proper fixer.
+ #also, this check is really weak, and might fail in some cases
+ if '\'\'".*"' in line:
+ line = lines[idx] = line.replace("''", "b")
+ changed = True
+
+ #check for py3k start marker
+ m = py3k_start_re.match(line)
+ if m:
+ if state in (0,2):
+ ident = m.group(1)
+ state = 1
+ idx += 1
+ continue
+ #states 1 this is an error...
+ raise SyntaxError("unexpected py3k-start marker on line %d of %r: %r" % (idx, name, line))
+
+ #check for py3k stop marker
+ if py3k_stop_re.match(line):
+ if state == 1:
+ state = 0
+ idx += 1
+ continue
+ #states 0,2 this is an error...
+ raise SyntaxError("unexpected py3k-stop marker on line %d of %r: %r" % (idx, name, line))
+
+ #check for py2k start marker
+ m = py2k_start_re.match(line)
+ if m:
+ if state in (0,1):
+ ident = m.group(1)
+ state = 2
+ idx += 1
+ continue
+ #states 2 this is an error...
+ raise SyntaxError("unexpected py2k-start marker on line %d of %r: %r" % (idx, name, line))
+
+ #check for py2k end marker
+ if py2k_stop_re.match(line):
+ if state == 2:
+ state = 0
+ idx += 1
+ continue
+ #states 0,1 this is an error...
+ raise SyntaxError("unexpected py2k-stop marker on line %d of %r: %r" % (idx, name, line))
+
+ #state 0 - leave non-marker lines alone
+ if state == 0:
+ idx += 1
+ continue
+
+ #state 1 - uncomment comment lines, throw error on bare lines
+ if state == 1:
+ m = bare_comment_re.match(line)
+ if not m:
+ raise SyntaxError("unexpected non-comment in py3k block on line %d of %r: %r" % (idx,name, line))
+ pad, content = m.group(1,2)
+ lines[idx] = pad + content
+ changed = True
+ idx += 1
+ continue
+
+ #state 2 - comment out all lines
+ if state == 2:
+ m = bare_re.match(line)
+ if not m:
+ raise RuntimeError("unexpected failure to parse line %d of %r: %r" % (idx, name, line))
+ pad, content = m.group(1,2)
+ if pad.startswith(ident): #try to put comments on same level
+ content = pad[len(ident):] + content
+ pad = ident
+ lines[idx] = "%s#%s" % (pad,content)
+ changed = True
+ idx += 1
+ continue
+
+ #should never get here
+ raise AssertionError("invalid state: %r" % (state,))
+
+ if changed:
+ return "\n".join(lines)
+ else:
+ return data
+
+orig_rs = RefactoringTool.refactor_string
+
+def refactor_string(self, data, name):
+ "replacement for RefactoringTool.refactor_string which honors conditional includes"
+ newdata = preprocess(data, name)
+ tree = orig_rs(self, newdata, name)
+ if tree and newdata != data:
+ tree.was_changed = True
+ return tree
+
+#=========================================================
+#main
+#=========================================================
+
+def patch2to3():
+ "frontend to patch preprocessor into lib2to3"
+ RefactoringTool.refactor_string = refactor_string
+
+#helper for development purposes - runs 2to3 w/ patch
+if __name__ == "__main__":
+ import sys
+ from lib2to3.main import main
+ patch2to3()
+ sys.exit(main("lib2to3.fixes"))
+
+#=========================================================
+#eof
+#=========================================================
diff --git a/passlib/_setup/stamp.py b/passlib/_setup/stamp.py
new file mode 100644
index 0000000..14abd59
--- /dev/null
+++ b/passlib/_setup/stamp.py
@@ -0,0 +1,57 @@
+"update version string during build"
+#=========================================================
+# imports
+#=========================================================
+from __future__ import with_statement
+#core
+import os
+import re
+import time
+from distutils.dist import Distribution
+#pkg
+#local
+__all__ = [
+ "stamp_source",
+ "stamp_distutils_output",
+]
+#=========================================================
+# helpers
+#=========================================================
+def get_command_class(opts, name):
+ return opts['cmdclass'].get(name) or Distribution().get_command_class(name)
+
+def stamp_source(base_dir, version, dry_run=False):
+ "update version string in passlib dist"
+ path = os.path.join(base_dir, "passlib", "__init__.py")
+ with open(path) as fh:
+ input = fh.read()
+ output = re.sub('(?m)^__version__\s*=.*$',
+ '__version__ = ' + repr(version),
+ input)
+ assert output != input, "failed to match"
+ if not dry_run:
+ os.unlink(path) # sdist likes to use hardlinks
+ with open(path, "w") as fh:
+ fh.write(output)
+
+def stamp_distutils_output(opts, version):
+
+ # subclass buildpy to update version string in source
+ _build_py = get_command_class(opts, "build_py")
+ class build_py(_build_py):
+ def build_packages(self):
+ _build_py.build_packages(self)
+ stamp_source(self.build_lib, version, self.dry_run)
+ opts['cmdclass']['build_py'] = build_py
+
+ # subclass sdist to do same thing
+ _sdist = get_command_class(opts, "sdist")
+ class sdist(_sdist):
+ def make_release_tree(self, base_dir, files):
+ _sdist.make_release_tree(self, base_dir, files)
+ stamp_source(base_dir, version, self.dry_run)
+ opts['cmdclass']['sdist'] = sdist
+
+#=========================================================
+# eof
+#=========================================================