Skip to content


Subversion checkout URL

You can clone with
Download ZIP


implement argparse and support overriding the skeleton with a filesystem... #44

merged 5 commits into from

2 participants


... dir or tarball (re #43)

It wouldn't be that hard to add something to grab from a URL like Django does, and to support zip files as well as tarballs, if there's interest...


This is a great idea that I wanted to do for some time. Give me some time to look at the diff.

  1. Styling: could you make sure we do foo,[space]bar everywhere?
  2. Why do we need the wsgiref added to requirements?

re 1) i'll update the pull request in a minute with a pass that tries to find all of those...
re 2) wsgiref is a dependency of boto, and I made requirements.txt with 'pip freeze' which is completist. We can take it out; it'll get installed anyway with boto.


ok, i think i fixed those issues, and added some more sugar as well. I think I've tested each case (no arg, local dir, local zip, local tar, url zip, and url tar) but it wouldn't hurt for someone else to do it too...


FWIW, a friend pointed me to Middleman's method for handling diverse templates. There's a bit of an appeal to the shorter form of the argument and the idea that they are listable... might be a thought for a future iteration?


Yeah I wanted that too. I think just a hosted file with handle, description and github repo would be pretty ideal.

@koenbok koenbok merged commit 89d6b06 into koenbok:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 68 additions and 29 deletions.
  1. +28 −14 cactus/
  2. +37 −15 cactus/
  3. +3 −0  requirements.txt
42 cactus/
@@ -7,7 +7,26 @@
import cactus
-def create(path):
+import argparse
+description = '''
+Usage: cactus [create|build|serve|deploy]
+ create: Create a new website skeleton at path
+ build: Rebuild your site from source files
+ serve <port>: Serve you website at local development server
+ deploy: Upload and deploy your site to S3
+def _init_parser():
+ parser = argparse.ArgumentParser(description=description)
+ parser.add_argument('command', metavar="COMMAND", help="The command to execute (one of [create|build|serve|deploy] )")
+ parser.add_argument('option1', metavar="OPTION1", nargs='?', help="option 1")
+ parser.add_argument('option2', metavar="OPTION2", nargs='?', help="option 2")
+ parser.add_argument('--skeleton', required=False, help="If provided, the path to a .tar.gz file or a directory which will be used in place of the default 'skeleton' for a cactus project.")
+ return parser
+def create(path, args):
"Creates a new project at the given path."
if os.path.exists(path):
@@ -17,7 +36,7 @@ def create(path):
site = cactus.Site(path)
- site.bootstrap()
+ site.bootstrap(skeleton=args.skeleton)
def build(path):
@@ -42,14 +61,7 @@ def deploy(path):
def help():
- print
- print 'Usage: cactus [create|build|serve|deploy]'
- print
- print ' create: Create a new website skeleton at path'
- print ' build: Rebuild your site from source files'
- print ' serve <port>: Serve you website at local development server'
- print ' deploy: Upload and deploy your site to S3'
- print
+ print description
def exit(msg):
print msg
@@ -57,9 +69,11 @@ def exit(msg):
def main():
- command = sys.argv[1] if len(sys.argv) > 1 else None
- option1 = sys.argv[2] if len(sys.argv) > 2 else None
- option2 = sys.argv[3] if len(sys.argv) > 3 else None
+ parser = _init_parser()
+ args = parser.parse_args()
+ command = args.command
+ option1 = args.option1
+ option2 = args.option2
# If we miss a command we exit and print help
if not command:
@@ -69,7 +83,7 @@ def main():
# Run the command
if command == 'create':
if not option1: exit('Missing path')
- create(option1)
+ create(option1, args)
elif command == 'build':
52 cactus/
@@ -1,4 +1,4 @@
-import os
+import os, os.path
import sys
import shutil
import logging
@@ -11,6 +11,8 @@
import socket
import tempfile
import tarfile
+import zipfile
+import urllib
import boto
@@ -64,24 +66,44 @@ def verify(self):'This does not look like a (complete) cactus project (missing "%s" subfolder)', p)
- def bootstrap(self):
+ def bootstrap(self, skeleton=None):
- Bootstrap a new project at a given path.
+ Bootstrap a new project at a given path. If provided, the skeleton argument will be used as the basis for the new cactus project, in place of the default skeleton. If provided, the argument can be a filesystem path to a directory, a tarfile, a zipfile, or a URL which retrieves a tarfile or a zipfile.
- from .skeleton import data
- skeletonFile = tempfile.NamedTemporaryFile(delete=False, suffix='.tar.gz')
- skeletonFile.write(base64.b64decode(data))
- skeletonFile.close()
+ skeletonArchive = skeletonFile = None
+ if skeleton is None:
+ from .skeleton import data
+"Building from data")
+ temp = tempfile.NamedTemporaryFile(delete=False, suffix='.tar.gz')
+ temp.write(base64.b64decode(data))
+ temp.close()
+ skeletonArchive =, mode='r')
+ elif os.path.isfile(skeleton):
+ skeletonFile = skeleton
+ else: # assume it's a URL
+ skeletonFile, headers = urllib.urlretrieve(skeleton)
- os.mkdir(self.path)
- skeletonArchive =, mode='r')
- skeletonArchive.extractall(path=self.path)
- skeletonArchive.close()
-'New project generated at %s', self.path)
+ if skeletonFile:
+ if tarfile.is_tarfile(skeletonFile):
+ skeletonArchive =, mode='r')
+ elif zipfile.is_zipfile(skeletonFile):
+ skeletonArchive = zipfile.ZipFile(skeletonFile)
+ else:
+ import pdb; pdb.set_trace()
+ logging.error("File %s is an unknown file archive type. At this time, skeleton argument must be a directory, a zipfile, or a tarball." % skeletonFile)
+ sys.exit()
+ if skeletonArchive:
+ os.mkdir(self.path)
+ skeletonArchive.extractall(path=self.path)
+ skeletonArchive.close()
+'New project generated at %s', self.path)
+ elif os.path.isdir(skeleton):
+ shutil.copytree(skeleton, self.path)
+'New project generated at %s', self.path)
+ else:
+ logging.error("Cannot process skeleton '%s'. At this time, skeleton argument must be a directory, a zipfile, or a tarball." % skeleton)
def context(self):
3  requirements.txt
@@ -0,0 +1,3 @@
Something went wrong with that request. Please try again.