From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp12.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms9.migadu.com with LMTPS id iKdpGx3nSWR5aAAASxT56A (envelope-from ) for ; Thu, 27 Apr 2023 05:08:13 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp12.migadu.com with LMTPS id sFAqGx3nSWTbxQAAauVa8A (envelope-from ) for ; Thu, 27 Apr 2023 05:08:13 +0200 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 0148935E74 for ; Thu, 27 Apr 2023 05:08:13 +0200 (CEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1prrya-0004ik-Fz; Wed, 26 Apr 2023 23:07:20 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1prryZ-0004iT-8r for emacs-orgmode@gnu.org; Wed, 26 Apr 2023 23:07:19 -0400 Received: from netyu.xyz ([152.44.41.246] helo=mail.netyu.xyz) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1prryW-00038d-Tz for emacs-orgmode@gnu.org; Wed, 26 Apr 2023 23:07:19 -0400 Received: from fw.net.yu.netyu.xyz ( [222.248.4.98]) by netyu.xyz (OpenSMTPD) with ESMTPSA id 8074e7f4 (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Thu, 27 Apr 2023 03:07:14 +0000 (UTC) References: User-agent: mu4e 1.9.22; emacs 30.0.50 To: Mehmet Tekman Cc: emacs-orgmode@gnu.org Subject: Re: [ANN] lisp/ob-tangle-sync.el Date: Thu, 27 Apr 2023 10:55:59 +0800 In-reply-to: Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Received-SPF: pass client-ip=152.44.41.246; envelope-from=ruijie@netyu.xyz; helo=mail.netyu.xyz X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Ruijie Yu From: Ruijie Yu via "General discussions about Org-mode." Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: emacs-orgmode-bounces+larch=yhetil.org@gnu.org X-Migadu-Flow: FLOW_IN X-Migadu-Country: US ARC-Seal: i=1; s=key1; d=yhetil.org; t=1682564893; a=rsa-sha256; cv=none; b=FdsH8p9PfnsjGNS2HYbEEfSf+VsjMHyGojnfqWBOWnDy6JHiAKOWAE9WaDOSbpl4krMM1t Bd1GXpYlkVM1vm95olqVfllfDn+LM2QBDbNS8+/oUkTC0zsbr6narArF/GbgAnykjOgcKx DwyFggQ2HUKFKVtCGusU4wTA58L5JNmZFuK2BJjdwncu0OJziwDTfJs/CcBlQ5zz+U1VL5 4oVsXD4fiBqyz4r9fIBIZFgK5EhY9InuxlGkU5acI2Pl6ef5GSCeBHVlxqhAdyRlVlKpgi ASRSBHfX44/U000eKnx3Gu7B/W8Yp97UzBkTpRckmOcPFBTQKxZVGFJSZiHzuA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=none; dmarc=pass (policy=none) header.from=gnu.org; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1682564893; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=PEXvoo2xZc/t7l2ojQAFOq+04TK25EqN5POHQbucd6Q=; b=nqjtVMW8AnaYhGdyAX0zG7FhX99IdWCtLd1l+5NdPTdvLu0cdBEbnmm6TFJftnEV5dfJqY vt91yaf8InAqB27+JBry48oHMgPl59ig41TRekTzAB6hJ9hRZXY6nnxjBJ+OAPnAwkXAux MFPiNvykhmsmR2SGIvZIbWa/lVDxwStqLa1yyUPQrEooCL3aXiaTXAPo3MPFo7Jn4tnx0T IZkUw6csQvMW03OyZmEl+Qc/tBU5gbiHKa86ufWbdERqGtlJr7k3Q7AHgHYO2W48awKNlJ bQU3BIQlgK3m7RhNIP0nli+KknQ7KF08x9QHtTxVSuQpdl4hkXQC9CQaUcC5sQ== X-Migadu-Scanner: scn1.migadu.com Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=pass (policy=none) header.from=gnu.org; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" X-Migadu-Spam-Score: -2.01 X-Spam-Score: -2.01 X-Migadu-Queue-Id: 0148935E74 X-TUID: KYDZjF2d92NZ Mehmet Tekman writes: > Dear fellow org-users, > > I would like to contribute some a new library into org-mode, which > performs automatic synchronization between tangled files and their > org-mode source blocks via a global minor mode > `org-babel-tangle-sync-mode' which uses the after-save-hook. Great idea! Some inline comments below. > diff --git a/lisp/ob-tangle-sync.el b/lisp/ob-tangle-sync.el > new file mode 100644 > index 000000000..61c23f647 > --- /dev/null > +++ b/lisp/ob-tangle-sync.el > @@ -0,0 +1,172 @@ > +;;; ob-tangle-sync.el --- Synchronize Source Code and Org Files -*- lexi= cal-binding: t; -*- > + > +;; Copyright (C) 2009-2023 Free Software Foundation, Inc. > + > +;; Author: Mehmet Tekman > +;; Keywords: literate programming, reproducible research > +;; URL: https://orgmode.org > + > +;; This file is part of GNU Emacs. > + > +;; GNU Emacs is free software: you can redistribute it and/or modify > +;; it under the terms of the GNU General Public License as published by > +;; the Free Software Foundation, either version 3 of the License, or > +;; (at your option) any later version. > + > +;; GNU Emacs is distributed in the hope that it will be useful, > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +;; GNU General Public License for more details. > + > +;; You should have received a copy of the GNU General Public License > +;; along with GNU Emacs. If not, see . > + > +;;; Commentary: > + > +;; Synchronize the code between source blocks and raw source-code files. > + > +;;; Code: > + > +(require 'org-macs) > +(org-assert-version) > + > +(require 'ol) > +(require 'org) > +(require 'org-element) > +(require 'ob-core) > + > +(defgroup org-babel-tangle-sync nil > + "Options for synchronizing source code and code blocks." > + :tag "Org Babel Tangle sync" > + :group 'org-babel-tangle) > + > +;;;###autoload > +(define-minor-mode org-babel-tangle-sync-mode > + "Global minor mode that synchronizes tangled files after every save." > + :global t Is there possibility to have a local minor mode (without introducing too much code changes)? > + :interactive t I believe interactive is the default? > + :lighter " o-ts" > + (if org-babel-tangle-sync-mode > + (add-hook 'after-save-hook 'org-babel-tangle-sync-synchronize nil = t) > + (remove-hook 'after-save-hook 'org-babel-tangle-sync-synchronize t))) > + > +(defcustom org-babel-tangle-sync-files nil > + "A list of `org-mode' files. > +When `org-babel-tangle-sync-mode' is enabled only files listed > +here are subject to the org-babel-tangle-sync treatment. If nil, > +then all org files with tangle headers are considered." > + :group 'org-babel-tangle-sync > + :type 'list > + :package-version '(Org . "9.6.5") > + :set (lambda (_var val) (mapcar #'(lambda (x) (expand-file-name x)) va= l))) You only need to say #'expand-file-name instead of the quoted lambda. Also, you need to set the variable, otherwise the variable `org-babel-tangle-sync-files' is undefined. What I have in mind is this: :set (lambda (var val) (set var (mapcar #'expand-file-name val))) > + > + > +(defun org-babel-tangle-sync--babel-tangle-jump (link block-name) > + "Jump from a tangled file to the Org file without returning anything. > +The location of the code block in the Org file is given by a > +combination of the LINK filename and header, followed by the > +BLOCK-NAME Org mode source block number. The code is borrowed > +heavily from `org-babel-tangle-jump-to-org'" > + ;; Go to the beginning of the relative block in Org file. > + ;; Explicitly allow fuzzy search even if user customized > + ;; otherwise. > + (let (org-link-search-must-match-exact-headline) > + (org-link-open-from-string link)) > + ;;(setq target-buffer (current-buffer)) > + (if (string-match "[^ \t\n\r]:\\([[:digit:]]+\\)" block-name) > + (let ((n (string-to-number (match-string 1 block-name)))) > + (if (org-before-first-heading-p) (goto-char (point-min)) > + (org-back-to-heading t)) > + ;; Do not skip the first block if it begins at point min. > + (cond ((or (org-at-heading-p) > + (not (eq (org-element-type (org-element-at-point)) > + 'src-block))) > + (org-babel-next-src-block n)) > + ((=3D n 1)) > + (t (org-babel-next-src-block (1- n))))) > + (org-babel-goto-named-src-block block-name)) > + (goto-char (org-babel-where-is-src-block-head)) > + (forward-line 1)) > + > +;;;###autoload > +(defun org-babel-tangle-sync-synchronize () > + "Synchronize a tangled code block to its source-specific file, or vice= versa. > +If the cursor is either within the source file or in destination > +tangled file, perform a desired tangling action. The tangling > +action by default is to detangle the tangled files' changes back > +to its source block, or to tangle the source block to its tangled > +file. Actions are one of `skip' (no action), `pull' (detangle > +only), `export' (tangle only), and `both' (default, synchronize > +in both directions). All `org-mode' source blocks and all tangled > +files with comments are considered valid targets, unless > +specified otherwise by `org-babel-tangle-sync-files'." > + (interactive) > + (let* ((link (save-excursion > + (progn (re-search-backward org-link-bracket-re nil t) > + (match-string-no-properties 0)))) Here you don't have to use `progn' because it is implied from `save-excursi= on'. > + (block-name (match-string 2)) > + (orgfile-p (string=3D major-mode "org-mode")) > + (tangled-file-p (and link (not orgfile-p)))) > + > + ;; Tangled File =E2=86=92 Source Block > + (if tangled-file-p > + ;; Examine the block: Get the source file and the desired tangle= -sync action > + (let* ((parsed-link (with-temp-buffer > + (let ((org-inhibit-startup nil)) > + (insert link) > + (org-mode) > + (goto-char (point-min)) > + (org-element-link-parser)))) > + (source-file (expand-file-name > + (org-element-property :path parsed-link))) > + (sync-action (save-window-excursion > + (progn > + (org-babel-tangle-sync--babel-tangle-jum= p link block-name) > + (alist-get :tangle-sync > + (nth 2 (org-babel-get-src-blo= ck-info > + 'no-eval))))))) > + ;; De-tangle file back to source block if: > + ;; - member of sync file list (or list is empty) > + ;; - source file tangle-sync action isn't "skip" or "export", > + (if (or (null org-babel-tangle-sync-files) > + (member source-file org-babel-tangle-sync-files)) > + (cond ((string=3D sync-action "skip") nil) > + ((string=3D sync-action "export") > + (save-window-excursion > + (progn (org-babel-tangle-sync--babel-tangle-jump = link block-name) > + (let ((current-prefix-arg '(16))) > + (call-interactively 'org-babel-tangle)) > + (message "Exported from %s" source-file)))) > + (t > + (save-window-excursion > + (org-babel-detangle) > + (message "Synced to %s" source-file)))))) > + > + ;; Source Block =E2=86=92 Tangled File (or Source Block =E2=86=90 = Tangled File (via "pull")) > + (when orgfile-p > + ;; Tangle action of Source file on Block if: > + ;; - member of sync file list (or list is empty) > + ;; Actions > + ;; - pull (Source Block =E2=86=90 File) > + ;; - skip (nothing) > + ;; - export, both, nil (Source Block =E2=86=92 File) > + (if (or (null org-babel-tangle-sync-files) > + (member buffer-file-name org-babel-tangle-sync-files)) > + > + (let* ((src-headers (nth 2 (org-babel-get-src-block-info 'no= -eval))) > + (tangle-file (cdr (assq :tangle src-headers))) > + (tangle-action (alist-get :tangle-sync src-headers))) > + (when tangle-file > + (cond ((string=3D tangle-action "pull") (save-excursion > + (org-babel-detan= gle tangle-file))) > + ((string=3D tangle-action "skip") nil) > + (t (let ((current-prefix-arg '(16))) > + (call-interactively 'org-babel-tangle) > + ;; Revert to see changes, then re-enable the = mode > + (with-current-buffer (get-file-buffer tangle-= file) > + (revert-buffer) > + (org-babel-tangle-sync-mode t)))))))))))) > + > +(provide 'ob-tangle-sync) > + > +;;; ob-tangle-sync.el ends here --=20 Best, RY [Please note that this mail might go to spam due to some misconfiguration in my mail server -- still investigating.]