0

My file has one book listed per line, title first followed by a tab character, followed by the author's name.

TITLE<TAB>AUTHOR
TITLE<TAB>AUTHOR
...

I want to swap the two fields, such that the author's name appears first on each line.

AUTHOR<TAB>TITLE
AUTHOR<TAB>TITLE
...

I could accomplish this with a keyboard macro, but it seems like the sort of thing which might come up again. It seems to me that Emacs Lisp should be able to do the job, but how to go about it? What if next time I want to delete a field, or filter lines based on specific fields?

In fact, AWK excels at exactly these kinds of tasks. What about Emacs Lisp?

EDIT: To reiterate, I'd like to do this with elisp, rather than evil, org, or other features.

1

5 Answers 5

4

Description

You should use regular expression replacement.

I'll describe how I would do the replacement with swiper, the built-in isearch-forward-regexp would work in a similar way but with more keys.

  1. C-s (swiper).
  2. Enter .* C-q TAB .*. There is a single space on each side of the tab char in the middle.
  3. Under the hood, swiper translates the input string ".* \t .*" into a regex string "\(.*\).*?\(\t\).*?\\(.*\)". Which I think is pretty great: the user input is nearly halved.
  4. M-q (swiper-query-replace). Remember the above regex string? It has capture 3 groups, the title is in the first, the tab in the second, and the author in the third. So you need to enter: \3 C-q TAB \1 RET.
  5. Finally, either keep pressing y or n on each item, or press ! once to do all the replacements.

Screenshots

Step 1-2-3

step-1

Step 4

step-2

Step 5

step-3

The result

step-4

Key bindigs summary

Here's what I got from view-lossage:

C-s [swiper]
.* [self-insert-command]
SPC [self-insert-command]
C-q [quoted-insert]
<tab> SPC [self-insert-command]
.* [self-insert-command]
M-q [swiper-query-replace]
\3 [self-insert-command]
C-q [quoted-insert]
<tab> \1 [self-insert-command]
<return> [exit-minibuffer]
!
3

My course of action would be:

  1. Copy the data into Org file (or buffer, where I then call M-xorg-mode).
  2. C-u C-u C-c | to convert it to an Org table.
  3. Manipulate the table columns as I need.
  4. M-xorg-table-exportRETorgtbl-to-tsvRET
1
  • Thanks, but this doesn't answer the question either.
    – Low Powah
    Commented Feb 14, 2016 at 21:01
3

Well, just as a challenge, here's some Emacs Lisp code to do something slightly more general than just swapping two selected columns:

(defun my/determine-column (col line)
  (cl-loop for word in line
           for i from 0
           summing (length word) into total
           if (> total col) do
           (cl-return i)
           finally (cl-return (1- i))))

(defun my/swap-columns (dir beg end)
  (interactive
   (let* ((dir (prefix-numeric-value current-prefix-arg)))
     (if (region-active-p)
         (list dir (region-beginning) (region-end))
       (list dir (point-min) (point-max)))))
  (save-excursion
    (let* ((lines (mapcar
                   (lambda (s) (split-string s "\t"))
                   (split-string (buffer-substring beg end) "\n" t)))
           (column (my/determine-column (current-column) (car lines)))
           (permuted (mod (+ column dir) (length (car lines)))))
      (delete-region beg end)
      (goto-char beg)
      (cl-loop for line in lines
               for temp = (nth column line) do
               (setf (nth column line) (nth permuted line)
                     (nth permuted line) temp)
               (insert (mapconcat 'identity line "\t") "\n")))))

The idea is that if you give it a negative argument it'll swap the column containing point that many columns leftwards, and if you give it a positive prefix argument, it'll swap that column that many columns rightwards. I didn't really check this for correctness or sane behavior (it does swap columns though).

3
  • Thank you very much for submitting a solution in Common Lisp. It isn't Emacs Lisp, which is what I asked for, but it is a lot closer than any other solution offered.
    – Low Powah
    Commented Feb 18, 2016 at 23:12
  • 1
    I'm not sure what's giving you the idea that this is not Emacs Lisp. The cl.el and cl-lib packages provide useful functions and macros borrowed from Common Lisp for Emacs Lisp, they do not form a Common Lisp implementation.
    – wasamasa
    Commented Feb 18, 2016 at 23:43
  • @LowPowah to add to what wasamasa wrote: if you want this to be "pure" Emacs Lisp (there used to be people who disliked cl-lib), you could (macroexpand-all '(cl-loop ...)) and use the expansion instead (beware it will require some manual labor to rename the automatically generated variables and such).
    – wvxvw
    Commented Feb 19, 2016 at 0:19
0

use evil-mode, then type,

%s/\([a-zA-Z0-9]*\)\t\([a-zA-Z0-9]*\)/\2\t\1/g

delete the first field,

%s/\([a-zA-Z0-9]*\)\t\([a-zA-Z0-9]*\)/\2/g

delete rows whose first field is TITLE,

g/TITLE\t/d

delete rows whose first field is NOT TITLE,

v/TITLE\t/d

The point is, LEARN regular expression for text processing.

3
  • As it stands, this answer should probably be converted into a comment. Please expand it by adding more content and (or) references. Commented Feb 12, 2016 at 6:24
  • As an improvement to your answer, may I suggest you read the original question first? Thank you very much.
    – Low Powah
    Commented Feb 14, 2016 at 20:57
  • usually people use the regex to do the text processing. if you prefer pure lisp way, C-h f describe-function query-replace-regexp, but the hard part is to get the regular expression right at the beginning. So there are some interactive UI to help. For non-vim users, swiper is good (as somebody already suggested) plugin with interactive UI feedback. For vim users, evil is good.
    – chen bin
    Commented Feb 15, 2016 at 1:18
0

Here's a minimally tested bit of elisp that will swap two tab-separated values on each line in the buffer, ignoring lines that don't meet that criterion. Note that there's a fair bit of boilerplate with the save-... stuff.

(defun swaperooni ()
  "Swap two tab-separated fields in each line in buffer."
  (interactive)
  (let ((re-1 "\\(.+\\)\t\\(.+\\)") (re-2 "\\2\t\\1"))
    (save-restriction
      (save-excursion
        (save-match-data
          (widen)
          (goto-char (point-min))
          (while (not (eobp))
            (let ((line (buffer-substring (point-at-bol) (point-at-eol))))
              (when (string-match re-1 line)
                (delete-region (point-at-bol) (point-at-eol))
                (insert (replace-regexp-in-string re-1 re-2 line)))
              (forward-line 1))))))))

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.