Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bruth committed Oct 21, 2012
0 parents commit e1f5c53
Show file tree
Hide file tree
Showing 102 changed files with 19,141 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
*.ipynb
*.sw?
*.py?
*.egg
*.egg-info
dist
docs
MANIFEST
29 changes: 29 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Copyright (c) The Children's Hospital of Philadelphia and individual
contributors.

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3. Neither the name of Django nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include README.md
include LICENSE

graft harvest/template
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Harvest

## Install

```python
pip install harvest
```

## Harvest CLI

### Start A New Project

```python
harvest init myproject
```

This command performs the following steps:

- Create a new virtualenv environment (name myproject-env)
- Install Django
- Create a starter project structure using the built-in Harvest template
- Install the base dependencies
- Sync and migrate a SQLite database, this requires you to answer a couple
prompts
- Collect the static files (mainly due to Cilantro)
38 changes: 38 additions & 0 deletions bin/harvest
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python

import harvest
from harvest.commands import init
from collections import OrderedDict
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter

# Top-level commands
commands = OrderedDict([
('init', init),
])

# Top-level argument parser
parser = ArgumentParser(description=harvest.__doc__,
version='Harvest v{}'.format(harvest.__version__),
formatter_class=ArgumentDefaultsHelpFormatter)

# Add sub-parsers for each command
subparsers = parser.add_subparsers(dest='command')

# Populate subparsers
for key in commands:
module = commands[key]
# Add it by name
subparser = subparsers.add_parser(key, add_help=False,
formatter_class=ArgumentDefaultsHelpFormatter)
# Update subparser with properties of module parser. Keep
# track of the generated `prog` since it is relative to
# the top-level command
prog = subparser.prog
subparser.__dict__.update(module.parser.__dict__)
subparser.prog = prog

# Parse command-line arguments
options = parser.parse_args()

# Get the module and call the main function
commands[options.command].parser.handle(options)
18 changes: 18 additions & 0 deletions harvest/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
__version_info__ = {
'major': 0,
'minor': 9,
'micro': 0,
'releaselevel': 'beta',
'serial': 1
}

def get_version(short=False):
assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final')
vers = ["%(major)i.%(minor)i" % __version_info__, ]
if __version_info__['micro']:
vers.append(".%(micro)i" % __version_info__)
if __version_info__['releaselevel'] != 'final' and not short:
vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial']))
return ''.join(vers)

__version__ = get_version()
Empty file added harvest/commands/__init__.py
Empty file.
102 changes: 102 additions & 0 deletions harvest/commands/init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from __future__ import print_function
import re
import os
import sys
import harvest
from fabric.api import prefix
from fabric.operations import local
from fabric.colors import red, green
from fabric.context_managers import hide, lcd
from harvest.decorators import cli

__doc__ = """\
Creates and sets up a new Harvest project.
"""

HARVEST_TEMPLATE_PATH = os.path.join(os.path.abspath(os.path.dirname(harvest.__file__)), 'template')
STARTPROJECT_ARGS = '--template {} -e py,ini,gitignore,in,conf,md,sample ' \
'-n Makefile'.format(HARVEST_TEMPLATE_PATH)


def valid_name(name):
if re.match(r'^[a-z_]\w*$', name, re.I) is not None:
return True
return False


@cli(description=__doc__)
def parser(options):
project_name = options.project_name
create_env = options.create_env
verbose = options.verbose

if not valid_name(project_name):
print(red("Error: The project name '{}' must be a valid Python "
"identifier.".format(project_name)))
sys.exit()

# Ensure the name does not conflict with an existing Python module
try:
__import__(project_name)
print(red("Error: The project name '{}' conflicts with an existing "
"Python module. Please choose another name.".format(project_name)))
sys.exit()
except ImportError:
pass

hidden_output = ['running']
if not verbose:
hidden_output.append('stdout')

with hide(*hidden_output):
# Check for virtualenv
if create_env:
env_path = '{}-env'.format(project_name)
if os.path.exists(env_path):
print(red("Error: Cannot create environment '{}'. A " \
"directory with the same already exists.".format(env_path)))
sys.exit()
print(green("Setting up a virtual environment '{}'...".format(env_path)))
local('virtualenv {}'.format(env_path))
else:
env_path = '.'

with lcd(env_path):
with prefix('source bin/activate'):
print(green('Installing Django...'))
local('pip install "django>=1.4,<1.5"')

print(green("Creating new Harvest project '{}'...".format(project_name)))
local('django-admin.py startproject {} {}'.format(STARTPROJECT_ARGS, project_name))

with lcd(os.path.join(env_path, project_name)):
with prefix('source ../bin/activate'):
print(green('Downloading and installing dependencies...'))
local('pip install -r requirements.txt')

print(green('Collecting static files...'))
local('make collect')

print(green('Setting up a SQLite database...'))
with hide('running'):
with lcd(os.path.join(env_path, project_name)):
with prefix('source ../bin/activate'):
local('./bin/manage.py syncdb --migrate')
print(green('''
Complete! Copy and paste the following in your shell:
cd {}/{}
source ../bin/activate
./bin/manage.py runserver
Then open up a web browser and go to: http://localhost:8000
'''.format(env_path, project_name)))


parser.add_argument('project_name', help='Name of the Harvest project. This '
'must be a valid Python identifier.')
parser.add_argument('--no-env', action='store_false', dest='create_env',
default=True, help='Prevents creating a virtualenv and sets up the ' \
'project in the current directory.')
parser.add_argument('--verbose', action='store_true',
help='Print stdout output during installation process.')
31 changes: 31 additions & 0 deletions harvest/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from argparse import ArgumentParser


def cached_property(func):
cach_attr = '_{}'.format(func.__name__)

@property
def wrap(self):
if not hasattr(self, cach_attr):
value = func(self)
if value is not None:
setattr(self, cach_attr, value)
return getattr(self, cach_attr, None)
return wrap


def cli(*args, **kwargs):
def decorator(func):
class Parser(ArgumentParser):
def handle(self, *args, **kwargs):
try:
func(*args, **kwargs)
except Exception, e:
self.error(e.message)

# No catching of exceptions
def handle_raw(self, *args, **kwargs):
func(*args, **kwargs)

return Parser(*args, **kwargs)
return decorator
18 changes: 18 additions & 0 deletions harvest/template/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
include requirements.txt
include wsgi.py
include bin/manage.py
include Makefile

graft _site
graft server

global-exclude .DS_Store

exclude {{ project_name }}/static/scripts/javascript/app.build.js
exclude {{ project_name }}/static/scripts/javascript/min/build.txt
exclude {{ project_name }}/conf/local_settings.py

prune _site/static
prune {{ project_name }}/static/scripts/coffeescript
prune {{ project_name }}/static/stylesheets/scss
prune tests
68 changes: 68 additions & 0 deletions harvest/template/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
WATCH_FILE = .watch-pid
MANAGE_SCRIPT = ./bin/manage.py
SITE_DIR = ./_site
STATIC_DIR = ./{{ project_name }}/static
COFFEE_DIR = ${STATIC_DIR}/scripts/coffeescript
JAVASCRIPT_DIR = ${STATIC_DIR}/scripts/javascript
JAVASCRIPT_SRC_DIR = ${JAVASCRIPT_DIR}/src
JAVASCRIPT_MIN_DIR = ${JAVASCRIPT_DIR}/min

SASS_DIR = ${STATIC_DIR}/stylesheets/scss
CSS_DIR = ${STATIC_DIR}/stylesheets/css

COMPILE_SASS = `which sass` \
--scss \
--style=compressed \
-r ${SASS_DIR}/lib/bourbon/lib/bourbon.rb \
${SASS_DIR}:${CSS_DIR}

COMPILE_COFFEE = `which coffee` -b -o ${JAVASCRIPT_SRC_DIR} -c ${COFFEE_DIR}
WATCH_COFFEE = `which coffee` -w -b -o ${JAVASCRIPT_SRC_DIR} -c ${COFFEE_DIR}

REQUIRE_OPTIMIZE = `which node` ./bin/r.js -o ${JAVASCRIPT_DIR}/app.build.js

all: build collect

setup:
@if [ ! -f ./{{ project_name }}/conf/local_settings.py ] && [ -f ./{{ project_name }}/conf/local_settings.py.sample ]; then \
echo 'Creating local_settings.py...'; \
cp ./{{ project_name }}/conf/local_settings.py.sample ./{{ project_name }}/conf/local_settings.py; \
fi;

build: sass coffee optimize

collect:
@echo 'Symlinking static files...'
@${MANAGE_SCRIPT} collectstatic --link --noinput > /dev/null

sass:
@echo 'Compiling Sass/SCSS...'
@mkdir -p ${CSS_DIR}
@${COMPILE_SASS} --update

coffee:
@echo 'Compiling CoffeeScript...'
@${COMPILE_COFFEE}

watch: unwatch
@echo 'Watching in the background...'
@${WATCH_COFFEE} > /dev/null 2>&1 & echo $$! > ${WATCH_FILE}
@${COMPILE_SASS} --watch > /dev/null 2>&1 & echo $$! >> ${WATCH_FILE}

unwatch:
@if [ -f ${WATCH_FILE} ]; then \
echo 'Watchers stopped'; \
for pid in `cat ${WATCH_FILE}`; do kill -9 $$pid; done; \
rm ${WATCH_FILE}; \
fi;

optimize: clean
@echo 'Optimizing JavaScript...'
@mkdir -p ${JAVASCRIPT_MIN_DIR}
@${REQUIRE_OPTIMIZE} > /dev/null

clean:
@rm -rf ${JAVASCRIPT_MIN_DIR}


.PHONY: all build sass coffee watch unwatch optimize
Loading

0 comments on commit e1f5c53

Please sign in to comment.