2

I am working on a Clojure codebase. Sometimes, we have code as:

:boxes [{:id "Do-not-map"                        :description "Do Not Map, expenses"             :line "0"  :cch-box-number "0"  :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true}}
                        {:id "Unconverted"                       :description "Unconverted"                      :line "0"  :cch-box-number "0"  :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :suppress-ui true}} ;¯\_(ツ)_/¯
                        {:id "Personal"                          :description "Personal"                         :line "0"  :cch-box-number "0"  :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :suppress-ui true}} ;¯\_(ツ)_/¯
                        {:id "Do-not-map-income"                 :description "Do Not Map, income"               :line "0"  :cch-box-number "0"  :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Revenue"}}
                        {:id "Do-not-map-assets"                 :description "Do Not Map, assets"               :line "0"  :cch-box-number "0"  :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "debit-norm" :tax-account-type "Assets"}}
                        {:id "Do-not-map-liabilities"            :description "Do Not Map, liabilities"          :line "0"  :cch-box-number "0"  :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Liabilities"}}
                        {:id "Do-not-map-equity"                 :description "Do Not Map, equity"               :line "0"  :cch-box-number "0"  :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Equity"}}]

The structured whitespace between keywords (words starting with :) is probably not an idiomatic style of Clojure code. I believe a famous style guide used by the community does not cover this. But, note that it looks nice in the text editor for human readability:

enter image description here

The code above was manually tweaked to be like that. The default code, on the other hand, is as follows (note the absence of structured whitespace on the image):

:boxes [{:id "SchE-4-Royalties-received"  :line  "4"  :description "Royalties received"  :qbd-name "Schedule E: Royalties received"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",110, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "504"  :ultratax-description "Royalty income"  :ultratax-form-line "Sch E, L4"  :cch-box-number ""  :tags {:fuzzy-regexs ["Royalty income"]  :contra-flip true}}
                        {:id "SchE-5-Advertising"  :line  "5"  :description "Advertising"  :qbd-name "Schedule E: Advertising"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",4, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "505"  :ultratax-description "Advertising"  :ultratax-form-line "Sch E, L5"  :cch-box-number ""  :tags {:fuzzy-regexs ["ads" "advertising" "google" "ad words" "facebook" "posting" "craigslist" "classifieds" "seo" "search"]}}
                        {:id "SchE-6-Auto-and-travel"  :line  "6"  :description "Auto and travel"  :qbd-name "Schedule E: Auto and travel"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",5, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "506"  :ultratax-description "Auto and travel"  :ultratax-form-line "Sch E, L6"  :cch-box-number ""  :tags {:fuzzy-regexs ["Auto" "car" "gas" "truck" "travel"]}}
                        {:id "SchE-7-Cleaning-and-maintenance"  :line  "7"  :description "Cleaning and maintenance"  :qbd-name "Schedule E: Cleaning and maintenance"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",6, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "507"  :ultratax-description "Cleaning and maintenance"  :ultratax-form-line "Sch E, L7"  :cch-box-number ""  :tags {:fuzzy-regexs ["cleaning" "maintenance"]}}
                        {:id "SchE-8-Commissions"  :line  "8"  :description "Commissions"  :qbd-name "Schedule E: Commissions"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",7, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "508"  :ultratax-description "Commissions"  :ultratax-form-line "Sch E, L8"  :cch-box-number ""  :tags {:fuzzy-regexs ["commission"]}}
                        {:id "SchE-9-Insurance-(does-not-include-PMI)"  :line  "9"  :description "Insurance"  :qbd-name "Schedule E: Insurance"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",8, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "509"  :ultratax-description "Insurance"  :ultratax-form-line "Sch E, L9"  :cch-box-number ""  :tags {:fuzzy-regexs ["insurance"]}}
                        {:id "SchE-10-Legal-and-other-professional-fees"  :line  "10"  :description "Legal and other professional fees"  :qbd-name "Schedule E: Legal and professional fees"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",10, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "510"  :ultratax-description "Legal and other prof fees"  :ultratax-form-line "Sch E, L10"  :cch-box-number ""  :tags {:fuzzy-regexs ["legal" "accounting" "bookkeeping" "consulting"]}}
                        {:id "SchE-11-Management-fees-(rental-agencies-and-property-management-companies)"  :line  "11"  :description "Management fees"  :qbd-name "Schedule E: Management fees"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",19, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "511"  :ultratax-description "Management fees"  :ultratax-form-line "Sch E, L11"  :cch-box-number ""  :tags {:fuzzy-regexs ["management"]}}
                        {:id "SchE-12-Mortgage-interest-paid-to-banks"  :line  "12"  :description "Mortgage interest paid to banks, etc."  :qbd-name "Schedule E: Mortgage interest expense"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",9, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "512"  :ultratax-description "Mortgage interest"  :ultratax-form-line "Sch E, L12"  :cch-box-number ""  :tags {:fuzzy-regexs ["mortgage"]}}
                        {:id "SchE-13-Other-interest"  :line  "13"  :description "Other interest"  :qbd-name "Schedule E: Other interest expense"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",29, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "513"  :ultratax-description "Other interest"  :ultratax-form-line "Sch E, L13"  :cch-box-number ""  :tags {:fuzzy-regexs ["interest"  "private loan interest" "private"]}}
                        {:id "SchE-14-Repairs"  :line  "14"  :description "Repairs"  :qbd-name "Schedule E: Repairs"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",11, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "514"  :ultratax-description "Repairs"  :ultratax-form-line "Sch E, L14"  :cch-box-number ""  :tags {:fuzzy-regexs ["repairs" "plumbing"]}}
                        {:id "SchE-15-Suppliers"  :line  "15"  :description "Suppliers"  :qbd-name "Schedule E: Supplies"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",12, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "515"  :ultratax-description "Supplies"  :ultratax-form-line "Sch E, L15"  :cch-box-number ""  :tags {:fuzzy-regexs ["supplies" "Supply"]}}
                        {:id "SchE-16-Taxes"  :line  "16"  :description "Taxes"  :qbd-name "Schedule E: Taxes"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",13, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "516"  :ultratax-description "Taxes"  :ultratax-form-line "Sch E, L16"  :cch-box-number ""  :tags {:fuzzy-regexs ["taxes" "tax" "property tax"]}}
                        {:id "SchE-17-Utilities"  :line  "17"  :description "Utilities"  :qbd-name "Schedule E: Utilities"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",14, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "517"  :ultratax-description "Utilities"  :ultratax-form-line "Sch E, L17"  :cch-box-number ""  :tags {:fuzzy-regexs ["utilities" "water" "gas" "garbage" "recology"]}}
                        {:id "SchE-18-Depreciation-expense-or-depletion"  :line  "18"  :description "Depreciation expense or depletion"  :qbd-name ""  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",30, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "520"  :ultratax-description "Depreciation"  :ultratax-form-line "Sch E, L18"  :cch-box-number ""  :tags {:fuzzy-regexs ["depreciation"]}}
                        {:id "SchE-19-Other"  :line  "19"  :description "Other"  :qbd-name "Schedule E: Other expenses"  :qbd-scd ""  :drake-qbd-tax-name ""  :drake-name ""  :cch-line-description ""  :import-tax-code ""  :lacerte-coord ["53","0",27, {:rental-property-ordinal-to-prefix true}]  :ultratax-coord "518"  :ultratax-description "Other expenses"  :ultratax-form-line "Sch E, L19"  :cch-box-number ""  :tags {:fuzzy-regexs ["other:" "other expense" "misc" "miscellaneous" "bank" "education" "professional" "development" "postage" "shipping" "travel" "meals"]}}]

On Emacs (and other text editors) it looks as:

enter image description here

I would like to adjust the latter code to be formatted like the former. Instead of doing it in a manual manner, I would like to use Emacs power to do the trick. This feels like a good call for macros. But, I do not know how to use keyboard macros on Emacs.

How to achieve the desired output?

Pedro Delfino
  • 1,489
  • 3
  • 16
  • 1
    Please don't ask whether something is the "best way" to accomplish something. That encourages opinion-based answers - it's something to ask at a discussion site perhaps, such as Reddit. – Drew Oct 18 '22 at 19:40
  • It might help if you reduced your example to a minimum for what you want to ask. – Drew Oct 18 '22 at 19:41

2 Answers2

2

Spacemacs has a function named spacemacs/align-repeat that can almost do what you want. The way it works is that you highlight a region and then run this function on it interactively. It will ask for a regexp pattern to align the text on, and in your case, you'd use :\w+. It's mapped to SPC x a r in Spacemacs, but you can say M-x spacemacs/align-repeat if you want.

Looking at the function, there isn't anything specific to Spacemacs in it, so I'm going to post it here.

(defun spacemacs/align-repeat (start end regexp &optional justify-right after)
  "Repeat alignment with respect to the given regular expression.
If JUSTIFY-RIGHT is non nil justify to the right instead of the
left. If AFTER is non-nil, add whitespace to the left instead of
the right."
  (interactive "r\nsAlign regexp: ")
  (let* ((ws-regexp (if (string-empty-p regexp)
                        "\\(\\s-+\\)"
                      "\\(\\s-*\\)"))
         (complete-regexp (if after
                              (concat regexp ws-regexp)
                            (concat ws-regexp regexp)))
         (group (if justify-right -1 1)))
(unless (use-region-p)
  (save-excursion
    (while (and
            (string-match-p complete-regexp (thing-at-point 'line))
            (= 0 (forward-line -1)))
      (setq start (point-at-bol))))
  (save-excursion
    (while (and
            (string-match-p complete-regexp (thing-at-point 'line))
            (= 0 (forward-line 1)))
      (setq end (point-at-eol)))))

(align-regexp start end complete-regexp group 1 t)))

If you dig through the source, you'll see that it's a refinement of the code found on the AlignCommands page on EmacsWiki.

Test Run

I'm going to start with this initial state.

[{:id "Do-not-map" :description "Do Not Map, expenses" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true}}
 {:id "Unconverted" :description "Unconverted" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :suppress-ui true}} ;¯\_(ツ)_/¯
 {:id "Personal" :description "Personal" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :suppress-ui true}} ;¯\_(ツ)_/¯
 {:id "Do-not-map-income" :description "Do Not Map, income" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Revenue"}}
 {:id "Do-not-map-assets" :description "Do Not Map, assets" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "debit-norm" :tax-account-type "Assets"}}
 {:id "Do-not-map-liabilities" :description "Do Not Map, liabilities" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Liabilities"}}
 {:id "Do-not-map-equity" :description "Do Not Map, equity" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Equity"}}]

Then I'm going to run spacemacs/align-repeat on it using :\w+ as my repeat pattern which yields the following.

[{ :id "Do-not-map"             :description "Do Not Map, expenses"    :line "0" :cch-box-number "0" :import-tax-code "0" :reporting { :omit-taxable true} :tags { :do-not-map true}}
 { :id "Unconverted"            :description "Unconverted"             :line "0" :cch-box-number "0" :import-tax-code "0" :reporting { :omit-taxable true} :tags { :do-not-map true :suppress-ui true}} ;¯\_(ツ)_/¯
 { :id "Personal"               :description "Personal"                :line "0" :cch-box-number "0" :import-tax-code "0" :reporting { :omit-taxable true} :tags { :do-not-map true :suppress-ui true}} ;¯\_(ツ)_/¯
 { :id "Do-not-map-income"      :description "Do Not Map, income"      :line "0" :cch-box-number "0" :import-tax-code "0" :reporting { :omit-taxable true} :tags { :do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Revenue"}}
 { :id "Do-not-map-assets"      :description "Do Not Map, assets"      :line "0" :cch-box-number "0" :import-tax-code "0" :reporting { :omit-taxable true} :tags { :do-not-map true :tax-norm-bal "debit-norm"  :tax-account-type "Assets"}}
 { :id "Do-not-map-liabilities" :description "Do Not Map, liabilities" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting { :omit-taxable true} :tags { :do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Liabilities"}}
 { :id "Do-not-map-equity"      :description "Do Not Map, equity"      :line "0" :cch-box-number "0" :import-tax-code "0" :reporting { :omit-taxable true} :tags { :do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Equity"}}]

It added some spaces after the { and before the :, so I'll apply the regexp replacement s/{ :/{:/g on the same region to tighten it up. (I'm an evil user.)

[{:id "Do-not-map"             :description "Do Not Map, expenses"    :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true}}
 {:id "Unconverted"            :description "Unconverted"             :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :suppress-ui true}} ;¯\_(ツ)_/¯
 {:id "Personal"               :description "Personal"                :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :suppress-ui true}} ;¯\_(ツ)_/¯
 {:id "Do-not-map-income"      :description "Do Not Map, income"      :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Revenue"}}
 {:id "Do-not-map-assets"      :description "Do Not Map, assets"      :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "debit-norm"  :tax-account-type "Assets"}}
 {:id "Do-not-map-liabilities" :description "Do Not Map, liabilities" :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Liabilities"}}
 {:id "Do-not-map-equity"      :description "Do Not Map, equity"      :line "0" :cch-box-number "0" :import-tax-code "0" :reporting {:omit-taxable true} :tags {:do-not-map true :tax-norm-bal "credit-norm" :tax-account-type "Equity"}}]

PS: Perl programmers (known for their penchant for ASCII art) like to do this kind of thing. Their code formatter, perltidy, does this by default.

g-gundam
  • 1,261
  • 1
  • 4
  • 14
2

Another way to do this in a more editor-agnostic way is to use zprint. It's a clojure formatter that's kind of crazy configurable, and there is a way to have it justify code.

From emacs, you can interactively run zprint on a buffer or region using the zprint-format package.

A benefit here is that many developers can share the same zprint config, even if they don't all use the same editor.

Chris Clark
  • 240
  • 1
  • 11