For the English part of the blog, see Content AND Presentation.
Today I have a very short and rather niche tip, but maybe someone will find it useful (as I did). Like many other people, I use Magit on a daily basis. One thing that bothered me a bit was the fact that when I pressed $
to see the output of the Git process, all colors were gone. This was a problem for me, since the pre-commit hook I use calls the TypeScript compiler to tell me about any potential problems with types in my code. By default, the output of tsc
is colored with the ANSI color codes. While the coloring can be turned out, the setting to do that is rather crude (it also disables pretty-printing/formatting), and after all, these colors serve a purpose and are genuinely useful. I decided that before I turn the formatting off, it may be a good idea to check if Magit has an option to allow coloring the Git output using ANSI codes.
Well, the answer is yes and no. Indeed, there is a variable which does exactly that, but it is not a user option (that is, it is defined with defvar
and not defcustom
), and it is not even documented (that is, it has no docstring!). Still, it seems that it does its job, so from now I have
(setq magit-process-finish-apply-ansi-colors t)
in my init file.
I suppose that the reason it is not advertised may have something to do with performance issues. It is known that coloring with ANSI codes comes with a performance hit. For example, here is the docstring of the magit-log-color-graph-limit
option:
Number of commits over which log graphs are not colored. When showing more commits than specified, then the ‘--color’ argument is silently dropped. This is necessary because the ‘ansi-color’ library, which is used to turn control sequences into faces, is just too slow.
So if you want to turn magit-process-finish-apply-ansi-colors
on, be aware that it might slow down Magit. So far, I haven’t experienced that, but I’ve been only using it for a few days now, so we’ll have to see.
Some time ago I mentioned having built a very simple web app to help me with budgeting with Ledger. Let me share it with you right now. It is extremely simple – the whole app resides in just one index.js
file which has less than 150 lines of code, and over half of it is boilerplate code or code related to authentication (which is very simplistic anyway).
The code is not something I would be especially proud of, but it doesn’t matter - it is but a simple hack put together in a few hours. It would probably be better to use the csv or xml export of Ledger instead of trying to parse the default format. Part of the config file is the list of Ledger command-line parameters, some of which (like --flat
) are essential for the app to even work. This is a terrible practice in “serious software”, since it means that putting legit-looking (but unexpected by the code) things in the config might break the app. It uses the execFileSync
function to run Ledger. This is another terrible practice, since it is a blocking function. This means that my app would most probably scale very poorly – Ledger is fast, but might not be fast enough. On the other hand, I knew that this app would be used by two users (possibly twice as much assuming it survives long enough for my kids to grow up and use it on a regular basis). Not “two users simultaneously”, mind you – two users altogether. Hence I decided – in a true situated software approach – that it’s not worth it to deal with any unnecessary complications, and execFileSync
is just simpler than dealing with callbacks or promises (even with async/await).
The most important question you could ask, however, is why not use Ledger’s budgeting capabilities? Well, even though I spent some time learning how to use them, I still find them a bit cryptic. More importantly, I wanted something a bit more fancy than what Ledger seems to offer. For each expense account included in my budget, I wanted my app to tell me more than just whether I’m still within the budget or not. For some categories of expenses, it is good to have “two” limits – a “soft” one (which my app calls “safe”) and a hard one. If I spend more than the soft limit, the bar for this category is shown in yellow; if I spend more than the hard limit, the bar is shown in red, which is a nice way of visualizing categories which are still within limits, but which should be treated with more care, so to speak.
Some categories, however, are treated in a yet another special way. There are things you spend money on once per month, like internet bills. For these categories showing the budget and the expenses so far is fine. Many categories, however, are things I spend money on every day (or almost every day), like food. For these ones (denoted in the config by the safe: 'linear'
option) the “soft limit” is just the fraction of the “hard” limit corresponding to the fraction of the month that passed to this point. This way I can set a budget for food and see if I’m within the budget every single day, which I find tremendously useful.
As I said, the app is very simplistic. For example, it always shows the budget for today – there is no way to see the budget for any other day (or month, for that matter). I might change it in the future, so that it could show for example the budget for the past month (or maybe even, say, current year) – but for now, it is perfectly enough for me.
Ah, did I mention? The app is in Polish, and all the strings are hardcoded in the view files. Sorry for that if you don’t speak Polish…
Edit: added the link to the app repo.
Almost three years ago I wrote about a very simple Emacs-Jira integration I have been using since then. As is often the case, I noticed some inconveniences with it, and decided to improve it.
Here is my problem: it’s difficult for me to remember about marking tasks as “done” in Org-mode. I change the status in Jira (since this is what the team expects and needs), but it’s usually not “done”, but “in code review” – when I finish a task, I submit it for review, and it is not “done” yet then. On the other hand, I don’t always even mark my tasks as “done” – in our workflow, this is often done by someone else (the person who deployed the code to production, which might or might not be me).
Of course, having over 700 tasks in the “todo” state is not very nice. My Org-mode file related to my job is pretty large (over 3 MB), and Org-mode can be a bit slow with large files. What I’d like to do is to archive done tasks (that is, move them to another file which is not normally used or manipulated, so it can be really large and it won’t bother me).
Obviously, the first step to achieve that is to mark done tasks as “done”. I don’t want to map all statuses we have in Jira to Org-mode statuses – after all, it’s Jira, not Org which is the “single source of truth” about the status of our tasks, and the reason I use Org-mode for them is not to keep their state, but to be able to clock them and to store personal notes (as opposed to Jira comments which I use for things the team should know about).
So, let’s use jira-terminal again. I can get the status of the task with jira-terminal detail -f status <task number>
. The drawback is that jira-terminal
prepends the word Status:
to its output (which doesn’t make much sense), but that can be remedied very easily with a bit of Elisp.
(defun jira-key-to-status (key) "Return the status of Jira task KEY." (when key (trim-trailing-whitespace (with-temp-buffer (unless (zerop (call-process "jira-terminal" nil t nil "detail" "-f" "status" key)) (error "Could not retrieve task data from Jira")) (goto-char (point-min)) (search-forward "Status: ") (buffer-substring-no-properties (point) (point-max))))))
Note how I support the case where key
is nil – as we will see, it may happen that this function will be called on a headline with no Jira task key, and I want it to silently do nothing then instead of throwing an error.
Now that we have the Jira status of the task, let’s convert it to Org status. I don’t want to get too fancy here and just map all statuses we have in Jira to TODO
and DONE
.
(defconst jira-statuses '(("Todo" . "TODO") ("Blocked" . "TODO") ("Done" . "DONE")) "Alist of Jira statuses and their Org counterparts.")
(In reality, we have twice as many, but that is irrelevant here.)
Now, getting the Org-compatible status of a Jira task is easy.
(defun jira-key-to-org-status (key) "Return the Org status of Jira task KEY." (when key (let ((jira-status (jira-key-to-status key))) (or (alist-get jira-status jira-statuses nil nil #'string=) (error "Jira status \"%s\" not found in `jira-statuses'" jira-status)))))
Again, I added (when key ...)
to this function, since otherwise it could complain that nil
is not a valid Jira status – not what I want. On the other hand, if the status is non-nil and not known to jira-statuses
, I explicitly want to be notified that I need to add it there, hence the error
.
The next step is updating the status of the task at point.
(defun jira-update-org-task-at-point () "Update the status of the task at point according to Jira." (interactive) (unless (org-at-heading-p) (error "Not at a heading")) (let* ((from-state (org-get-todo-state)) (heading (org-get-heading t t t t)) (key (when (string-match "\\([A-Z]\\)+-\\([0-9]\\)+" heading) (match-string 0 heading))) (to-state (jira-key-to-org-status key))) (unless (or (null from-state) (null to-state) (string= from-state to-state)) (org-todo to-state) (org-add-log-note))))
Here again I take care to check for possible error conditions. First of all, for this function to work, the point needs to be on an Org heading. This is actually a bit debatable. On the one hand, when calling jira-update-task-at-point
interactively on a large Org entry with a lot of notes, it might happen that the status is not even visible on the screen. Updating something without a clear visual indication does not seem a good idea, and if I bind jira-update-org-task-at-point
to some key I could press by accident, this could happen. On the other hand, it seems safe to assume that if the task is marked “Done” on Jira, updating it as DONE
in Org is fine even if the user does not see/notice that. Still, I decided to be on the safe side.
The next thing here is that the status is only updated if both from-state
and to-state
are non-nil and not equal. If from-state
is nil, it means that the current headline does not even have a TODO status, so it probably does not correspond to a Jira task. (All my headlines which do correspond to Jira tasks have a TODO status.) If to-state
is nil, it means that the Jira task could not be found – this may happen for example if there is no task key (string of the form LT-1337
) in the headline. If both from-state
and to-state
are non-nil, we compare them and if they are equal, we don’t do anything, and we update the Org TODO state otherwise.
Finally, I run org-add-log-note
. The reason I want to do this is that this is how Org mode puts a note saying that the state was changed from TODO
to DONE
when configured to do so. This is one of the darker corners of Org mode: without this invocation, the note was stored, too, but if I ran jira-update-org-task-at-point
in a loop (I’ll show how in a minute), only the last task had the note. The reason is clear – there is some hackery involving post-command-hook
here. I would very much prefer if there was some argument to org-todo
telling Emacs to add a note about the state change, but it is what it is. Note: I’m not complaining too much, Org mode is a very complicated piece of software and it’s quite probable that the complexity is there for a reason – but it would be much simpler if org-todo
could insert that note itself.
Now the next thing I want to do is to update all TODO tasks in my Org file automatically. What I want to do is to iterate over all not-DONE
headlines and run jira-update-org-task-at-point
on them. Of course, the go-to solution when you want to iterate over headlines in an Org file is org-map-entries
:
(org-map-entries #'jira-update-org-task-at-point "TODO<>*\"DONE\"" nil 'archive 'comment)
As you can see, I skip headlines which have no TODO state (that’s what the star does); I also skip archived and commented out subtrees. Of course, I don’t want to run this manually, so let’s make it into a command.
(defun jira-update-all-todos () "Update the status of all tasks in the region or buffer." (interactive) (save-restriction (when (region-active-p) (narrow-to-region (region-beginning) (region-end))) (let ((inhibit-message t) (message-log-max nil)) (org-map-entries #'jira-update-org-task-at-point "TODO<>*\"DONE\"" nil 'archive 'comment))))
Since I don’t want to see the “Note stored” message repeated as many times as many headline states were updated, I suppress both displaying it in the echo area and in the **Messages** buffer.
And that’s it for today! As usual, Emacs (and Org mode) turn out to be ideal for users like me who like to mould them into exactly what they need.
CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode