pyKook Users' Guide
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:
- Impremented in pure Rython and runs any platform which Python supports.
- Input file (called 'cookbook') is named 'Kookbook.py', which is equivarent to 'Makefile' of Make or 'build.xml' of Ant.
- Cookbook's format is pure Python. You can write any Python code in kookbook.
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.
@product()
decorator represents filename which is generated by the recipe. This takes only an argument.@ingreds()
decorator represents filenames which are required to generate product. This can take several arguments.- function
file_xxx()
represents how to generate the product from ingredients. It is called as recipe function. Name of recipe function should befile_xxx()
ortask_xxx()
and it should take an argument. - function description is regarded as recipe description.
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.
# 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.
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).
- If product is older than ingredients, that recipe will be executed.
- If product is newer than or have the same timestamp as ingredients, that recipe will not be executed.
- If recipe of ingredient is executed but content of ingredient is not changed, then recipe of product will not be executed and product will be 'touched'.
- If you specify command-line option '
-F
', these rules are ignored and all recipes are executed forcedly.
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
pyk
command which is equivarent topykook
, becausepykook
is too long to type many times :)
Product and Ingredients
Product and ingredient names are referable as property of recipe function's argument.
- Property 'c.product' represents product.
- Property 'c.ingreds' represents ingredients.
- Property 'c.ingred' represents the first item of ingredients (= c.ingreds[0]).
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)")
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:
'*.o'
will be coverted intor'^(.*?)\.o$'
.'*.??.txt'
will be converted into tor'^(.*?)\.(..)\.txt$'
.
Matched strings with metacharacter ('*' or '?') are accessable by $(1)
, $(2)
, ... in @ingreds()
decorator.
## 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)
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.
## 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 file 'hello.h' exists, product 'hello.o' depends on ingredients 'hello.c' and 'hello.h'.
- If file 'hello.h' doesn't exist, product 'hello.o' depends on only 'hello.c'.
if_exists()
is useful especially when used with generic recipes.
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)
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'.
## 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.
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.
## 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.
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.
## 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.
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.
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.
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 .
## 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:
- 'hello.o' will be compiled from 'hello.c'.
- 'optparse.o' will not be compiled because it is specified as material.
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 @cmdopts()
decorator.
## 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) @cmdopts("-d dir: directory to install (default '/usr/local/bin')", "--command=command: command name (default '%s')" % command) def task_install(c, *args): opts, rests = c.parse_cmdopts(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.
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 @cmdopts
arguments.
@cmdopts("-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): """test of @cmdopts""" opts, rests = c.parse_cmdopts(args) print "opts:", repr(opts) print "rests:", repr(rests)
sh> pykook -L Properties: Task recipes: echo : test of @cmdopts -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
.
-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 pyk
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.
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.1', "1.0.0", s) s = s.replace('copyright(c) 2008 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.