
releases:

  - release:  0.7.1
    date:     2011-11-11
    bugfix:

      - Fixed to include 'kook/books/*.py' into .egg file.


  - release:  0.7.0
    date:     2011-11-05
    enhancements:

      - (EXPERIMENTAL!!) Remote command execution (ssh and sftp) is available.
        This is very useful to deploy your application into servers.

        Ex (Kookbook.py)::

	    from kook.remote import Remote
	    remote = Remote(
	        hosts    = ['www1', 'www2', 'user3@www3:10022'],
	        port     = 22,
	        user     = 'user1',
	        #password = None,      # for login, '~/.ssh/id_rsa' and sudo
	        passphrase = None,     # only for '~/.ssh/id_rsa'
	        sudo_password = 'xxx', # only for sudo command
	    )

	    @recipe
	    @remotes(remote)
	    def hostinfo(c):
	        """show hostname"""
	        ssh = c.ssh
	        ssh('hostname')        # run command with ssh
	        ssh('whomai')          # run command with ssh
	        ssh.sudo('whoami')     # run command with sudo

	    @recipe
	    @remotes(remote)
	    def exchange(c):
	        """upload and download files"""
	        ssh = c.ssh
	        with ssh.pushd('work/apps'):
	            ssh.get('file.remote')    # download a file
	            ssh.put('file.local')     # upload a file
	            ssh.mget('remote.*')      # download files
	            ssh.mput('local.*')       # upload files

        Notice that you must configure ssh at first and confirm that
        you can log into servers without typing password::

	    bash> ssh user1@www1
	    bash> ssh user1@www2
	    bash> ssh -p 10022 user3@www3
	    bash> kk hostinfo
	    ### * showinfo (recipe=showinfo)
	    [user1@www1]$ hostame
	    www1
	    [user1@www1]$ whoami
	    user1
	    [user1@www1]$ sudo whoami
	    root
	    [user2@www2]$ hostame
	    www2
	    [user2@www2]$ whoami
	    user2
	    [user2@www2]$ sudo whoami
	    root
	    [user3@www3]$ hostame
	    www3
	    [user3@www3]$ whami
	    user3
	    [user3@www3]$ sudo whoami
	    root

        Currently commands are executed sequencially (not in parallel).

      - (EXPERIMENTAL!!) Password object supported.
        Password object asks you to enter password in prompt when necessary.

        Ex (Kookbook.py)::

	    from kook.remote import Remote, Password
	    remote = Remote(
	        hosts         = ['user1@www1:22'],
	        #password     = Password('login'),
	        passphrase    = Password('~/.ssh/id_rsa'),
	        sudo_password = Password('sudo command')
	    )
	    #
	    @recipe
	    @remotes(remote)
	    def remote_test(c):
	        ssh = c.ssh
	        ssh.sudo('whoami')

        Output example::

	    bash> kk remote_test
	    ### * remote_test (recipe=remote_test)
	    Password for ~/.ssh/id_rsa: 
	    Password for sudo command: 
	    [user1@www1]$ sudo whoami
	    root

        It is easy to share password object.

        Ex (Kookbook.py)::

	    from kook.remote import Remote, Password
	    passwd = Password()
	    remote = Remote(
	        hosts         = ['user1@www1:22'],
	        password      = passwd,
	        passphrase    = passwd,
	        sudo_password = passwd,
	    )

    changes:

      - Category class is changed to convers all instance methods into staticmethods.

        Ex (Kookbook.py):

	    class apache(Category):
	        @recipe
	        def start(c):
	           system('apachectl start')

	    ## start() is converted into staticmethod
	    assert type(apache.__dict__['start']) == staticmethod
	    from types import FunctionType
	    assert type(apache.start) == FuntionType

	This makes execution of other recipes in category easier.

	    class apache(Category):
	        @recipe
	        def start(c):
	           ...
	        @recipe
	        def stop(c):
	           ...
	        @recipe
	        def restart(c):
	           apache.start(c)    # execute other recipe
	           apache.stop(c)     # execute other recipe

      - (Internal) kook.config.stdout and kook.config.stderr are removed.



  - release:  0.6.0
    date:     2011-10-27
    enhances:

      - 'kookbook' variable is available in your cookbook.
        Using it, you can do:

	* Find recipe and manipulate it (see below).
	* Load other cookbook (see below).
	* Set default product (instead of 'kook_default_product').
	* Set materials (instead of 'kook_materials').

        Example::

	    ## set default product
	    kookbook.default = 'hello.o'

	    ## set materials
	    kookbook.materials = ['index.html']
	    kookbook.materials.extend(['A.html', 'B.html'])

      - Recipe meta-programming support.
        You can find and manipulate recipes::

	    @recipe('*.o', ['$(1).c'])
	    def file_o(c):
	        system(c%"gcc -c $(ingred)")

	    ## find recipe and manipulate it
	    r = kookbook['foo.o']
	    print(r.product)   #=> 'foo.o'
	    print(r.ingreds)   #=> ['foo.c']
	    r.ingreds.append('foo.h')
	    def file_foo_o(c):
	        kookbook.get_recipe('*.o').method(c)  ## equivarent to file_o(c)
	    r.method = file_hello_o

      - Load other cookbooks by kookbook.load().
        This enables you to split your Kookbook.py into several files.

	    ## load recipes and properties from other cookbook
	    kookbook.load('./book.py')        # load book in the same directory as Kookbook.py
	    kookbook.load('../book.py')       # load book in the parent directory as Kookbook.py
	    kookbook.load('../../book.py')    # load book in the parent's parent directory
	    kookbook.load('.../book.py')      # search book in the parent directory recursively
	    kookbook.load('~/book.py')        # load book in home directory
	    kookbook.load('@foo/book.py')     # '@module' == dirname(module.__file__)

	Notice that kookbook.load() loads only recipes and properties.
	If you want to load everything, pass True as 2nd argument.

	    kookbook.load('./foo.py')        # load recipes and properties
	    kookbook.load('./foo.py', True)  # load all data (i.e share everything)

        If you set '__export__' in foobar.py, data are imported into
	your Kookbook.py.
        See 'kook/books/clean.py' or 'kook/books/all.py' as examples.

      - Support some useful task recipes: clean, sweep, and all.

        * 'clean' is a recipe to remove by-products (such as *.o or *.java).
        * 'sweep' is a recipe to remove products and by-products.
        * 'all' is a recipe to produce all products.

	Exaple::

	    ## load cookbook
	    ## ('@kook' is equivarent to 'os.path.dirname(kook.__file__)')
	    kookbook.load("@kook/books/clean.py")
	    kookbook.load("@kook/books/all.py")

	    ## add file patterns to remove
	    CLEAN.extend(["**/*.o", "**/*.class"])   # by-products
	    SWEEP.extend(["*.egg", "*.war"])         # products

	    ## add product names you want to produce
	    ALL.extend(['product1', 'product2'])

      - Namespace is now supported. It is called as 'Category' in Kook.
        Category is just an class which is provided by Kook.
        Category can be nested.

        Ex::

            class git(Category):             # outer namespace

              class stash(Category):         # nested namespace

                @recipe
                def __index__(c):            # task recipe (default)
                  """git stash list"""
                  system("git stash list")

                @recipe
                def save(c):                 # task recipe
                  """git stash save"""
                  system("git stash save")

                @recipe
                def pop(c):                  # task recipe
                  """git stash pop"""
                  system("git stash pop")

        Result::

            bash> pykook -l
            Properties:

            Task recipes:
              git:stash           : git stash list
              git:stash:save      : git stash save
              git:stash:pop       : git stash pop

            File recipes:

            bash> kk git:stash
            ### * git:stash (recipe=__index__)
            $ git stash list

            bash> kk git:stash:save
            ### * git:stash:save (recipe=save)
            $ git stash save

        Currently only task recipe is available in Category.

      - Concatenation supported.
        You can concatenate your cookbook and pyKook libraries into a file.
	Using concatenated file, user doesn't need to install pyKook at all.
        To concatenated files, add the following into your cookbook::

	    kookbook.load('@kook/books/concatenate')
	    #CONCATENATE_MODULES.append(foo.bar.module)  # if you want
	    #CONCATENATE_BOOKS.append('foo/bar/book.py') # if you want

	And type in terminal::

	    bash> pykook -o yourscript Kookbook.py
	    bash> chmod a+x yourscript
	    bash> ./yourscript -l

	If you don't specify 'Kookbook.py', it means that all pyKook
	libraries are concatenated into a file. You can use it instead
	of 'pykook' script.

	    bash> pykook -o yourscript
	    bash> chmod a+x yourscript
	    bash> ./yourscript -h

      - Argument description is available.
        In the following example, the last argument of @spices() is
	argument description of task_copy().

        Ex (Kookbook.py)::

	    @recipe
	    @spices('-r: recursive', 'src dest')   # argument description
	    def task_copy(c, *args, **kwargs):
	        """copy files"""
		if kwargs.get('r'):
		    cp_pr(*args)
		else:
		    cp_p(*args)

        Argument desription is displayed when '-l' or '-L' option is specified.

        Ex (command-line)::

	    $ kk -l
	    Properties:

	    Task recipes:
	      copy src dest       : copy files
	        -r                    recursive

	    File recipes:

      - Private spice option is available.
        If you don't specify spice option's help message, it is regarded as
	private and is not showin to users by command-line option '-l' or '-L'.
        In the following example, spice options '-x' and '-y' are private
	because they don't have help message.

        Ex (Kookbook.py)::

	    @recipe
	    @spices('-x', '-y arg')   # private spice options
	    def foo(c, *args, **kwargs):
	        """dummy recipe"""
		print(kwargs)

        Ex (command-line)::

            bash> kk -l    # notice that neither '-x' nor '-y' are shown
            Properties:

            Task recipes:
              foo                 : dummy recipe

            File recipes:

      - New command 'pushd()' provided.
        This is almost same as 'chdir()', but it can be used as decorator.

	    ## for Python >= 2.5
	    with pushd("tmp"):
	        # ... do something in 'tmp' directory ...
	    # ... back to previous directory automatically ...

	    ## for Python <= 2.4
	    @pushd("tmp")
	    def do():     # called by pushd() automatically!
	        # ... do something in 'tmp' directory ...
	    # ... back to previous directory automatically ...


  - release:  0.0.5
    date:     2009-11-02
    enhances:

      - Command 'chdir()' can now take callable object as 2nd argument.
        In this case, (1) change directory, (2) call callable object,
	and (3) back to original directory.
	This feature is provided for Python 2.4 or older which doesn't
	support with-statement.

            def f():
	       system 'python test_all.py'
	    chdir('test', f)
            ## above is same as:
	    #with chdir('test'):
	    #    system 'python test_all.py'

    changes:

      - 'kk' command now searches Kookbook.py in parent directory recursively
        without $KK_CLIMB=1 setting.


  - release:  0.0.4
    date:     2009-09-06
    changes:

      - Compact style of @recipe decorator supported.
        ex::
           ## normal style
	   @recipe
	   @product('*.o')
	   @ingreds('$(1).c', '$(1).h')
	   def file_o(c):
	       system(c%"gcc -c $(ingre)")

           ## compact style
	   @recipe('*.o', ['$(1).c', '$(1).h'])
	   def file_o(c):
	       system(c%"gcc -c $(ingre)")

      - 'kk' script supports '$KK_CLIMB' environment variable.
        If you set it, 'kk' script searches parent directories
        when 'Kookbook.py' is not found.
        ex::
           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

      - New command-line option '-R' (recursively) supported.
        If you specify '-R', pykook command searches Kookbook.py
	in parent directory recursively.
        ex::
           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             # OK
           ### * clean (recipe=clean)
           $ rm **/*.pyc



  - release:  0.0.3
    date:     2009-08-09
    changes:

      - IMPORTANT!!
        New '@recipe' decorator is required for each recipe function.
        If function is decorated by '@recipe', 'task_' prefix is not necessary.

        ex:
          ## previous version
          def task_clean(c):    # 'task_' prefix is required
            rm_rf("*.o")

          ## since this release
          @release              # @release decorator is required
          def clean(c):         # 'task_' prefix is not necessary
            rm_rf("*.o")

        See http://www.kuwata-lab.com/kook/pykook-users-guide.html#cookbook-recipekind
        for details.

      - Library codes are much refactored.

    enhancements:

      - IMPORTANT!!
        New feature to support command-line script framework.
        You can convert Kookbook.py into command-line script.
        See http://www.kuwata-lab.com/kook/pykook-users-guide.html#topic-framework
        for details.

      - New command-line option '-n' (no exec) supported.
        If you specify '-n', commands such as 'cp()' or 'rm()' are not executed.
        In other words, '-n' means 'dry-run'.

      - Add a lot of test scripts.

    bugfixes:

      - A bug related timestamp detection is now fixed.
        There was a case that product file was not updated even when
        ingredient files were updated.

      - A bug about recipe tree is fixed. There was a case that the same recipe
        can be invoke more than once when an intermediate recipe was required
        from several recipes.


  - release:  0.0.2
    date:     2009-07-30
    enhancements:

      - Python 3 support.

      - Add 'kk' script which is shortcat for kk command.

    changes:

      - Decorator '@cmdopts()' is renamed to '@spices()', and
        there is no need to call parse_cmdopts().

            ### prev version
            @cmdopts('-v: verbose', '-f file: filename')
            def task_example(c, *args):
                opts, rests = c.parse_cmdopts(args)
                verbose = opts.get('v', False):
                fileame = opts.get('f', None)

            ### since this release (0.0.2)
            @spices('-v: verbose', '-f file: filename')
            def task_example(c, *args, *kwargs):
                opts, rests = kwarts, args
                verbose = opts.get('v', False):
                fileame = opts.get('f', None)

      - Remove 'pyk' script

      - Testing library is changed from Python's unittest library
        into 'test/oktest.py'.

    bugfixes:

      - glob2() now recognizes files on current directory.


  - release:  0.0.1
    date:     2008-10-19
    enhancements:

      - First release
