pyKook Users' Guide

release: 0.0.2

Preface

pyKook is a software build tool such as Make, 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 frequently.

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".
@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".
@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")

The following is an example of invoking pykook command(*1). 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 (func=file_hello_o)
$ gcc -g -c hello.c
### * hello (func=file_hello)
$ gcc -g -o hello hello.o

pyKook checks both timestamps and contents of files (= products, ingredients).

sh> pykook hello               # 1st time
### ** hello.o (func=file_hello_o)
$ gcc -g -c hello.c
### * hello (func=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 (func=file_hello_o)
$ gcc -g -c hello.c            # compiled, because hello.c is newer than hello.o.
### * hello (func=file_hello)
$ touch hello   # skipped      # skipped, because content of hello.o is not changed.

sh> pykook -F hello            # 4th time (forcedly)
### ** hello.o (func=file_hello_o)
$ gcc -g -c hello.c
### * hello (func=file_hello)
$ gcc -g -o hello hello.o
(*1)
pyKook also provides kk command which is equivarent to pykook, because pykook is too long to type many times :)

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".
@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".
@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 (func=file_hello_o)
$ gcc -g -c hello.c
### * hello (func=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]))
system(c%"gcc -g -o $(product) $(ingreds[0])")

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

CC     = 'gcc'             # global variable

@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
@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
@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 (func=file_ext_o)
$ gcc -g -c hello.c
### * hello (func=file_hello)
$ gcc -g -o hello hello.o

It is able to specify regular expression instead of filename pattern. For example, re.compile(r'^(.*)\.o$') is available as product instead of *.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.
@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.
@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
@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
@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 (func=file_hello_o)
$ gcc -g -c hello.c
### * hello (func=file_hello)
$ gcc -g -o hello hello.o

File Recipes and Task Recipes

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, the execution of recipe 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

Recipe function of file and task recipes should be started with 'file_' and 'task_' repectively.

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

Kookbook.py: Symbolic recipes
## file 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
@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
@product("clean")      # omittable
def task_clean(c):
    """remove '*.o' files"""
    rm_f("*.o")

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

You can omit '@product("all")' because pyKook will guess product name (= 'all') from recipe function name (= 'task_all').

'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 (func=file_ext_o)
$ gcc -g -c hello.c
### ** hello (func=file_hello)
$ gcc -g -o hello hello.o
### * all (func=task_all)

sh> pykook clean
### * clean (func=task_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 document.

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
@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
@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
def task_clean(c):
    """remove '*.o' files"""
    rm_f("*.o")

## task 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 (func=file_ext_o)
$ gcc -g -c hello.c
### ** hello (func=file_hello)
$ gcc -g -o hello hello.o
### * all (func=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)

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

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

def task_clean(c):
    """remove '*.o' files"""
    rm_f("*.o")

@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:
  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 (func=file_ext_o)
$ gcc -g -c hello.c
### ** hello (func=file_command)
$ gcc -g -o hello hello.o
### * all (func=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 (func=file_ext_o)
$ gcc -g -O2 -Wall -c hello.c
### ** foo (func=file_command)
$ gcc -g -O2 -Wall -o foo hello.o
### * all (func=task_all)

Property file is another way to specify properties. If you have create property file 'Property.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:
  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
@product(command)
@ingreds("hello.o", "optparse.o")
def file_command(c):
    system(c%"$(CC) $(CFLAGS) -o $(product) $(ingreds)")

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

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

In this example:

command-line example
sh> pykook all
### *** hello.o (func=file_ext_o)            # only hello.o is compiled
$ gcc -g -O2 -c hello.c
### ** hello (func=file_command)
$ gcc -g -O2 -o hello hello.o optparse.o
### * all (func=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
@product(command)
@ingreds("hello.o", "optparse.o")
def file_command(c):
    system(c%"$(CC) $(CFLAGS) -o $(product) $(ingreds)")

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

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

@ingreds(command)
@spices("-d dir: directory to install (default '/usr/local/bin')",
        "--command=command: command name (default '%s')" % command)
def task_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 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 (func=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()
@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 task_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 (func=task_echo)
opts: {'debug': True, 'help': True, 'd': 99, 'f': 'hello.c'}
rests: ('AAA', 'BBB')


Other features

Debug mode

Command-line option -D or -D2 turn on debug mode and 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 (func=file_hello_o)
### ** hello.o (func=file_hello_o)
$ gcc -g -c hello.c
*** debug: ++ end hello.o (content changed)
*** debug: + create hello (func=file_hello)
### * hello (func=file_hello)
$ gcc -g -o hello hello.o
*** debug: + end hello (content changed)

Short command

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


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 ]

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

@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: Define 'task_xxx()' instead of 'file_xxx()'.

## Use 'task_clean()' instead of 'file_clean()'
def file_clean(c):   #=> KookRecipeError: clean: product not created (in file_clean()).
    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.

## Use "$(1).c" instead of "*.c"
@product("*.o")
@ingreds("*.c")  #=> KookRecipeError: *.c: can't find any recipe to produce.
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.

    ## Don't forget to add "c%" if you want to use "$()".
    @product("*.o")
    @ingreds("$(1).c")
    def file_ext_o(c):
        system("gcc -c $(ingred)")
            #=> KookCommandError: sh: line 1: ingred: command not found" error.


References

Filesystem Functions

The following functions are available in 'method*:' part of recipes.

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.2',   "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.