# Count number of lines between two consecutive matches

I have files such as these:

``````0000
0030
+ Something
+ Another thing
+ One more thing
0200
+ Something else
+ And one more thing
0230
``````

In the above, the numbers represent time in `HHMM` format. I want to transform it to:

``````0010 + Something
0020 + Another thing
0030 + One more thing
0115 + Something else
0200 + And one more thing
``````

I will explain the transformation in the above example:

• between `0030` and `0200` there are 3 lines. And time difference between `0000` and `0030` is 30 minutes. So each item below `0030` gets a 10 minutes increment starting from `0000`. And thus the 3 new timestamps are `0010`, `0020` and `0030` respectively.

• similarly between `0200` and `0230` there are 2 lines. And the time difference between `0030` and `0200` is 90 minutes. So each item below `0200` gets 45 minutes increment starting from `0030`. So the 2 new timestamps are `0115` and `0200` respectively.

I have made some progress in writing an elisp-macro for this transformation task:

1. I could modify the answer to this emacs.stackexchange post to compute the time difference between times.

2. I could write a function that takes in time and minutes elapsed to print new time after adding elapsed minutes to time.

3. Now the only thing that I need to complete my recipe is: the number of items between two consecutive `HHMM` patterns so that I can split the difference of time evenly over the items.

4. Lastly I plan to wrap up all the above inside a `(while (search-forward-regexp "pattern" nil :noerror)` to apply the above macros to everywhere applicable inside the file.

Here is the summary of my failed attempts so far:

I recorded a keyboard macro to mark the region between two consecutive `HHMM` pattern and then I am trying to use `(count-lines start end)`. However I get the error:

``````Symbol's value as a variable is void: start
``````

Here is my code snippet for this attempt:

``````(defun timesheet-fill-times ()
(interactive)
(let (diff lines)
(setq diff (timesheet-time-diff))
(timesheet-mark-lines)
(setq lines (count-lines start end))
(message "%d diff %d lines" diff lines)
)
)
``````

where `timesheet-time-diff` is my elisp macro to compute time difference between consecutive patterns and `timesheet-mark-lines` is my recorded keyboard macro to mark the relevant region. I tried to modify the function definition to: `(defun timesheet-fill-times (start end) ;; rest of code )`. Now the error becomes:

``````Wrong number of arguments
``````

However, strangely, the following works alright: I have defined this function:

``````(defun my-count-lines-region (start end)
(interactive "r")
(message "%d"
(count-lines start end)))
``````

So if I keep my cursor on `+` after `0030` and do `M-x timesheet-mark-lines RET M-x my-count-lines-region RET` I get 3 as expected. And similarly with the cursor on `+` after `0200` I get 2 as expected.

My other approach I thought of was that: I can match for the pattern `HHMM - some lines - HHMM` by using something like this:

``````(re-search-forward (concat "^\\([0-2][0-9]\\)\\([0-5][0-9]\\)"
"\\(\\(.\\|[\n]\\)*?\\)"
"\\([0-2][0-9]\\)\\([0-5][0-9]\\)"
) nil :noerror)
``````

Is there someway I can count the number of newline characters in that match?

• My guess is that it would be simpler to write this in python (or perl if you are so inclined). Jul 3 at 3:10
• Sure, great suggestion. But, I am trying to learn lisp. And it feels so great when something works! Jul 3 at 4:33
• You will not learn much lisp of this, but you could use `count-words-region`. Despite its name, it also gives you the number of lines is the region. Jul 3 at 5:17
• You can get the match with `match-string 0`. You will find what exactly this pattern matches to (which does not include multiple lines). Otherwise you could `split-string` on `\n` and get the `length` of the resulting list. You can test your regexp with the `regexp-builder`. Jul 3 at 6:02

@Inspired_Blue I did not try to completely follow and understand your answer. But as a matter of feedback, I will post here my way of solving the problem. While incidentally it inserts the correct times, you should still add functions to get the correct times for the general case.

I strongly recommend you to get familiar with `edebug` as it is only a really tiny bit of reading, while it will greatly boost your productivity. Then practice how to use `edebug`, while using it for reading my solution.

In short, in my solution I first parse the buffer into the following list (call `se-answer` interactively on your original text to see a pretty printed version of the list), where the car of each element is a pair (cons) with the start and end times, and the cdr forms a list containing the lines (the length of this list, effectively counts the number of lines between consecutive matches, providing an answer to your question).

``````(((0 . 30))
((30 . 200) "+ Something\n" "+ Another thing\n" "+ One more thing\n")
((200 . 230) "+ Something else\n" "+ And one more thing\n"))
``````

Subsequently I parse the list 'back' to your transformed text using a "list-eater" (term used in the books "Land of lisp" and "Realm of racket")

``````;; define handy macro for pretty printing
(defmacro parse-print (data &optional stream mode)
"Return value but pretty print when called interactively.
Optionally set STREAM (see `pp'). If STREAM is a buffer then
optionally set MODE for buffer (for syntax highlight)."
`(if (not (called-interactively-p))
,data
(pp ,data ,stream)
(when (bufferp ,stream))
(pop-to-buffer ,stream)
(when ,mode
(funcall ,mode))))

(interactive)
;; to prevent infinite loop first delete all trailing blank lines and
;; whitespace, and if exist final newline
(delete-trailing-whitespace)
(goto-char (point-max))
(when (bolp)
(delete-backward-char 1))
(goto-char (point-min))
;; parse buffer
(let (blocks) ;; we designate blocks for each part between a start- and end-time
(while (not (eobp))
;; use only this while loops body for single block (e.g. for your keyboard
;; macro)
(let (lines ;; per block we collect (push) the text lines in (to) a list
(start-time (thing-at-point 'number))) ;; also we collect the start-time
(forward-line)
(while (not (thing-at-point 'number))
(push (thing-at-point 'line t) lines)
(forward-line))
(push (cons (cons start-time (thing-at-point 'number))
(nreverse lines)) ;; and collect (push) the start- and end-time plus
blocks)                 ;; the lines in the block in (to) the list of blocks
(end-of-line)))
(unless (called-interactively-p)
(erase-buffer))
;; NOTE that you can call this function/command interactively to see the
;; pretty printed list.
(parse-print (nreverse blocks) (get-buffer-create "parsed-list") 'emacs-lisp-mode)))

(defun insert-transformed-text (parsed-buffer)
;; First an exit condition for. A list eater 'eats' the elements of the list
;; so we exit when there is only one element left (which can optionally get
;; inserted to the buffer).
(if (not (cdr parsed-buffer))
(if (y-or-n-p "Include final time?")
(insert (format "%s" (cdaar parsed-buffer)))
nil)
(int-size (/ (- (cdaar parsed-buffer) (caaar parsed-buffer)) n)))
(let ((time (+ (caaar parsed-buffer) int-size)))
(insert (format "%s %s" time x))
(setq time (+ time int-size)))))
;; By passing back the 'tail' of the list, the function 'slowly eats' the
;; list.
(insert-transformed-text (cdr parsed-buffer))))
``````

Find a buffer with `C-x C-f` and give it some name ending with the `.el` (this opens a buffer in `emacs-lisp-mode`), paste and load the code in that buffer. Then open a scratch buffer in a split screen and paste your original text/list. Then instrumentalize the `insert-transformed-text` (or the `se-answer`) function/command (see edebug), and finally in the scratch buffer (with ONLY your original text) run the command `M-x insert-transformed-text`.

Have fun!

SECOND EDIT

I see now that you already found `count-lines` (I read over it). So the `(interactive "r")` provides your function with `region-beginning` and `region-end` as its arguments (see here and here).

FIRST EDIT

Programmatically, `count-words-region` does not fulfil your needs directly. But by inspecting the function you will find the function `count-words--message`, from which you get another answer by inspecting its code (simpler than my provided solution).

END EDIT

As it seems you want to mark the region, I provided a simplest answer as a comment. Now I am not sure what you ask exactly, but more programmatically, I would propose something as follows:

``````(defun se-answer ()
(interactive)
(let ((start-time (thing-at-point 'number))
(forward-line)
(re-search-forward "^[0-9]+")
(let ((end-time (thing-at-point 'number))
(message "start-time %s\nstart-line %s\nend-time   %s\nend-line   %s\nnum lines: %s"
start-time
start-line
end-time
end-line
(- end-line start-line 1)))))
``````

@dalanicolai's `se-answer` function (see here), I believe, is superior to my solution as mine uses regular expressions and hence is possibly slower. Hence I accept his solution as the answer to this question.

However I will share my solution here as well (for anyone who may learn something new from code snippets on stackexchange, like I do):

``````(defun my-count-occurences (regex string)
(my-recursive-count regex string 0))

(defun my-recursive-count (regex string start)
(if (string-match regex string start)
(+ 1 (my-recursive-count regex string (match-end 0)))
0))

(defun timesheet-count-items ()
(interactive)
(let (match diff lines)
(save-excursion
(if (re-search-forward (concat "^\\(\\(.\\|[\n]\\)*?\\)"
"\\([0-2][0-9][0-5][0-9]\\)"
) nil :noerror)
(my-count-occurences "\n" (match-string 1))
))))

(defun timesheet-time-diff ()
(interactive)
(let (begin-hh begin-mm end-hh end-mm diff)
(save-excursion
(if (re-search-backward (concat "^\\([0-2][0-9]\\)\\([0-5][0-9]\\)"
"\\(\\(.\\|[\n]\\)*?\\)"
"\\([0-2][0-9]\\)\\([0-5][0-9]\\)"
) nil :noerror)
(progn
(setq begin-hh (string-to-number (match-string 1)))
(setq begin-mm (string-to-number (match-string 2)))
(setq end-hh (string-to-number (match-string 5)))
(setq end-mm (string-to-number (match-string 6)))
(setq diff (- (+ end-mm
(* 60 end-hh))
(+ begin-mm
(* 60 begin-hh))))
(list begin-hh begin-mm diff)
)
)
)))

(interactive)
(let (M h m O)
(save-excursion
(progn
(setq M (+ (* 60 hh) mm de))
(setq h (/ M 60))
(setq m (% M 60))
(setq O (concat (format "%02d" h)(format "%02d" m)))
))))

(defun timesheet-fill-time ()
(interactive)
(let (temp count diff step start-hh start-mm delta)
(save-excursion
(setq count (timesheet-count-items))
(setq temp (timesheet-time-diff))
(setq start-hh (nth 0 temp)
start-mm (nth 1 temp)
diff (nth 2 temp))
(setq step (/ diff count))
(while (> count 0)
(setq count (- count 1))
(setq delta (- diff (* count step)))
(beginning-of-line)
(insert (concat (timesheet-time-add start-hh start-mm delta)
" "))
(next-line)
)
)))

(defun timesheet-process ()
(interactive)
(goto-char (point-min))
(while (re-search-forward "
\\([^
0-9]\\)" nil t)
(backward-char 1)
(timesheet-fill-time))
(goto-char (point-min))
(while (search-forward-regexp "
\\([0-9]\\{4\\}\\)
" nil t) (replace-match "
" t nil))
)
``````

So to compare my solution with @dalanicolai's solution:

1. I solve for the entire transformation as in the example of my question.
2. Clearly @dalanicolai's solution is more elegant and pithy.
3. I will have to adapt @dalanicolai's solution to my needs to carry out the entire transformation (which I plan to do sometime soon and add as an edit to my answer).
4. My files have about 400-600 lines. And even my solution runs almost instantaneously.

Lastly, if you happen to read my code and are kind enough to offer me feedback, please do. As I have just started learning `lisp`, I will value such things immensely!

• FYI, I have once more edited my answer Jul 3 at 11:16
• Many thanks! Very helpful. Jul 3 at 13:51