From 16c3622825a28a6eebaea1944644403613e4d418 Mon Sep 17 00:00:00 2001 From: Akira Komamura Date: Fri, 3 Dec 2021 22:44:34 +0900 Subject: [PATCH] Initial commit --- flake.lock | 7 +++++ flake.nix | 5 ++++ nix/default.nix | 18 +++++++++++++ nix/dropUntil.nix | 8 ++++++ nix/dropWhile.nix | 11 ++++++++ nix/excludeOrgSubtreesOnHeadlines.nix | 37 +++++++++++++++++++++++++++ nix/matchOrgHeadline.nix | 4 +++ nix/matchOrgTag.nix | 4 +++ nix/splitWith.nix | 11 ++++++++ nix/tangleOrgBabel.nix | 37 +++++++++++++++++++++++++++ test/test.nix | 33 ++++++++++++++++++++++++ test/test.org | 20 +++++++++++++++ 12 files changed, 195 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/default.nix create mode 100644 nix/dropUntil.nix create mode 100644 nix/dropWhile.nix create mode 100644 nix/excludeOrgSubtreesOnHeadlines.nix create mode 100644 nix/matchOrgHeadline.nix create mode 100644 nix/matchOrgTag.nix create mode 100644 nix/splitWith.nix create mode 100644 nix/tangleOrgBabel.nix create mode 100644 test/test.nix create mode 100644 test/test.org diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..5999137 --- /dev/null +++ b/flake.lock @@ -0,0 +1,7 @@ +{ + "nodes": { + "root": {} + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ce938c4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,5 @@ +{ + description = "Nix library for extracting source blocks from Org"; + + outputs = { ... }: { lib = import ./nix; }; +} diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..7b237af --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,18 @@ +with builtins; +let + excludeOrgSubtreesOnHeadlines = import ./excludeOrgSubtreesOnHeadlines.nix; + + matchOrgTag = import ./matchOrgTag.nix; + matchOrgHeadline = import ./matchOrgHeadline.nix; + matchOrgHeadlines = headlines: s: + builtins.any (t: matchOrgHeadline t s) headlines; + + tangleOrgBabel = pkgs.callPackage ./tangleOrgBabel.nix { + processLines = processOrgLines; + }; +in +{ + inherit matchOrgTag matchOrgHeadline matchOrgHeadlines; + inherit excludeOrgSubtreesOnHeadlines; + inherit tangleOrgBabel; +} diff --git a/nix/dropUntil.nix b/nix/dropUntil.nix new file mode 100644 index 0000000..27b8780 --- /dev/null +++ b/nix/dropUntil.nix @@ -0,0 +1,8 @@ +pred: items: +with builtins; +let + xs = import ./dropWhile.nix (x: ! (pred x)) items; +in +if length xs > 0 +then tail xs +else [ ] diff --git a/nix/dropWhile.nix b/nix/dropWhile.nix new file mode 100644 index 0000000..6172be1 --- /dev/null +++ b/nix/dropWhile.nix @@ -0,0 +1,11 @@ +pred: +with builtins; +let + go = items: + if length items == 0 + then [ ] + else if ! (pred (head items)) + then items + else go (tail items); +in +go diff --git a/nix/excludeOrgSubtreesOnHeadlines.nix b/nix/excludeOrgSubtreesOnHeadlines.nix new file mode 100644 index 0000000..2c68fc1 --- /dev/null +++ b/nix/excludeOrgSubtreesOnHeadlines.nix @@ -0,0 +1,37 @@ +# Exclude lines of Org subtrees by a heading predicate +pred: +with builtins; +let + dropWhile = import ./dropWhile.nix; + + splitListWith = import ./splitWith.nix; + + isHeadline = s: substring 0 1 s == "*"; + + genericHeadlineRegexp = "(\\*+)[[:space:]].+"; + + getHeadlineLevel = headline: + stringLength (head (match genericHeadlineRegexp headline)); + + prependOptionalStars = n: rest: + if n == 0 + then rest + else prependOptionalStars (n - 1) ("\\*?" + rest); + + makeSubtreeEndRegexp = outlineLevel: + prependOptionalStars (outlineLevel - 1) "\\*[[:space:]].+"; + + dropTillSubtreeEnd = level: + dropWhile (s: !(isHeadline s && match (makeSubtreeEndRegexp level) s != null)); + + go_ = cut: + cut.before + ++ + (if cut.sep == null + then [ ] + else go (dropTillSubtreeEnd (getHeadlineLevel cut.sep) cut.after)); + + go = lines: go_ (splitListWith (s: isHeadline s && pred s) lines); + +in +go diff --git a/nix/matchOrgHeadline.nix b/nix/matchOrgHeadline.nix new file mode 100644 index 0000000..a656173 --- /dev/null +++ b/nix/matchOrgHeadline.nix @@ -0,0 +1,4 @@ +headlineText: s: +builtins.match + "\\*+[[:space:]]+${headlineText}([[:space:]]+:[^[:space:]]+:)?[[:space:]]*" + s != null diff --git a/nix/matchOrgTag.nix b/nix/matchOrgTag.nix new file mode 100644 index 0000000..285b4ec --- /dev/null +++ b/nix/matchOrgTag.nix @@ -0,0 +1,4 @@ +tag: s: +builtins.match + "\\*+[[:space:]].+:${tag}:.*" + s != null diff --git a/nix/splitWith.nix b/nix/splitWith.nix new file mode 100644 index 0000000..0e17689 --- /dev/null +++ b/nix/splitWith.nix @@ -0,0 +1,11 @@ +pred: +with builtins; +let + go = before: rest: + if (length rest == 0) + then { inherit before; sep = null; after = [ ]; } + else if pred (head rest) + then { inherit before; sep = head rest; after = tail rest; } + else (go (before ++ [ (head rest) ]) (tail rest)); +in +go [ ] diff --git a/nix/tangleOrgBabel.nix b/nix/tangleOrgBabel.nix new file mode 100644 index 0000000..894ec0c --- /dev/null +++ b/nix/tangleOrgBabel.nix @@ -0,0 +1,37 @@ +# Quick-and-dirty re-implementation of org-babel-tangle in Nix. +{ languages +, processLines +}: +string: +with builtins; +let + lines = filter isString (split "\n" string); + + dropUntil = import ./dropUntil.nix; + + blockStartRegexp = + "[[:space:]]*\#\\+[Bb][Ee][Gg][Ii][Nn]_[Ss][Rr][Cc][[:space:]]+" + + "(" + (concatStringsSep "|" languages) + ")" + + "([[:space:]].*)?"; + + isBlockStart = line: match blockStartRegexp line != null; + + splitListWith = import ./splitWith.nix; + + blockEndRegexp = "[[:space:]]*\#\\+[Ee][Nn][Dd]_[Ss][Rr][Cc].*"; + + isBlockEnd = line: match blockEndRegexp line != null; + + go = acc: xs: + let + st1 = dropUntil isBlockStart xs; + st2 = splitListWith isBlockEnd st1; + in + if length xs == 0 + then acc + else if length st1 == 0 + then acc + else (go (acc ++ [ st2.before ]) st2.after); + +in +concatStringsSep "\n" (concatLists (go [ ] (processLines lines))) diff --git a/test/test.nix b/test/test.nix new file mode 100644 index 0000000..0fc0826 --- /dev/null +++ b/test/test.nix @@ -0,0 +1,33 @@ +# nix-instantiate --eval --strict test/test.nix +with builtins; +let + pkgs = import (fetchTree (fromJSON (readFile ../flake.lock)).nodes.nixpkgs.locked) { + system = builtins.currentSystem; + }; + exclude = import ../nix/excludeOrgSubtreesOnHeadlines.nix; + matchOrgTag = import ../nix/matchOrgTag.nix; + matchOrgHeadline = import ../nix/matchOrgHeadline.nix; + dropUntil = import ../nix/dropUntil.nix; + lines = filter isString (split "\n" (readFile ./test.org)); +in +pkgs.lib.runTests { + testTagPredicate = { + expr = head (exclude (matchOrgTag "ARCHIVE") lines); + expected = "* Good morning"; + }; + + testHeadlinePredicate = { + expr = head (exclude (matchOrgHeadline "Archived entry") lines); + expected = "* Good morning"; + }; + + testSubtree = { + expr = head (dropUntil (s: s == "Hello!") (exclude (matchOrgTag "irregular") lines)); + expected = "* Get to work"; + }; + + testTagPredicate2 = { + expr = pkgs.lib.last (exclude (matchOrgTag "optional") lines); + expected = "It was a good day!"; + }; +} diff --git a/test/test.org b/test/test.org new file mode 100644 index 0000000..fee1bea --- /dev/null +++ b/test/test.org @@ -0,0 +1,20 @@ +* Archived entry :ARCHIVE: +** Child inside an archive entry +* Good morning +Good morning! +* Zao :optional: +/Zao/ means good morning in Mandarin Chinese. +Chinese is optional for non-Chinese people living in countries other than China. +* Hello +Hello! +** Irreguar cases :irregular: +*** If he/she isn't fine +Call an ambulance. +Why did he come to the office in the first place? +* Get to work +Do you love your work? +* Bye +Bye! +It was a good day! +* Zaijian :optional: +/Zaijian/ means goodbye in Mandarin Chinese.