Showing posts with label Hook. Show all posts
Showing posts with label Hook. Show all posts

Monday, September 3, 2012

Customizing Zsh (Part 1): Hooks and RPrompt

This post follows my post on the zsh macros, and explain how to use the zsh hooks "preexec", and how to customize your (right) prompt to give information about something that changes (current branch of git, date, …).

1 Preexec hook

To enable the hooks, the user first has to load the add-zsh-hook function. To achieve that goal, paste the following line:

autoload -U add-zsh-hook

Once it is done, we are able to add and remove a function from a hook. For our case, we suppose we want to add the hook_function to the preexec hook. The following snippet shows how to do that.

hook_function()
{
  echo $1
  echo $2
  echo $3
}

add-zsh-hook preexec hook_function      # Add it to the preexec hook.
# add-zsh-hook -d preexec hook_function # Remove it for this hook.

Adding and removing a function from a hook is done the same way for every hooks.

The preexec hook is ran each time a command is read by the shell and is about to be executed.

Each function run by the preexec hook receives three arguments. The first one is the line as it was written. The second line is the line with alias expanded and truncated with a certain size limit. The third line is the full line with alias expanded. This thread shows an example. For the macros module, I decided to use the third expression because I want to be able to use my aliases in my macros. But that depends of the application you want to write.

2 Interactive prompt

You know that, there is plenty of ways to customize your prompts in Zsh. I'll just present one of them today, some post about the same topic might follow.

What I present today is how to use your RPROMPT to print some information about what you want, and is actualized every time you enter a new command. It is easy to do, here is the first step:

setopt prompt_subst

Maybe you have recognized the beginning of what you have to add to your configuration file to make the zsh macros module working? Well done! Otherwise, it doesn't matter. So what does this little line? According to the man (man zshoptions): "If set, parameter expansion, command substitution and arithmetic expansion are performed in prompts".

Let's see what happens if we don't set this option:

$ msg="Hello"
$ RPROMPT="($msg)"
$                                       (Hello)
$ msg="Goodbye"                         (Hello)
$                                       (Hello)
$ echo $RPROMPT                         (Hello)
(Hello)
$                                       (Hello)

Pretty annoying right? In fact, the shell expands $msg before it is received by RPROMPT, so what happens is simple, it prints the value of what he receives: "Hello" literally. So, let's see what happens if we re-execute the same sequence of commands with the prompt_subst option set?

$ setopt prompt_subst
$ msg="Hello"
$ RPROMPT="($msg)"
$                                       (Hello)
$ msg="Goodbye"                         (Hello)
$                                       (Hello)
$ echo $RPROMPT                         (Hello)
(Hello)
$                                       (Hello)

Here is the most common error (I think) that leads your prompt not to expand your variables. The RPROMPT command doesn't know there is a variable to expand, and you have to prevent your shell to expand it by single-quoting your assignation. This way:

$ RPROMPT='($msg)'
$ echo $RPROMPT                         (Hello)
($msg)
$ msg="Goodbye"                         (Hello)
$                                       (Goodbye)

There is two things to be careful with when you want to have your prompt expanding some content: the option prompt_subst must be set, and the content of the variable RPROMPT contains the thing you want to be expanded each time (Think about single quoting it!).

Now let's see what we can do with it! If you are a module writer, you can use a variable as flag (as I did), or give a function that allows to get information about something (as it's done by zsh to allow user to get vcs information).

For getting your branch in your RPROMPT, I recommend you to read the answer of ko-dos which is very complete. If you just paste the code, it will work. But you know why it uses single quotes, and why there must be the prompt_subst option set. For the zstyle part, I didn't try to understand it. One day, I'll try :)

Let's see how get the time in your right prompt. First, how to get the time only when calling the date command? I read this post to find the right format. It is just date +%T. Now let's apply what we have learn:

$ RPROMPT='$(date +%T)'
$                                       (23:42:00)

Now you are just limited by your needs and by your imagination :) If you make your own custom prompt, please share it in comments. I hope you like it!

Sunday, March 11, 2012

How to use git to avoid writing ChangeLog by hand?


The standard GNU defines what must be a ChangeLog file (see:
http://www.gnu.org/prep/standards/html_node/Change-Logs.html). The
main goal of this is to be able to track bugs, and to understand the
history of a project.


1 Why keeping a ChangeLog?


In the past, we must keep a ChangeLog file for each project, since
there was no tool able to give all the history in every condition.
I am too young to know the work flow with CVS and other tool. I
learn the control version with SVN. But to have access to all the
history of a project, we'll need to be connected. And it is long.

Now we have git (or Mercurial, but I don't know this one), which are
distributed system, and they allow to keep all the history of a project
in local. So, why should we keep a ChangeLog file?

Pros

  • When the project is released, the `git log` is not accessible.
  • There is copyright issue in free software.
  • It is easy to write a good ChangeLog with Emacs (and I'm sure it
    is easy with vim too).

Cons

  • There is several tools to generate a ChangeLog file with the output
    of git log. I think about the tool 'gitlog-to-changelog' from the
    gnulib project (see: http://www.gnu.org/software/gnulib/).
  • It is common, when playing with branches and rebasing a lot, to have
    conflict only in the ChangeLog file.

By generating the ChangeLog when making a release (or an archive), we
solve the problem of the history and the copyright. We can use a
ChangeLog file, not in the repository (maybe it is a good idea to put
the "ChangeLog" in the '.gitignore'), to write the log message, and
then we can use a little script to take the first entry and give it to
git.

For example, a simple function like this can do the trick:


commit()
{
   [[ ! -f ChangeLog ]] && {
   echo 'no ChangeLog in current directory' >&2
   return 1
   }

   git commit -m "`sed'1d;/^....-..-../Q;s/^\t//;' ChangeLog`" "$@"
}

This script is highly enhanceable. This is just an idea of what could
be the script I am talking about.



2 Using Emacs to write the ChangeLog


Now let's talk about the work flow, and the use of Emacs for writing
the log. Let's suppose we have a ChangeLog file at the root of the
project. The main idea is each time you modify something in a file,
you hit "C-x 4 a", and its open the ChangeLog, and add an entry
(which follows the GNU Coding Standard), you just have to write the
meaning of your change.

Before committing, think about add a one-line summary !

There is a little problem with Emacs at this level. In the past, the
common work flow was one commit by day. And the One True Editor
follows this standard. So there is a way to bypass this, an option
allows the function behind "C-x 4 a" to create a new entry. But it
does it each time, and this is not what we want. So I create a little
wrapper around this. Here is the function:


(defun new-changelog-entry()
  (interactive)
  (setq add-log-always-start-new-record t)
  (add-change-log-entry-other-window)
  (setq add-log-always-start-new-record nil))

; "C-x 5 a" runs new-changelog-entry.
(global-set-key "5a" (quote new-changelog-entry))

The idea is to set the option before calling, and unset it after. So,
only when we want to create a new entry, a new entry is created. :D


3 Be sure the log follows a good format


In the aim to be able to translate the output of `git log` into a
GNU standard compliant ChangeLog, the commit message must follow
a strict format. So, how to achieve this goal?

Git provides several kind of hooks. A hook is a script called when a
specific operation occurs. There is a lot of source on the web to know
what is a hook. Here I'll talk about the use of a script (developed by
me and one of my teacher) for solving the format of the git log.

It is a script which can be run server-side or client-side. You can
find this script here:
https://github.com/Enki-Prog/tools/blob/master/git/update. Here I will
talk about the problem of getting all the commit between two push. And
the way to know easily the file modified when committing.

The strategy we applied, is to authorize any kind of log in a personal
branch (`pseudo/feature'), and to reject a push when it is not (either
a `candidates/feature' or a branch with no `/') and don't follow the
format.

There are several thing checked, and it is shared by the two way to
call this script. The explanation above are talking about the way
to get the commits, and the information to be able to check.

The way we check after, is less interesting I think, because if you
read this article, maybe it is because you want to develop yourself
this kind of tools. And you just need the way to don't have to look
a lot on the web how to make this, all the information you need are
here or at worst on the script.



3.1 Server-side

We receive three arguments: the ref name, the old revision, and the
new revision.

  1. If the new revision is a null sha1, it means it is a branch deletion.
    So, nothing to do here.
  2. If the old revision is a null sha1, it is a branch creation.

    In this case, to get the new revision, the command to get the
    commits, we need to call:


    git rev-parse --not $otherbranches | git rev-list --stdin newrev
    

  3. In the other case, we need to replace newrev by "oldrev..newrev".

To get the `$otherbranches' the command is:


git for-each-ref --format='%(refname)' refs/heads |
  grep -F -x -v $refname |
  grep -x 'refs/heads/\(candidates/.*\|[^/]*\)'

The first line gets the list of branches. The second filters out the
current branch. And the last one, keeps only the one which are non
personal branch.



3.2 Client-side

In this case, we have only one argument: the path to the temporary
file which contains the log which will be tested. In this case it is
easier, because there is only one commit. The question is, how to get
the list of modified file? There is several way, but the one I found
the simpler, is to make:


git status --porcelain

The output is simple: Two characters, and the filename (eventually two,
in the case of a `git mv`). If the first character is not a ? or a space,
the file is in the index and ready to be committed.



4 Conclusion


We have talk about the question "should I keep a ChangeLog in my
project?". And I developed on how to make this change in a good way.
Thanks to git, Emacs, a tool to check if the log is correct and
`gitlog-to-changelog'. Obviously, this is the way I choose for me,
and each part I present can be switched.

Feel free to leave a comment with your opinion and/or your suggestion :)