| @@ -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. |