| @@ -113,8 +113,9 @@ public enum Babel { |
| 113 | 113 | |
| 114 | 114 | public struct Options: Sendable, Equatable { |
| 115 | 115 | public var python = "python3" |
| 116 | | /// `shell-file-name`, for blocks in the `shell` language. |
| 117 | | public var shell = "/bin/sh" |
| 116 | /// `shell-file-name`, from `SHELL` as Emacs sets it: runs blocks in the `shell` language |
| 117 | /// and chooses how variables are written in the `:cache` hash. |
| 118 | public var shell = ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/sh" |
| 118 | 119 | public init() {} |
| 119 | 120 | } |
| 120 | 121 | |
| @@ -262,6 +263,8 @@ public enum Babel { |
| 262 | 263 | static func plan(_ block: SrcBlockInfo, params: Params, model: DocumentModel, text ns: NSString, options: Options, |
| 263 | 264 | location: Range<Int>, name: String?, inline: Bool = false, refs: References? = nil, useCache: Bool = true) -> Result<BabelJob, Failure> { |
| 264 | 265 | let refs = refs ?? References(resolved: [:], options: options) |
| 266 | var params = params |
| 267 | params.evaluateLisp() |
| 265 | 268 | guard let language = block.language else { return .failure(.message("This block has no language")) } |
| 266 | 269 | let eval = params.single[":eval"]?.lowercased() |
| 267 | 270 | if eval == "never" || eval == "no" || eval == "never-export" || eval == "no-export" { |
| @@ -298,7 +301,7 @@ public enum Babel { |
| 298 | 301 | let finishBase = { (kind: BabelJob.Finish.Kind) in |
| 299 | 302 | BabelJob.Finish(kind: kind, language: language, params: params.results, output: output, wrap: params.wrap, name: name, inline: inline, |
| 300 | 303 | 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"])) |
| 304 | colnames: tables.picked(tables.colnames, ":colnames", params), rownames: tables.picked(tables.rownames, ":rownames", params)) |
| 302 | 305 | } |
| 303 | 306 | let vars: [(String, VarValue)] |
| 304 | 307 | var stdin: VarValue? |
| @@ -581,14 +584,26 @@ public enum Babel { |
| 581 | 584 | var colnames: [(name: String, names: [Cell])] = [] |
| 582 | 585 | var rownames: [(name: String, names: [Cell])] = [] |
| 583 | 586 | |
| 584 | | /// `org-babel-pick-name`: the last variable's names, unless the header argument is |
| 585 | | /// absent, whose `nil` selects none. |
| 586 | | func picked(_ names: [(name: String, names: [Cell])], _ selector: String?) -> [String]? { |
| 587 | | guard selector != nil, let last = names.last else { return nil } |
| 587 | /// `org-babel-pick-name`: a Lisp list given as the header argument, or the last |
| 588 | /// variable's names, unless the header argument is absent, whose `nil` selects none. |
| 589 | func picked(_ names: [(name: String, names: [Cell])], _ key: String, _ params: Params) -> [String]? { |
| 590 | if let list = params.evaluated[key].flatMap(Babel.lispNames) { return list } |
| 591 | guard params.single[key] != nil, let last = names.last else { return nil } |
| 588 | 592 | return last.names.map(Babel.cellText) |
| 589 | 593 | } |
| 590 | 594 | } |
| 591 | 595 | |
| 596 | /// A Lisp list of strings and numbers, as names. |
| 597 | static func lispNames(_ value: Sexp) -> [String]? { |
| 598 | guard case .list(let items) = value else { return nil } |
| 599 | return try? items.map { item in |
| 600 | if let s = item.string { return s } |
| 601 | let printed = Elisp.printed(item) |
| 602 | guard number(printed) == printed else { throw Failure.message(printed) } |
| 603 | return printed |
| 604 | } |
| 605 | } |
| 606 | |
| 592 | 607 | static func cellText(_ c: Cell) -> String { |
| 593 | 608 | switch c { |
| 594 | 609 | case .number(let n): n |
| @@ -600,8 +615,8 @@ public enum Babel { |
| 600 | 615 | /// by default when only its second row is a rule; its first column with `:rownames`; its |
| 601 | 616 | /// rules unless `:hlines yes`. |
| 602 | 617 | static func disassemble(_ vars: [(String, VarValue)], params: Params) throws -> Tables { |
| 603 | | for key in [":colnames", ":rownames"] where params.lisp.contains(key) { |
| 604 | | throw Failure.message("\(key) with a Lisp value isn't supported yet; nothing was run.") |
| 618 | for key in [":colnames", ":rownames"] where params.lisp.contains(key) && params.evaluated[key].flatMap(lispNames) == nil { |
| 619 | throw Failure.message("\(key) \(params.single[key] ?? "") is Lisp that only Emacs can evaluate; nothing was run.") |
| 605 | 620 | } |
| 606 | 621 | let colnames = params.single[":colnames"] |
| 607 | 622 | let rownames = params.single[":rownames"] |
| @@ -708,8 +723,8 @@ public enum Babel { |
| 708 | 723 | /// What `org-babel-sha1-hash` hashes: the header arguments after `org-babel-process-params`, |
| 709 | 724 | /// sorted by key and printed with `%S`, then the expanded body. |
| 710 | 725 | static func cacheInput(language: String, params: Params, tables: Tables, expanded: String, call: Bool) throws -> String { |
| 711 | | if !params.lisp.isEmpty { |
| 712 | | throw Failure.message(":cache with Lisp header values isn't supported yet; nothing was run.") |
| 726 | if let key = params.lisp.first(where: { params.evaluated[$0] == nil }) { |
| 727 | throw Failure.message(":cache with \(key) \(params.single[key] ?? "") needs Emacs to evaluate the Lisp; nothing was run.") |
| 713 | 728 | } |
| 714 | 729 | let ignored: Set<String> = ["replace", "silent", "none", "discard", "append", "prepend"] |
| 715 | 730 | let drawsFile = language == "dot" || language == "plantuml" |
| @@ -719,7 +734,9 @@ public enum Babel { |
| 719 | 734 | var entries: [(key: String, printed: String)] = [] |
| 720 | 735 | for (key, value) in defaults.merging(params.single, uniquingKeysWith: { $1 }) { |
| 721 | 736 | let printed: String |
| 722 | | if value.isEmpty { |
| 737 | if let evaluated = params.evaluated[key] { |
| 738 | printed = Elisp.printed(evaluated, escape: true) |
| 739 | } else if value.isEmpty { |
| 723 | 740 | continue |
| 724 | 741 | } else if key == ":exports" { |
| 725 | 742 | printed = lispString(value.split(whereSeparator: \.isWhitespace).map(String.init).filter { !ignored.contains($0) }.sorted().joined(separator: " ")) |
| @@ -803,10 +820,32 @@ public enum Babel { |
| 803 | 820 | var single: [String: String] = [:] |
| 804 | 821 | var vars: [(name: String, value: String)] = [] |
| 805 | 822 | var wrap: String? |
| 806 | | /// Keys whose value is Lisp to evaluate (`org-babel-read`): unquoted, starting with `(`. |
| 823 | /// Keys whose value is Lisp to evaluate (`org-babel-read`): unquoted, starting with `(`, `'`, `` ` `` or `[`. |
| 807 | 824 | var lisp: Set<String> = [] |
| 808 | 825 | /// Keys whose value was a quoted string, which `org-babel-read` doesn't take for a number. |
| 809 | 826 | var quoted: Set<String> = [] |
| 827 | /// Lisp values evaluated to something other than a string or number, such as a list of |
| 828 | /// column names. |
| 829 | var evaluated: [String: Sexp] = [:] |
| 830 | |
| 831 | /// `org-babel-read` on the Lisp values the interpreter can evaluate: a string or number |
| 832 | /// takes the form's place; other values are kept in `evaluated`. |
| 833 | mutating func evaluateLisp() { |
| 834 | for key in lisp { |
| 835 | guard let form = try? LispReader.readFirst(single[key] ?? "").sexp, let value = try? Elisp().eval(form) else { continue } |
| 836 | let printed = Elisp.printed(value) |
| 837 | if let s = value.string { |
| 838 | single[key] = s |
| 839 | quoted.insert(key) |
| 840 | lisp.remove(key) |
| 841 | } else if Babel.number(printed) == printed { |
| 842 | single[key] = printed |
| 843 | lisp.remove(key) |
| 844 | } else { |
| 845 | evaluated[key] = value |
| 846 | } |
| 847 | } |
| 848 | } |
| 810 | 849 | |
| 811 | 850 | static let resultGroups: [Set<String>] = [ |
| 812 | 851 | ["file", "list", "vector", "table", "scalar", "verbatim"], |
| @@ -859,7 +898,7 @@ public enum Babel { |
| 859 | 898 | let quoted = key != ":var" && raw.count >= 2 && raw.hasPrefix("\"") && raw.hasSuffix("\"") |
| 860 | 899 | let value = quoted ? Params.unescape(String(raw.dropFirst().dropLast())) : raw |
| 861 | 900 | if key != ":var" { |
| 862 | | if !quoted, value.hasPrefix("(") { lisp.insert(key) } else { lisp.remove(key) } |
| 901 | if !quoted, let first = value.first, "('`[".contains(first) { lisp.insert(key) } else { lisp.remove(key) } |
| 863 | 902 | if quoted { self.quoted.insert(key) } else { self.quoted.remove(key) } |
| 864 | 903 | } |
| 865 | 904 | switch key { |
| @@ -1060,7 +1099,8 @@ public enum Babel { |
| 1060 | 1099 | let extra = arguments.map { [splitArguments($0).map { (":var", $0) }] } ?? [] |
| 1061 | 1100 | var inner = refs |
| 1062 | 1101 | inner.visiting.insert(name) |
| 1063 | | let params = try Params(block: block, model: model, text: text, extra: extra) |
| 1102 | var params = try Params(block: block, model: model, text: text, extra: extra) |
| 1103 | params.evaluateLisp() |
| 1064 | 1104 | // A cached block gives its result without running, as `org-babel-execute-src-block` does. |
| 1065 | 1105 | var useCache = true |
| 1066 | 1106 | if params.single[":cache"] == "yes", let language = block.language { |