10

I have the below in my documentation:

#+INCLUDE: "code/basic.sv" :src systemverilog :lines "14-117"

Here line 14 is where I have class basic extends .. and line 116 is where I have endclass.

Is there a way to auto insert the numbers 14 and 117 (=116+1) so that I don't have to manually update them every time I modify the code/basic.sv?

3
  • So you always want it to go from class to endclass?
    – Malabarba
    Sep 24, 2014 at 6:31
  • 1
    No. That was an example. I am thinking of a solution where I can provide regex for begin and end lines.. Something would evaluate a function org-include-src(FILE, LANGUAGE, REGEX_BEGIN, REGEX_END) Sep 24, 2014 at 7:42
  • One way is, placing some sort of unique markers (begin end) in the the included file and find them with a function which would be hooked to org-export-before-processing-hook to preprocess for the line numbers. Another way is just send a feature request mail to org mailing list :)
    – kindahero
    Sep 25, 2014 at 13:45

2 Answers 2

8

Here is another option. This one is let's you customize the regular expressions on a per-include basis. It should fit better with some workflows as you're not limited to extension-based definitions.

To Use

Do something like the following in your org-file. (The :lines keyword is optional)

#+INCLUDE: "code/my-class.sv" :src systemverilog :range-begin "^class" :range-end "^endclass" :lines "14-80"

The function will visit "my-class.sv" and search for those two regexps, and then it will update the :lines keyword according with the match result.

If :range-begin is missing, the range will be "-80".
If :range-end is missing, the range will be "14-".

The Code

(add-hook 'before-save-hook #'endless/update-includes)

(defun endless/update-includes (&rest ignore)
  "Update the line numbers of #+INCLUDE:s in current buffer.
Only looks at INCLUDEs that have either :range-begin or :range-end.
This function does nothing if not in org-mode, so you can safely
add it to `before-save-hook'."
  (interactive)
  (when (derived-mode-p 'org-mode)
    (save-excursion
      (goto-char (point-min))
      (while (search-forward-regexp
              "^\\s-*#\\+INCLUDE: *\"\\([^\"]+\\)\".*:range-\\(begin\\|end\\)"
              nil 'noerror)
        (let* ((file (expand-file-name (match-string-no-properties 1)))
               lines begin end)
          (forward-line 0)
          (when (looking-at "^.*:range-begin *\"\\([^\"]+\\)\"")
            (setq begin (match-string-no-properties 1)))
          (when (looking-at "^.*:range-end *\"\\([^\"]+\\)\"")
            (setq end (match-string-no-properties 1)))
          (setq lines (endless/decide-line-range file begin end))
          (when lines
            (if (looking-at ".*:lines *\"\\([-0-9]+\\)\"")
                (replace-match lines :fixedcase :literal nil 1)
              (goto-char (line-end-position))
              (insert " :lines \"" lines "\""))))))))

(defun endless/decide-line-range (file begin end)
  "Visit FILE and decide which lines to include.
BEGIN and END are regexps which define the line range to use."
  (let (l r)
    (save-match-data
      (with-temp-buffer
        (insert-file file)
        (goto-char (point-min))
        (if (null begin)
            (setq l "")
          (search-forward-regexp begin)
          (setq l (line-number-at-pos (match-beginning 0))))
        (if (null end)
            (setq r "")
          (search-forward-regexp end)
          (setq r (1+ (line-number-at-pos (match-end 0)))))
        (format "%s-%s" l r)))))
1
  • 2
    This is great! Now I can use this to export multiple snippets from the same file. Snippet 1: #+INCLUDE: "code/basic.sv" :src systemverilog :range-begin "// Example 1" :range-end "// End of Example 1". Snippet 2: #+INCLUDE: "code/basic.sv" :src systemverilog :range-begin "// Example 2" :range-end "// End of Example 2". The execution is flawless! Thanks for implementing this this quick! Sep 26, 2014 at 1:38
5

The best way I can think of is to update these numbers immediately before exporting or before evaluating.

The Updater

This is the function that goes through the buffer. You can bind it to a key, or add it to a hook. The following code updates the lines whenever you save the file, but if your use case is different, just find out which hook you need! (org-mode is full of hooks)

(add-hook 'before-save-hook #'endless/update-includes)

(defun endless/update-includes (&rest ignore)
  "Update the line numbers of all #+INCLUDE:s in current buffer.
Only looks at INCLUDEs that already have a line number listed!
This function does nothing if not in org-mode, so you can safely
add it to `before-save-hook'."
  (interactive)
  (when (derived-mode-p 'org-mode)
    (save-excursion
      (goto-char (point-min))
      (while (search-forward-regexp
              "^\\s-*#\\+INCLUDE: *\"\\([^\"]+\\)\".*:lines *\"\\([-0-9]+\\)\""
              nil 'noerror)
        (let* ((file (expand-file-name (match-string-no-properties 1)))
               (lines (endless/decide-line-range file)))
          (when lines
            (replace-match lines :fixedcase :literal nil 2)))))))

The Regexps

This is where you define the regexps which will be used as the first and last lines to be included. You can give a list of regexps for each file extension.

(defcustom endless/extension-regexp-map 
  '(("sv" ("^class\\b" . "^endclass\\b") ("^enum\\b" . "^endenum\\b")))
  "Alist of regexps to use for each file extension.
Each item should be
    (EXTENSION (REGEXP-BEGIN . REGEXP-END) (REGEXP-BEGIN . REGEXP-END))
See `endless/decide-line-range' for more information."
  :type '(repeat (cons string (repeat (cons regexp regexp)))))

The background worker

This is the guy that does most of the work.

(defun endless/decide-line-range (file)
  "Visit FILE and decide which lines to include.
The FILE's extension is used to get a list of cons cells from
`endless/extension-regexp-map'. Each cons cell is a pair of
regexps, which determine the beginning and end of region to be
included. The first one which matches is used."
  (let ((regexps (cdr-safe (assoc (file-name-extension file)
                                  endless/extension-regexp-map)))
        it l r)
    (when regexps
      (save-match-data
        (with-temp-buffer
          (insert-file file)
          (while regexps
            (goto-char (point-min))
            (setq it (pop regexps))
            (when (search-forward-regexp (car it) nil 'noerror)
              (setq l (line-number-at-pos (match-beginning 0)))
              (when (search-forward-regexp (cdr it) nil 'noerror)
                (setq regexps nil
                      r (line-number-at-pos (match-end 0))))))
          (when r (format "%s-%s" l (+ r 1))))))))
9
  • 1
    If I may suggest, edebug the two functions and then invoke the first one with M-x. That should be very informative. :-)
    – Malabarba
    Sep 25, 2014 at 14:33
  • The function by itself runs fine. But the hook needs to pass an argument to the function it is calling. From the docs for org-export-before-processing-hook, Every function in this hook will be called with one argument: the back-end currently used, as a symbol. As we are not passing any argument, we get the error run-hook-with-args: Wrong number of arguments. Now I am not sure what argument to add to endless/update-includes... (&optional dummy) ? Sep 25, 2014 at 14:55
  • @kaushalmodi oops, my bad. I've updated the answer. You can use thing you wrote as well.
    – Malabarba
    Sep 25, 2014 at 15:00
  • OK.. adding (&optional dummy) actually worked! But an interesting side-effect of calling the function via hook. If I call the function using M-x, it modifies the .org file with the updated line numbers. But if I simply export to html and allow the hook to call the function, the updated line numbers are reflected only in the exported file, NOT in the .org file. Sep 25, 2014 at 15:00
  • @kaushalmodi Yes, that's how org hooks work. You can add it to before-save-hook instead.
    – Malabarba
    Sep 25, 2014 at 15:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.