commit 77331262069503c6e03181c72ab0555ffc1ed168 Author: Ionut Adrian Ciolan Date: Tue Jun 17 13:48:02 2025 +0300 first commit diff --git a/README.org b/README.org new file mode 100644 index 0000000..b7ade27 --- /dev/null +++ b/README.org @@ -0,0 +1,76 @@ +#+title: org-auto-tangle +#+author: Yilkal Argaw + + +This package (i.e org-auto-tangle) is a very simple emacs package that +allows you to automatically tangle org files on save. This is done by +adding the option ~#+auto_tangle: t~ in your org file. + +The tangling process happens asynchronously so it will not block your +emacs session. + +* USAGE + +Simply require the package in you emacs init and hook it into org-mode. + +#+begin_src emacs-lisp + +(require 'org-auto-tangle) + +(add-hook 'org-src-mode-hook 'org-auto-tangle-mode) + +#+end_src + +or you can use use-package + +#+begin_src emacs-lisp +(use-package org-auto-tangle + :load-path "site-lisp/org-auto-tangle/" ;; this line is necessary only if you cloned the repo in your site-lisp directory + :defer t + :hook (org-src-mode . org-auto-tangle-mode)) +#+end_src + +If the minor mode is on, it will try to automatically tangle +your org files if they contain a non nil value for the +~#+auto_tangle:~ option. + +You can configure auto-tangle as the default behavior for all org buffers by +setting the ~org-auto-tangle-default~ variable to ~t~. In this case, you can disable +it for some buffers by setting the ~#+auto_tangle:~ option to ~nil~. + +The ~#+auto_tangle:~ option may also be used to specify variables that should be +preserved in the asynchronous tangling process. For example, if you have +installed a newer version of ~org-mode~ or additional Babel processors, using + +#+begin_src org + #+auto_tangle: vars:load-path +#+end_src + +will be sure that they are available during tangling. The ~vars~ option takes a +colon-separated list so multiple variables may be specified + +#+begin_src org + #+auto_tangle: vars:calendar-longitude:calendar-latitude:calendar-location-name +#+end_src + +It is also possible to disable auto-tangling by adding the ~nil~ option to the +line without removing any ~vars~ list. + +#+begin_src org + #+auto_tangle: vars:load-path nil +#+end_src + +* Babel Auto Tangle Safelist +Add a list of files to the safelist to autotangle with noweb evaluation +#+begin_src emacs-lisp + (setq org-auto-tangle-babel-safelist '( + "~/system.org" + "~/test.org" + )) +#+end_src + + +* License + +This package (i.e. ~org-auto-tangle~) is licensed under the the 2-Clause BSD License. + diff --git a/iadrian-org-tangle.el b/iadrian-org-tangle.el new file mode 100644 index 0000000..b69a467 --- /dev/null +++ b/iadrian-org-tangle.el @@ -0,0 +1,179 @@ +;;; iadrian-iadrian-org-auto-tangle.el --- Automatically and Asynchronously tangles org files on save -*- lexical-binding: t; -*- + +;; Original Author: Yilkal Argaw +;; Original License: https://github.com/yilkalargaw/org-auto-tangle/blob/master/License.org +;; Maintainer: Ionut Adrian Ciolan +;; Version: 0.6.1 +;; Keywords: outlines +;; Package-Requires: ((emacs "24.1") (async "1.9.3")) + +;; This file is not part of GNU Emacs + +;;; Commentary: + +;; It is common to want to tangle org files everytime you save your changes, +;; especially for tangled init files. So this program allows you to +;; do so using #+auto_tangle option in an org file. It also accomplishes +;; this feat asynchronously so it does not let you Emacs session hang. + +;;; Usage: + +;; - Add #+auto_tangle: t to your tangled org file +;; - Make changes to the emacs file and save your changes + +(require 'async) +(require 'cl-lib) +(require 'org) +(require 'ox) ; org-export--parse-option-keyword + +(defcustom iadrian-org-auto-tangle-default nil + "Default behavior of iadrian-org-auto-tangle. + +If nil (default), auto-tangle will only happen on buffers with +the `#+auto_tangle: t' keyword. If t, auto-tangle will happen on +all Org buffers unless `#+auto_tangle: nil' is set." + :group 'iadrian-org-auto-tangle + :type 'boolean) + +(defcustom iadrian-org-auto-tangle-babel-safelist '() + "List of full path of files for which code blocks need to be evaluated. + +By default, code blocks are not evaluated during the auto-tangle to avoid +possible code execution from unstrusted source. To enable code blocks evaluation +for a specific file, add its full path to this list." + :group 'iadrian-org-auto-tangle + :type '(repeat (file :tag "Full file path"))) + +(defun iadrian-org-auto-tangle-find-value (buffer) + "Return the value of the `auto_tangle' keyword in BUFFER." + (with-current-buffer buffer + (cdr (assoc "AUTO_TANGLE" (org-collect-keywords '("AUTO_TANGLE")))))) + +;; This is modeled after `org-export-filters-alist', since it is +;; passed to `org-export--parse-option-keyword'. +(defconst iadrian-org-auto-tangle-options-alist + '((:with-vars nil "vars" iadrian-org-auto-tangle-with-vars)) + "Alist between auto-tangle properties and ways to set them. + +The key of the alist is the property name, and the value is a list +like (KEYWORD OPTION DEFAULT BEHAVIOR) where: + +KEYWORD is a string representing a buffer keyword, or nil. Each + property defined this way can also be set, during subtree + export, through a headline property named after the keyword + with the \"EXPORT_\" prefix (i.e. DATE keyword and EXPORT_DATE + property). +OPTION is a string that could be found in an #+OPTIONS: line. +DEFAULT is the default value for the property. +BEHAVIOR determines how Org should handle multiple keywords for + the same property. It is a symbol among: + nil Keep old value and discard the new one. + t Replace old value with the new one. + `space' Concatenate the values, separating them with a space. + `newline' Concatenate the values, separating them with + a newline. + `split' Split values at white spaces, and cons them to the + previous list. + `parse' Parse value as a list of strings and Org objects, + which can then be transcoded with, e.g., + `org-export-data'. It implies `space' behavior. + +Values set through KEYWORD and OPTION have precedence over +DEFAULT.") + +(defgroup iadrian-org-auto-tangle nil + "Automatic tangling of `org-mode' documents." + :tag "Org Auto Tangle" + :group 'org-babel) + +(defcustom iadrian-org-auto-tangle-with-vars nil + "Non-nil means pass VARS variables to the async tangling process. + +This option can also be set with the AUTO_TANGLE keyword, +e.g. \"vars:calendar-latitude\". + +The `org-src-preserve-indentation', `org-babel-pre-tangle-hook', +and `org-babel-post-tangle-hook' variables are automatically +preserved and do not need to be listed here." + :group 'iadrian-org-auto-tangle + :type '(repeat (symbol :tag "Variable name"))) + +(defun iadrian-org-auto-tangle--get-inbuffer-options () + "Return current buffer auto-tangle options, as a plist. + +Assume buffer is in Org mode. Narrowing, if any, is ignored." + (let (plist) + ;; Read options in the current buffer and return value. + (dolist (entry (org-collect-keywords '("AUTO_TANGLE")) plist) + (pcase entry + (`("AUTO_TANGLE" . ,values) + (setq plist + (apply #'org-combine-plists + plist + (mapcar (lambda (v) + (let ((org-export-options-alist))) + (iadrian-org-auto-tangle--parse-auto-tangle-keyword v)) + values)))))))) + +(defun iadrian-org-auto-tangle--parse-auto-tangle-keyword (auto-tangle) + "Parse an AUTO-TANGLE line and return values as a plist." + (let ((org-export-options-alist iadrian-org-auto-tangle-options-alist)) + (org-export--parse-option-keyword auto-tangle))) + +(defun iadrian-org-auto-tangle-async (file) + "Invoke `org-babel-tangle-file' asynchronously on FILE." + (message "Tangling %s..." (buffer-file-name)) + (async-start + (let* ((buf-vars (plist-get (iadrian-org-auto-tangle--get-inbuffer-options) + :with-vars)) + (with-vars (if buf-vars + (mapcar #'intern + (org-uniquify (org-split-string + (symbol-name buf-vars) ":"))) + iadrian-org-auto-tangle-with-vars)) + (preserved (mapcar (lambda (v) + (cons v (symbol-value v))) + (append '(org-src-preserve-indentation + org-babel-pre-tangle-hook + org-babel-post-tangle-hook) + with-vars))) + (evaluate (not (member file iadrian-org-auto-tangle-babel-safelist)))) + (lambda () + (require 'org) + (let ((start-time (current-time)) + (non-essential t) + (org-confirm-babel-evaluate evaluate)) + (cl-progv (mapcar #'car preserved) (mapcar #'cdr preserved) + (org-babel-tangle-file file)) + (format "%.2f" (float-time (time-since start-time)))))) + (let ((message-string (format "Tangling %S completed after" file))) + (lambda (tangle-time) + (message "%s %s seconds" message-string tangle-time))))) + +(defun iadrian-org-auto-tangle-tangle-if-needed () + "Call iadrian-org-auto-tangle-async if needed. + +Tangle will happen depending on the value of +`iadrian-org-auto-tangle-default' and on the presence and value of the +`#+auto_tangle' keyword in the current buffer. If present, +`#+auto_tangle' always overrides `iadrian-org-auto-tangle-default'." + (let ((auto-tangle-kw (iadrian-org-auto-tangle-find-value (current-buffer)))) + (when (and (derived-mode-p 'org-mode) + (if auto-tangle-kw + (not (member "nil" auto-tangle-kw)) + iadrian-org-auto-tangle-default)) + (iadrian-org-auto-tangle-async (buffer-file-name))))) + +;;;###autoload +(define-minor-mode iadrian-org-auto-tangle-mode + "Automatically tangle org-mode files with the option #+auto_tangle: t." + :lighter " org-a-t" + + (if iadrian-org-auto-tangle-mode + (add-hook 'after-save-hook #'iadrian-org-auto-tangle-tangle-if-needed + nil 'local) + (remove-hook 'after-save-hook #'iadrian-org-auto-tangle-tangle-if-needed 'local))) + +(provide 'iadrian-org-auto-tangle) + +;;; iadrian-org-auto-tangle.el ends here