summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS2
-rw-r--r--COPYING339
-rw-r--r--panfry/__init__.py0
-rw-r--r--panfry/_version.py192
-rwxr-xr-xpanfry/cli.py144
-rwxr-xr-xpanfry/document.py159
-rwxr-xr-xpanfry/main.py46
-rwxr-xr-xpanfry/page.py59
-rwxr-xr-xpanfry/templater.py44
-rwxr-xr-xpanfry/util.py365
-rwxr-xr-xsetup.py121
-rw-r--r--versioneer.py649
12 files changed, 2120 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..479a3bb
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+PanFry is written and maintained by Scott Bahling <sbahling@mudgum.net>
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/panfry/__init__.py b/panfry/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/panfry/__init__.py
diff --git a/panfry/_version.py b/panfry/_version.py
new file mode 100644
index 0000000..84381f5
--- /dev/null
+++ b/panfry/_version.py
@@ -0,0 +1,192 @@
+
+IN_LONG_VERSION_PY = True
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by githubs download-from-tag
+# feature). Distribution tarballs (build by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
+
+# This file is released into the public domain. Generated by
+# versioneer-0.7 (https://github.com/warner/python-versioneer)
+
+# these strings will be replaced by git during git-archive
+git_refnames = "$Format:%d$"
+git_full = "$Format:%H$"
+
+
+import subprocess
+
+def run_command(args, cwd=None, verbose=False):
+ try:
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
+ except EnvironmentError, e:
+ if verbose:
+ print "unable to run %s" % args[0]
+ print e
+ return None
+ stdout = p.communicate()[0].strip()
+ if p.returncode != 0:
+ if verbose:
+ print "unable to run %s (error)" % args[0]
+ return None
+ return stdout
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+ # the code embedded in _version.py can just fetch the value of these
+ # variables. When used from setup.py, we don't want to import
+ # _version.py, so we do it with a regexp instead. This function is not
+ # used from _version.py.
+ variables = {}
+ try:
+ for line in open(versionfile_source,"r").readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["full"] = mo.group(1)
+ except EnvironmentError:
+ pass
+ return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+ refnames = variables["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print "variables are unexpanded, not using"
+ return {} # unexpanded, so not in an unpacked git-archive tarball
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ for ref in list(refs):
+ if not re.search(r'\d', ref):
+ if verbose:
+ print "discarding '%s', no digits" % ref
+ refs.discard(ref)
+ # Assume all version tags have a digit. git's %d expansion
+ # behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us
+ # distinguish between branches and tags. By ignoring refnames
+ # without digits, we filter out many common branch names like
+ # "release" and "stabilization", as well as "HEAD" and "master".
+ if verbose:
+ print "remaining refs:", ",".join(sorted(refs))
+ for ref in sorted(refs):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print "picking %s" % r
+ return { "version": r,
+ "full": variables["full"].strip() }
+ # no suitable tags, so we use the full revision id
+ if verbose:
+ print "no suitable tags, using full revision id"
+ return { "version": variables["full"].strip(),
+ "full": variables["full"].strip() }
+
+def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
+ # this runs 'git' from the root of the source tree. That either means
+ # someone ran a setup.py command (and this code is in versioneer.py, so
+ # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
+ # the source tree), or someone ran a project-specific entry point (and
+ # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
+ # containing directory is somewhere deeper in the source tree). This only
+ # gets called if the git-archive 'subst' variables were *not* expanded,
+ # and _version.py hasn't already been rewritten with a short version
+ # string, meaning we're inside a checked out source tree.
+
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # some py2exe/bbfreeze/non-CPython implementations don't do __file__
+ return {} # not always correct
+
+ # versionfile_source is the relative path from the top of the source tree
+ # (where the .git directory might live) to this file. Invert this to find
+ # the root from __file__.
+ root = here
+ if IN_LONG_VERSION_PY:
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ root = os.path.dirname(here)
+ if not os.path.exists(os.path.join(root, ".git")):
+ if verbose:
+ print "no .git in", root
+ return {}
+
+ GIT = "git"
+ if sys.platform == "win32":
+ GIT = "git.cmd"
+ stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
+ cwd=root)
+ if stdout is None:
+ return {}
+ if not stdout.startswith(tag_prefix):
+ if verbose:
+ print "tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)
+ return {}
+ tag = stdout[len(tag_prefix):]
+ stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+ if stdout is None:
+ return {}
+ full = stdout.strip()
+ if tag.endswith("-dirty"):
+ full += "-dirty"
+ return {"version": tag, "full": full}
+
+
+def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
+ if IN_LONG_VERSION_PY:
+ # We're running from _version.py. If it's from a source tree
+ # (execute-in-place), we can work upwards to find the root of the
+ # tree, and then check the parent directory for a version string. If
+ # it's in an installed application, there's no hope.
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # py2exe/bbfreeze/non-CPython don't have __file__
+ return {} # without __file__, we have no hope
+ # versionfile_source is the relative path from the top of the source
+ # tree to _version.py. Invert this to find the root from __file__.
+ root = here
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ # we're running from versioneer.py, which means we're running from
+ # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
+ here = os.path.abspath(sys.argv[0])
+ root = os.path.dirname(here)
+
+ # Source tarballs conventionally unpack into a directory that includes
+ # both the project name and a version string.
+ dirname = os.path.basename(root)
+ if not dirname.startswith(parentdir_prefix):
+ if verbose:
+ print "guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % (root, dirname, parentdir_prefix)
+ return None
+ return {"version": dirname[len(parentdir_prefix):], "full": ""}
+
+tag_prefix = "panfry-"
+parentdir_prefix = "panfry-"
+versionfile_source = "panfry/_version.py"
+
+def get_versions(default={"version": "0.0.1", "full": ""}, verbose=False):
+ variables = { "refnames": git_refnames, "full": git_full }
+ ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
+ if not ver:
+ ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
+ if not ver:
+ ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
+ verbose)
+ if not ver:
+ ver = default
+ return ver
+
diff --git a/panfry/cli.py b/panfry/cli.py
new file mode 100755
index 0000000..6b4a64d
--- /dev/null
+++ b/panfry/cli.py
@@ -0,0 +1,144 @@
+#
+# Copyright (c) 2013 Scott Bahling, <sbahling@mudgum.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program (see the file COPYING); if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+#
+################################################################
+#
+# cli.py
+# command line argument handling
+
+import yaml
+import argparse
+import copy
+
+__version__ = '0.0.1'
+
+#################################################################
+#
+# The argparse setup is mapped out
+# in a yaml based config called cli_config. The init_argparse
+# methd uses the cli_config to setup the main and sub command
+# parsers.
+#
+#################################################################
+
+#################################################################
+# Argument Parser configuration (YAML)
+#################################################################
+
+cli_config = """
+Args:
+ debug_enabled:
+ flags: [-d, --debug]
+ action: store_true
+ default: False
+ help: 'Turn on verbose messages.'
+
+ run_quiet:
+ flags: [-q, --quiet]
+ action: store_true
+ default: False
+ help: 'Turn off all (debug, warn, info) messages.'
+
+ logfile:
+ flags: [-L, --logfile]
+ default: ''
+ help: 'File to write logs to. Default ./pldptools.log'
+
+ pub_path:
+ flags: [-p, --pubdir]
+ default: './pub'
+ help: 'Directory to place generated documents'
+
+ src_path:
+ flags: [-s, --srcdir]
+ default: '.'
+ help: 'Directory where document source is located'
+
+ templates_path:
+ flags: [-T, --templates]
+ default: ''
+ help: 'Directory where document templates are located'
+
+ css:
+ flags: [-C, --css]
+ default: 'css/style.css'
+ help: |
+ css file for html pages.
+ Includes full path relative to html directory.
+
+
+Parser:
+ help:
+ args:
+ - debug_enabled
+ - run_quiet
+ - logfile
+
+Subparsers:
+ gen:
+ help: generate document from source
+ args:
+ - src_path
+ - pub_path
+ - templates_path
+ - css
+"""
+
+config = yaml.load(cli_config)
+
+parser_config = config.get('Parser', {})
+args = config.get('Args', {})
+subs = config.get('Subparsers', {})
+
+
+def _add_arg(parser, argtype):
+ opts = copy.copy(args.get(argtype))
+ if opts:
+ opts.setdefault('dest', argtype)
+ flags = opts.pop('flags', [])
+ parser.add_argument(*flags, **opts)
+ else:
+ print "Unknown argument type: %s" % argtype
+
+
+def init_argparser():
+
+ description = parser_config.get('description', '')
+ formatter_class = argparse.RawTextHelpFormatter
+ parser = argparse.ArgumentParser(description=description,
+ formatter_class=formatter_class,
+ )
+ subparsers = parser.add_subparsers()
+
+ version = 'Panfry version: %s' % __version__
+ parser.add_argument('--version', action='version', version=version)
+
+ # setup main parser
+ for item in config['Parser'].get('args', []):
+ _add_arg(parser, item)
+
+ # setup sub-command parsers
+ for sub, conf in subs.items():
+ subparser = subparsers.add_parser(sub,
+ help=conf.get('help', ''),
+ formatter_class=formatter_class,
+ )
+ subparser.set_defaults(cmd=sub)
+ for item in conf.get('args', []):
+ _add_arg(subparser, item)
+
+ return parser
diff --git a/panfry/document.py b/panfry/document.py
new file mode 100755
index 0000000..42ef959
--- /dev/null
+++ b/panfry/document.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
+import re
+import pandoc
+from tempfile import NamedTemporaryFile
+from panfry.util import *
+
+
+class Document:
+
+ def __init__(self, path):
+ self.src_path = path
+ self.meta = self.get_meta(path)
+ self.pages = self.get_pages(path)
+
+ css_file = 'css/style.css'
+ workdir = 'stdocs-work'
+
+ def get_pages(self, path):
+ pages = []
+ filelist = get_lines(self.meta['TOC'])
+ for filename in filelist:
+ source = read_file(os.path.join(path, filename))
+ if source:
+ pages.append(Page(filename, source))
+ else:
+ print("WARNING: Page: %s not found or is empty." % filename)
+
+ return pages
+
+ def get_meta(self, path):
+ metafiles = ['TOC', 'HEADER']
+ meta = {}
+ for filename in metafiles:
+ source = read_file(os.path.join(path, filename))
+ if source:
+ meta[filename] = source
+ else:
+ print("!E: %s not found or is empty. Aborting..." % filename)
+ exit(1)
+
+ return meta
+
+ def set_templater(self, templater):
+ self.templater = templater
+
+ def next_page(self, page):
+ try:
+ idx = self.pages.index(page)
+ except:
+ return ''
+ if idx >= len(self.pages) - 1:
+ return ''
+ return self.pages[idx+1]
+
+ def prev_page(self, page):
+ try:
+ idx = self.pages.index(page)
+ except:
+ return ''
+ if idx == 0:
+ return ''
+ return self.pages[idx-1]
+
+ @property
+ def toc_links(self):
+ '''
+ Returns a list of dictionaries. Each dictionary element contains
+ a page title and the html file name.
+ '''
+ links = []
+ for page in self.pages:
+ title, level = page.title
+ links.append(dict(link=unicode(page.htmlfile, "utf8"),
+ text=unicode(title, "utf8"),
+ level=level,
+ ))
+
+ return links
+
+ @property
+ def pdf_filename(self):
+ header = self.meta['HEADER']
+ m = re.match('(^%)(.*\n.*)(%*)', header)
+ if m:
+ title = m.group(2).strip()
+ title = re.sub(r'\s+', ' ', title)
+ title = re.sub(r'[\n ]', '_', title)
+ title = re.sub(r'[:,]', '-', title)
+ title = re.sub(r'_+', '_', title)
+ title = re.sub(r'-_', '-', title)
+ title = re.sub(r'-+', '-', title)
+ else:
+ title = os.path.split(self.src_path)[1]
+
+ return title + '.pdf'
+
+ def publish_pdf(self, pub_path):
+ pdf_path = os.path.join(pub_path, self.pdf_filename)
+ src = self.meta['HEADER']
+ for page in self.pages:
+ src += '\n%s' % page.source
+
+ doc = pandoc.Document()
+ doc.markdown = src
+
+ pandoc.set_cwd(os.path.abspath(self.src_path))
+ doc.to_file(pdf_path)
+ pandoc.set_cwd(None)
+
+ return self.pdf_filename
+
+ def publish_css(self, pub_path):
+ src = os.path.join(self.src_path, 'css')
+ dst = os.path.join(pub_path, 'css')
+ if os.path.isdir(src):
+ copy(src, dst)
+
+ def publish_images(self, pub_path):
+ src_path = self.src_path
+ if os.path.exists(os.path.join(src_path, 'images/html')):
+ src = os.path.join(src_path, 'images/html')
+ else:
+ src = os.path.join(src_path, 'images')
+
+ dst = os.path.join(pub_path, 'images')
+ if os.path.exists(src):
+ copy(src, dst)
+
+ def publish_html(self, pub_path):
+ pandoc.set_cwd(None)
+ for page in self.pages:
+ print("generating %s..." % page.htmlfile)
+ template_file = NamedTemporaryFile(mode='w',
+ suffix='pf.template',
+ delete=False)
+ template = self.templater.page_template(self, page)
+ template_file.write(template)
+ template_file.close()
+ doc = pandoc.Document()
+ doc.add_argument('toc')
+ doc.add_argument('template=%s' % template_file.name)
+ doc.add_argument('css=%s' % self.css_file)
+ doc.markdown = page.source
+ content = doc.html
+ write_file(os.path.join(pub_path, page.htmlfile),
+ unicode(content, 'utf-8'))
+
+ # If there is not explicit index.html, then link 'index.html'
+ # to the toplevel page.
+ if not 'index.md' in self.pages:
+ src = os.path.join(self.pages[0].htmlfile)
+ ref = os.path.join(pub_path, 'index.html')
+ os.symlink(src, ref)
+
+ ###### Copy any images to publish directory
+ self.publish_css(pub_path)
+ self.publish_images(pub_path)
diff --git a/panfry/main.py b/panfry/main.py
new file mode 100755
index 0000000..1a7acf7
--- /dev/null
+++ b/panfry/main.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
+import shutil
+import panfry.cli
+from panfry.templater import Templater
+from panfry.document import Document
+
+
+def get_env():
+ env = panfry.cli.init_argparser().parse_args()
+ if not os.path.isdir(env.src_path):
+ print("%s directory not found. Aborting..." % env.src_path)
+ exit(1)
+
+ env.pub_path = os.path.abspath(env.pub_path)
+
+ if not env.templates_path:
+ env.templates_path = os.path.join(env.src_path, 'templates')
+
+ if not os.path.isdir(env.templates_path):
+ print("No templates path found. Aborting...")
+ exit(1)
+
+ return env
+
+def main():
+ env = get_env()
+ if os.path.exists(env.pub_path):
+ shutil.rmtree(env.pub_path)
+ os.mkdir(env.pub_path)
+
+ document = Document(env.src_path)
+ document.set_templater(Templater(env.templates_path))
+
+ ###### Create PDF
+ pdffile = document.publish_pdf(env.pub_path)
+ print("Wrote PDF: %s" % pdffile)
+
+ ###### Create HTML
+ document.publish_html(env.pub_path)
+
+ exit(0)
+
+if __name__ == "__main__":
+ main()
diff --git a/panfry/page.py b/panfry/page.py
new file mode 100755
index 0000000..27b4cb1
--- /dev/null
+++ b/panfry/page.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import re
+from panfry.utils import get_lines
+
+
+class Page:
+ '''
+ Represents single source file.
+ '''
+ def __init__(self, filename, source):
+ self.filename = filename
+ self.source = source
+
+ @property
+ def title(self):
+ '''
+ Returns a tuple containing a string representing the page title
+ and an integer representing the level in the document outline
+ hierarchy.
+
+ The level is preserved so that page titles can be properly
+ positioned (indented) in the table of contents.
+
+ If the page has a pandoc title block, the title is retreived
+ from there and returned with a level of 1
+
+ If a title block is not found, the first heading is returned
+ with the corrisponding heading level.
+
+ Finally, if no title block or headings are found in the page,
+ the filename is returned as the title with underscores changed
+ to spaces.
+ '''
+ title = ''
+ for line in get_lines(self.source):
+ if line.startswith('% '):
+ title = line.split(' ', 1)[1].strip()
+ if '(' and ')' in line:
+ num = line.split('(')[1][0]
+ return 'man(%s) %s' % (num, title.split('(')[0].strip())
+ else:
+ return (title, 1)
+ if re.match('[=]{2}', line):
+ return (title, 1)
+ if re.match('[-]{2}', line):
+ return (title, 2)
+ if re.match('#+.+[A-z|0-9]', line):
+ level = len(re.match('#+', line).group())
+ return (line.split(' ', 1)[1].strip(), level)
+ title = line.strip()
+
+ title = self.filename.replace('_', ' ')
+
+ return (title, 1)
+
+ @property
+ def htmlfile(self):
+ return '.'.join(self.filename.split('.')[:-1]) + '.html'
diff --git a/panfry/templater.py b/panfry/templater.py
new file mode 100755
index 0000000..c420a12
--- /dev/null
+++ b/panfry/templater.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from jinja2 import Environment, FileSystemLoader
+
+
+class Templater:
+ ''' Pandoc Template generator.
+ Creates a template suitible for passing to pandoc for html generation.
+
+ The most prominent feature is adding the custom table of contents
+ navigator that navigates accross muliple html pages.
+
+ The generator references a jinja2 template file called html5.template
+ located either in the directory 'templates' under the source path,
+ or in the directory passed as an option.
+ '''
+ def __init__(self, path):
+ self.templates_path = path
+ self.j2 = Environment(loader=FileSystemLoader(self.templates_path),
+ line_statement_prefix='#')
+
+ def page_template(self, doc, page):
+ '''
+ Returns a new page template for use by pandoc.
+
+ Arguments:
+ - doc: Panfry.Document.
+ - page: Panfry.Page from Panfry.Document to generate template for.
+ '''
+ options = dict(toc=doc.toc_links)
+ options['page'] = unicode(page.htmlfile, "utf8")
+ options['pdf'] = unicode(doc.pdf_filename)
+ options['prev'] = u''
+ options['next'] = u''
+ prevpage = doc.prev_page(page)
+ nextpage = doc.next_page(page)
+ if prevpage:
+ options['prev'] = unicode(prevpage.htmlfile, "utf8")
+ if nextpage:
+ options['next'] = unicode(nextpage.htmlfile, "utf8")
+
+ template = self.j2.get_template('html5.template')
+
+ return template.render(options=options).encode('utf-8')
diff --git a/panfry/util.py b/panfry/util.py
new file mode 100755
index 0000000..c711599
--- /dev/null
+++ b/panfry/util.py
@@ -0,0 +1,365 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
+import shutil
+import re
+import argparse
+import pandoc
+import cli
+from tempfile import NamedTemporaryFile
+from jinja2 import Environment, FileSystemLoader
+
+
+class Templater:
+ ''' Pandoc Template generator.
+ Creates a template suitible for passing to pandoc for html generation.
+
+ The most prominent feature is adding the custom table of contents
+ navigator that navigates accross muliple html pages.
+
+ The generator references a jinja2 template file called html5.template
+ located either in the directory 'templates' under the source path,
+ or in the directory passed as an option.
+ '''
+ def __init__(self, path):
+ self.templates_path = path
+ self.j2 = Environment(loader=FileSystemLoader(self.templates_path),
+ line_statement_prefix='#')
+
+ def page_template(self, doc, page):
+ '''
+ Returns a new page template for use by pandoc.
+
+ Arguments:
+ - doc: Panfry.Document.
+ - page: Panfry.Page from Panfry.Document to generate template for.
+ '''
+ options = dict(toc=doc.toc_links)
+ options['page'] = unicode(page.htmlfile, "utf8")
+ options['pdf'] = unicode(doc.pdf_filename)
+ options['prev'] = u''
+ options['next'] = u''
+ prevpage = doc.prev_page(page)
+ nextpage = doc.next_page(page)
+ if prevpage:
+ options['prev'] = unicode(prevpage.htmlfile, "utf8")
+ if nextpage:
+ options['next'] = unicode(nextpage.htmlfile, "utf8")
+
+ template = self.j2.get_template('html5.template')
+
+ return template.render(options=options).encode('utf-8')
+
+
+class Page:
+ '''
+ Represents single source file.
+ '''
+ def __init__(self, filename, source):
+ self.filename = filename
+ self.source = source
+
+ @property
+ def title(self):
+ '''
+ Returns a tuple containing a string representing the page title
+ and an integer representing the level in the document outline
+ hierarchy.
+
+ The level is preserved so that page titles can be properly
+ positioned (indented) in the table of contents.
+
+ If the page has a pandoc title block, the title is retreived
+ from there and returned with a level of 1
+
+ If a title block is not found, the first heading is returned
+ with the corrisponding heading level.
+
+ Finally, if no title block or headings are found in the page,
+ the filename is returned as the title with underscores changed
+ to spaces.
+ '''
+ title = ''
+ for line in get_lines(self.source):
+ if line.startswith('% '):
+ title = line.split(' ', 1)[1].strip()
+ if '(' and ')' in line:
+ num = line.split('(')[1][0]
+ return 'man(%s) %s' % (num, title.split('(')[0].strip())
+ else:
+ return (title, 1)
+ if re.match('[=]{2}', line):
+ return (title, 1)
+ if re.match('[-]{2}', line):
+ return (title, 2)
+ if re.match('#+.+[A-z|0-9]', line):
+ level = len(re.match('#+', line).group())
+ return (line.split(' ', 1)[1].strip(), level)
+ title = line.strip()
+
+ title = self.filename.replace('_', ' ')
+
+ return (title, 1)
+
+ @property
+ def htmlfile(self):
+ return '.'.join(self.filename.split('.')[:-1]) + '.html'
+
+
+class Document:
+
+ def __init__(self, path):
+ self.src_path = path
+ self.meta = self.get_meta(path)
+ self.pages = self.get_pages(path)
+
+ css_file = 'css/style.css'
+ workdir = 'stdocs-work'
+
+ def get_pages(self, path):
+ pages = []
+ filelist = get_lines(self.meta['TOC'])
+ for filename in filelist:
+ source = read_file(os.path.join(path, filename))
+ if source:
+ pages.append(Page(filename, source))
+ else:
+ print("WARNING: Page: %s not found or is empty." % filename)
+
+ return pages
+
+ def get_meta(self, path):
+ metafiles = ['TOC', 'HEADER']
+ meta = {}
+ for filename in metafiles:
+ source = read_file(os.path.join(path, filename))
+ if source:
+ meta[filename] = source
+ else:
+ print("!E: %s not found or is empty. Aborting..." % filename)
+ exit(1)
+
+ return meta
+
+ def set_templater(self, templater):
+ self.templater = templater
+
+ def next_page(self, page):
+ try:
+ idx = self.pages.index(page)
+ except:
+ return ''
+ if idx >= len(self.pages) - 1:
+ return ''
+ return self.pages[idx+1]
+
+ def prev_page(self, page):
+ try:
+ idx = self.pages.index(page)
+ except:
+ return ''
+ if idx == 0:
+ return ''
+ return self.pages[idx-1]
+
+ @property
+ def toc_links(self):
+ '''
+ Returns a list of dictionaries. Each dictionary element contains
+ a page title and the html file name.
+ '''
+ links = []
+ for page in self.pages:
+ title, level = page.title
+ links.append(dict(link=unicode(page.htmlfile, "utf8"),
+ text=unicode(title, "utf8"),
+ level=level,
+ ))
+
+ return links
+
+ @property
+ def pdf_filename(self):
+ header = self.meta['HEADER']
+ m = re.match('(^%)(.*\n.*)(%*)', header)
+ if m:
+ title = m.group(2).strip()
+ title = re.sub(r'\s+', ' ', title)
+ title = re.sub(r'[\n ]', '_', title)
+ title = re.sub(r'[:,]', '-', title)
+ title = re.sub(r'_+', '_', title)
+ title = re.sub(r'-_', '-', title)
+ title = re.sub(r'-+', '-', title)
+ else:
+ title = os.path.split(self.src_path)[1]
+
+ return title + '.pdf'
+
+ def publish_pdf(self, pub_path):
+ pdf_path = os.path.join(pub_path, self.pdf_filename)
+ src = self.meta['HEADER']
+ for page in self.pages:
+ src += '\n%s' % page.source
+
+ doc = pandoc.Document()
+ doc.markdown = src
+
+ pandoc.set_cwd(os.path.abspath(self.src_path))
+ doc.to_file(pdf_path)
+ pandoc.set_cwd(None)
+
+ return self.pdf_filename
+
+ def publish_css(self, pub_path):
+ src = os.path.join(self.src_path, 'css')
+ dst = os.path.join(pub_path, 'css')
+ if os.path.isdir(src):
+ copy(src, dst)
+
+ def publish_images(self, pub_path):
+ src_path = self.src_path
+ if os.path.exists(os.path.join(src_path, 'images/html')):
+ src = os.path.join(src_path, 'images/html')
+ else:
+ src = os.path.join(src_path, 'images')
+
+ dst = os.path.join(pub_path, 'images')
+ if os.path.exists(src):
+ copy(src, dst)
+
+ def publish_html(self, pub_path):
+ pandoc.set_cwd(None)
+ for page in self.pages:
+ print("generating %s..." % page.htmlfile)
+ template_file = NamedTemporaryFile(mode='w',
+ suffix='pf.template',
+ delete=False)
+ template = self.templater.page_template(self, page)
+ template_file.write(template)
+ template_file.close()
+ doc = pandoc.Document()
+ doc.add_argument('toc')
+ doc.add_argument('template=%s' % template_file.name)
+ doc.add_argument('css=%s' % self.css_file)
+ doc.markdown = page.source
+ content = doc.html
+ write_file(os.path.join(pub_path, page.htmlfile),
+ unicode(content, 'utf-8'))
+
+ # If there is not explicit index.html, then link 'index.html'
+ # to the toplevel page.
+ if not 'index.md' in self.pages:
+ src = os.path.join(self.pages[0].htmlfile)
+ ref = os.path.join(pub_path, 'index.html')
+ os.symlink(src, ref)
+
+ ###### Copy any images to publish directory
+ self.publish_css(pub_path)
+ self.publish_images(pub_path)
+
+
+def copy(src, dst, ignore=None):
+ if os.path.isdir(src):
+ shutil.copytree(src, dst, True, ignore)
+ else:
+ shutil.copy(src, dst)
+
+
+def open_file(path, mode='r'):
+ if mode == 'w' or os.path.isfile(path):
+ return open(path, mode)
+ else:
+ return None
+
+
+def write_file(path, content):
+ fd = open_file(path, mode='w')
+ if fd:
+ fd.write(content.encode('utf-8'))
+ fd.close()
+ return path
+ else:
+ return None
+
+
+def read_file(path):
+ content = ''
+ fd = open_file(path)
+ if fd:
+ content = fd.read()
+ fd.close()
+ return content
+ else:
+ print("Error: could not open %s" % path)
+ return content
+
+
+def read_file_lines(path):
+ content = []
+ fd = open_file(path)
+ if fd:
+ content = fd.readlines()
+ fd.close()
+ return content
+
+
+def get_lines(content):
+ '''
+ return list of content split by line.
+ Leading/trailing blank lines are not reserved.
+ '''
+ return content.strip().split('\n')
+
+
+def init_argparser():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('src_path', nargs='?',
+ default='.',
+ help='Path of document sources')
+ parser.add_argument('pub_path', nargs='?',
+ default='./pub',
+ help='Directory to place output')
+ parser.add_argument('-T', '--templates', dest='templates_path',
+ default='',
+ help='Directory to find templates')
+ parser.add_argument('-C', '--css', dest='css_file',
+ default='',
+ help='css file for html pages',
+ )
+ return parser
+
+
+def get_env():
+ env = cli.init_argparser().parse_args()
+ if not os.path.isdir(env.src_path):
+ print("%s directory not found. Aborting..." % env.src_path)
+ exit(1)
+
+ env.pub_path = os.path.abspath(env.pub_path)
+
+ if not env.templates_path:
+ env.templates_path = os.path.join(env.src_path, 'templates')
+
+ if not os.path.isdir(env.templates_path):
+ print("No templates path found. Aborting...")
+ exit(1)
+
+ return env
+
+if __name__ == "__main__":
+ env = get_env()
+ if os.path.exists(env.pub_path):
+ shutil.rmtree(env.pub_path)
+ os.mkdir(env.pub_path)
+
+ document = Document(env.src_path)
+ document.set_templater(Templater(env.templates_path))
+
+ ###### Create PDF
+ pdffile = document.publish_pdf(env.pub_path)
+ print("Wrote PDF: %s" % pdffile)
+
+ ###### Create HTML
+ document.publish_html(env.pub_path)
+
+ exit(0)
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..091306e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+import os
+import sys
+from distutils.core import setup
+from distutils.util import convert_path
+from fnmatch import fnmatchcase
+import versioneer
+
+PROJECT = 'panfry'
+
+versioneer.versionfile_source = '%s/_version.py' % PROJECT
+versioneer.versionfile_build = '%s/_version.py' % PROJECT
+versioneer.tag_prefix = '%s-' % PROJECT # tags are like 1.2.0
+versioneer.parentdir_prefix = '%s-' % PROJECT # dirname like 'myproject-1.2.0'
+
+################################################################################
+# find_package_data is an Ian Bicking creation.
+
+# Provided as an attribute, so you can append to these instead
+# of replicating them:
+standard_exclude = ('*.py', '*.pyc', '*~', '.*', '*.bak', '*.swp*')
+standard_exclude_directories = ('.*', 'CVS', '_darcs', './build',
+ './dist', 'EGG-INFO', '*.egg-info',
+ './test')
+
+
+def find_package_data(
+ where='.', package='',
+ exclude=standard_exclude,
+ exclude_directories=standard_exclude_directories,
+ only_in_packages=True,
+ show_ignored=False):
+ """
+ Return a dictionary suitable for use in ``package_data``
+ in a distutils ``setup.py`` file.
+
+ The dictionary looks like::
+
+ {'package': [files]}
+
+ Where ``files`` is a list of all the files in that package that
+ don't match anything in ``exclude``.
+
+ If ``only_in_packages`` is true, then top-level directories that
+ are not packages won't be included (but directories under packages
+ will).
+
+ Directories matching any pattern in ``exclude_directories`` will
+ be ignored; by default directories with leading ``.``, ``CVS``,
+ and ``_darcs`` will be ignored.
+
+ If ``show_ignored`` is true, then all the files that aren't
+ included in package data are shown on stderr (for debugging
+ purposes).
+
+ Note patterns use wildcards, or can be exact paths (including
+ leading ``./``), and all searching is case-insensitive.
+
+ This function is by Ian Bicking.
+ """
+
+ out = {}
+ stack = [(convert_path(where), '', package, only_in_packages)]
+ while stack:
+ where, prefix, package, only_in_packages = stack.pop(0)
+ for name in os.listdir(where):
+ fn = os.path.join(where, name)
+ if os.path.isdir(fn):
+ print >> sys.stderr, ("Scanning: %s" % fn)
+ bad_name = False
+ for pattern in exclude_directories:
+ if (fnmatchcase(name, pattern)
+ or fn.lower() == pattern.lower()):
+ bad_name = True
+ if show_ignored:
+ print >> sys.stderr, (
+ "Directory %s ignored by pattern %s"
+ % (fn, pattern))
+ break
+ if bad_name:
+ continue
+ if os.path.isfile(os.path.join(fn, '__init__.py')):
+ if not package:
+ new_package = name
+ else:
+ new_package = package + '.' + name
+ stack.append((fn, '', new_package, False))
+ else:
+ stack.append((fn, prefix + name + '/',
+ package, only_in_packages))
+ elif package or not only_in_packages:
+ # is a file
+ bad_name = False
+ for pattern in exclude:
+ if (fnmatchcase(name, pattern)
+ or fn.lower() == pattern.lower()):
+ bad_name = True
+ if show_ignored:
+ print >> sys.stderr, (
+ "File %s ignored by pattern %s"
+ % (fn, pattern))
+ break
+ if bad_name:
+ continue
+ print >> sys.stderr, ("Adding: %s" % prefix+name)
+ out.setdefault(package, []).append(prefix+name)
+ print >> sys.stderr, (out)
+ return out
+############################################################################
+
+setup(name=PROJECT,
+ version=versioneer.get_version(),
+ cmdclass=versioneer.get_cmdclass(),
+ description='Panfry',
+ author='Scott Bahling',
+ author_email='sbahling@mudgum.net',
+ packages=[PROJECT],
+ entry_points={
+ 'console_scripts':
+ ['panfry = panfry.main:main']}
+ )
diff --git a/versioneer.py b/versioneer.py
new file mode 100644
index 0000000..ac77fb5
--- /dev/null
+++ b/versioneer.py
@@ -0,0 +1,649 @@
+#! /usr/bin/python
+
+"""versioneer.py
+
+(like a rocketeer, but for versions)
+
+* https://github.com/warner/python-versioneer
+* Brian Warner
+* License: Public Domain
+* Version: 0.7
+
+This file helps distutils-based projects manage their version number by just
+creating version-control tags.
+
+For developers who work from a VCS-generated tree (e.g. 'git clone' etc),
+each 'setup.py version', 'setup.py build', 'setup.py sdist' will compute a
+version number by asking your version-control tool about the current
+checkout. The version number will be written into a generated _version.py
+file of your choosing, where it can be included by your __init__.py
+
+For users who work from a VCS-generated tarball (e.g. 'git archive'), it will
+compute a version number by looking at the name of the directory created when
+te tarball is unpacked. This conventionally includes both the name of the
+project and a version number.
+
+For users who work from a tarball built by 'setup.py sdist', it will get a
+version number from a previously-generated _version.py file.
+
+As a result, loading code directly from the source tree will not result in a
+real version. If you want real versions from VCS trees (where you frequently
+update from the upstream repository, or do new development), you will need to
+do a 'setup.py version' after each update, and load code from the build/
+directory.
+
+You need to provide this code with a few configuration values:
+
+ versionfile_source:
+ A project-relative pathname into which the generated version strings
+ should be written. This is usually a _version.py next to your project's
+ main __init__.py file. If your project uses src/myproject/__init__.py,
+ this should be 'src/myproject/_version.py'. This file should be checked
+ in to your VCS as usual: the copy created below by 'setup.py
+ update_files' will include code that parses expanded VCS keywords in
+ generated tarballs. The 'build' and 'sdist' commands will replace it with
+ a copy that has just the calculated version string.
+
+ versionfile_build:
+ Like versionfile_source, but relative to the build directory instead of
+ the source directory. These will differ when your setup.py uses
+ 'package_dir='. If you have package_dir={'myproject': 'src/myproject'},
+ then you will probably have versionfile_build='myproject/_version.py' and
+ versionfile_source='src/myproject/_version.py'.
+
+ tag_prefix: a string, like 'PROJECTNAME-', which appears at the start of all
+ VCS tags. If your tags look like 'myproject-1.2.0', then you
+ should use tag_prefix='myproject-'. If you use unprefixed tags
+ like '1.2.0', this should be an empty string.
+
+ parentdir_prefix: a string, frequently the same as tag_prefix, which
+ appears at the start of all unpacked tarball filenames. If
+ your tarball unpacks into 'myproject-1.2.0', this should
+ be 'myproject-'.
+
+To use it:
+
+ 1: include this file in the top level of your project
+ 2: make the following changes to the top of your setup.py:
+ import versioneer
+ versioneer.versionfile_source = 'src/myproject/_version.py'
+ versioneer.versionfile_build = 'myproject/_version.py'
+ versioneer.tag_prefix = '' # tags are like 1.2.0
+ versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0'
+ 3: add the following arguments to the setup() call in your setup.py:
+ version=versioneer.get_version(),
+ cmdclass=versioneer.get_cmdclass(),
+ 4: run 'setup.py update_files', which will create _version.py, and will
+ append the following to your __init__.py:
+ from _version import __version__
+ 5: modify your MANIFEST.in to include versioneer.py
+ 6: add both versioneer.py and the generated _version.py to your VCS
+"""
+
+import os, sys, re
+from distutils.core import Command
+from distutils.command.sdist import sdist as _sdist
+from distutils.command.build import build as _build
+
+versionfile_source = None
+versionfile_build = None
+tag_prefix = None
+parentdir_prefix = None
+
+VCS = "git"
+IN_LONG_VERSION_PY = False
+
+
+LONG_VERSION_PY = '''
+IN_LONG_VERSION_PY = True
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by githubs download-from-tag
+# feature). Distribution tarballs (build by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
+
+# This file is released into the public domain. Generated by
+# versioneer-0.7 (https://github.com/warner/python-versioneer)
+
+# these strings will be replaced by git during git-archive
+git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
+git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
+
+
+import subprocess
+
+def run_command(args, cwd=None, verbose=False):
+ try:
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
+ except EnvironmentError, e:
+ if verbose:
+ print "unable to run %%s" %% args[0]
+ print e
+ return None
+ stdout = p.communicate()[0].strip()
+ if p.returncode != 0:
+ if verbose:
+ print "unable to run %%s (error)" %% args[0]
+ return None
+ return stdout
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+ # the code embedded in _version.py can just fetch the value of these
+ # variables. When used from setup.py, we don't want to import
+ # _version.py, so we do it with a regexp instead. This function is not
+ # used from _version.py.
+ variables = {}
+ try:
+ for line in open(versionfile_source,"r").readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["full"] = mo.group(1)
+ except EnvironmentError:
+ pass
+ return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+ refnames = variables["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print "variables are unexpanded, not using"
+ return {} # unexpanded, so not in an unpacked git-archive tarball
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ for ref in list(refs):
+ if not re.search(r'\d', ref):
+ if verbose:
+ print "discarding '%%s', no digits" %% ref
+ refs.discard(ref)
+ # Assume all version tags have a digit. git's %%d expansion
+ # behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us
+ # distinguish between branches and tags. By ignoring refnames
+ # without digits, we filter out many common branch names like
+ # "release" and "stabilization", as well as "HEAD" and "master".
+ if verbose:
+ print "remaining refs:", ",".join(sorted(refs))
+ for ref in sorted(refs):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print "picking %%s" %% r
+ return { "version": r,
+ "full": variables["full"].strip() }
+ # no suitable tags, so we use the full revision id
+ if verbose:
+ print "no suitable tags, using full revision id"
+ return { "version": variables["full"].strip(),
+ "full": variables["full"].strip() }
+
+def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
+ # this runs 'git' from the root of the source tree. That either means
+ # someone ran a setup.py command (and this code is in versioneer.py, so
+ # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
+ # the source tree), or someone ran a project-specific entry point (and
+ # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
+ # containing directory is somewhere deeper in the source tree). This only
+ # gets called if the git-archive 'subst' variables were *not* expanded,
+ # and _version.py hasn't already been rewritten with a short version
+ # string, meaning we're inside a checked out source tree.
+
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # some py2exe/bbfreeze/non-CPython implementations don't do __file__
+ return {} # not always correct
+
+ # versionfile_source is the relative path from the top of the source tree
+ # (where the .git directory might live) to this file. Invert this to find
+ # the root from __file__.
+ root = here
+ if IN_LONG_VERSION_PY:
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ root = os.path.dirname(here)
+ if not os.path.exists(os.path.join(root, ".git")):
+ if verbose:
+ print "no .git in", root
+ return {}
+
+ GIT = "git"
+ if sys.platform == "win32":
+ GIT = "git.cmd"
+ stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
+ cwd=root)
+ if stdout is None:
+ return {}
+ if not stdout.startswith(tag_prefix):
+ if verbose:
+ print "tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix)
+ return {}
+ tag = stdout[len(tag_prefix):]
+ stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+ if stdout is None:
+ return {}
+ full = stdout.strip()
+ if tag.endswith("-dirty"):
+ full += "-dirty"
+ return {"version": tag, "full": full}
+
+
+def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
+ if IN_LONG_VERSION_PY:
+ # We're running from _version.py. If it's from a source tree
+ # (execute-in-place), we can work upwards to find the root of the
+ # tree, and then check the parent directory for a version string. If
+ # it's in an installed application, there's no hope.
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # py2exe/bbfreeze/non-CPython don't have __file__
+ return {} # without __file__, we have no hope
+ # versionfile_source is the relative path from the top of the source
+ # tree to _version.py. Invert this to find the root from __file__.
+ root = here
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ # we're running from versioneer.py, which means we're running from
+ # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
+ here = os.path.abspath(sys.argv[0])
+ root = os.path.dirname(here)
+
+ # Source tarballs conventionally unpack into a directory that includes
+ # both the project name and a version string.
+ dirname = os.path.basename(root)
+ if not dirname.startswith(parentdir_prefix):
+ if verbose:
+ print "guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %% \
+ (root, dirname, parentdir_prefix)
+ return None
+ return {"version": dirname[len(parentdir_prefix):], "full": ""}
+
+tag_prefix = "%(TAG_PREFIX)s"
+parentdir_prefix = "%(PARENTDIR_PREFIX)s"
+versionfile_source = "%(VERSIONFILE_SOURCE)s"
+
+def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
+ variables = { "refnames": git_refnames, "full": git_full }
+ ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
+ if not ver:
+ ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
+ if not ver:
+ ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
+ verbose)
+ if not ver:
+ ver = default
+ return ver
+
+'''
+
+
+import subprocess
+
+def run_command(args, cwd=None, verbose=False):
+ try:
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
+ except EnvironmentError, e:
+ if verbose:
+ print "unable to run %s" % args[0]
+ print e
+ return None
+ stdout = p.communicate()[0].strip()
+ if p.returncode != 0:
+ if verbose:
+ print "unable to run %s (error)" % args[0]
+ return None
+ return stdout
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+ # the code embedded in _version.py can just fetch the value of these
+ # variables. When used from setup.py, we don't want to import
+ # _version.py, so we do it with a regexp instead. This function is not
+ # used from _version.py.
+ variables = {}
+ try:
+ for line in open(versionfile_source,"r").readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ variables["full"] = mo.group(1)
+ except EnvironmentError:
+ pass
+ return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+ refnames = variables["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print "variables are unexpanded, not using"
+ return {} # unexpanded, so not in an unpacked git-archive tarball
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ for ref in list(refs):
+ if not re.search(r'\d', ref):
+ if verbose:
+ print "discarding '%s', no digits" % ref
+ refs.discard(ref)
+ # Assume all version tags have a digit. git's %d expansion
+ # behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us
+ # distinguish between branches and tags. By ignoring refnames
+ # without digits, we filter out many common branch names like
+ # "release" and "stabilization", as well as "HEAD" and "master".
+ if verbose:
+ print "remaining refs:", ",".join(sorted(refs))
+ for ref in sorted(refs):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print "picking %s" % r
+ return { "version": r,
+ "full": variables["full"].strip() }
+ # no suitable tags, so we use the full revision id
+ if verbose:
+ print "no suitable tags, using full revision id"
+ return { "version": variables["full"].strip(),
+ "full": variables["full"].strip() }
+
+def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
+ # this runs 'git' from the root of the source tree. That either means
+ # someone ran a setup.py command (and this code is in versioneer.py, so
+ # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
+ # the source tree), or someone ran a project-specific entry point (and
+ # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
+ # containing directory is somewhere deeper in the source tree). This only
+ # gets called if the git-archive 'subst' variables were *not* expanded,
+ # and _version.py hasn't already been rewritten with a short version
+ # string, meaning we're inside a checked out source tree.
+
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # some py2exe/bbfreeze/non-CPython implementations don't do __file__
+ return {} # not always correct
+
+ # versionfile_source is the relative path from the top of the source tree
+ # (where the .git directory might live) to this file. Invert this to find
+ # the root from __file__.
+ root = here
+ if IN_LONG_VERSION_PY:
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ root = os.path.dirname(here)
+ if not os.path.exists(os.path.join(root, ".git")):
+ if verbose:
+ print "no .git in", root
+ return {}
+
+ GIT = "git"
+ if sys.platform == "win32":
+ GIT = "git.cmd"
+ stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
+ cwd=root)
+ if stdout is None:
+ return {}
+ if not stdout.startswith(tag_prefix):
+ if verbose:
+ print "tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)
+ return {}
+ tag = stdout[len(tag_prefix):]
+ stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+ if stdout is None:
+ return {}
+ full = stdout.strip()
+ if tag.endswith("-dirty"):
+ full += "-dirty"
+ return {"version": tag, "full": full}
+
+
+def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
+ if IN_LONG_VERSION_PY:
+ # We're running from _version.py. If it's from a source tree
+ # (execute-in-place), we can work upwards to find the root of the
+ # tree, and then check the parent directory for a version string. If
+ # it's in an installed application, there's no hope.
+ try:
+ here = os.path.abspath(__file__)
+ except NameError:
+ # py2exe/bbfreeze/non-CPython don't have __file__
+ return {} # without __file__, we have no hope
+ # versionfile_source is the relative path from the top of the source
+ # tree to _version.py. Invert this to find the root from __file__.
+ root = here
+ for i in range(len(versionfile_source.split("/"))):
+ root = os.path.dirname(root)
+ else:
+ # we're running from versioneer.py, which means we're running from
+ # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
+ here = os.path.abspath(sys.argv[0])
+ root = os.path.dirname(here)
+
+ # Source tarballs conventionally unpack into a directory that includes
+ # both the project name and a version string.
+ dirname = os.path.basename(root)
+ if not dirname.startswith(parentdir_prefix):
+ if verbose:
+ print "guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % \
+ (root, dirname, parentdir_prefix)
+ return None
+ return {"version": dirname[len(parentdir_prefix):], "full": ""}
+
+import sys
+
+def do_vcs_install(versionfile_source, ipy):
+ GIT = "git"
+ if sys.platform == "win32":
+ GIT = "git.cmd"
+ run_command([GIT, "add", "versioneer.py"])
+ run_command([GIT, "add", versionfile_source])
+ run_command([GIT, "add", ipy])
+ present = False
+ try:
+ f = open(".gitattributes", "r")
+ for line in f.readlines():
+ if line.strip().startswith(versionfile_source):
+ if "export-subst" in line.strip().split()[1:]:
+ present = True
+ f.close()
+ except EnvironmentError:
+ pass
+ if not present:
+ f = open(".gitattributes", "a+")
+ f.write("%s export-subst\n" % versionfile_source)
+ f.close()
+ run_command([GIT, "add", ".gitattributes"])
+
+
+SHORT_VERSION_PY = """
+# This file was generated by 'versioneer.py' (0.7) from
+# revision-control system data, or from the parent directory name of an
+# unpacked source archive. Distribution tarballs contain a pre-generated copy
+# of this file.
+
+version_version = '%(version)s'
+version_full = '%(full)s'
+def get_versions(default={}, verbose=False):
+ return {'version': version_version, 'full': version_full}
+
+"""
+
+DEFAULT = {"version": "unknown", "full": "unknown"}
+
+def versions_from_file(filename):
+ versions = {}
+ try:
+ f = open(filename)
+ except EnvironmentError:
+ return versions
+ for line in f.readlines():
+ mo = re.match("version_version = '([^']+)'", line)
+ if mo:
+ versions["version"] = mo.group(1)
+ mo = re.match("version_full = '([^']+)'", line)
+ if mo:
+ versions["full"] = mo.group(1)
+ return versions
+
+def write_to_version_file(filename, versions):
+ f = open(filename, "w")
+ f.write(SHORT_VERSION_PY % versions)
+ f.close()
+ print "set %s to '%s'" % (filename, versions["version"])
+
+
+def get_best_versions(versionfile, tag_prefix, parentdir_prefix,
+ default=DEFAULT, verbose=False):
+ # returns dict with two keys: 'version' and 'full'
+ #
+ # extract version from first of _version.py, 'git describe', parentdir.
+ # This is meant to work for developers using a source checkout, for users
+ # of a tarball created by 'setup.py sdist', and for users of a
+ # tarball/zipball created by 'git archive' or github's download-from-tag
+ # feature.
+
+ variables = get_expanded_variables(versionfile_source)
+ if variables:
+ ver = versions_from_expanded_variables(variables, tag_prefix)
+ if ver:
+ if verbose: print "got version from expanded variable", ver
+ return ver
+
+ ver = versions_from_file(versionfile)
+ if ver:
+ if verbose: print "got version from file %s" % versionfile, ver
+ return ver
+
+ ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
+ if ver:
+ if verbose: print "got version from git", ver
+ return ver
+
+ ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose)
+ if ver:
+ if verbose: print "got version from parentdir", ver
+ return ver
+
+ if verbose: print "got version from default", ver
+ return default
+
+def get_versions(default=DEFAULT, verbose=False):
+ assert versionfile_source is not None, "please set versioneer.versionfile_source"
+ assert tag_prefix is not None, "please set versioneer.tag_prefix"
+ assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix"
+ return get_best_versions(versionfile_source, tag_prefix, parentdir_prefix,
+ default=default, verbose=verbose)
+def get_version(verbose=False):
+ return get_versions(verbose=verbose)["version"]
+
+class cmd_version(Command):
+ description = "report generated version string"
+ user_options = []
+ boolean_options = []
+ def initialize_options(self):
+ pass
+ def finalize_options(self):
+ pass
+ def run(self):
+ ver = get_version(verbose=True)
+ print "Version is currently:", ver
+
+
+class cmd_build(_build):
+ def run(self):
+ versions = get_versions(verbose=True)
+ _build.run(self)
+ # now locate _version.py in the new build/ directory and replace it
+ # with an updated value
+ target_versionfile = os.path.join(self.build_lib, versionfile_build)
+ print "UPDATING", target_versionfile
+ os.unlink(target_versionfile)
+ f = open(target_versionfile, "w")
+ f.write(SHORT_VERSION_PY % versions)
+ f.close()
+
+class cmd_sdist(_sdist):
+ def run(self):
+ versions = get_versions(verbose=True)
+ self._versioneer_generated_versions = versions
+ # unless we update this, the command will keep using the old version
+ self.distribution.metadata.version = versions["version"]
+ return _sdist.run(self)
+
+ def make_release_tree(self, base_dir, files):
+ _sdist.make_release_tree(self, base_dir, files)
+ # now locate _version.py in the new base_dir directory (remembering
+ # that it may be a hardlink) and replace it with an updated value
+ target_versionfile = os.path.join(base_dir, versionfile_source)
+ print "UPDATING", target_versionfile
+ os.unlink(target_versionfile)
+ f = open(target_versionfile, "w")
+ f.write(SHORT_VERSION_PY % self._versioneer_generated_versions)
+ f.close()
+
+INIT_PY_SNIPPET = """
+from ._version import get_versions
+__version__ = get_versions()['version']
+del get_versions
+"""
+
+class cmd_update_files(Command):
+ description = "modify __init__.py and create _version.py"
+ user_options = []
+ boolean_options = []
+ def initialize_options(self):
+ pass
+ def finalize_options(self):
+ pass
+ def run(self):
+ ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py")
+ print " creating %s" % versionfile_source
+ f = open(versionfile_source, "w")
+ f.write(LONG_VERSION_PY % {"DOLLAR": "$",
+ "TAG_PREFIX": tag_prefix,
+ "PARENTDIR_PREFIX": parentdir_prefix,
+ "VERSIONFILE_SOURCE": versionfile_source,
+ })
+ f.close()
+ try:
+ old = open(ipy, "r").read()
+ except EnvironmentError:
+ old = ""
+ if INIT_PY_SNIPPET not in old:
+ print " appending to %s" % ipy
+ f = open(ipy, "a")
+ f.write(INIT_PY_SNIPPET)
+ f.close()
+ else:
+ print " %s unmodified" % ipy
+ do_vcs_install(versionfile_source, ipy)
+
+def get_cmdclass():
+ return {'version': cmd_version,
+ 'update_files': cmd_update_files,
+ 'build': cmd_build,
+ 'sdist': cmd_sdist,
+ }
+