Kook Users' Guide

makoto kuwata <kwa(at)kuwata-lab.com>
release: $Release$
last update: $Date: 2006-09-24 16:12:24 +0900 (Sun, 24 Sep 2006) $

Preface

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

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

Features:

  • Implemented in pure PHP5 and runs any platfrom which PHP5 supports.
  • Input file (called 'cookbook') is named 'Kookbook.yaml', which is equivarent to 'Makefile' of Make or 'build.xml' of Ant.
  • Cookbook's format is YAML which is much easier for human to read and write than XML.
  • You can write any PHP code in Kookbook: 'if', 'foreach', and so on. It brings the power of PHP to Kook.

Kook is implemented in PHP5 because exception is supported only in PHP5 and not supported in PHP4. You need to install PHP5 before using Kook.

Caution! Kook is currently under experimental. It means that the design and specification of Kook may change frequently.

Table of Contents

Quick guide to YAML

YAML is a text-base format to describe stuctured data. The purpose of YAML is similar to XML but YAML is more friendly for human to read and write than XML.

You should learn about YAML because cookbook ('Kookbook.yaml') is described in YAML format. This chapter will help you to learn YAML in quickly. See the following page for details.

Sequence

Sequence is a list of element.

Sequence in YAML (block-style)
- aaa
- bbb
- ccc
Sequence in YAML (flow-style)
[ aaa, bbb, ccc ]

These are equivarent to the following PHP code.

Sequence in PHP
array('aaa', 'bbb', 'ccc')

Mapping

Mapping is a map or hash of pairs of key and value.

Mapping in YAML (block-style)
one: 1
two: 2
three: 3
Mapping in YAML (flow-style)
{ one: 1, two: 2, three: 3 }

These are equivarent to the following PHP code.

Mapping in PHP
array('one'=>1, 'two'=>2, 'three'=>3)

Combination of Sequence and Mapping

Sequence and mapping are nestable in each other.

The following is an example of a sequence of mappings.

Sequence of mappings (block-style and flow-style)
- name:   Foo
  email:  foo@mail.com
  age:    30
  married: yes
- name:   Bar
  email:  bar@mail.org
  gender: F
- { name: Baz, age: 19, married: no }

The above is equivarent to the following PHP code.

Sequence of mappings in PHP
array(
  array('name'=>'Foo', 'email'=>'foo@mail.com', 'age'=>30, 'married'=>TRUE),
  array('name'=>'Bar', 'email'=>'bar@mail.com', 'gender'=>'F'),
  array('name'=>'Baz', 'age'=>19, 'married'=>FALSE)
)

The following is an example of a mapping of sequences.

Mapping of seqeunces (block-style and flow-style)
members:
  - Foo
  - Bar
  - Baz
tasks:
  - write code
  - test code
  - write documents
sub-projects: [ sub1, sub2 ]

The above is equivarent to the following PHP code.

Sequence of mappings in PHP
array(
  'members'=>array('Foo', 'Bar', 'Baz'),
  'tasks'=>array('write code', 'test code', 'write documents'),
  'sub-projects'=>array('sub1', 'sub2')
)

Scalar and Data Type

YAML determines data type of elemet automatically.

Data types which YAML supports
- foobar           # string
- 123              # integer
- 3.14159          # float
- true             # boolean (true)
- yes              # boolean (true)
- on               # boolean (true)
- false            # boolean (false)
- no               # boolean (false)
- off              # boolean (false)
- 2006-01-01       # date
- 2006-01-01 00:00:00  # timestamp
- 'true'           # string
- "false"          # string

YAML data is represented by the combination of the followings.

  • Collection (Sequecne and Mapping)
  • Scalar (string, integer, float, boolean, ...)

This feature is similar to JSON. In fact, you can regard YAML's flow-style as JSON.

Block text

'|' means start of block-text. Block-text is convenient if string has several lines.

Block text example
text1: |
  foo
  bar
  baz
text2: |
  foo
    bar
      baz

This is equivarent to the following PHP code.

Block text in PHP
array('text1'=>'foo
bar
baz
'),
'text2'=>'foo
  bar
    baz
')
)

Anchor and Alias

Anchor is feature to mark data. Alias is feature to refer anchored data.

Anchor and alias example
- name:  foo
  desc:  &anchor1 description
  text:  &anchor2 |
    blocktext
    blocktext
    blocktext

- name:  bar
  desc:  *anchor1
  text:  *anchor2

This is equivarent to the following PHP code.

Anchor and alias in PHP
$anchor1 = "description";
$anchor2 = <<<END
blocktext
blocktext
blocktext

END;

array(
  array(
    'name' => 'foo',
    'desc' => $anchor1,
    'text' => $anchor2,
    ),
  array(
    'name' => 'bar',
    'desc' => $anchor1,
    'text' => $anchor2,
    ),
);

Comment

YAML comment starts with the '#' character. Between '#' and end of line is YAML comment.

# comment
    # comment
members:        # comment
  - name: Foo   # comment
    age:  19    # comment

YAML has only line comment.

Special Characters

YAML uses several characters to describe features.

char feature
[ start of inline-style sequence
] end of inline-style sequence
{ start of inline-style mapping
} end of inline-style mapping
- sequence element in block-style
? mapping element in block-style
: separator of key and value in block-style and flow-style
* alias
& anchor
! data type
# comment
| text in block-style
> text in block-style

Notice that these characters may cause YAML parse error if you don't quote them with single quotation or double quotation.

## OK
- foo *bar* baz

## NG  (because '*foo' means alias in YAML)
- *foo* bar baz

## OK
- '*foo* bar baz'

See YAML Cookbook or YAML specification for details.

Cookbook

Recipes

Cookbook should contains some recipes.

  • 'product:' part represents filename which is generated by the recipe.
  • 'desc:' part represents the description about the recipe. Description is showed when command-line option '-l' specified.
  • 'ingreds:' part represents filenames which are required to generate product. The part takes sequence of ingredient names.
  • 'method*:' part represents how to generate the product from the ingredients. The part is described in PHP code (tail asterisk '*' means that "this is PHP code").

In the 'method*:' part, some helper functions provided by Kook are available. For exaple, function 'k_sys()' invokes OS-depend command. See References for details about helper functions.

The following is an example of recipe definitions in cookbook.

Kookbook.yaml: Compile hello.c so that to generate 'hello' command.
recipes:

  - product:    hello
    desc:       generates hello command
    ingreds:    [ hello.o ]
    method*: |
      k_sys("gcc -g -o hello hello.o");

  - product:    hello.o
    desc:       compile 'hello.c' and 'hello.h'
    ingreds:    [ hello.c, hello.h ]
    method*: |
      k_sys("gcc -g -c hello.c");

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

command-line example
$ kook -l
*** ERROR!
'Kookbook1.yaml': not found.
$ kook hello
*** ERROR!
'Kookbook1.yaml': not found.

One of the aim of Kook is fusion of 'declarative' and 'procedural'. Attributes of recipe are described in declarative format (=YAML) and steps how to generate product are described in procedural format (=PHP).

The benefits of this style are:

  • Easy to read and write for human. Readability and Writeability is very important.
  • Powerful to write complex logics. You can get power of PHP in YAML.

PHP is easy to read and write but not a little suitable for DSL(Domain Specific Language). Embedding PHP code in YAML is more suitable for DSL.

$product and $ingreds

Product and ingredient names are referable as local variable in recipe's 'method*:' part.

  • Variable '$product' represents product.
  • Variable '$ingreds' represents ingredients.
  • Variable '$ingred' represents the first item of ingredients (= $ingreds[0]).
Kookbook.yaml: Use $product and $ingreds
recipes:

  - product:   hello
    desc:      generates hello command
    ingreds:   [ hello.o ]
    method*: |
      k_sys("gcc -g -o $product $ingred");
      # or k_sys("gcc -g -o $product {$ingreds[0]}");

  - product:   hello.o
    desc:      compile 'hello.c' and 'hello.h'
    ingreds:   [ hello.c, hello.h ]
    method*: |
      k_sys("gcc -g -c $ingred");
      # or k_sys("gcc -g -c {$ingreds[0]}");
command-line example
$ kook hello
*** ERROR!
'Kookbook2.yaml': not found.

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 used by preg_match().

Kook converts file name pattern into regular expression. For example, file name pattern '*.o' is coverted to regular expression '/^(.*)\.o$/', and '*.??.txt' to '/^(.*)\.(.)(.)\.txt$/'. Matched strings with metacharacter ('*' or '?') are accessable by '$(1)', '$(2)', ... in recipe definition and local variable '$m' in 'method*:' part.

Kookbook.yaml: Compile hello.c so that to generate 'hello' command.
recipes:

  ## specific recipe
  - product:   hello
    desc:      generates hello command
    ingreds:   [ hello.o ]
    method*: |
      k_sys("gcc -g -o $product $ingred");

  ## generic recipe
  - product:   *.o        # or /^(.*)\.o$/
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c, $(1).h ]
    method*: |
      k_sys("gcc -g -c $(1).c");
      # or "gcc -g -c {$m[1]}.c"
      # or "gcc -g -c $ingred"
command-line example
$ kook -l
*** ERROR!
'Kookbook3.yaml': not found.
$ kook hello
*** ERROR!
'Kookbook3.yaml': not found.

It is able to specify regular expression instead of filename pattern. For example, /^(.*)\.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.
recipes:

  - product:   hello.o
    ingreds:   [ hello.c ]
    method*: |
      k_sys("gcc -g -O2 -o $product $ingred");

  - product:   *.o
    ingreds:   [ *.c, *.h ]
    method*: |
      k_sys("gcc -g     -o $product $ingred");

Toppings

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 the case, you can use 'toppings:' part which resolve the problem. For example when 'toppings: [hello.h]' is specified in recipe definition, Kook 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'.

'toppings:' part is useful especially when used with generic recipes.

Kookbook.yaml: Example of 'toppings:'
recipes:

  ## specific recipe
  - product:   hello
    desc:      generates hello command
    ingreds:   [ hello.o ]
    method*: |
      k_sys("gcc -g -o $product $ingred");

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      k_sys("gcc -g -c $ingred");   # or "gcc -g -c $(1).c"
command-line example
$ kook hello
*** ERROR!
'Kookbook4.yaml': not found.

Symbolic Recipes

Symbolic recipe is a recipe which is not combined with any files. Product name of symbolic recipe starts with colon (':').

For example, symbolic 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.yaml: Symbolic recipes
recipes:
  ## specific recipe
  - product:   hello
    desc:      generates hello command
    ingreds:   [ hello.o ]
    method*: |
      k_sys("gcc -g -O2 -o $product $ingred");

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      k_sys("gcc -g -O2 -c $ingred");   # or "gcc -g -c $(1).c"

  ## symbolic recipes
  - product:   :clean
    method*: |
      k_delete('*.o');   # remove '*.o' files

  - product:   :all
    ingreds:   [ hello ]
command-line example
$ kook -l
*** ERROR!
'Kookbook5.yaml': not found.
$ kook :all
*** ERROR!
'Kookbook5.yaml': not found.
$ ls
Kookbook.yaml    hello    hello.c    hello.h    hello.o
$ kook :clean
*** ERROR!
'Kookbook5.yaml': not found.
$ ls
kookbook.yaml    hello    hello.c    hello.h

Kook have several well-known symbolic product. Symbolic recipes which product is contained in the following list are got public automatically.

: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

Properties

Property is a pair of name and value. You can set properties in cookbook.

You can get property value with '$(name)' notation in property definition and recipe definition. And properties are accessable as local variable in 'method*' part of recipe definition.

Property name should match to regular expression '/^[_a-zA-Z]\w*$/'. Notice that period ('.') or hyphen ('-') are not available as property name character.

Kookbook: properties
properties:
  - cc:         gcc
  - c_flags:    -g -O2
  - basename:   hello

recipes:

  ## specific recipe
  - product:   $(basename)
    desc:      generates $(basename) command
    ingreds:   [ $(basename).o ]
    method*: |
      k_sys("$cc $c_flags -o $product $ingred");

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      k_sys("$cc $c_flags -c $ingred");      # or "$cc $c_flags -c $(1).c"

  ## symbolic recipes
  - product:   :clean
    method*: |
      k_delete('*.o');   # remove '*.o' files

  - product:   :all
    ingreds:   [ $basename ]
command-line example
$ kook hello
*** ERROR!
'Kookbook6.yaml': not found.

Properties are shown when command-line option '-l' is specified. And it is able to overwrite properties in command-line option '--propname=propvalue'. Properties specified in command-line are prior to that in Kookbook.

command-line example
$ kook -l
*** ERROR!
'Kookbook6.yaml': not found.
$ kook -l --basename=foobar
*** ERROR!
'Kookbook6.yaml': not found.

Property file is another way to specify properties. If you have create property file 'Kookbook.props' or 'Properties.yaml', kook command automatically read it and set properties.

Property file example
basename:  hello
c_flags:   -g -O2

Property value can be specified in both YAML format and PHP code. If you append '*' in tail of property name, Kook regard it's value as PHP code. Property value is to be refered by '$(propname)' in YAML and by '$propname' in PHP code.

Property values specified as PHP code.
properties:
  - srcdir:      src/main/java
  - junit_jar:   lib/junit.jar
  - path_elems:  [ '.', $(srcdir), $(junit_jar) ]
  - classpath*:  join(PATH_SEPARATOR, $path_elems)

recipes:
  - product:     :echo
    method*: |
        echo var_export($classpath);   #=> '.:src/main/java:lib/junit.jar'

Parameters

Parameters are similar to properties but have the following differences.

  • Not able to override parameter value in command-line option nor property file.
  • Not shown with command-line option '-l' (but shown with '-L').

You can consider properties as 'global variables' or 'public variable', and parameters as 'local variables' or 'private variable'.

Similar to properties, parameters value can be refered by '$(paramname)' in YAML and by '$paramname' (local variable) in PHP code.

There is a special parameter 'kook_product'. If the parameter is set, kook regard it as the default target product when target product is not specified in command-line.

Kookbook.yaml: parameters
properties:
  - cc:         gcc
  - c_flags:    -g -O2

parameters:
  - basename:      hello
  - kook_product:  $(basename)    # default product when no product
                                        # specified in command-line

recipes:

  ## specific recipe
  - product:   $(basename)
    desc:      generates $(basename) command
    ingreds:   [ $(basename).o ]
    method*: |
      k_sys("$cc $c_flags -o $basename $basename.c");
      # or k_sys("$cc $c_flags -o $product $ingred");

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      k_sys("$cc $c_flags -c $ingred");

  ## symbolic recipes
  - product:   :clean
    method*: |
      k_delete('*.o');   # remove '*.o' files

  - product:   :all
    ingreds:   [ $(basename) ]
command-line example
$ kook -L
*** ERROR!
'Kookbook7.yaml': not found.
$ kook
*** ERROR!
'Kookbook7.yaml': not found.

Material

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

Using 'materials:' part, you can tell Kook that 'optparse.o' is not a product .

Kookbook.yaml: materials
properties:
  - cc:         gcc
  - c_flags:    -g -O2

parameters:
  - basename:      hello
  - kook_product:  $(basename)

materials:
  - optparse.o

recipes:
  ## specific recipe
  - product:   $(basename)
    desc:      generates $(basename) command
    ingreds:   [ $(basename).o, optparse.o ]
    method*: |
      k_sys("$cc $c_flags -o $product $ingred optparse.o");

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      k_sys("$cc $c_flags -c $ingred");         # or "$cc $c_flags -c $(1).c"

  ## symbolic recipes
  - product:   :clean
    method*: |
      k_delete('*.o');

  - product:   :all
    ingreds:   [ $(basename) ]
command-line example
$ kook
*** ERROR!
'Kookbook8.yaml': not found.

Other features

Invoke Recipes Forcedly

Command-line option '-F' invokes recipes forcedly, it means that timestamp of files are ignored.

Kookbook.yaml:
recipes:

  - product:   hello
    ingreds:   [ hello.o ]
    method*: |
        k_sys("gcc -o $product $ingred");

  - product:   hello.o
    ingreds:   [ hello.c, hello.h ]
    method*: |
        k_sys("gcc -c $ingred");
command-line example
## create 'hello'
$ kook hello
*** ERROR!
'Kookbook10.yaml': not found.

## 'hello' is newer than 'hello.c' and 'hello.h'
$ kook hello
*** ERROR!
'Kookbook10.yaml': not found.

## create 'hello' forcedly
$ kook -F hello
*** ERROR!
'Kookbook10.yaml': not found.

Validation check for Cookbook

Command-line option '-c' do validation checks for cookbook. It checks schema check of cookbook and PHP syntax check.

Kookbook.yaml: cookbook for validation example
property:                    # 'properties:' is correct
  - cc:       gcc
  - c_flags:  -g -O2

recipes:
  - product:  hello
    method*: |
      k_sys("$cc $c_flags hello.c");
      k_rename('a.out', 'hello');
validation example
$ kook -c
*** ERROR!
'Kookbook11.yaml': not found.
Kookbook.yaml: cookbook for validation example
properties:
  - cc:       gcc
  - c_flags:  -g -O2

recipes:
  - product:  hello
    method*: |
      k_sys("$cc $c_flags hello.c");
      k_rename('a.out', 'hello', );    # extra comma
validation example
$ kook -c
*** ERROR!
'Kookbook12.yaml': not found.

Nested Array

You can specify not only filenames but also array of filenames as ingredient on 'ingreds:', 'toppings:', 'byprods:' and 'coprods:'. In addition, nested arrays are also available. These arrays are flattened by kook.

Kookbook.yaml: specify nested array as a ingredient
parameters:
  - project:     hello
  - src_files*:  glob('lib/*.php')              # array
  - text_files:  [README, ChangeLog, COPYING]   # array (sequence)
  - all_files:   [$(src_files), $(text_files)]  # nested array

recipes:
  - product:  :inspect
    ingreds:  [ package.xml, $(all_files) ]   # nested array will be flatten
    method*: |
      # $all_files is a nested sequence
      echo "*** \$all_files (nested)\n";
      var_export($all_files);
      echo "\n";

      # $ingreds is flattened
      echo "*** \$ingreds (flattened)\n";
      var_export($ingreds);
      echo "\n";
command-line example
$ kook :inspect
*** ERROR!
'Kookbook13.yaml': not found.

Preparation

It is able to load external PHP scripts and to define functions with 'preparation*:' section. You can write any PHP code in 'preparation*:' section (tail asterisk '*' means that 'this is PHP code').

'preparation*:' section is evaluated by kook before evaluating 'properties:', 'parameters:', and 'recipes:' sections. Functions you defined in 'preparation*:' section are available in 'properties:', 'parameters:', or 'recipe:' section.

Kookbook.yaml: preparation example
preparation*: |

  require_once('my-util.php');     # load external PHP script

  function my_join_path($array) {  # define original function
    $buf = array();
    foreach ($array as $item) {
      if ($item) {
        $buf[] = $item;
      }
    }
    return join($buf, PATH_SEPARATOR);
  }

parameters:
  - path_elems:  [ '.', src/main/java, null, '', lib/junit.jar ]
  - classpath*:  my_join_path($path_elems)

Recipe Listing

Command-line option '-l' shows list of properties and recipes which has description and '-L' shows list of properties, parameters, and all of recipes.

Kookbook.yaml: cookbook example
properties:
  - cc:       gcc
  - c_flags:  -g -O2

parameters:
  - basename:  hello
  - kook_product:  $(basename)

recipes:

  # this will be shown with '-l' option because it has 'desc:'
  - product:  hello
    desc:     create hello command

  # this will not be shown with '-l' option  because no 'desc:'
  - product:  *.o
    #desc:    no comment
command-line example
$ kook -l
*** ERROR!
'Kookbook14.yaml': not found.
$ kook -L
*** ERROR!
'Kookbook14.yaml': not found.

Kook set description of symbolic recipe automatically if product name is ':all', ':clean', ':clear', or ':test'. This means that these symbolic recipes are listed automatically even when you omit recipe description.

Kookbook.yaml: cookbook example
recipes:
  # the following are shown regardless no-description.
  - product:  :all
    #desc:    no description
  - product:  :clean
    #desc:    no description
  - product:  :clear
    #desc:    no description
  - product:  :test
    #desc:    no description
command-line example
$ kook -l
*** ERROR!
'Kookbook15.yaml': not found.

Recipe-local Parameters

Recipe can have it's own local parameters. Recipe local parameters cannot be refered by other recipes.

This is convenient to override property or parameter specified in cookbook, and to share method definition with other recipe by YAML anchor and alias.

Kookbook.yaml: cookbook example
parameters:
  - c_flags:     -g -Wall

recipes:
  - product:     *.o
    ingreds:     [ $(1).c ]
    toppings:    [ $(1).h ]
    method*: &gcc |
        k_sys("gcc $c_flags -c $ingred");

  - product:     foo.o
    ingreds:     [ foo.c ]
    params:
      - c_flags:   $(c_flags) -O2
    method*:     *gcc
command-line example
$ kook -n hello.o
*** ERROR!
'Kookbook16.yaml': not found.
$ kook -n foo.o
*** ERROR!
'Kookbook16.yaml': not found.

By-Products and Co-Products

'byprods:' and 'coprods:' sections represent by-products and co-products of the recipe. Currently, these section have no feature than description.

Local variables $byprods and $coprods in 'method*:' section represents by-products and co-products of the recipe. Local variable $byprod is equivarent to $byprods[0] and $coprod is equivarent to $coprods[0].

Kookbook.yaml: example of by-products.
recipes:
  - product:    *.dvi
    ingreds:    [ $(1).tex ]
    byprods:    [ $(1).aux, $(1).log ]
    method*: |
        k_sys("latex $ingred");
        k_delete($byprods);

References

Filesystem Functions

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

k_copy(path1, path2)
Copy a file or direcotry to other file or directory. Notice that k_copy() cannot copy file or directory to existing directory. If you want to copy file or directory into a directory, use k_mcopy().
k_copy("file", "file2");    # OK
k_copy("file", "newfile");  # OK
k_copy("file", "dir1");     # NG - use k_mcopy() instead of k_copy()
k_copy("dir",  "dir2");     # NG if dir2 already exists
k_copy("dir",  "newdir");   # OK
The reason why k_copy() cannot copy files to directory is to prevent creating miss-typed filename. For example, unix command 'cp' creates new file if destination file is not exist even when you have typo destination file name. It's not good user-interface.
## you are intended to copy 'index.html' to 'public_html/index.html',
## but new file 'pubic_html' will be created because of typo.
$ cp index.html pubic_html   # 'pubic_html' is typo of 'public_html'
k_mcopy(dir, path1, path2, ...)
Copy several files or directories to the target directory. Notice that k_mcopy() doesn't allow to change file names.
k_mcopy("dir", "file1", "file2");  # OK
k_mcopy("dir/file1", "file1");     # NG - use k_copy()
k_mcopy("dir", glob("*.php"), glob("*.html"));  # OK
k_clone(dir, path1, path2, ...)
Equivarent to k_mcopy().
k_rename(old_path, new_path)
Rename a file or directory to new name.
k_rename("file", "file2");   # OK
k_rename("file", "dir");     # NG
k_rename("dir",  "dir2");    # NG if dir2 already exists
k_rename("dir",  "newdir");  # OK
The reason why k_rename() cannot move files to directory is to prevent creating miss-typed filename. For example, unix command 'mv' creates new file if destination file is not exist even when you have typo destination file name. It's not good interface.
## you are intended to rename 'index.html' to 'public_html/index.html',
## but new file 'pubic_html' will be created because of typo.
$ mv index.html pubic_html   # 'pubic_html' is typo of 'public_html'
k_move(dir, path1, path2, ...)
Move several files or directories to the target directory. Notice that k_move() doesn't rename file or directory. It only moves location of files or directories.
k_move("dir", "file1", "file2");  # OK
k_move("dir/file1", "file1");     # NG - use k_rename()
k_move("dir", glob("*.php"), glob("*.html"));  # OK
k_delete(path1, path2, .... )
Delete files or directories. Direcotry is deleted recursively.
k_move("file1", "file2", "dir1", "dir2");      # OK
k_move(glob("src/**/*.o"), glob("tmp/**/*"));  # OK
k_mkdir(dir1, dir2, .... )
Make directories. It is equivarent to 'mkdir -p' command.
k_mkdir("src/main/php/Kook");  # OK even if 'src/main/php' doesn't exist
k_store(dir, path1, path2, ...)
Copy files or directories to target directory with path-structure keeped.
# copy "bin/*" and "lib/**/*.php" to "dir/bin/*" and "dir/lib/**/*.php"
k_store("dir", "bin/*", "lib/**/*.php");
k_store2(dir, prefix, path1, path2, ...)
Copy files or directories to target directory with path-structure keeped, and prefix is removed from path.
# copy "/var/tmp/foo/bar/*" to "dir/foo/bar/*"
k_store2("dir", "/var/tmp/", "/var/tmp/foo/bar/*")
k_sys(command-string)
Execute platform depended command such as compile command, archive command, and so on.
k_sys("gcc -O2 -ansi -Wall -o hello hello.c");
k_edit(replacer, file1, file2, ...)
Edit content of files according to replacer. replacer is a list of pair of regular expression and replace-string.
$replacer = array(
    array('/\$Release\$/',   '2.0.1'),
    array('/\$Copyright\$/', 'copyright(c) 2006 kuwata-lab.com'),
    );
k_edit($replacer, "src/**/*.php");
k_touch(path1, path2, ...)
Touch files or directories.
k_touch("hello.c");
k_echo(str)
Echo string.
k_echo("comment");
k_chdir(dir)
Change directory. This function returns the directory path which is before changing directory.
$dir = k_chdir("main/src");
k_backdir(dir)
Change direcotry to back. ex:
$cwd = k_chdir('test/');  # change directory
k_sys('phpunit FooBarTest');
k_backdir($cwd);          # back to the directory
k_chmod(mode, path1[, path2, ...])
Change permission of files or directories. mode should be octal number.
k_chmod(0777, "lib/**/*.php");
k_chmod_files(mode, path1[, path2, ...])
Change permission of files. If path is directory then ignored. mode should be octal number.
k_chmod_files(0777, "lib/**/*");
k_chmod_dirs(mode, path1[, path2, ...])
Change permission of directories. If path is not a directory then ignored. mode should be octal number.
k_chmod_dirs(0777, "lib/**/*");
k_tar_cf(archive.tar, path1[, path2, ...])
Create TAR file.
k_tar_cf("kook.tar", "lib/**/*.php", "bin", "README.txt");
k_tar_czf(archive.tar.gz, path1[, path2, ...])
Create TAR file.
k_tar_czf("kook.tar.gz", "lib/**/*.php", "bin", "README.txt");
k_tar_cjf(archive.tar.bz2, path1[, path2, ...])
Create TAR file.
k_tar_cjf("kook.tar.bz2", "lib/**/*.php", "bin", "README.txt");

The above functions can take arrays as file or directory name. For example, the following code is available.

## specify filenames in strings
k_mcopy('dir', 'file1.txt', 'file2.txt', 'file3.txt');

## specify filenames with filename pattern
k_mcopy('dir', '*.txt');

## specify filenames in array.
$files = array('file1.txt', 'file2.txt', 'file3.txt');
k_mcopy($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.

Helper Functions

The following fuctions are available at 'properties:', 'parameters:', 'preparation*:', and 'method*:' in cookbook.

kook_glob($pattern)
Extended glob() function. The difference between kook_glob() and glob() is the following:
  • '**/' pattern support. It matches directory recursively.
  • '{x,y,z}' pattern support. It is equivarent to GLOB_BRACE option.
example code:
$files = kook_glob("lib/**/*.php");
var_export($files);
example result:
array (
  0 => 'lib/main/foo.php',
  1 => 'lib/main/bar.php',
  2 => 'lib/baz.php',
)
kook_array_prefix($array, $prefix)
Add $prefix to head of each elements in $array. This is easier to understand than preg_replace('/^/', $prefix, $array).
example code:
$arr = array('foo', 'bar', 'baz');
$arr2 = kook_array_prefix($arr, 'lib/');
var_export($arr2);
result:
array (
  0 => 'lib/foo',
  1 => 'lib/bar',
  2 => 'lib/baz',
)
kook_array_suffix($array, $suffix)
Add $suffix to tail of each elements in $array. This is easier to understand than preg_replace('/$/', $suffix, $array).
example code:
$arr = array('foo', 'bar', 'baz');
$arr2 = kook_array_suffix($arr, '.php');
var_export($arr2);
result:
array (
  0 => 'foo.php',
  1 => 'bar.php',
  2 => 'baz.php',
)
kook_array_sandwich($array, $postfix, $suffix)
Add $prefix to head and $suffix to tail of each elements in $array.
example code:
$arr = array('foo', 'bar', 'baz');
$arr2 = kook_array_sandwich($arr, 'lib/', '.php');
var_export($arr2);
result:
array (
  0 => 'lib/foo.php',
  1 => 'lib/bar.php',
  2 => 'lib/baz.php',
)
kook_array_flatten($array)
Flatten nested array to a normal array.
example code:
$nested_arr = array('a', array('b', 'c', array('d'), 'e'));
$normal_arr = kook_array_flatten($nested_arr);
var_export($normal_arr);
result:
array (
  0 => 'a',
  1 => 'b',
  2 => 'c',
  3 => 'd',
  4 => 'e',
)

this is useful especially with 'glob()'.

$arr = array(glob('*.php'), glob('*.html'), 'README');
$files = kook_array_flatten($arr);
kook_array_matched($array, $regex)
Select elements which matches to $regex in $array. PHP function 'preg_match()' is used internally so that $regex should be perl-style regular expression.
example code:
$arr = array('foo', 'bar', 'baz');
$arr2 = kook_array_matched($arr, '/^ba/');
var_export($arr2);
result:
array (
  0 => 'bar',
  1 => 'baz',
)

kook_array_matched() is equivarent to the following code.

function kook_array_matched($array, $regexp) {
    $array = preg_grep($regexp, $array);
    return array_values($array);
}
kook_array_unmatched($array, $regex)
Select elements which doesn't match to $regex in $array. PHP function 'preg_match()' is used internally so that $regex should be perl-style regular expression.
example code:
$arr = array('foo', 'bar', 'baz');
$arr2 = kook_array_unmatched($arr, '/^ba/');
var_export($arr2);
result:
array (
  0 => 'foo',
)

This is useful especially exclude 'CVS' directory

$files = kook_glob('**/*');
$files = kook_array_unmatched($files, '/\/CVS\//')

kook_array_unmatched() is equivarent to the following code.

function kook_array_unmatched($array, $regexp) {
    $array = preg_grep($regexp, $array, PREG_GREP_INVERT);
    return array_values($array);
}

Command-line options

Usage: kook [-hvTnqQFlLc] [-b bookname] product

Options:

-h
Print help and quit.
-v
Print version and quit.
-l
List properties and described recipes.
-L
List all propeties, parameters, and recipes.
-q
Quiet mode.
-Q
More quiet mode.
-F
Force recipes to execute (ignore timestamps).
-n
No execution (print 'k_xxx()' command functions).
-N
No execution (print 'method*:' part of recipes).
-T
Don't untabify cookbook. In default Kook command expands tab characters in cookbook into spaces, because YAML parser doesn't allow cookbook (= YAML document) to include tab characters. Command-line option '-T' doesn't expand tab characters.
-c
Validation check of cookbook.
-b bookname
Cookbook filename.
-P propfile
Property filename (default is 'Kookbook.props' and 'properties.yaml'). '-P -' means not to read property file.
--propname=propvalue
Property name and value.