Babel :var tables with rules and Lisp values !152

merged merged by cmc on 2026-10-08 00:49 UTC · krz/orgstar:babel-var-tables into main

3 files changed, +169 −23

Layout: unified · split

Sources/OrgCore/Compute/Babel.swift +142 −22
@@ -68,6 +68,10 @@ public struct BabelJob: Sendable, Equatable {
68 var writesFile = false 68 var writesFile = false
69 /// `:cache yes`: the hash for the `#+RESULTS[…]:` line. 69 /// `:cache yes`: the hash for the `#+RESULTS[…]:` line.
70 var hash: String? 70 var hash: String?
71 /// Column and row names a table `:var` gave up, put back on a table result
72 /// (`org-babel-reassemble-table`).
73 var colnames: [String]?
74 var rownames: [String]?
71 } 75 }
72} 76}
73 77
@@ -290,14 +294,17 @@ public enum Babel {
290 } 294 }
291 let toFile = params.results.contains("file") 295 let toFile = params.results.contains("file")
292 var hash: String? 296 var hash: String?
297 var tables = Tables()
293 let finishBase = { (kind: BabelJob.Finish.Kind) in 298 let finishBase = { (kind: BabelJob.Finish.Kind) in
294 BabelJob.Finish(kind: kind, language: language, params: params.results, output: output, wrap: params.wrap, name: name, inline: inline, 299 BabelJob.Finish(kind: kind, language: language, params: params.results, output: output, wrap: params.wrap, name: name, inline: inline,
295 file: toFile ? file : nil, fileDescription: params.single[":file-desc"], hash: hash) 300 file: toFile ? file : nil, fileDescription: params.single[":file-desc"], hash: hash,
301 colnames: tables.picked(tables.colnames, params.single[":colnames"]), rownames: tables.picked(tables.rownames, params.single[":rownames"]))
296 } 302 }
297 let vars: [(String, VarValue)] 303 let vars: [(String, VarValue)]
298 var stdin: VarValue? 304 var stdin: VarValue?
299 do { 305 do {
300 vars = try params.vars.map { ($0.name, try resolve($0.value, model: model, text: ns, refs: refs)) } 306 tables = try disassemble(params.vars.map { ($0.name, try resolve($0.value, model: model, text: ns, refs: refs)) }, params: params)
307 vars = tables.vars
301 stdin = try params.single[":stdin"].map { try resolve($0, model: model, text: ns, refs: refs) } 308 stdin = try params.single[":stdin"].map { try resolve($0, model: model, text: ns, refs: refs) }
302 } catch let needed as Needed { 309 } catch let needed as Needed {
303 // Asked about once, with every block the references reach; asked every time when 310 // Asked about once, with every block the references reach; asked every time when
@@ -318,7 +325,7 @@ public enum Babel {
318 } 325 }
319 if useCache, !inline, params.single[":cache"] == "yes" { 326 if useCache, !inline, params.single[":cache"] == "yes" {
320 do { 327 do {
321 let state = try cacheState(language: language, params: params, vars: vars, body: body, location: location, name: name, 328 let state = try cacheState(language: language, params: params, tables: tables, body: body, location: location, name: name,
322 call: location != block.range, text: ns) 329 call: location != block.range, text: ns)
323 if state.current { return .failure(.message(cachedMessage(state.value))) } 330 if state.current { return .failure(.message(cachedMessage(state.value))) }
324 // Appended or prepended results accumulate, so they carry no hash. 331 // Appended or prepended results accumulate, so they carry no hash.
@@ -574,13 +581,110 @@ public enum Babel {
574 return lines.joined(separator: "\n") 581 return lines.joined(separator: "\n")
575 } 582 }
576 583
584 // MARK: - Tables in variables
585
586 /// Variables after `org-babel-disassemble-tables`, with the names taken off their tables.
587 struct Tables {
588 var vars: [(String, VarValue)] = []
589 var colnames: [(name: String, names: [Cell])] = []
590 var rownames: [(name: String, names: [Cell])] = []
591
592 /// `org-babel-pick-name`: the last variable's names, unless the header argument is
593 /// absent, whose `nil` selects none.
594 func picked(_ names: [(name: String, names: [Cell])], _ selector: String?) -> [String]? {
595 guard selector != nil, let last = names.last else { return nil }
596 return last.names.map(Babel.cellText)
597 }
598 }
599
600 static func cellText(_ c: Cell) -> String {
601 switch c {
602 case .number(let n): n
603 case .text(let s): s
604 }
605 }
606
607 /// `org-babel-disassemble-tables`: a table's header row comes off when `:colnames` asks, or
608 /// by default when only its second row is a rule; its first column with `:rownames`; its
609 /// rules unless `:hlines yes`.
610 static func disassemble(_ vars: [(String, VarValue)], params: Params) throws -> Tables {
611 for key in [":colnames", ":rownames"] where params.lisp.contains(key) {
612 throw Failure.message("\(key) with a Lisp value isn't supported yet; nothing was run.")
613 }
614 let colnames = params.single[":colnames"]
615 let rownames = params.single[":rownames"]
616 let hlines = params.single[":hlines"] ?? "no"
617 var tables = Tables()
618 for (name, value) in vars {
619 guard case .table(var rows) = value else {
620 tables.vars.append((name, value))
621 continue
622 }
623 let auto = rows.count > 1 && rows[0] != nil && rows[1] == nil && !rows.dropFirst(2).contains(where: { $0 == nil })
624 if colnames != "no", colnames != nil || auto {
625 while let first = rows.first, first == nil { rows.removeFirst() }
626 if let header = rows.first ?? nil {
627 rows.removeFirst(rows.count > 1 && rows[1] == nil ? 2 : 1)
628 tables.colnames.append((name, header))
629 }
630 }
631 if let rownames, rownames != "no" {
632 let kept = rows.compactMap { $0 }
633 tables.rownames.append((name, kept.map { $0.first ?? .text("") }))
634 rows = kept.map { Array($0.dropFirst()) }
635 }
636 if hlines != "yes" { rows = rows.filter { $0 != nil } }
637 tables.vars.append((name, .table(rows)))
638 }
639 return tables
640 }
641
642 /// `org-babel-reassemble-table` on a result.
643 static func reassemble(_ value: Value, _ finish: BabelJob.Finish) -> Value {
644 guard case .table(var rows) = value else { return value }
645 if let names = finish.rownames, names.count == rows.count {
646 var next = names.makeIterator()
647 rows = rows.map { row in row.map { [next.next() ?? ""] + $0 } }
648 }
649 if let names = finish.colnames, let first = rows.first ?? nil, first.count == names.count {
650 rows = [names, nil] + rows
651 }
652 return .table(rows)
653 }
654
655 /// A `:var` value that is Lisp (`org-babel-read`): evaluated, as a number, string, list or
656 /// table.
657 static func lispValue(_ value: String) throws -> VarValue {
658 let refusal = Failure.message(":var value \(value) is Lisp that only Emacs can evaluate.")
659 guard let form = try? LispReader.readFirst(value).sexp, let result = try? Elisp().eval(form) else { throw refusal }
660 func cell(_ v: Sexp) throws -> Cell {
661 if let s = v.string { return .text(s) }
662 if let n = number(Elisp.printed(v)), Elisp.printed(v) == n { return .number(n) }
663 throw refusal
664 }
665 if let s = result.string { return .scalar(s) }
666 if case .list(let items) = result, !items.isEmpty {
667 if items.allSatisfy({ if case .list = $0 { return true }; return $0 == .symbol("hline") }) {
668 return .table(try items.map { item in
669 if item == .symbol("hline") { return nil }
670 guard case .list(let cells) = item else { throw refusal }
671 return try cells.map(cell)
672 })
673 }
674 return .list(try items.map(cell))
675 }
676 if case .number(let n) = try? cell(result) { return .number(n) }
677 throw refusal
678 }
679
577 // MARK: - Cache 680 // MARK: - Cache
578 681
579 /// `:cache yes` (`org-babel-execute-src-block`): the block's hash, and whether its results 682 /// `:cache yes` (`org-babel-execute-src-block`): the block's hash, and whether its results
580 /// already carry it, with their value when it can be read. 683 /// already carry it, with their value when it can be read.
581 static func cacheState(language: String, params: Params, vars: [(String, VarValue)], body: String, location: Range<Int>, name: String?, 684 static func cacheState(language: String, params: Params, tables: Tables, body: String, location: Range<Int>, name: String?,
582 call: Bool, text: NSString) throws -> (hash: String, current: Bool, value: VarValue?) { 685 call: Bool, text: NSString) throws -> (hash: String, current: Bool, value: VarValue?) {
583 let input = try cacheInput(language: language, params: params, vars: vars, expanded: expandedBody(language: language, vars: vars, body: body), call: call) 686 let input = try cacheInput(language: language, params: params, tables: tables,
687 expanded: expandedBody(language: language, vars: tables.vars, body: body), call: call)
584 let hash = Insecure.SHA1.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined() 688 let hash = Insecure.SHA1.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined()
585 guard let line = existingResults(name: name, block: location, text: text), resultsHash(at: line, text: text) == hash else { 689 guard let line = existingResults(name: name, block: location, text: text), resultsHash(at: line, text: text) == hash else {
586 return (hash, false, nil) 690 return (hash, false, nil)
@@ -610,9 +714,9 @@ public enum Babel {
610 714
611 /// What `org-babel-sha1-hash` hashes: the header arguments after `org-babel-process-params`, 715 /// What `org-babel-sha1-hash` hashes: the header arguments after `org-babel-process-params`,
612 /// sorted by key and printed with `%S`, then the expanded body. 716 /// sorted by key and printed with `%S`, then the expanded body.
613 static func cacheInput(language: String, params: Params, vars: [(String, VarValue)], expanded: String, call: Bool) throws -> String { 717 static func cacheInput(language: String, params: Params, tables: Tables, expanded: String, call: Bool) throws -> String {
614 if !params.lisp.isEmpty || params.single[":colnames"] != nil || params.single[":rownames"] != nil { 718 if !params.lisp.isEmpty {
615 throw Failure.message(":cache with Lisp header values, :colnames or :rownames isn't supported yet; nothing was run.") 719 throw Failure.message(":cache with Lisp header values isn't supported yet; nothing was run.")
616 } 720 }
617 let ignored: Set<String> = ["replace", "silent", "none", "discard", "append", "prepend"] 721 let ignored: Set<String> = ["replace", "silent", "none", "discard", "append", "prepend"]
618 let drawsFile = language == "dot" || language == "plantuml" 722 let drawsFile = language == "dot" || language == "plantuml"
@@ -640,7 +744,10 @@ public enum Babel {
640 entries.append((":result-type", words.contains("output") ? "output" : "value")) 744 entries.append((":result-type", words.contains("output") ? "output" : "value"))
641 entries.append((":results", lispString(kept.joined(separator: " ")))) 745 entries.append((":results", lispString(kept.joined(separator: " "))))
642 if let wrap = params.wrap, !wrap.isEmpty { entries.append((":wrap", lispString(wrap))) } 746 if let wrap = params.wrap, !wrap.isEmpty { entries.append((":wrap", lispString(wrap))) }
643 for (name, value) in vars { 747 for (key, names) in [(":colname-names", tables.colnames), (":rowname-names", tables.rownames)] where !names.isEmpty {
748 entries.append((key, "(" + names.map { "(\($0.name) " + lispLiteral(.list($0.names)).dropFirst() }.joined(separator: " ") + ")"))
749 }
750 for (name, value) in tables.vars {
644 switch value { 751 switch value {
645 case .scalar, .number: entries.append((":var", "(\(name) . \(lispLiteral(value)))")) 752 case .scalar, .number: entries.append((":var", "(\(name) . \(lispLiteral(value)))"))
646 case .list, .table: entries.append((":var", "(\(name) " + lispLiteral(value).dropFirst())) 753 case .list, .table: entries.append((":var", "(\(name) " + lispLiteral(value).dropFirst()))
@@ -650,8 +757,8 @@ public enum Babel {
650 return sorted.joined(separator: ":") + "-" + expanded 757 return sorted.joined(separator: ":") + "-" + expanded
651 } 758 }
652 759
653 /// `org-babel-read-result` for fixed-width lines, a table without rules or a plain list after 760 /// `org-babel-read-result` for fixed-width lines, a table or a plain list after the
654 /// the `#+RESULTS` line at `line`; nil for anything else. 761 /// `#+RESULTS` line at `line`; nil for anything else.
655 static func readResult(after line: Int, text ns: NSString) -> VarValue? { 762 static func readResult(after line: Int, text ns: NSString) -> VarValue? {
656 var location = NSMaxRange(ns.lineRange(for: NSRange(location: line, length: 0))) 763 var location = NSMaxRange(ns.lineRange(for: NSRange(location: line, length: 0)))
657 var lines: [String] = [] 764 var lines: [String] = []
@@ -673,10 +780,13 @@ public enum Babel {
673 return number(value).map(VarValue.number) ?? .scalar(value) 780 return number(value).map(VarValue.number) ?? .scalar(value)
674 } 781 }
675 if first.hasPrefix("|") { 782 if first.hasPrefix("|") {
676 var rows: [[Cell]] = [] 783 var rows: [[Cell]?] = []
677 for l in lines { 784 for l in lines {
678 guard l.hasPrefix("|") else { break } 785 guard l.hasPrefix("|") else { break }
679 if l.hasPrefix("|-") { return nil } 786 if l.hasPrefix("|-") {
787 rows.append(nil)
788 continue
789 }
680 let cells = l.dropFirst().split(separator: "|", omittingEmptySubsequences: false).map { $0.trimmingCharacters(in: .whitespaces) } 790 let cells = l.dropFirst().split(separator: "|", omittingEmptySubsequences: false).map { $0.trimmingCharacters(in: .whitespaces) }
681 rows.append((l.hasSuffix("|") ? Array(cells.dropLast()) : cells).map(cell)) 791 rows.append((l.hasSuffix("|") ? Array(cells.dropLast()) : cells).map(cell))
682 } 792 }
@@ -877,14 +987,15 @@ public enum Babel {
877 switch value(finish, output) { 987 switch value(finish, output) {
878 case .none: return .scalar("") 988 case .none: return .scalar("")
879 case .string(let s): return number(s).map(VarValue.number) ?? .scalar(s) 989 case .string(let s): return number(s).map(VarValue.number) ?? .scalar(s)
880 case .table(let rows): return .table(rows.compactMap { $0?.map(cell) }) 990 case .table(let rows): return .table(rows.map { $0?.map(cell) })
881 } 991 }
882 } 992 }
883 993
884 enum VarValue: Equatable, Sendable { 994 enum VarValue: Equatable, Sendable {
885 case scalar(String) 995 case scalar(String)
886 case number(String) 996 case number(String)
887 case table([[Cell]]) 997 /// Rows; nil is a rule (`hline`).
998 case table([[Cell]?])
888 case list([Cell]) 999 case list([Cell])
889 } 1000 }
890 1001
@@ -949,6 +1060,7 @@ public enum Babel {
949 static func resolve(_ value: String, model: DocumentModel, text: NSString, refs: References) throws -> VarValue { 1060 static func resolve(_ value: String, model: DocumentModel, text: NSString, refs: References) throws -> VarValue {
950 if value.hasPrefix("\""), value.hasSuffix("\""), value.count >= 2 { return .scalar(Params.unescape(String(value.dropFirst().dropLast()))) } 1061 if value.hasPrefix("\""), value.hasSuffix("\""), value.count >= 2 { return .scalar(Params.unescape(String(value.dropFirst().dropLast()))) }
951 if let n = number(value) { return .number(n) } 1062 if let n = number(value) { return .number(n) }
1063 if let first = value.first, "('`[".contains(first) { return try lispValue(value) }
952 if let known = refs.resolved[value] { return known } 1064 if let known = refs.resolved[value] { return known }
953 if let (block, arguments) = referencedBlock(value, model: model), let name = block.name { 1065 if let (block, arguments) = referencedBlock(value, model: model), let name = block.name {
954 guard !refs.visiting.contains(name) else { throw Failure.message("\(name) refers to itself through :var") } 1066 guard !refs.visiting.contains(name) else { throw Failure.message("\(name) refers to itself through :var") }
@@ -959,9 +1071,9 @@ public enum Babel {
959 // A cached block gives its result without running, as `org-babel-execute-src-block` does. 1071 // A cached block gives its result without running, as `org-babel-execute-src-block` does.
960 var useCache = true 1072 var useCache = true
961 if params.single[":cache"] == "yes", let language = block.language { 1073 if params.single[":cache"] == "yes", let language = block.language {
962 let vars = try params.vars.map { ($0.name, try resolve($0.value, model: model, text: text, refs: inner)) } 1074 let tables = try disassemble(params.vars.map { ($0.name, try resolve($0.value, model: model, text: text, refs: inner)) }, params: params)
963 let body = nowebEvaluates(params.single[":noweb"]) ? try expandNoweb(block, params: params, model: model, text: text) : normalizedBody(block) 1075 let body = nowebEvaluates(params.single[":noweb"]) ? try expandNoweb(block, params: params, model: model, text: text) : normalizedBody(block)
964 let state = try cacheState(language: language, params: params, vars: vars, body: body, location: block.range, name: name, call: false, text: text) 1076 let state = try cacheState(language: language, params: params, tables: tables, body: body, location: block.range, name: name, call: false, text: text)
965 if state.current { 1077 if state.current {
966 if let value = state.value { return value } 1078 if let value = state.value { return value }
967 useCache = false 1079 useCache = false
@@ -982,14 +1094,18 @@ public enum Babel {
982 } 1094 }
983 let start = NSMaxRange(text.lineRange(for: NSRange(r, in: text as String))) 1095 let start = NSMaxRange(text.lineRange(for: NSRange(r, in: text as String)))
984 var location = start 1096 var location = start
985 var rows: [[Cell]] = [] 1097 var rows: [[Cell]?] = []
986 var items: [Cell] = [] 1098 var items: [Cell] = []
987 while location < text.length { 1099 while location < text.length {
988 let lineRange = text.lineRange(for: NSRange(location: location, length: 0)) 1100 let lineRange = text.lineRange(for: NSRange(location: location, length: 0))
989 let line = text.substring(with: lineRange).trimmingCharacters(in: .newlines) 1101 let line = text.substring(with: lineRange).trimmingCharacters(in: .newlines)
990 let trimmed = line.trimmingCharacters(in: .whitespaces) 1102 let trimmed = line.trimmingCharacters(in: .whitespaces)
991 if trimmed.hasPrefix("|") { 1103 if trimmed.hasPrefix("|") {
992 if trimmed.hasPrefix("|-") { throw Failure.message("Tables with rules in :var aren't supported yet.") } 1104 if trimmed.hasPrefix("|-") {
1105 rows.append(nil)
1106 location = NSMaxRange(lineRange)
1107 continue
1108 }
993 let cells = trimmed.dropFirst().split(separator: "|", omittingEmptySubsequences: false).map { $0.trimmingCharacters(in: .whitespaces) } 1109 let cells = trimmed.dropFirst().split(separator: "|", omittingEmptySubsequences: false).map { $0.trimmingCharacters(in: .whitespaces) }
994 rows.append((trimmed.hasSuffix("|") ? Array(cells.dropLast()) : cells).map(cell)) 1110 rows.append((trimmed.hasSuffix("|") ? Array(cells.dropLast()) : cells).map(cell))
995 } else if let m = trimmed.firstMatch(of: /^[-+*] (.*)$/), rows.isEmpty { 1111 } else if let m = trimmed.firstMatch(of: /^[-+*] (.*)$/), rows.isEmpty {
@@ -1021,7 +1137,7 @@ public enum Babel {
1021 switch value { 1137 switch value {
1022 case .scalar(let s), .number(let s): return s 1138 case .scalar(let s), .number(let s): return s
1023 case .list(let items): return items.map(cell).joined(separator: "\n") 1139 case .list(let items): return items.map(cell).joined(separator: "\n")
1024 case .table(let rows): return rows.map { $0.map(cell).joined(separator: "\t") }.joined(separator: "\n") 1140 case .table(let rows): return rows.compactMap { $0?.map(cell).joined(separator: "\t") }.joined(separator: "\n")
1025 } 1141 }
1026 } 1142 }
1027 1143
@@ -1037,7 +1153,7 @@ public enum Babel {
1037 case .scalar(let s): return lispString(s, triple: s.contains("\n") || s.contains("\r")) 1153 case .scalar(let s): return lispString(s, triple: s.contains("\n") || s.contains("\r"))
1038 case .number(let n): return n 1154 case .number(let n): return n
1039 case .list(let items): return "[" + items.map(cell).joined(separator: ", ") + "]" 1155 case .list(let items): return "[" + items.map(cell).joined(separator: ", ") + "]"
1040 case .table(let rows): return "[" + rows.map { "[" + $0.map(cell).joined(separator: ", ") + "]" }.joined(separator: ", ") + "]" 1156 case .table(let rows): return "[" + rows.map { $0.map { "[" + $0.map(cell).joined(separator: ", ") + "]" } ?? "None" }.joined(separator: ", ") + "]"
1041 } 1157 }
1042 } 1158 }
1043 1159
@@ -1058,7 +1174,7 @@ public enum Babel {
1058 case .scalar(let s): return lispString(s) 1174 case .scalar(let s): return lispString(s)
1059 case .number(let n): return n 1175 case .number(let n): return n
1060 case .list(let items): return "(" + items.map(cell).joined(separator: " ") + ")" 1176 case .list(let items): return "(" + items.map(cell).joined(separator: " ") + ")"
1061 case .table(let rows): return "(" + rows.map { "(" + $0.map(cell).joined(separator: " ") + ")" }.joined(separator: " ") + ")" 1177 case .table(let rows): return "(" + rows.map { $0.map { "(" + $0.map(cell).joined(separator: " ") + ")" } ?? "hline" }.joined(separator: " ") + ")"
1062 } 1178 }
1063 } 1179 }
1064 1180
@@ -1229,6 +1345,10 @@ public enum Babel {
1229 1345
1230 /// The language's result handling: `org-babel-result-cond` and the table import. 1346 /// The language's result handling: `org-babel-result-cond` and the table import.
1231 static func value(_ finish: BabelJob.Finish, _ output: BabelOutput) -> Value { 1347 static func value(_ finish: BabelJob.Finish, _ output: BabelOutput) -> Value {
1348 reassemble(rawValue(finish, output), finish)
1349 }
1350
1351 static func rawValue(_ finish: BabelJob.Finish, _ output: BabelOutput) -> Value {
1232 let params = finish.params 1352 let params = finish.params
1233 if params.contains("file") { 1353 if params.contains("file") {
1234 // `org-babel-result-to-file`: a link to `:file`, or to the result as a path. 1354 // `org-babel-result-to-file`: a link to `:file`, or to the result as a path.
Tests/OrgCoreTests/BabelCacheTests.swift +2
@@ -27,6 +27,8 @@ struct BabelCacheTests {
27 Case(text: "#+begin_src emacs-lisp :cache yes :lexical yes :results output\n(princ 7)\n#+end_src\n", runs: ["#+begin_src", "#+begin_src"]), 27 Case(text: "#+begin_src emacs-lisp :cache yes :lexical yes :results output\n(princ 7)\n#+end_src\n", runs: ["#+begin_src", "#+begin_src"]),
28 Case(text: "#+NAME: a\n#+begin_src sh\necho hi\n#+end_src\n\n#+CALL: a() :cache yes\n", runs: ["#+CALL", "#+CALL"]), 28 Case(text: "#+NAME: a\n#+begin_src sh\necho hi\n#+end_src\n\n#+CALL: a() :cache yes\n", runs: ["#+CALL", "#+CALL"]),
29 Case(text: "#+PROPERTY: header-args :cache yes\n#+begin_src sh :session none\necho prop\n#+end_src\n", runs: ["#+begin_src", "#+begin_src"]), 29 Case(text: "#+PROPERTY: header-args :cache yes\n#+begin_src sh :session none\necho prop\n#+end_src\n", runs: ["#+begin_src", "#+begin_src"]),
30 Case(text: "#+NAME: tbl\n| a | b |\n|---+---|\n| 1 | x |\n\n#+begin_src python :cache yes :var t=tbl :colnames yes :rownames yes\nreturn t\n#+end_src\n",
31 runs: ["#+begin_src", "#+begin_src"]),
30 // Edited after the first run, so it runs again. 32 // Edited after the first run, so it runs again.
31 Case(text: "#+begin_src sh :cache yes\necho one\n#+end_src\n", runs: ["#+begin_src", "#+begin_src"], edits: [("echo one", "echo two")]), 33 Case(text: "#+begin_src sh :cache yes\necho one\n#+end_src\n", runs: ["#+begin_src", "#+begin_src"], edits: [("echo one", "echo two")]),
32 // A reference to a cached block takes its result without running it. 34 // A reference to a cached block takes its result without running it.
Tests/OrgCoreTests/BabelTests.swift +25 −1
@@ -84,7 +84,12 @@ enum TestBabelRunner {
84} 84}
85 85
86struct BabelTests { 86struct BabelTests {
87 static let cases: [String] = [ 87 static let cases: [String] = rawCases.map {
88 $0.replacingOccurrences(of: "TBL#+begin_src", with: "#+NAME: tbl\n| a | b |\n|---+---|\n| 1 | x |\n| 2 | y |\n\n#+begin_src")
89 .replacingOccurrences(of: "MULTI#+begin_src", with: "#+NAME: multi\n|---+---|\n| a | b |\n|---+---|\n| 1 | x |\n|---+---|\n| 2 | y |\n\n#+begin_src")
90 }
91
92 static let rawCases: [String] = [
88 "#+begin_src sh\necho hello\n#+end_src\n", 93 "#+begin_src sh\necho hello\n#+end_src\n",
89 "#+begin_src sh\necho hello\n#+end_src\nNext paragraph.\n", 94 "#+begin_src sh\necho hello\n#+end_src\nNext paragraph.\n",
90 "#+begin_src sh\necho hello\n#+end_src\n\nNext paragraph.\n", 95 "#+begin_src sh\necho hello\n#+end_src\n\nNext paragraph.\n",
@@ -157,6 +162,25 @@ struct BabelTests {
157 "#+begin_src emacs-lisp :var s=greet\n(concat s \"!\")\n#+end_src\n\n#+NAME: greet\n#+begin_src emacs-lisp\n\"hi\"\n#+end_src\n", 162 "#+begin_src emacs-lisp :var s=greet\n(concat s \"!\")\n#+end_src\n\n#+NAME: greet\n#+begin_src emacs-lisp\n\"hi\"\n#+end_src\n",
158 "#+begin_src python :var s=words\nreturn s.upper()\n#+end_src\n\n#+NAME: words\n#+begin_src sh :results output\nprintf 'two words'\n#+end_src\n", 163 "#+begin_src python :var s=words\nreturn s.upper()\n#+end_src\n\n#+NAME: words\n#+begin_src sh :results output\nprintf 'two words'\n#+end_src\n",
159 "#+begin_src emacs-lisp :var rows=pairs\n(length rows)\n#+end_src\n\n#+NAME: pairs\n#+begin_src sh\nprintf 'a 1\\nb 2\\nc 3\\n'\n#+end_src\n", 164 "#+begin_src emacs-lisp :var rows=pairs\n(length rows)\n#+end_src\n\n#+NAME: pairs\n#+begin_src sh\nprintf 'a 1\\nb 2\\nc 3\\n'\n#+end_src\n",
165 // Tables with rules, `:colnames`, `:rownames` and `:hlines`.
166 "TBL#+begin_src python :var t=tbl\nreturn t\n#+end_src\n",
167 "TBL#+begin_src python :var t=tbl :colnames yes\nreturn t\n#+end_src\n",
168 "TBL#+begin_src python :var t=tbl :colnames no\nreturn t\n#+end_src\n",
169 "TBL#+begin_src python :var t=tbl :rownames yes\nreturn t\n#+end_src\n",
170 "TBL#+begin_src python :var t=tbl :rownames yes :colnames yes\nreturn [[c * 2 for c in r] for r in t]\n#+end_src\n",
171 "TBL#+begin_src python :var t=tbl :colnames yes\nreturn [r + [9] for r in t]\n#+end_src\n",
172 "MULTI#+begin_src python :var t=multi :hlines yes\nreturn t\n#+end_src\n",
173 "MULTI#+begin_src python :var t=multi\nreturn t\n#+end_src\n",
174 "MULTI#+begin_src python :var t=multi :colnames yes\nreturn t\n#+end_src\n",
175 "TBL#+begin_src emacs-lisp :var tb=tbl\ntb\n#+end_src\n",
176 "TBL#+begin_src emacs-lisp :var tb=tbl :colnames yes\n(mapcar #'reverse tb)\n#+end_src\n",
177 "MULTI#+begin_src emacs-lisp :var tb=multi :hlines yes\ntb\n#+end_src\n",
178 "MULTI#+begin_src sh :stdin multi :results output\ncat\n#+end_src\n",
179 "#+begin_src python :var t=tb2 :colnames yes\nreturn t\n#+end_src\n\n#+NAME: tb2\n#+begin_src python\nreturn [['h1', 'h2'], None, [1, 2]]\n#+end_src\n",
180 // Lisp values.
181 "#+begin_src python :var l='(1 2) m='((1 2) (3 4)) n=(+ 1 2) s='(\"a\" \"b\")\nreturn [l, m, n, s]\n#+end_src\n",
182 "#+begin_src emacs-lisp :var l='(1 2) h='((1 2) hline (3 4)) s=(concat \"a\" \"b\")\n(list l h s)\n#+end_src\n",
183 "#+begin_src sh :var n=(* 6 7)\necho $n\n#+end_src\n",
160 ] 184 ]
161 185
162 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil)) 186 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))