├── requirements.txt ├── poachplatelib ├── __init__.py ├── meta.py └── package.py ├── .gitignore ├── MANIFEST.in ├── INSTALL ├── .travis.yml ├── bin └── poachplate ├── setup.py ├── test └── testpoachplate.py ├── LICENSE ├── Makefile └── README /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /poachplatelib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | env 4 | packages 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README 2 | include LICENSE 3 | include INSTALL 4 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Unpack the package 2 | 3 | Run 4 | 5 | ``python setup.py install`` 6 | 7 | (or use your favorite install mechanism) -------------------------------------------------------------------------------- /poachplatelib/meta.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.5" 2 | __author__ = "matt harrison" 3 | __email__ = "matthewharrison@gmail.com" 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.3" 4 | - "2.7" 5 | - "2.6" 6 | - "pypy" 7 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 8 | #install: PLEASE CHANGE ME 9 | # command to run tests, e.g. python setup.py test 10 | script: make test -------------------------------------------------------------------------------- /bin/poachplate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | import poachplatelib.package 6 | 7 | if __name__ == "__main__": 8 | sys.exit(poachplatelib.package.main(sys.argv) or 0) 9 | # try: 10 | # sys.exit(poachplatelib.package.main(sys.argv) or 0) 11 | # except Exception, e: 12 | # sys.stderr.write('%s\n'%str(e)) 13 | # raise e 14 | # sys.exit(1) 15 | 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except: 4 | from distutils.core import setup 5 | 6 | 7 | from poachplatelib import meta 8 | 9 | setup(name="poachplate", 10 | version=meta.__version__, 11 | author=meta.__author__, 12 | description="a script to create python script boilerplate (setup.py/package/README/etc)", 13 | scripts=["bin/poachplate"], 14 | package_dir={"poachplatelib":"poachplatelib"}, 15 | packages=['poachplatelib'], 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /test/testpoachplate.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from poachplatelib import package 4 | 5 | class TestScript(unittest.TestCase): 6 | def setUp(self): 7 | self.p = package.Package('test') 8 | self.p.generate() 9 | 10 | def test_setup(self): 11 | self.assertEqual(self.p._setup, "# Copyright (c) 2013 FILL IN\n#from distutils.core import setup\nfrom setuptools import setup\n\nfrom testlib import meta\n\nsetup(name='test',\n version=meta.__version__,\n author=meta.__author__,\n description='FILL IN',\n scripts=['bin/test'],\n package_dir={'testlib':'testlib'},\n packages=['testlib'],\n)\n") 12 | def test_script(self): 13 | self.assertEqual(self.p._script, """#!/usr/bin/env python 14 | # Copyright (c) 2013 FILL IN 15 | 16 | import sys 17 | 18 | import testlib 19 | 20 | if __name__ == '__main__': 21 | try: 22 | sys.exit(testlib.main(sys.argv)) 23 | except Exception, e: 24 | sys.stderr.write('%s\\n'%str(e)) 25 | sys.exit(1) 26 | 27 | """) 28 | 29 | if __name__ == "__main__": 30 | unittest.main() 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Matt Harrison 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # variables to use sandboxed binaries 2 | #PIP := PIP_DOWNLOAD_CACHE=$${HOME}/.pip_download_cache env/bin/pip 3 | PIP := env/bin/pip 4 | NOSE := env/bin/nosetests 5 | PY := env/bin/python 6 | 7 | # -------- Environment -------- 8 | # env is a folder so no phony is necessary 9 | env: 10 | virtualenv env 11 | 12 | .PHONY: deps 13 | deps: env packages/.done 14 | # see http://tartley.com/?p=1423&cpage=1 15 | # --upgrade needed to force local (if there's a system install) 16 | $(PIP) install --upgrade --no-index --find-links=file://$${PWD}/packages -r requirements.txt 17 | 18 | packages/.done: 19 | mkdir packages; \ 20 | $(PIP) install --download packages -r requirements.txt;\ 21 | touch packages/.done 22 | 23 | # rm_env isn't a file so it needs to be marked as "phony" 24 | .PHONY: rm_env 25 | rm_env: 26 | rm -rf env 27 | 28 | 29 | # --------- Dev -------------------- 30 | .PHONY: dev 31 | dev: deps 32 | $(PY) setup.py develop 33 | 34 | 35 | # --------- Testing ---------- 36 | .PHONY: test 37 | test: deps $(NOSE) 38 | $(NOSE) 39 | 40 | # nose depends on the nosetests binary 41 | $(NOSE): packages/.done-nose 42 | $(PIP) install --upgrade --no-index --find-links=file://$${PWD}/packages nose 43 | 44 | packages/.done-nose: 45 | # need to drop this marker. otherwise it 46 | # downloads everytime 47 | $(PIP) install --download packages nose;\ 48 | touch packages/.done-nose 49 | 50 | 51 | 52 | # --------- PyPi ---------- 53 | .PHONY: build 54 | build: env 55 | $(PY) setup.py sdist 56 | 57 | .PHONY: upload 58 | upload: env 59 | $(PY) setup.py sdist register upload 60 | 61 | .PHONY: clean 62 | clean: 63 | rm -rf dist *.egg-info 64 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009 Matt Harrison 2 | 3 | Intro 4 | ===== 5 | 6 | Poachplate is a script for generating python scripts. Really it just 7 | generates boilerplate content, such as: 8 | 9 | * setup.py 10 | * bin/script 11 | * scriptlib/__init__.py 12 | * test/test.py 13 | * README 14 | * LICENSE 15 | 16 | Some of the content is just "FILL IN", but this can be fixed by 17 | setting the appropriate values in a ``~/.config/poachplate.ini`` file, 18 | that looks like this:: 19 | 20 | [properties] 21 | author : FILL IN 22 | email : FILL IN 23 | version : 0.1 24 | 25 | Installation 26 | ============ 27 | 28 | Unpack the tarball and run:: 29 | 30 | make dev 31 | 32 | This will create a virtual environment ``env`` and run ``python 33 | setup.py develop`` inside of it. 34 | 35 | Invocation 36 | ========== 37 | 38 | Invoke the script (after activating the virtualenv - 39 | ``env/bin/activate``) like so:: 40 | 41 | poachplate FooBar 42 | 43 | And it will create the following "Compromise Layout", where there is a 44 | script but the implementation goes in ``scriptlib/__init__.py`` (or 45 | where you want to put it). The idea is to have a script in the PATH 46 | that is executable (so people can run it), as well as a package in 47 | PYTHONPATH, so people can import/test/reuse the code. 48 | 49 | Here's the layout:: 50 | 51 | FooBar/ 52 | INSTALL 53 | LICENSE 54 | README 55 | MANIFEST.in 56 | setup.py 57 | bin/ 58 | foobar # small wrapper 59 | foobarlib/ 60 | __init__.py # put implementation here 61 | meta.py # version/author/email 62 | test/ 63 | test.py 64 | 65 | Todo 66 | ==== 67 | 68 | * Be verbose during creating, ie: creating "FooBar/".... 69 | * Use templates in files for content 70 | * Fix issues people have 71 | * Add features people want 72 | * Perhaps OT. Provide a library of common script functions. 73 | 74 | Changelog 75 | ========= 76 | 77 | 0.1 - Feb 12, 2009 78 | ------------------ 79 | 80 | Initial release 81 | 82 | 0.1.4 - June 2, 2009 83 | -------------------- 84 | 85 | Various bug fixes - Thanks to Chad Harrington 86 | 87 | 0.1.5 - March 7, 2013 88 | 89 | Retweaking, py3 fixes 90 | -------------------------------------------------------------------------------- /poachplatelib/package.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2009 Matt Harrison 3 | 4 | import ConfigParser 5 | from contextlib import contextmanager 6 | import datetime 7 | import optparse 8 | import os 9 | import sys 10 | 11 | 12 | from poachplatelib import meta 13 | 14 | DEFAULT_CONFIG = """ 15 | [properties] 16 | author : 'FILL IN' 17 | email : 'FILL IN' 18 | version : 0.1 19 | """ 20 | 21 | CONFIG_LOC = os.path.expanduser('~/.config/poachplate.ini') 22 | YEAR = datetime.date.today().strftime('%Y') 23 | 24 | COPYRIGHT = '# Copyright (c) %(year)s %(author)s' 25 | 26 | INIT = '''#!/usr/bin/env python 27 | %(script_copyright)s 28 | 29 | import sys 30 | import optparse 31 | 32 | import meta 33 | 34 | def main(prog_args): 35 | parser = optparse.OptionParser(version=meta.__version__) 36 | opt, args = parser.parse_args(prog_args) 37 | 38 | if __name__ == '__main__': 39 | sys.exit(main(sys.argv)) 40 | 41 | ''' 42 | 43 | 44 | MANIFEST = """include README 45 | include LICENSE 46 | include INSTALL 47 | """ 48 | 49 | TEST = """%(script_copyright)s 50 | 51 | import unittest 52 | 53 | import %(libname)s 54 | 55 | class Test%(libnamecap)s(unittest.TestCase): 56 | def test_main(self): 57 | pass 58 | 59 | if __name__ == '__main__': 60 | unittest.main() 61 | """ 62 | 63 | SCRIPT = """#!/usr/bin/env python 64 | %(script_copyright)s 65 | 66 | import sys 67 | 68 | import %(libname)s 69 | 70 | if __name__ == '__main__': 71 | try: 72 | sys.exit(%(libname)s.main(sys.argv)) 73 | except Exception, e: 74 | sys.stderr.write('%%s\\n'%%str(e)) 75 | sys.exit(1) 76 | 77 | """ 78 | 79 | META = """%(script_copyright)s 80 | 81 | __version__ = '%(version)s' 82 | __author__ = '%(author)s' 83 | __email__ = '%(email)s' 84 | """ 85 | 86 | SETUP = """%(script_copyright)s 87 | #from distutils.core import setup 88 | from setuptools import setup 89 | 90 | from %(libname)s import meta 91 | 92 | setup(name='%(name)s', 93 | version=meta.__version__, 94 | author=meta.__author__, 95 | description='FILL IN', 96 | %(script)s 97 | package_dir={'%(libname)s':'%(libname)s'}, 98 | packages=['%(libname)s'], 99 | ) 100 | """ 101 | 102 | MAKEFILE = """# variables to use sandboxed binaries 103 | PIP := PIP_DOWNLOAD_CACHE=$${HOME}/.pip_download_cache env/bin/pip 104 | NOSE := env/bin/nosetests 105 | PY := env/bin/python 106 | 107 | # -------- Environment -------- 108 | # env is a folder so no phony is necessary 109 | env: 110 | virtualenv env 111 | 112 | .PHONY: deps 113 | deps: env packages/.done 114 | # see http://tartley.com/?p=1423&cpage=1 115 | $(PIP) install --no-index --find-links=file://$${PWD}/packages -r requirements.txt 116 | 117 | packages/.done: 118 | mkdir packages; \ 119 | $(PIP) install --download packages -r requirements.txt;\ 120 | touch packages/.done 121 | 122 | # rm_env isn't a file so it needs to be marked as "phony" 123 | .PHONY: rm_env 124 | rm_env: 125 | rm -rf env 126 | 127 | # --------- Testing ---------- 128 | .PHONY: test 129 | test: deps $(NOSE) 130 | $(NOSE) 131 | 132 | # nose depends on the nosetests binary 133 | $(NOSE): packages/.done-nose 134 | $(PIP) install --upgrade --no-index --find-links=file://$${PWD}/packages nose 135 | 136 | packages/.done-nose: 137 | # need to drop this marker. otherwise it 138 | # downloads everytime 139 | $(PIP) install --download packages nose;\ 140 | touch packages/.done-nose 141 | 142 | # --------- PyPi ---------- 143 | 144 | .PHONY: build 145 | build: 146 | $(PY) setup.py sdist 147 | 148 | .PHONY: upload 149 | upload: 150 | $(PY) setup.py sdist upload 151 | 152 | """ 153 | 154 | @contextmanager 155 | def push_pop_dir(dirname): 156 | cur_dir = os.getcwd() 157 | os.chdir(dirname) 158 | yield 159 | os.chdir(cur_dir) 160 | 161 | class _Unset(object): 162 | """ We need a value to represent unset for configuration. None 163 | doesn't really work since the user might want None to be a valid 164 | value 165 | """ 166 | pass 167 | 168 | 169 | def cascade_value(opt=None, opt_name=None, # optparse 170 | env_name=None, # os.environ 171 | cfg=None, cfg_section=None, cfg_name=None, # ConfigParser 172 | default=None): 173 | """ 174 | Allow a posix style cascading config 175 | """ 176 | # get from cmd line 177 | value = _Unset() 178 | if opt and opt_name: 179 | try: 180 | value = opt.__getattr__(opt_name) 181 | except AttributeError, e: 182 | pass 183 | if not isinstance(value, _Unset): 184 | return value 185 | 186 | # get from env 187 | if env_name: 188 | try: 189 | value = os.environ[env_name] 190 | except KeyError, e: 191 | pass 192 | if not isinstance(value, _Unset): 193 | return value 194 | 195 | # get from config file 196 | if cfg and cfg_section and cfg_name: 197 | try: 198 | value = cfg.get(cfg_section, cfg_name) 199 | except ConfigParser.NoOptionError, e: 200 | pass 201 | if not isinstance(value, _Unset): 202 | return value 203 | 204 | return default 205 | 206 | 207 | def create_config(): 208 | if not os.path.exists(CONFIG_LOC): 209 | create_file(fout(CONFIG_LOC), DEFAULT_CONFIG) 210 | 211 | def create_file(fout, content=None): 212 | if not content is None: 213 | fout.write(content) 214 | 215 | def fout(name): 216 | out = open(name, 'w') 217 | return out 218 | 219 | class Package(object): 220 | def __init__(self, name, libname=None, scriptname=None, opt=None, cfg_loc=None, 221 | bin_dir='bin', test_dir='test'): 222 | self.name = name 223 | pep8name = self.name.lower() 224 | self.libname = libname or pep8name+'lib' 225 | 226 | for filename in [pep8name, self.libname]: 227 | if not filename[0].isalpha(): 228 | raise NameError, '%s should start with alpha character' % filename 229 | self.bin_dir = bin_dir 230 | self.scriptname = scriptname or pep8name 231 | self.scriptpath = """scripts=['%(bin_dir)s/%(file)s'],""" % \ 232 | {'bin_dir':self.bin_dir,'file':self.scriptname} if \ 233 | self.scriptname else '' 234 | 235 | self.test_dir = test_dir 236 | 237 | cfg = None 238 | if cfg_loc and os.path.exists(cfg_loc): 239 | cfg = ConfigParser.ConfigParser() 240 | cfg.read(cfg_loc) 241 | 242 | self.author = cascade_value(opt=opt, opt_name='author', 243 | cfg=cfg, cfg_section='properties', cfg_name='author', 244 | default='FILL IN') 245 | self.version = cascade_value(opt=opt, opt_name='version', 246 | cfg=cfg, cfg_section='properties', cfg_name='version', 247 | default='0.1') 248 | self.email = cascade_value(opt=opt, opt_name='email', 249 | cfg=cfg, cfg_section='properties', cfg_name='email', 250 | default='FILL IN') 251 | 252 | 253 | 254 | 255 | def generate(self): 256 | self._copyright = COPYRIGHT % {'author':self.author,'year':YEAR} 257 | self._init = INIT % {'script_copyright':self._copyright} 258 | self._meta = META % {'version':self.version, 259 | 'author':self.author, 260 | 'email':self.email, 261 | 'script_copyright':self._copyright} 262 | self._setup = SETUP % {'name':self.name, 263 | 'script':self.scriptpath, 264 | 'libname':self.libname, 265 | 'script_copyright':self._copyright} 266 | self._requirements = '' 267 | self._makefile = MAKEFILE 268 | self._script = SCRIPT % {'libname':self.libname, 269 | 'script_copyright':self._copyright} 270 | self._test = TEST %{'libname':self.libname, 271 | 'libnamecap':self.libname.capitalize(), 272 | 'script_copyright':self._copyright} 273 | 274 | def write(self): 275 | self.generate() 276 | if os.path.exists(self.name): 277 | raise NameError, '%s project directory already exists' % self.name 278 | 279 | os.makedirs(self.name) 280 | with push_pop_dir(self.name): 281 | 282 | os.makedirs(self.libname) 283 | with open(os.path.join(self.libname, '__init__.py'), 'w') as fout: 284 | fout.write(self._init) 285 | with open(os.path.join(self.libname, 'meta.py'), 'w') as fout: 286 | fout.write(self._meta) 287 | with open('setup.py', 'w') as fout: 288 | fout.write(self._setup) 289 | with open('requirements.txt', 'w') as fout: 290 | fout.write(self._requirements) 291 | with open('Makefile', 'w') as fout: 292 | fout.write(MAKEFILE) 293 | 294 | for fname in ['README', 'LICENSE', 'INSTALL']: 295 | with open(fname, 'w') as fout: 296 | fout.write('FILL IN') 297 | 298 | with open('MANIFEST.in', 'w') as fout: 299 | fout.write(MANIFEST) 300 | 301 | if self.scriptname: 302 | # Make a nice non .py script for end users. It catches 303 | # exceptions and just prints out the error rather than a stack 304 | # trace, so as to not scare the end user. 305 | os.makedirs(self.bin_dir) 306 | with open(os.path.join(self.bin_dir, self.scriptname), 'w') as fout: 307 | fout.write(self._script) 308 | 309 | os.makedirs(self.test_dir) 310 | with open(os.path.join(self.test_dir, 'test%s.py' % self.libname), 'w') as fout: 311 | fout.write(self._test) 312 | 313 | 314 | 315 | def main(prog_args): 316 | usage = """Specify a library name for your script. If your library name has capital letters 317 | in it, they will be converted to lowercase for package and script names.""" 318 | parser = optparse.OptionParser(usage=usage, version=meta.__version__) 319 | opt, args = parser.parse_args(prog_args) 320 | 321 | if len(sys.argv) > 1: 322 | p = Package(sys.argv[1], opt=opt, cfg_loc=CONFIG_LOC) 323 | p.write() 324 | else: 325 | parser.print_help() 326 | 327 | 328 | 329 | if __name__ == '__main__': 330 | sys.exit(main(sys.argv) or 0) 331 | --------------------------------------------------------------------------------