Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
A straightforward and versatile build system written in python
JavaScript Python CSS Other
Branch: master

Merge pull request #32 from bitdeli-chef/master

Add a Bitdeli Badge to README
latest commit ae46d1e2ed
@etabard etabard authored
Failed to load latest commit information.
bin 1.3 release
later/envjs
plugins/sublimetext2
puke
.gitignore + gitignore
LICENSE Update doc and add license
MANIFEST.in Puke jsdoc
README.md Add a Bitdeli badge to README
go.sh Notes about the XCode dependency
setup.py console.say on Mac OS X (e.g console.say("Build failed!"))

README.md

<!!!> New puke ahead! Read https://github.com/webitup/puke/wiki <!!!>

Building: puke it right

   .-'"'-.
  / `. ,' \
 |  ,' `.  |
 |   ___   |
  \ ( . ) /
   '-.:.-'
     .:.
     :::          
     :::
     ::.
     '::
      ' 

Beware!

This is mostly an experimental tool, built out of a growing displeasure having to work with existing similar softwares, and for the sole purpose of fitting our specific build needs.

While giving puke an OSS license made sense, while the author is happy to work on it, and while it might suit you, it's quite likely that it won't, and that you would find yourself more satisfied with one of the other solution out there (rake and scons come to mind).

Technology

We are using python, and a homemade system named "puke". It's quite similar to rake, jake, jasy, scons, etc, except:

  • it doesn't suck ass, unlike ruby
  • it installs painless, unlike ruby
  • it's extremely straightforward and does just a couple of simple things, avoiding both bloat and indigest documentation
  • it's python, so it's cool and sexy, unlike ruby :)

Features

Basic file manipulation, js linting via closure, minification via closure and YUI, scss parser, js documentation via jsdoctoolkit, VirtualEnv creation with package installation, system package check.

Changelog

1.5

  • Speak mode on build fail/success (Mac OS X only)
  • console.say on Mac OS X (e.g console.say("Build failed!"))
  • Require.merge() makes now a deep merge
  • sh() can take now multiple commandes : sh(['cd somepath', 'do something'])
  • prompt('message', 'default') to prompt during the build
  • New FileSystem api
  • New System api
  • New Utils api
  • New VirtualEnv api
  • patch('dir/to/patch', patchfile) (Supports unix patch format only : man patch)
  • Task parameters (puke task arg1 arg2)
  • Task infos (puke task -i docstring style)
  • few fixes

Install

There is two ways to get there.

Either the recommended sandboxed way (using brew, for MacOSX), read 1.

Or the "system" way (MacOSX and Linux), read 2.

If you don't understand what I'm saying, you are on mac, so just follow 1. If you do, then you already have a working python + easy_install environment, right? Move on to step 3.

1. Sandboxed way

# Install XCode:
http://developer.apple.com/technologies/xcode.html

# Install brew:
sudo mkdir /usr/local
sudo chmod g+rwx /usr/local
sudo chown root:staff local
/usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/gist/323731)"

# Install python with brew:
brew install python

# Update your .profile so that the brew python is used
echo 'export PATH="/usr/local/share/python:/usr/local/bin:/usr/local/sbin:$PATH"' >> ~/.profile
source ~/.profile

# Double-check that the sandboxed python is used
which easy_install
# Should output/usr/local/share/python/easy_install
which python
# Should output /usr/local/bin/python

Now go to step three.

2. The system way

You have nothing special to do, but you will need to be root in some of the following steps.

Go to step three now.

3. Install pip

If on Mac:

easy_install pip

If on Linux, just make sure you got pip:

# If debian based...
sudo aptitude install python-pip
# or do it whatever way pleases you

4. Install puke

# Get some puke on you
pip install --upgrade puke

Whenever you want to upgrade to the latest version, just do it again

pip install puke --upgrade

Yeah, that's it.

Gotchas

  • some puke functionalities require a working jre. Lion has one by default (does it?). Ubuntu fans might just do a:

    sudo aptitude install openjdk-7-jre
    
  • if you happen to have libyaml-dev installed, you need to have python-dev as well. This is not a puke requirement, but a pyyaml gotcha actually.

Oh, did I mentioned the BIG FAT WARNING MANU BUG?

First time puke runs, it does patch some internal dependency (closure). So, IN THE CASE YOU USED THE SYSTEM WAY, do this after install:

sudo puke --patch

Don't forget to do that (yet again, only if you installed puke as ROOT) every time you install.

That's it.

Usage

You pretty much create a puke file, which is a very simple python script that describes build tasks for your project. You may name it either "pukefile", or "pukefile.py".

Then you just call "puke someTaskName".

Help me puke :

 
$ puke --help
Usage: puke [options]

Options:
  -h, --help            show this help message and exit
  -c, --clear           Spring time, clean all the vomit
  -q, --quiet           don't print status messages to stdout
  -v, --verbose         print more detailed status messages to stdout
  -t, --tasks           list tasks
  -l LOGFILE, --log=LOGFILE
                        Write debug messages to given logfile
  -f FILE, --file=FILE  Use the given build script
  -p, --patch           Patch closure
  -s, --speak           puke speaks on fail/success (Mac OS X only)

Can't remember your tasks ? Just puke it

$ puke --tasks
 No tasks to execute. Please choose from: 
 test: coin coin
 simple: Simple Test
 lint: lint
 doc: Documentation
 gate: Build gate package

Puke 10 seconds API reference

Header:

#!/usr/bin/env puke
# -*- coding: utf8 -*-

Defining a simple task:

Name your task "default" in order to have it executed by simply puke-ing

@task("Simple Test")
def simple():
   console.log("Do something")

import python script

| pukefile.py
| helpers.py

=> helpers.py
from puke import *

def test():
  f = FileList('some/path')
  print "success"

=> pukefile.py
import helpers

helpers.test()

Calling a task from another task:

executeTask('simple')

Defining "default" task:

@task("")
def default():
   executeTask('simple')

@task("Simple Test")
def simple():
   console.log("Do something")

Executing tasks with args

Your pukefile

@task('with args')
def test(required, optional = 'default'):
   """Python docstring style comment"""
   print "Required : %s" % required
   print "Optional : %s" % optional

Execute puke

puke test value
>>> Required : value
>>> Optional : default

Need help with your params ?

puke test --info
>>> -------------------------------------
>>> * Help test (task description) 
>>> -------------------------------------
>>> Help on function test in module puke:
>>>
>>> test(required, optional = 'default')
>>>    Python docstring style comment

Require (json / yaml)

r = Require('global.yaml')
r.merge('build.yaml')

Working with environment variables (Yaml example):

params:
  #           envvar name | default
  build_dir: "${BUILD_DIR}|/usr/toto/build/"
  string: "toto"

Yak it and make the puke easier!

r = Require('global.yaml')
r.yak('params')

@task("Simple Test")
def simple():
   console.log("Easy to get my conf", Yak.build_dir, Yak.string)

#Check if your param exists
if 'build_dir' in Yak:
   print "yes"

Straight access to environment variables

Env.get('BUILD_DIR', 'default')

Logging:

# Info levels
console.info("info", arg2, ...)
console.confirm("confirm", arg2, ...)
console.log("log", arg2, ...)
console.debug("debug", arg2, ...)
console.warn("warn", arg2, ...)
console.error("error", arg2, ...)

console.header("header")
console.fail("fail")


#Mac OS X only
console.say("Build failed!")

Speak options (Mac OS X only):

  #Manually enable/disable speak mode
  Console.SPEAK_ENABLED = True

  #Custom message on build fail
  Console.SPEAK_MESSAGE_ON_FAIL = "Shit happens..."
  Console.SPEAK_MESSAGE_ON_SUCCESS = "Holy macaroni!"

  #Deactivate success alert
  Console.SPEAK_MESSAGE_ON_SUCCESS = None

  #Deactivate fail alert
  Console.SPEAK_MESSAGE_ON_FAIL = None

Prompt :

#prompt(message,default)
answer = prompt('How are you doing ?', 'fuck off')

Getting a FileList:

list = FileList("foldername", filter = "*.js", exclude = "*.min.js")`

# multicriteria
list2 = FileList("src", filter = "*.css,*.scss")

#merge lists
list.merge(list2) 

or

list = ["somefilepath", "someother", "http://example.com/something"]

Note that http:// urls are supported

Note that SCSS are automatically parsed

Merging files into one:

combine(list, "build/test.js")

Deep-copying (folder list):

deepcopy(list, 'build/copy/')

Minifying (with closure for js, and yahoo ui for css):

minify("build/test.js", "build/test.min.js")

Patch (unix patch format)

patch('dir/to/patch', patchfile)

Pack/unpack (gz, zip)

Packing :

#zip
pack(list, "folder/something.zip")

#gz
pack(list, "folder/something.tar.gz")

Unpacking :

#zip
unpack('folder/something.zip', 'folder/test-unpack/')

#gz
unpack('build/something.tar.gz', 'folder/test-unpack/')

Call system:

sh("pwd")
#get output
pwd = sh("pwd")
#multiple commands
sh(['cd somepath', 'do something])

Get FileList stats:

stats(list, title = "JS Stats")
 - JS stats :
   ~ Files : 65
   ~ Lines : 23477  (361 per file)
   ~ Size : 1.8KB (28.0bytes per file)

Perform in-file pattern replacement:

sed = Sed()
sed.add('$TOTO$', 'troulute')
combine(list, "build/test.js", replace = sed)
deepcopy(list, "build/test.js", replace = sed)

Using jslint (see linting for more):

jslint(list, fix = False, relax = False, fail = True)

Using jsdoctoolkit (see documenting javascript for more):

jsdoc(list, "docdestination", [template = "templatepath"])

Clear puke cache

$ puke -c
 
 Spring time, cleaning all the vomit around ...
 ...
 You're good to go !

FileSystem

Creates missing hierarchy levels for given directory

FileSystem.makedir('somepath/to/dir')

Get file content

FileSystem.readfile('path/to/file')

Remove file or dir (recursively)

#With protection if you're trying to remove : './', '/', '~/', '~', '.', '..', '../'
FileSystem.remove('something')

Copy a file

#creates dirs if dst doesn't exist
#Does the copy only if the file doesn't exist or if the file has been modified
#To force to copy anyway, use  force = True
FileSystem.copyfile(src, dst)

Create a file and write your content

FileSystem.writefile('file', 'content')

Check if the path exists

FileSystem.exists('path')

Check if the path is a file

FileSystem.isfile('somefile')

Check if the path is a dir

FileSystem.path('somedir')

Get an OS path (eg : build/lib/folder on Unix)

FileSystem.join('build', 'lib','folder')

Get the absolute path

FileSystem.abspath('./')

Get file name

FileSystem.basename('/path/to/toto.py')
>>> 'toto.py'

System

Check platform

if System.OS == System.MACOS:
   #do something macos related
elif System.OS == System.LINUX:
   #do something linux related
elif System.OS == System.WINDOWS:
   #Achtung windows ...

Get user login

#Return None if something went wrong
System.LOGIN

Check if a system package is here :

System.check_package('nginx')
>>>   nginx is M.I.A
>>>     => "brew install nginx"
>>>   /!\ BUILD FAIL : nginx not installed

#Check version too (>= <= > < ==)
System.check_package('varnish', '>=3.0.1')
>>> varnish : INSTALLED (3.0.0 not >=3.0.1)
>>> * Continue anyway ? [Y/N default=Y]
>>> N
>>>  /!\ BUILD FAIL : Failed on version comp varnish 

#Only check on specific platform (System.LINUX, System.MACOS, System.LINUX, "all")
System.check_package('libcaca', platform=System.LINUX)

Get package version

System.get_package_version('uwsgi')
>>> "0.9.9"

VirtualEnv (Create and manage virtualenvs)

Create

env = VirtualEnv()
env.create('path/to/create/env', python='python3|python|python2.7|...')
>>> * Creating env "test" ...
>>>   virtualenv : OK (1.6.4)
>>>   Python version : 3.2.2 ...
>>>   Env "test"  created 

Load an existing virtualenv

env = VirtualEnv()
env.load('test')

install package

env.install('webob')
>>> * Install "webob" in env "test" ...
>>>   Package "webob" is ready

install package in a specific version

env.install('webob', '1.1.1')
>>> * Install "webob" in env "test" ...
>>>   Package "webob" is ready

install / force upgrade if installed

env.install('webob', upgrade=True)

upgrade package / all packages

env.upgrade('webob')
env.upgrade('*')

Check package / auto fix

#check if the named package respect the version
env.check_package('webob', '>=1.1.1')

#check it and fix if the cond fails (make an install or upgrade or downgrade)
env.check_package('webob', '>=1.1.1', fix = True)

List packages

env.list()
>>>  * List packages (env "test")
>>>    - PIL (1.1.6)
>>>    - PyYAML (3.10)
>>>    - WebOb (1.2b2)

Package info

env.package_info('webob')

Remove env

env.remove()

Utils

Merge two deep dicts non-destructively

a = {'a': 1, 'b': {1: 1, 2: 2}, 'd': 6}
b = {'c': 3, 'b': {2: 7}, 'd': {'z': [1, 2, 3]}}
Utils.deepmerge(a, b)
>>> {'a': 1, 'b': {1: 1, 2: 7}, 'c': 3, 'd': {'z': [1, 2, 3]}}

License

MIT license, see LICENSE file

Guidelines

Nope.

Gotcha

Don't puke on yourself!

Bitdeli Badge

Something went wrong with that request. Please try again.