pyKook Users' Guide

release: 0.0.4

Preface

pyKook is a software build tool such as Make, Rake, Ant, SCons or Cook. It is implemented in Python and runs any platform Python support. Basic command (copy, move, rename, mkdir, ...) is also implemented in Python and allows you to execute platform-depended command.

pyKook liken build process to cooking. Input file is called 'ingredient', output is 'product', task is 'recipe', build file is 'cookbook'. pyKook generates products from ingredients according to recipes. You describe products, ingredients, and recipes in cookbook.

Features:

Caution! pyKook is currently under experimental. It means that the design and specification of pyKook may change without prior notice.

Table of Contents



Cookbook

This sectipn describes how to write cookbook.

Recipes

Cookbook should contains some recipes. Each recipes are described with function and function decorators.

In cookbook, some helper functions provided by pyKook are available. For exaple, function 'system()' invokes OS-depend command. See References for details about helper functions.

The following is an example of recipe definitions in cookbook.

Kookbook.py: Compile hello.c so that to generate 'hello' command.
# product "hello" depends on "hello.o".
@recipe
@product("hello")
@ingreds("hello.o")
def file_hello(c):
    """generates hello command"""           # recipe description
    system("gcc -g -o hello hello.o")

# product "hello.o" depends on "hello.c" and "hello.h".
@recipe
@product("hello.o")
@ingreds("hello.c", "hello.h")
def file_hello_o(c):
    """compile 'hello.c' and 'hello.h'"""   # recipe description
    system("gcc -g -c hello.c")

pyKook also provides short-notation. See the following example which is equivarent to the above, or see this section for details.

Example of Short-notation
# product "hello" depends on "hello.o".
@recipe("hello", ["hello.o"])
def file_hello(c):
    """generates hello command"""           # recipe description
    system("gcc -g -o hello hello.o")

# product "hello.o" depends on "hello.c" and "hello.h".
@recipe("hello.o", ["hello.c", "hello.h"])
def file_hello_o(c):
    """compile 'hello.c' and 'hello.h'"""   # recipe description
    system("gcc -g -c hello.c")

The following is an example of invoking pykook command. Command-line option '-l' shows recipes which have description. It means that recipes which have description are public recipes. Command-line option '-L' shows all recipes.

command-line example
sh> pykook -l
Properties:

Task recipes:

File recipes:
  hello               : generates hello command
  hello.o             : compile 'hello.c' and 'hello.h'

(Tips: you can set 'kook_default_product' variable in your kookbook.)

sh> pykook hello
### ** hello.o (recipe=file_hello_o)
$ gcc -g -c hello.c
### * hello (recipe=file_hello)
$ gcc -g -o hello hello.o

pyKook also provides kk command which is equivarent to pykook, because pykook is too long to type many times :) See this section for details.


Timestamp and content

pyKook checks both timestamp and content of files (= products, ingredients).

sh> pykook hello               # 1st time
### ** hello.o (recipe=file_hello_o)
$ gcc -g -c hello.c
### * hello (recipe=file_hello)
$ gcc -g -o hello hello.o

sh> pykook hello               # 2nd time
                               # nothing, because hello is already created.

sh> touch hello.c              # touch hello.c
sh> pykook hello               # 3rd time
### ** hello.o (recipe=file_hello_o)
$ gcc -g -c hello.c            # compiled, because hello.c is newer than hello.o.
### * hello (recipe=file_hello)
$ touch hello   # skipped      # skipped, because content of hello.o is not changed.

sh> pykook -F hello            # 4th time (forcedly)
### ** hello.o (recipe=file_hello_o)
$ gcc -g -c hello.c
### * hello (recipe=file_hello)
$ gcc -g -o hello hello.o

Product and Ingredients

Product and ingredient names are referable as property of recipe function's argument.

Kookbook.py: Use c.product and c.ingreds
# product "hello" depends on "hello.o".
@recipe
@product("hello")
@ingreds("hello.o")
def file_hello(c):
    """generates hello command"""
    system("gcc -g -o %s %s" % (c.product, c.ingred))
    # or system("gcc -g -o %s %s" % (c.product, c.ingreds[0]))
    # or system(c%"gcc -g -o $(product) $(ingreds[0])")

# product "hello.o" depends on "hello.c" and "hello.h".
@recipe
@product("hello.o")
@ingreds("hello.c", "hello.h")
def file_hello_o(c):
    """compile 'hello.c' and 'hello.h'"""
    system("gcc -g -c %s" % c.ingred)
    # or system("gcc -g -c %s" % c.ingreds[0])
    # or system(c%"gcc -g -c $(ingred)")
command-line example
sh> pykook hello
### ** hello.o (recipe=file_hello_o)
$ gcc -g -c hello.c
### * hello (recipe=file_hello)
$ gcc -g -o hello hello.o

pyKook provides convenience way to embed variables into string literal. For example, the followings are equivarent.

system("gcc -g -o %s %s" % (c.product, c.ingreds[0]))    # or c.ingred
system(c%"gcc -g -o $(product) $(ingreds[0])")           # or $(ingred)

You can write local or global variables in $() as well as product or ingreds.

CC     = 'gcc'             # global variable

@recipe
@product("hello")
@ingreds("hello.o")
def file_hello(c):
    CFLAGS = '-g -Wall'    # local variable
    system(c%"$(CC) $(CFLAGS) -o $(product) $(ingreds[0])")

Specific recipe and generic recipe

Specific recipe is a recipe which is combined to a certain file. Product name of specific recipe is a concrete file name.

Generic recipe is a recipe which is combined to a pattern of file name. Product name of generic recipe is a pattern with metacharacter or regular expression.

pyKook converts file name pattern into regular expression. For example:

Matched strings with metacharacter ('*' or '?') are accessable by $(1), $(2), ... in @ingreds() decorator.

Kookbook.py: Compile hello.c so that to generate 'hello' command.
## specific recipe
@recipe
@product("hello")
@ingreds("hello.o")
def file_hello(c):
    """generates hello command"""
    system(c%"gcc -g -o $(product) $(ingred)")
    # or system("gcc -g -o %s %s" % (c.product, c.ingred))
    # or system("gcc -g -o %s %s" % (c.product, c.ingreds[0]))

## generic recipe
@recipe
@product("*.o")        # or @product(re.compile(r'^(.*?)\.o$'))
@ingreds("$(1).c", "$(1).h")
def file_ext_o(c):
    """compile '*.c' and '*.h'"""
    system(c%"gcc -g -c $(1).c")
    # or system("gcc -g -c %s.c" % c.m[1])
    # or system("gcc -g -c %s" % c.ingred)
command-line example
sh> pykook -l
Properties:

Task recipes:

File recipes:
  hello               : generates hello command
  *.o                 : compile '*.c' and '*.h'

(Tips: you can set 'kook_default_product' variable in your kookbook.)

sh> pykook hello
### ** hello.o (recipe=file_ext_o)
$ gcc -g -c hello.c
### * hello (recipe=file_hello)
$ gcc -g -o hello hello.o

It is able to specify regular expression instead of filename pattern. For example, @product(re.compile(r'^(.*)\.o$')) is available as product instead of @product('*.o'). Grouping in regular expression is referable by $(1), $(2), ... in the same way.

Specific recipe is prior to generic recipe. For example, recipe 'hello.o' is used and recipe '*.o' is not used to generate 'hello.o' when target product is 'hello.o' in the following example.

Specific recipe is prior to generic recipe.
## When target is 'hello.o', this specific recipe will be used.
@recipe
@product("hello.o")
@ingreds("hello.c")
def file_hello_o(c):
    system(c%"gcc -g -O2 -o $(product) $(ingred)")

## This generic recipe will not be used, because specific recipe
## is prior than generic recipe.
@recipe
@product("*.o")
@ingreds("$(1).c", "$(1).h")
def file_o(c):
    system(c%"gcc -g     -o $(product) $(ingred)")

Conditional Ingredients

There may be a case that ingredient file exists or not. For example, product 'foo.o' depends on 'foo.c' and 'foo.h', while product 'bar.o' depends only on 'bar.c'.

In this case, you can use if_exists() helper function which resolve the problem. For example, when if_exists("hello.h") is specified in @ingreds(), pyKook detect dependency as following.

if_exists() is useful especially when used with generic recipes.

Kookbook.py: Example of if_exists()
## specific recipe
@recipe
@product("hello")
@ingreds("hello.o")
def file_hello(c):
    """generates hello command"""
    system(c%"gcc -g -o $(product) $(ingred)")
    # or system("gcc -g -o %s %s" % (c.product, c.ingred))
    # or system("gcc -g -o %s %s" % (c.product, c.ingreds[0]))

## generic recipe
@recipe
@product("*.o")        # or @product(re.compile(r'^(.*?)\.o$'))
@ingreds("$(1).c", if_exists("$(1).h"))
def file_hello_o(c):
    """compile '*.c' and '*.h'"""
    system(c%"gcc -g -c $(1).c")
    # or system("gcc -g -c %s.c" % c.m[1])
    # or system("gcc -g -c %s" % c.ingred)
command-line example
sh> pykook hello
### ** hello.o (recipe=file_hello_o)
$ gcc -g -c hello.c
### * hello (recipe=file_hello)
$ gcc -g -o hello hello.o

File Recipe and Task Recipe

In pyKook, there are two kind of recipe.

File recipe
File recipe is a recipe which generates a file. In the other word, product of recipe is a file. If product is not generated, recipe execution will be failed.
Task recipe
Task recipe is a recipe which is not aimed to generate files. For example, task recipe 'clean' will remove '*.o' files and it doesn't generate any files.

Here is a matrix table of recipe kind.

Specific recipe Generic recipe
File recipe Specific file recipe Generic file recipe
Task recipe Specific task recipe Generic task recipe

pyKook determines recipe kind ('file' or 'task') according the following simple rule:

How to determine recipe kind?

In the following example, task recipe clean is a recipe to delete '*.o' files and is not combined to file 'clean'. Also task recipe all is a recipe to call recipe of 'hello' and is not combined to file 'all'.

Kookbook.py: Symbolic recipes
## file recipe
@recipe
@product("hello")
@ingreds("hello.o")
def file_hello(c):
    """generates hello command"""
    system(c%"gcc -g -o $(product) $(ingred)")
    # or system("gcc -g -o %s %s" % (c.product, c.ingred))
    # or system("gcc -g -o %s %s" % (c.product, c.ingreds[0]))

## file recipe
@recipe
@product("*.o")        # or @product(re.compile(r'^(.*?)\.o$'))
@ingreds("$(1).c", if_exists("$(1).h"))
def file_ext_o(c):
    """compile '*.c' and '*.h'"""
    system(c%"gcc -g -c $(1).c")
    # or system("gcc -g -c %s.c" % c.m[1])
    # or system("gcc -g -c %s" % c.ingred)

## task recipe
@recipe
def clean(c):
    """remove '*.o' files"""
    rm_f("*.o")

## task recipe
## (in order to avoid to overwrite 'all()' built-in function,
##  add 'task_' prefix to function name.)
@recipe
@ingreds("hello")
def task_all(c):
    """create all files"""
    pass

'pykook -l' will display task recipes and file recipes.

command-line example
sh> pykook -l
Properties:

Task recipes:
  clean               : remove by-products
  all                 : cook all products

File recipes:
  hello               : generates hello command
  *.o                 : compile '*.c' and '*.h'

(Tips: you can set 'kook_default_product' variable in your kookbook.)

sh> pykook all
### *** hello.o (recipe=file_ext_o)
$ gcc -g -c hello.c
### ** hello (recipe=file_hello)
$ gcc -g -o hello hello.o
### * all (recipe=task_all)

sh> pykook clean
### * clean (recipe=clean)
$ rm -f *.o

sh> ls -F
Kookbook.py    hello*    hello.c    hello.h

pyKook have several well-known task name. Task recipes which product name is in the following list will be got pubilic automatically. For example, if you have defined 'all' task recipe, it will be displayed by 'pykook -l' even when recicpe function doesn't have any description.

all
create all products
clean
remove by-products
clear
remove all products and by-products
deploy
deploy products
config
configure
setup
setup
install
install products
test
do test

Default Product

If you set kook_default_product variable, pykook command will use it as default product.

Kookbook.py.yaml: specify default product name
## global variables
basename = 'hello'
command  = basename
kook_default_product = 'all'     # default product name

## file recipe
@recipe
@product(command)
@ingreds(basename + ".o")
def file_hello(c):
    """generates hello command"""
    system(c%"gcc -g -o $(product) $(ingred)")
    # or system("gcc -g -o %s %s" % (c.product, c.ingred))
    # or system("gcc -g -o %s %s" % (c.product, c.ingreds[0]))

## file recipe
@recipe
@product("*.o")        # or @product(re.compile(r'^(.*?)\.o$'))
@ingreds("$(1).c", if_exists("$(1).h"))
def file_ext_o(c):
    """compile '*.c' and '*.h'"""
    system(c%"gcc -g -c $(1).c")
    # or system("gcc -g -c %s.c" % c.m[1])
    # or system("gcc -g -c %s" % c.ingred)

## task recipe
@recipe
def clean(c):
    """remove '*.o' files"""
    rm_f("*.o")

## task recipe
@recipe
@ingreds(command)
def task_all(c):
    """create all files"""
    pass

If you specify kook_default_product, you can omit target product name in commad-line.

command-line example
sh> pykook           # you can omit target product name
### *** hello.o (recipe=file_ext_o)
$ gcc -g -c hello.c
### ** hello (recipe=file_hello)
$ gcc -g -o hello hello.o
### * all (recipe=task_all)

Properties

Property is a global variable which value can be overwrited in command-line option.

Property is defined by prop() function. It takes property name and default value as arguments.

Kookbook.py: properties
## global variables (not properties)
basename = 'hello'
kook_default_product = 'all'

## properties
CC       = prop('CC', 'gcc')
CFLAGS   = prop('CFLAGS', '-g -O2')
command  = prop('command', basename)

## file recipes
@recipe
@product(command)
@ingreds(basename + ".o")
def file_command(c):
    system(c%"$(CC) $(CFLAGS) -o $(product) $(ingred)")

@recipe
@product("*.o")
@ingreds("$(1).c", if_exists("$(1).h"))
def file_ext_o(c):
    system(c%"$(CC) $(CFLAGS) -c $(ingred)")

## task recipes
@recipe
def clean(c):
    """remove '*.o' files"""
    rm_f("*.o")

@recipe
@ingreds(command)
def task_all(c):
    pass

Properties are shown when command-line option '-l' is specified.

command-line example
sh> pykook -l
Properties:
  CC                  : 'gcc'
  CFLAGS              : '-g'
  command             : 'hello'

Task recipes:
  clean               : remove '*.o' files
  all                 : cook all products

File recipes:

(Tips: you can set 'kook_default_product' variable in your kookbook.)

If you don't specify any property values in command-line, default values are used.

command-line example
sh> pykook all
### *** hello.o (recipe=file_ext_o)
$ gcc -g -c hello.c
### ** hello (recipe=file_command)
$ gcc -g -o hello hello.o
### * all (recipe=task_all)

If you specify property values in command-line, that values are used instead of default values.

sh> pykook --command=foo --CFLAGS='-g -O2 -Wall' all
### *** hello.o (recipe=file_ext_o)
$ gcc -g -O2 -Wall -c hello.c
### ** foo (recipe=file_command)
$ gcc -g -O2 -Wall -o foo hello.o
### * all (recipe=task_all)

Property file is another way to specify properties. If you have create property file 'Properties.py' in current directory, pykook command reads it and set property values automatically.

Properties.py
CFLAGS = '-g -O2 -Wall'
command = 'foo'

Don't forget to write prop('prop-name', 'default-value') in your cookbook even when property file exists.

Result of pykook -l will be changed when property file exists.

sh> pykook -l
Properties:
  CC                  : 'gcc'
  CFLAGS              : '-g -O2 -Wall'
  command             : 'foo'

Task recipes:
  clean               : remove '*.o' files
  all                 : cook all products

File recipes:

(Tips: you can override properties with '--propname=propvalue'.)

Materials

There is an exception in any case. Assume that you have a file 'optparse.o' which is supplied by other developer and no source. pyKook will try to find 'optparse.c' and failed in the result.

Using 'kook_materials' variable, you can tell pyKook that 'optparse.o' is not a product.

Kookbook.py: materials
## global variables (not properties)
basename = 'hello'
kook_default_product = 'all'
kook_materials = ['optparse.o', ]   # specify materials

## properties
CC       = prop('CC', 'gcc')
CFLAGS   = prop('CFLAGS', '-g -O2')
command  = prop('command', basename)

## recipes
@recipe
@product(command)
@ingreds("hello.o", "optparse.o")
def file_command(c):
    system(c%"$(CC) $(CFLAGS) -o $(product) $(ingreds)")

@recipe
@product("*.o")
@ingreds("$(1).c", if_exists("$(1).h"))
def file_ext_o(c):
    system(c%"$(CC) $(CFLAGS) -c $(ingred)")

@recipe
@ingreds(command)
def task_all(c):
    pass

In this example:

command-line example
sh> pykook all
### *** hello.o (recipe=file_ext_o)            # only hello.o is compiled
$ gcc -g -O2 -c hello.c
### ** hello (recipe=file_command)
$ gcc -g -O2 -o hello hello.o optparse.o
### * all (recipe=task_all)

Command-line options for recipe

You can specify command-line options for certain recipes by @spices() decorator.

Kookbook.py: command-line options for recipes
## global variables (not properties)
basename = 'hello'
kook_default_product = 'all'
kook_materials = ['optparse.o', ]   # specify materials

## properties
CC       = prop('CC', 'gcc')
CFLAGS   = prop('CFLAGS', '-g -O2')
command  = prop('command', basename)

## recipes
@recipe
@product(command)
@ingreds("hello.o", "optparse.o")
def file_command(c):
    system(c%"$(CC) $(CFLAGS) -o $(product) $(ingreds)")

@recipe
@product("*.o")
@ingreds("$(1).c", if_exists("$(1).h"))
def file_ext_o(c):
    system(c%"$(CC) $(CFLAGS) -c $(ingred)")

@recipe
@ingreds(command)
def all(c):
    pass

@recipe
@ingreds(command)
@spices("-d dir: directory to install (default '/usr/local/bin')",
        "--command=command: command name (default '%s')" % command)
def install(c, *args, **kwargs):
    opts, rests = kwargs, args
    dir = opts.get('d', '/usr/local/bin')  # get option value
    cmd = opts.get('command', command)     # get option value
    system(c%"sudo cp $(command) $(dir)/$(cmd)")   # or use 'install' command

Command-line options of recipes are displayed by '-l' or '-L' option.

command-line example
sh> pykook -l
Properties:
  CC                  : 'gcc'
  CFLAGS              : '-g -O2'
  command             : 'hello'

Task recipes:
  all                 : cook all products
  install             : install product
    -d dir                directory to install (default '/usr/local/bin')
    --command=command     command name (default 'hello')

File recipes:

kook_default_product: all

(Tips: 'c%"gcc $(ingreds[0])"' is more natural than '"gcc %s" % c.ingreds[0]'.)

You can specify command-line options for the recipe.

sh> pykook install -d /opt/local/bin --command=hellow
### * install (recipe=task_install)
$ sudo cp hello /opt/local/bin/hellow
Password: *******

This feature can replace many small scripts with pyKook.

The following is an example to show styles of @spices arguments.

Kookbook.py: example of @spices()
@recipe
@spices("-h:      help",            # short opts (no argument)
        "-f file: filename",        # short opts (argument required)
        "-d[N]:   debug level",     # short opts (optional argument)
        "--help:  help",            # long opts (no argument)
        "--file=file: filename",    # long opts (argument required)
        "--debug[=N]: debug level", # long opts (optional argument)
        )
def echo(c, *args, **kwargs):
    """test of @spices"""
    opts, rests = kwargs, args
    print "opts:", repr(opts)
    print "rests:", repr(rests)
result
sh> pykook -L
Properties:

Task recipes:
  echo                : test of @spices
    -h                    help
    -f file               filename
    -d[N]                 debug level
    --help                help
    --file=file           filename
    --debug[=N]           debug level

File recipes:

(Tips: you can override properties with '--propname=propvalue'.)

sh> pykook echo -f hello.c -d99 --help --debug AAA BBB
### * echo (recipe=task_echo)
opts: {'debug': True, 'help': True, 'd': 99, 'f': 'hello.c'}
rests: ('AAA', 'BBB')


Other features

Command-line Scripting Framework

pyKook supports to create command-line script.

The points are:

'appsvr' script
#!/usr/bin/env pykook -X

from kook.utils import CommandOptionError

kook_desc = "start/stop web application server"

app = prop('app', 'helloworld')

@recipe
@spices("-p port: port number", "-d: debug")
def start(c, *args, **kwargs):
    """start server process"""
    p = kwargs.get("p", 8080)
    d = kwargs.get("d") and "-d" or ""
    _app = args and args[0] or app
    system("nohup python dev_appserver.py -p %s %s %s &" % (p, d, _app))

@recipe
def stop(c):
    """stop server process"""
    system_f("ps auxw | awk '/python dev_appserver.py/ && !/awk/{print $2}' | xargs kill")
result
### Don't forget to make script executable!
sh> chmod a+w appsvr

### Show help
sh> ./appsvr -h
appsvr - start/stop web application server

sub-commands:
  start           : start server process
  stop            : stop server process

(Type 'appsvr -h subcommand' to show options of sub-commands.)

### Show help for each sub-command
sh> ./appsvr -h start
appsvr start - start server process
  -p port              : port number
  -d                   : debug

### Invoke sub-command
sh> ./appsvr start -p 4223
appending output to nohup.out
sh> ./appsvr stop

Short command

pyKook provides kk command which is the same as pykook command, because pykook is too long to type many times :)

sh> kk all    # this is more confortable to type than pykook :)

In fact, kk is a shell script to invoke pykook or plkook command according to filename of cookbook. For example, pykook will be invoked by kk when Kookbook.py exists, or plkook will be invoked when Kookbook.pl exists. Therefore kk script requires Kookbook.py to invoke pykook command.

### you can't invoke kk when Kookbook.py doesn't exist
sh> ls Kookbook.py
ls: Kookbook.py: No such file or directory
sh> kk -h
kk: No kookbook found.

Search kookbook in parent directory

If you set $KK_CLIMB environment variable to 1, kk script searches parent directory recursively for kookbook.

sh> ls -F
Kookbook.py    src/    test/
sh> cd src/foo/bar/
sh> kk clean                       # ERROR
kk: No kookbook found.
sh> export KK_CLIMB=1
sh> kk clean                       # OK
### * clean (recipe=clean)
$ rm **/*.pyc

Or if you specify '-R' command-line option to pykook, it searches parent directory recursively for kookbook.

sh> ls -F
Kookbook.py    src/    test/
sh> cd src/foo/bar/
sh> pykook clean                   # ERROR
pykook: Kookbook.py: not found.
sh> pykook -R clean
### * clean (recipe=clean)
$ rm **/*.pyc

NOTICE : In both cases, current directory will be changed to parent directory in which Kookbook.py exists.


Short notation

pyKook provides short notation of recipe.

### normal notation                   ### short notation
@recipe                               @recipe('*.o', ['$(1).c', '$(1).h'])
@product('*.o')                       def file_o(c):
@ingreds('$(1).c', '$(1).h')             system(c%"gcc -o $(ingred)")
def file_o(c):
   system(c%"gcc -c $(ingred)")

@recipe                               @recipe('build', ['hello.o'])
@ingreds('hello.o')                   def task_build(c):
def build(c):                            system(c%"gcc -o hello *.o")
   system(c%"gcc -o hello *.o")

@recipe() decorator can take two arguments.


Debug mode

Command-line option -D or -D2 turn on debug mode and debug message will be displayed. -D2 is higher debug level than -D.

example of -D
sh> pykook -D hello 
*** debug: + begin hello
*** debug: ++ begin hello.o
*** debug: +++ material hello.c
*** debug: +++ material hello.h
*** debug: ++ create hello.o (recipe=file_hello_o)
### ** hello.o (recipe=file_hello_o)
$ gcc -g -c hello.c
*** debug: ++ end hello.o (content changed)
*** debug: + create hello (recipe=file_hello)
### * hello (recipe=file_hello)
$ gcc -g -o hello hello.o
*** debug: + end hello (content changed)

Invoke Recipes Forcedly

Command-line option '-F' invokes recipes forcedly. In the other words, timestamp of files are ignored when '-F' is specified.


Nested Array

You can specify not only filenames but also list of filenames as ingredient @ingreds(). pyKook flatten arguments of @ingreds() automatically.

Kookbook.py: specify list of filenames
from glob import glob
sources = glob("*.c")
objects = [ s.replace(".c", ".o") for s in sources ]

@recipe
@product("hello")
@ingreds(objects)    # specify list of filenams
def file_hello(c):
    system(c%"gcc -o $(product) $(ingreds)")  # expanded to filenames

@recipe
@product("*.o")
@ingreds("$(1).c")
def file_ext_o(c):
    sysytem(c%"gcc -c $(ingred)")


Trouble Shooting

xxx: product not created (in file_xxx())

Q: I got the "xxx: product not created (in file_xxx())." error.

.A: You may define file recipe instead of task recipe. Don't specify '@product()' if you want to define task recipe.

    ## This will cause error
    @recipe
    @product("clean")
    def clean(c):   #=> KookRecipeError: clean: product not created (in file_clean()).
        rm_f("*.o")

    ## Don't specify @product()
    @recipe
    def clean(c):   #=> ok
        rm_f("*.o")

    ## Or add 'task_' prefix to function name
    @recipe
    @product("clean")
    def task_clean(c):        #=> almost equivarent to above recipe
        rm_f("*.o")

*.c: can't find any recipe to produce.

Q: I got the "*.c: can't find any recipe to produce." error.

A: Use "$(1).c" instead of "*.c" in @ingreds() argument.

    ## This will cause error because "*.c" is used in ingredients.
    @recipe
    @product("*.o")
    @ingreds("*.c")  #=> KookRecipeError: *.c: can't find any recipe to produce.
    def file_ext_o(c):
        system(c%"gcc -c $(ingred)")

    ## Use "$(1).c" instead of "*.c"
    @recipe
    @product("*.o")
    @ingreds("$(1).c")  #=> ok
    def file_ext_o(c):
        system(c%"gcc -c $(ingred)")

sh: line 1: ingred: command not found

Q: I got the "sh: line 1: ingred: command not found" error.

A: Add "c%" at the beginning of command string.

    ## "c%" is forgetten
    @recipe
    @product("*.o")
    @ingreds("$(1).c")
    def file_ext_o(c):
        system("gcc -c $(ingred)")
            #=> KookCommandError: sh: line 1: ingred: command not found" error.

    ## Don't forget to add "c%" if you want to use "$()".
    @recipe
    @product("*.o")
    @ingreds("$(1).c")
    def file_ext_o(c):
        system(c%"gcc -c $(ingred)")


References

Filesystem Functions

The following functions are available in recipe.

system(cmmand-string)
Execute command-string. If command status is not zero then exception is raised.
system("gcc hello.c")
system_f(command-string)
Execute command-string. Command statuis is ignored.
echo(string)
Echo string. Newline is printed.
echo("OK.")
echo_n(string)
Echo string. Newline is not printed.
echo_n("Enter your name: ")
cd(dir)
Change directory. Return current directory.
cwd = cd("build")
...
cd(cwd)              # back to current directry
chdir(dir)
Change current directory temporary. If this is used with Python's with-statement, current directory will be backed automatically.
with chdir("build") as d:
   ...     # into "build" directory
# back to current directry automatically
mkdir(path)
Make directory.
mkdir("lib")
mkdir_p(path)
Make directory. If parent directory is not exist then it is created automatically.
mkdir_p("foo/bar/baz")
rm(path[, path2, ...])
Remove files.
rm('*.html', '*.txt')
rm_r(path[, path2, ...])
Remove files or directories recursively.
rm_r('*')
rm_f(path[, path2, ...])
Remove files forcedly. No errors reported even if path doesn't exist.
rm_f('*.html', '*.txt')
rm_rf(path[, path2, ...])
Remove files or directories forcedly. No errors reported even if path doesn't exist.
rm_rf('*')
touch(path[, path2, ...])
Touch files or directories. If path doesn't exist then empty file is created.
touch('*.c')
cp(file1, file2)
Copy file1 to file2.
cp('foo.txt', 'bar.txt')
cp(file, file2, ..., dir)
Copy file to dir.
cp('*.txt', '*.html', 'dir')
cp_r(path1, path2)
Copy path1 to path2 recursively.
cp_r('dir1', 'dir2')
cp_r(path, path2, ..., dir)
Copy path to dir recursively. Directory dir must exist.
cp_r('lib', 'doc', 'test', 'dir')
cp_p(file1, file2)
Copy file1 to file2. Timestams is preserved.
cp_p('foo.txt', 'bar.txt')
cp_p(file, file2, ..., dir)
Copy file to dir. Timestams is preserved. Directory dir must exist.
cp_p('*.txt', '*.html', 'dir')
cp_pr(path1, path2)
Copy path1 to path2 recursively. Timestams is preserved.
cp_pr('lib', 'lib.bkup')
cp_pr(path, path2, ..., dir)
Copy path to dir recursively. Directory dir must exist. Timestams is preserved.
cp_pr('lib/**/*.rb', 'test/**/*.rb', 'tmpdir')
mv(file1, file2)
Rename file1 to file2.
mv('foo.txt', 'bar.txt')
mv(path, path2, ..., dir)
Move path to dir.
mv('lib/*.rb', 'test/*.rb', 'tmpdir')
store(path, path2, ..., dir)
Copy path (files or directories) to dir with keeping path-structure.
store("lib/**/*.py", "doc/**/*.{html,css}", "dir")
## ex.
##   "lib/kook/__init__.py"  is copied into "dir/lib/kook/__init__.py"
##   "lib/kook/utils.py"     is copied into "dir/lib/kook/utils.py"
##   "lib/kook/main.py"      is copied into "dir/lib/kook/main.py"
##   "doc/users-guide.html"  is copied into "dir/doc/users-guide.html"
##   "doc/docstyle.css"      is copied into "dir/doc/docstyle.css"
store_p(path, path2, ..., dir)
Copy path (files or directories) to dir with keeping path-structure. Timestamp is preserved.
store_p("lib/**/*.py", "doc/**/*.html", "dir")
edit(path, path2, ..., by=replacer)
Edit file content. Keyword argument 'by' should be a callable to edit content, or list of tuples of replacing pattern and string.
## edit by list of regular expression and string
replacer = [
    (r'\$Release\$', "1.0.0"),
    (r'\$Copyright\$', "copyright(c) 2008 kuwata-lab.com"),
]
edit("lib/**/*.py", "doc/**/*.{html,css}", by=replacer)
## edit by function
def replacer(s):
    s = s.replace('0.0.4',   "1.0.0", s)
    s = s.replace('copyright(c) 2008-2009 kuwata-lab.com all rights reserved.', "copyright(c) 2008 kuwata-lab.com", s)
    return s
edit("lib/**/*.py", "doc/**/*.{html,css}", by=replacer)

The above functions can take lists or tuples as file or directory names. (If argument is list or tuple, it is flatten by kook.utils.flatten().)

For example, the following code is available.

## copy all files into dir
files = ['file1.txt', 'file2.txt', 'file3.txt']
cp(files, 'dir')

The following file pattern is available.

*
Matches sequence of any character.
?
Matches a character.
{a,b,c}
Matches a or b or c.
**/
Matches directory recursively.