A Problem … and a Solution
You are given the following table 1
a b c d aaaa bb ccc ddddd aaa bbb cccc dddd aa bb ccccccc ddd
How would you generate this table
[ a , b , c , d ] [ aaaa, bb , ccc , ddddd ] [ aaa , bbb, cccc , dddd ] [ aa , bb , ccccccc, ddd ]
and this table
a [ b , c ] d aaaa [ bb , ccc ] ddddd aaa [ bbb, cccc ] dddd aa [ bb , ccccccc ] ddd
and this table
[ <a> , <b> , <c> , <d> ] [ <aaaa>, <bb> , <ccc> , <ddddd> ] [ <aaa> , <bbb>, <cccc> , <dddd> ] [ <aa> , <bb> , <ccccccc>, <ddd> ]
and this table
a [ <b> , <c> ] d aaaa [ <bb> , <ccc> ] ddddd aaa [ <bbb>, <cccc> ] dddd aa [ <bb> , <ccccccc> ] ddd
A answer is you use one of the Emacs’ built-in library called delim-col
1.
What does delim-col
do?
The delim-col
package describes itself as
delim-col
helps to prettify columns in a text region or rectangle
In my opinion, the above description doesn’t tell much about what it offers a user. Instead, I would describe the package as
delim-col
creates pretty tables from a text region and helps convert those tables in to different table formats likeTSV
1,CSV
1,Org
1,LaTeX
1,GFM
1, 2 or any other makeshift format that you may come up with.
What does delim-col
NOT do?
In doing the above conversions, you would expect that delim-col
prompts you for all these parameters
- the regexp which separates each column
- a string to be inserted before (and after) each column
- a string to be inserted between each column
- a string to be inserted at the beginning (and end of) each row
and a way to add padding.
Unfortunately, the above parameters are hard-coded and aren’t gathered from the user on each run.
Step 1: Create a menu
Copy the Emacs Lisp
snippet1 below to your .emacs
and restart your Emacs
.
This snippet does the following
-
-
- modifies the commands
delimit-columns-region
anddelimit-columns-rectangle
so that they prompt you for input on every run. - adds a sub-menu named
Extra Tools
to the menu. To this sub-menu, it adds another sub-menu namedDelimit Columns in ...
.
- modifies the commands
-
(defun my-delimits-column-region (orig-fun &rest args) (let ((delimit-columns-separator (read-regexp (format "%s (%s): " "Specify the regexp which separates each column" delimit-columns-separator) (list delimit-columns-separator))) (delimit-columns-before (read-string (format "%s (%s): " "Specify a string to be inserted before each column" delimit-columns-before) nil nil delimit-columns-before)) (delimit-columns-after (read-string (format "%s (%s): " "Specify a string to be inserted after each column" delimit-columns-after) nil nil delimit-columns-after)) (delimit-columns-str-separator (read-string (format "%s (%s): " "Specify a string to be inserted between each column" delimit-columns-str-separator) nil nil delimit-columns-str-separator)) (delimit-columns-str-before (read-string (format "%s (%s): " "Specify a string to be inserted before the first column" delimit-columns-str-before) nil nil delimit-columns-str-before)) (delimit-columns-str-after (read-string (format "%s (%s): " "Specify a string to be inserted after the last column" delimit-columns-str-after) nil nil delimit-columns-str-after)) (delimit-columns-format (let* ((choices '(("Align Columns" . t) ("No Formatting") ("Align Separators" . separator) ("Pad Columns" . padding))) (default-choice (car (rassoc delimit-columns-format choices))) (choice (completing-read (format "%s (%s): " "Specify how to format columns" default-choice) choices nil t nil nil default-choice))) (message "%s" choice) (assoc-default choice choices)))) (apply orig-fun args))) (advice-add 'delimit-columns-region :around #'my-delimits-column-region) (advice-add 'delimit-columns-rectangle :around #'my-delimits-column-region) (define-key-after global-map [menu-bar extra-tools] (cons "Extra Tools" (easy-menu-create-menu "Extra Tools" nil)) 'tools) (easy-menu-define my-delim-col-menu nil "Menu for Delim Col" '("Delimit Columns in ..." ["Region" delimit-columns-region :help "Prettify all columns in a text region"] ["Rectangle" delimit-columns-rectangle :help "Prettify all columns in a text rectangle"] "---" ["Customize" delimit-columns-customize :help "Customization of `columns' group"])) (easy-menu-add-item (current-global-map) '("menu-bar" "extra-tools") my-delim-col-menu)
Step 2: Ensure that you have the Extra Tools
submenu
Once you restart your Emacs, you would see an entry for Extra Tools
in the menu bar. If you aren’t seeing this submenu, repeat the previous step.
Step 3: Copy-paste the table in to an Emacs buffer
Copy-paste the input table either from this article or from the delim-col
library 1. It is important to note that the table scraped from this article will have spaces (and not tabs), but the one from delim-col
will have tabs.
Step 4: Mark the region or rectangle
Step 5: Do the conversion
Emacs will prompt you for further input. Key in the parameters as you deem fit.
The prompt string shows the factory default settings, and they are enclosed within ()
. You can press Enter
to accept the factory settings.
When copy-pasting the original table from this web article in to your Emacs, the table will contain spaces. So, when prompted with Specify the regexp which separates each column
use +
(i.e. SPC+
).
When prompted with Specify how to format columns
you can press TAB
, and choose among the candidates shown. As you see from screenshot below, for my own run, I chose Align Separators
.
Here is the output I get at the end of one such run.
Concluding Words
You can pose the original problem to experienced users of Emacs. Each user will have his own suggestions. Most users will suggest rectangle commands1. Some users will suggest that you use org-table-convert-region
1 to convert the original table in to Org
-format table1, and create a makeshift code based on orgtbl-to-generic
1. (I bet, no user will suggest delim-col
because it is a package which lacks an entry in the user manual.) None of the solutions you hear have the simplicity and quickness as delim-col
. In my opinion, delim-col
is a very useful tool to have in your toolkit.
Appendix
This article on delim-col
was created as an aid to one of my friends1, a newcomer to Emacs. She wanted to convert a table scraped from web1 in to a Python dictionary1.
Specifically, she wanted to convert an HTML table that looks like this
in to a python
dictionary that looks like this
With a little imagination, it is easy to see how she can use delim-col
to achieve much of what she wants.
That said, I hear those of you asking me “What if the second column in the friend’s table happened to be numeric, and she didn’t want the numeric fields quoted …”. In this case, I suppose, delim-col
doesn’t have anything immediate to offer, and I am afraid, the friend needs to switch to other means.
Very nice. Would you please consider converting this to a patch to delim-col.el (I think make it so that a prefix argument
C-u
triggers the prompts), and submitting it with M-x report-emacs-bug ? This seems like an obvious enhancement to make upstream.LikeLike