Download
(require 'yasnippet)
(require 'cl)
(defgroup awk-it nil
"Run awk interactively on region."
:version "0.75"
:group 'external)
(defcustom awk-it-load-hook nil
"*Hook that gets run after the awk-it has been loaded."
:type 'hook
:group 'awk-it)
(defcustom awk-it-default-separator " "
"*Default AWK field separator - if nil AWK default is used."
:type 'string
:group 'awk-it)
(defcustom awk-it-default-row-filter "$0 !~ /^$/"
"*Default AWK row filter - if nil '$0 !~ /^$/' is used."
:type 'string
:group 'awk-it)
(defcustom awk-it-file-first-line "#!/bin/awk"
"*First line inserted when writing AWK file."
:type 'string
:group 'awk-it)
(defcustom awk-it-default-mode 1
"*Default AWK it! mode (1 - simplified; 2 - raw single match; 3 - full raw)."
:type 'number
:group 'awk-it)
(defcustom awk-it-undo t
"*AWK it! undo; after expansion undo to before awk-it, nil to disable."
:type 'boolean
:group 'awk-it)
(defcustom awk-it-indent t
"*Toggles if epanded data should be indented."
:type 'boolean
:group 'awk-it)
(defcustom awk-it-extra-awk-code nil
"*Extra AWK code that AWK it! will insert into final script."
:type 'string
:group 'awk-it)
(defcustom awk-it-toggle-trim-keybinding "C-c t"
"*AWK it! toogle field separator trim keybinding."
:type 'string
:group 'awk-it)
(defcustom awk-it-field-separator-keybinding "C-c s"
"*AWK it! field separator change keybinding."
:type 'string
:group 'awk-it)
(defcustom awk-it-next-mode-keybinding "C-c <up>"
"*AWK it! next mode change keybinding."
:type 'string
:group 'awk-it)
(defcustom awk-it-previous-mode-keybinding "C-c <down>"
"*AWK it! previous mode change keybinding."
:type 'string
:group 'awk-it)
(defvar awk-it-fs awk-it-default-separator "AWK field separator.")
(defvar awk-it-type awk-it-default-mode "Type of code (1 - simplified; 2 - raw single match; 3 - full raw).")
(defvar awk-it-trim nil "Trim spaces/tabs around field separator.")
(defvar awk-it-old-trim-keybinding nil "Holds old command called by toggle trim keystroke.")
(defvar awk-it-old-fs-keybinding nil "Holds old command called by change field separator keystroke.")
(defvar awk-it-old-next-mode-keybinding nil "Holds old command called by next mode keystroke.")
(defvar awk-it-old-previous-mode-keybinding nil "Holds old command called by previous mode keystroke.")
(defvar awk-it-beginning nil "Starting point od data/expansion.")
(defvar awk-it-data nil "Data for AWK to process.")
(defvar awk-it-code nil "AWK code to transform data.")
(defvar awk-it-point nil "Begining of data/head.")
(defvar awk-it-count 0 "Number of chars of expansion.")
(defvar awk-it-undo-before nil "Buffer undo list before AWK it!")
(defconst AWK-IT-M-STRING "\"\\(\\\\.+\\|[^\"]+\\)\"" "Regex match for strings.")
(defconst AWK-IT-M-STRING-REPLACEMENT-PREFIX "___" "Prefix for string replacement token.")
(defun awk-it (beg end)
"Run AWK for each line between point and mark."
(interactive "r")
(setq awk-it-type 1)
(awk-it-full beg end 'awk-it-process awk-it-default-separator))
(defun awk-it-with-separator (beg end fs)
"Run AWK for each line between point and mark, specifying custom
field separator."
(interactive "r\nsAWK it! field separator: ")
(setq awk-it-type 1)
(awk-it-full beg end 'awk-it-process fs))
(defun awk-it-single (beg end)
"Run AWK code(single) for each line between point and mark."
(interactive "r")
(setq awk-it-type 2)
(awk-it-full beg end 'awk-it-process " "))
(defun awk-it-single-with-separator (beg end fs code)
"Run AWK code(single) for each line between point and mark,
specifying custom field separator."
(interactive "r\nsAWK it! field separator: ")
(setq awk-it-type 2)
(awk-it-full beg end 'awk-it-process fs))
(defun awk-it-raw (beg end)
"Run AWK code(raw) for each line between point and mark."
(interactive "r")
(setq awk-it-type 3)
(awk-it-full beg end 'awk-it-process awk-it-default-separator))
(defun awk-it-file (beg end file)
"Run AWK code on a region from FILE(auto expands)."
(interactive "r\nfAWK file: ")
(delete-region beg end)
(insert (shell-command-to-string (concat "echo \"" awk-it-data "\" | awk -f " file))))
(defun awk-it-with-file (beg end file)
"Run AWK it! with code from FILE(inserts code into template field)."
(interactive "r\nfAWK file: ")
(setq awk-it-code
(with-temp-buffer
(insert-file-contents file)
(buffer-string))
awk-it-type 3)
(awk-it-full beg end 'awk-it-process " " t))
(defun awk-it-to-file (file)
"Save last AWK it! code to file(as raw AWK code)."
(interactive "GAWK file: ")
(with-temp-buffer
(insert (concat
awk-it-file-first-line "
"
(replace-regexp-in-string "\" *auto_quote *\"" "'"
(cond
((= awk-it-type 1)
(awk-it-single-2-raw (awk-it-simple-2-single awk-it-code)))
((= awk-it-type 2)
(awk-it-single-2-raw awk-it-code))
((= awk-it-type 3)
awk-it-code)))))
(write-file file)))
(defun awk-it-to-kill-ring ()
"Save last AWK it! code to kill-ring(as specified by mode)."
(interactive)
(kill-new awk-it-code))
(defun awk-it-version ()
"Returns current AWK it! version."
"0.75")
(defun awk-it-fs ()
"Returns actual AWK field separator to be used."
(let ((fs-temp (cond
((string= awk-it-fs "\\") "\\\\")
(t awk-it-fs))))
(if awk-it-trim (concat "[ \\t]*" fs-temp "[ \\t]*") fs-temp)))
(defun awk-it-full (beg end func &optional fs file)
"AWK it! - full func.; captures data and sets up yasnippet."
(when awk-it-undo (setq buffer-undo-list (cons 'AWK-IT buffer-undo-list)))
(save-window-excursion
(setq awk-it-beginning beg
awk-it-fs fs)
(setq awk-it-old-next-mode-keybinding (local-key-binding (awk-it-macro-expand-value kbd awk-it-next-mode-keybinding)))
(local-set-key (awk-it-macro-expand-value kbd awk-it-next-mode-keybinding) 'awk-it-next-type)
(setq awk-it-old-previous-mode-keybinding (local-key-binding (awk-it-macro-expand-value kbd awk-it-previous-mode-keybinding)))
(local-set-key (awk-it-macro-expand-value kbd awk-it-previous-mode-keybinding) 'awk-it-previous-type)
(setq awk-it-old-trim-keybinding (local-key-binding (awk-it-macro-expand-value kbd awk-it-toggle-trim-keybinding)))
(local-set-key (awk-it-macro-expand-value kbd awk-it-toggle-trim-keybinding)
(lambda ()
(interactive)
(setq awk-it-trim (not awk-it-trim))
(let ((s (car (yas--snippets-at-point))))
(if s (yas--update-mirrors s)))))
(setq awk-it-old-fs-keybinding (local-key-binding (awk-it-macro-expand-value kbd awk-it-field-separator-keybinding)))
(local-set-key (awk-it-macro-expand-value kbd awk-it-field-separator-keybinding)
(lambda (x)
(interactive "sAWK it! field separator: ")
(setq awk-it-trim nil
awk-it-fs x)
(let ((s (car (yas--snippets-at-point))))
(if s (yas--update-mirrors s)))))
(setq awk-it-data (buffer-substring beg end)
awk-it-point beg)
(add-hook 'yas/after-exit-snippet-hook 'awk-it-yas-completed nil t)
(yas/expand-snippet (concat
"Data: " (awk-it-get-line-with-max-separators beg end) "
AWK pattern: ${1:"(if file (replace-regexp-in-string "\\(\\$\\|`\\)" "\\\\\\1" awk-it-code) "pattern") "}
${1:$(" (symbol-name func) " text)}") beg end)))
(defun awk-it-process (code)
"Sends AWK code and data to the shell."
(let ((result ""))
(setq awk-it-code code
result (shell-command-to-string (concat
"echo \"" awk-it-data "\" | awk -v auto_quote=\"'\" ' "
(replace-regexp-in-string "'" "\" auto_quote \""
(cond
((= awk-it-type 1)
(awk-it-single-2-raw (awk-it-simple-2-single code)))
((= awk-it-type 2)
(awk-it-single-2-raw code))
((= awk-it-type 3)
code)))
" ' ")))
(when (< 0 (length result))
(setq result (substring result 0 (- (length result) 1))))
(setq awk-it-count (length result))
result))
(defun awk-it-yas-completed ()
"After yas completion hook - removes header from buffer and itself
from hook."
(when awk-it-point
(goto-char awk-it-point)
(kill-line (+ 2 (count ?
(append awk-it-code nil)))))
(local-set-key (awk-it-macro-expand-value kbd awk-it-toggle-trim-keybinding) awk-it-old-trim-keybinding)
(local-set-key (awk-it-macro-expand-value kbd awk-it-field-separator-keybinding) awk-it-old-fs-keybinding)
(local-set-key (awk-it-macro-expand-value kbd awk-it-next-mode-keybinding) awk-it-old-next-mode-keybinding)
(local-set-key (awk-it-macro-expand-value kbd awk-it-previous-mode-keybinding) awk-it-old-previous-mode-keybinding)
(remove-hook 'yas/after-exit-snippet-hook 'awk-it-yas-completed t)
(when awk-it-indent
(indent-region awk-it-beginning (+ awk-it-beginning awk-it-count)))
(when awk-it-undo
(setq buffer-undo-list
(let ((found nil))
(loop for x in buffer-undo-list
when (eql x 'AWK-IT) do (setq found t)
when (and (or found x) (not (eql x 'AWK-IT)))
collect x)))))
(defun awk-it-update-yas-field ()
"Updates yasnippet field(AWK code)."
(let ((snippet (car (yas--snippets-at-point))))
(when snippet
(let ((field (car (yas--snippet-fields snippet))))
(goto-char (yas--field-start field))
(insert awk-it-code)
(delete-region (point) (yas--field-end field))))))
(defun awk-it-next-type ()
"Converts AWK code to next type(mode)."
(interactive)
(when (= awk-it-type 3)
(setq awk-it-type 0))
(setq awk-it-type (+ awk-it-type 1)
awk-it-code (cond
((= awk-it-type 1)
(awk-it-single-2-simple (awk-it-raw-2-single awk-it-code)))
((= awk-it-type 2)
(awk-it-simple-2-single awk-it-code))
((= awk-it-type 3)
(awk-it-single-2-raw awk-it-code))))
(let ((s (car (yas--snippets-at-point))))
(awk-it-update-yas-field)
(if s (yas--update-mirrors s))))
(defun awk-it-previous-type ()
"Converts AWK code to previous type(mode)."
(interactive)
(when (= awk-it-type 1)
(setq awk-it-type 4))
(setq awk-it-type (- awk-it-type 1)
awk-it-code (cond
((= awk-it-type 1)
(awk-it-single-2-simple awk-it-code))
((= awk-it-type 2)
(awk-it-raw-2-single awk-it-code))
((= awk-it-type 3)
(awk-it-single-2-raw (awk-it-simple-2-single awk-it-code)))))
(let ((s (car (yas--snippets-at-point))))
(awk-it-update-yas-field)
(if s (yas--update-mirrors s))))
(defun awk-it-simple-2-single (code)
"Converts simple CODE to single. "
(awk-it-n-regex-replace
(concat "print \""
(awk-it-n-regex-replace code
'(("\"" "\\\\\"")
("\\(\\$[1234567890]+\\)" "\" \\1 \"")
("
" "\\\\n\\\\
")
))
"\"")
'(("print *\"\" *" "print ")
(" *[^\\]\"\" *$" ""))))
(defun awk-it-single-2-raw (code)
"Converts single CODE to raw."
(let ((fs (awk-it-fs))
(EL "
"))
(concat
EL
(if (not (string= fs " ")) (concat "BEGIN {" EL " FS=\"" fs "\";" EL "}" EL EL))
(if awk-it-default-row-filter awk-it-default-row-filter "$0 !~ /^$/")
" { " EL
" " code (unless (string-match "; *$" code) ";") EL
"}" EL
EL
"/^$/ { print }"
(when awk-it-extra-awk-code (concat EL EL awk-it-extra-awk-code)))))
(defun awk-it-raw-2-single (code)
"Converts raw CODE to single."
(destructuring-bind (fs scode) (awk-it-find-single code)
(setq awk-it-fs fs)
scode))
(defun awk-it-single-2-simple (code)
"Converts single CODE to simple."
(replace-regexp-in-string "" "\\1"
(awk-it-n-regex-replace (replace-regexp-in-string "^ *print *\\(.*\\)$" "\\1" code)
'(("
" "#AWK-IT-NEWLINE#")
(" *\"\\(.*\\)\" *" "\\1")
("; *$" "")
(" *$" "")
("\\\\\"" "\"")
("\" \\($[1234567890]+\\) \"" "\\1")
("\\\\n\\\\" "")
("#AWK-IT-NEWLINE#" "
")))))
(defun awk-it-flat-clean-awk (string)
"Removes comments and newlines from AWK code in STRING."
(apply 'concat
(remove-if (lambda (x) (string= "" x))
(mapcar (lambda (x) (awk-it-n-regex-replace x
'((" *#.*" "")
("^ *\\(.*\\) *$" "\\1"))))
(split-string string "
" t)))))
(defun awk-it-extract-awk-matches (string)
"Retuns list of (match code) pairs from AWK code in STRING."
(let ((found nil)
(lpos 0)
(pos 0)
(pc 0)
(curr-match ""))
(while (string-match "[{}]" string pos)
(setq pos (match-end 0))
(if (and (match-beginning 0)
(string= "{" (substring string (match-beginning 0) (match-end 0))))
(progn
(when (< pc 1)
(setq curr-match (substring string lpos (- pos 1))
lpos (- pos 1)))
(setq pc (+ 1 pc)))
(setq pc (- pc 1))
(when (< pc 1)
(setq found (append found (list (list curr-match (substring string lpos pos))))
lpos pos))))
found))
(defun awk-it-find-single (string)
"Finds field separator (or returns default) and default row filter
code (if any) from AWK code in STRING."
(let* ((strings (awk-it-extract-strings string))
(matches (awk-it-extract-awk-matches (awk-it-flat-clean-awk (awk-it-n-regex-replace string
(mapcar*
(lambda (x y)
(list (concat "\"" (replace-regexp-in-string "\\\\" "\\\\\\\\" y) "\"") (concat AWK-IT-M-STRING-REPLACEMENT-PREFIX (number-to-string x))))
(number-sequence 0 (length strings))
strings) t))))
(fs awk-it-default-separator)
(code ""))
(mapc (lambda (x)
(destructuring-bind (a b) x
(when (string-match " *BEGIN *" a)
(when (string-match (concat "FS *=[^;\"]*" AWK-IT-M-STRING-REPLACEMENT-PREFIX "\\([1234567890]+\\)") b)
(setq fs (nth (string-to-number
(and (match-beginning 1)
(substring b (match-beginning 1) (match-end 1)))) strings))))
(when (string-match (concat " *" awk-it-default-row-filter " *") a)
(setq code (substring b 1 (- (length b) 1)))))) matches)
(list fs (awk-it-return-strings code strings))))
(defun awk-it-extract-strings (string &optional start)
"Extracts substrings from STRING. Starts from START or 0."
(unless start (setq start 0))
(if (string-match AWK-IT-M-STRING string start)
(let ((head (match-string 1 string) ))
(append (list head) (awk-it-extract-strings string (+ (match-end 0)))))
nil))
(defun awk-it-return-strings (string strings)
"Replaces substring placeholders from STRING with values
in STRINGS."
(awk-it-n-regex-replace string
(mapcar*
(lambda (x y)
(list (concat AWK-IT-M-STRING-REPLACEMENT-PREFIX (number-to-string x)) (concat "\"" y "\"")))
(number-sequence 0 (length strings))
strings) t))
(defun awk-it-get-line-with-max-separators (beg end &optional separator)
"Returns line with max # of separators inside region."
(save-window-excursion
(save-restriction
(narrow-to-region beg end)
(goto-char (point-min))
(let ((count 0)
(final-point (point)))
(while (not (eobp))
(let ((x (count-matches (if (not separator) " " separator) (line-beginning-position) (line-end-position))))
(when (< count x)
(setq count x
final-point (line-beginning-position))))
(forward-line))
(goto-char final-point)
(buffer-substring (line-beginning-position) (line-end-position))))))
(defun awk-it-n-regex-replace (string list &optional literal)
"Makes multiple regex replacements using ((search replace) . )
syntax."
(if list
(destructuring-bind ((a b) . rest) list
(awk-it-n-regex-replace (replace-regexp-in-string a b string nil literal) rest literal))
string))
(defmacro awk-it-macro-expand-value (macro &rest list)
"Expands MACRO with values of variables in LIST."
`(,(symbol-function macro) ,@(mapcar 'symbol-value list)))
(provide 'awk-it)
(run-hooks 'awk-it-load-hook)