| @@ -326,7 +326,7 @@ public enum Babel { |
| 326 | 326 | if useCache, !inline, params.single[":cache"] == "yes" { |
| 327 | 327 | do { |
| 328 | 328 | let state = try cacheState(language: language, params: params, tables: tables, body: body, location: location, name: name, |
| 329 | | call: location != block.range, text: ns) |
| 329 | call: location != block.range, text: ns, options: options) |
| 330 | 330 | if state.current { return .failure(.message(cachedMessage(state.value))) } |
| 331 | 331 | // Appended or prepended results accumulate, so they carry no hash. |
| 332 | 332 | if params.results.contains("replace") { hash = state.hash } |
| @@ -342,16 +342,8 @@ public enum Babel { |
| 342 | 342 | |
| 343 | 343 | if shells.contains(language) { |
| 344 | 344 | let valueIsExitStatus = params.results.contains("value") |
| 345 | | var lines: [String] = [] |
| 346 | | for (name, value) in vars { |
| 347 | | let s: String |
| 348 | | switch value { |
| 349 | | case .scalar(let v), .number(let v): s = v |
| 350 | | default: return .failure(.message("Tables and lists in :var aren't supported for \(language) yet.")) |
| 351 | | } |
| 352 | | lines.append("\(name)='" + s.replacingOccurrences(of: "'", with: "'\"'\"'") + "'") |
| 353 | | } |
| 354 | 345 | let executable = language == "shell" ? options.shell : language |
| 346 | let lines = shellAssignments(vars, shell: executable, params: params) |
| 355 | 347 | let cmdline = params.single[":cmdline"] |
| 356 | 348 | let shebang = params.single[":shebang"].flatMap { $0.contains(where: { !$0.isWhitespace }) ? $0 : nil } |
| 357 | 349 | if session == nil, stdin != nil || cmdline != nil || shebang != nil { |
| @@ -363,7 +355,7 @@ public enum Babel { |
| 363 | 355 | let command = Babel.scriptFileToken + (stdin != nil || cmdline != nil ? " " + (cmdline ?? "") : "") |
| 364 | 356 | var job = BabelJob( |
| 365 | 357 | language: language, runner: .process(executable: executable, arguments: ["-c", command]), |
| 366 | | input: stdin.map(shellString) ?? "", directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk, |
| 358 | input: stdin.map { shellString($0) } ?? "", directory: dir, block: location, blockText: blockText, alwaysAsk: alwaysAsk, |
| 367 | 359 | finish: finishBase(.shell(valueIsExitStatus: valueIsExitStatus)) |
| 368 | 360 | ) |
| 369 | 361 | job.script = script |
| @@ -682,9 +674,11 @@ public enum Babel { |
| 682 | 674 | /// `:cache yes` (`org-babel-execute-src-block`): the block's hash, and whether its results |
| 683 | 675 | /// already carry it, with their value when it can be read. |
| 684 | 676 | static func cacheState(language: String, params: Params, tables: Tables, body: String, location: Range<Int>, name: String?, |
| 685 | | call: Bool, text: NSString) throws -> (hash: String, current: Bool, value: VarValue?) { |
| 677 | call: Bool, text: NSString, options: Options) throws -> (hash: String, current: Bool, value: VarValue?) { |
| 678 | // The hash is taken outside the shell's own binding of `shell-file-name`, so its |
| 679 | // variables are written for the user's shell whatever the block's language. |
| 686 | 680 | let input = try cacheInput(language: language, params: params, tables: tables, |
| 687 | | expanded: expandedBody(language: language, vars: tables.vars, body: body), call: call) |
| 681 | expanded: expandedBody(language: language, vars: tables.vars, body: body, shell: options.shell, params: params), call: call) |
| 688 | 682 | let hash = Insecure.SHA1.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined() |
| 689 | 683 | guard let line = existingResults(name: name, block: location, text: text), resultsHash(at: line, text: text) == hash else { |
| 690 | 684 | return (hash, false, nil) |
| @@ -697,10 +691,9 @@ public enum Babel { |
| 697 | 691 | } |
| 698 | 692 | |
| 699 | 693 | /// The body with its variables, as the language's `org-babel-expand-body:` writes it. |
| 700 | | static func expandedBody(language: String, vars: [(String, VarValue)], body: String) -> String { |
| 694 | static func expandedBody(language: String, vars: [(String, VarValue)], body: String, shell: String, params: Params) -> String { |
| 701 | 695 | if shells.contains(language) { |
| 702 | | let lines = vars.map { "\($0.0)='" + shellString($0.1).replacingOccurrences(of: "'", with: "'\"'\"'") + "'" } |
| 703 | | return (lines + [body]).joined(separator: "\n") |
| 696 | return (shellAssignments(vars, shell: shell, params: params) + [body]).joined(separator: "\n") |
| 704 | 697 | } |
| 705 | 698 | if language == "python" { |
| 706 | 699 | return (vars.map { "\($0.0)=\(pythonLiteral($0.1))" } + [body]).joined(separator: "\n") |
| @@ -1073,7 +1066,7 @@ public enum Babel { |
| 1073 | 1066 | if params.single[":cache"] == "yes", let language = block.language { |
| 1074 | 1067 | let tables = try disassemble(params.vars.map { ($0.name, try resolve($0.value, model: model, text: text, refs: inner)) }, params: params) |
| 1075 | 1068 | let body = nowebEvaluates(params.single[":noweb"]) ? try expandNoweb(block, params: params, model: model, text: text) : normalizedBody(block) |
| 1076 | | let state = try cacheState(language: language, params: params, tables: tables, body: body, location: block.range, name: name, call: false, text: text) |
| 1069 | let state = try cacheState(language: language, params: params, tables: tables, body: body, location: block.range, name: name, call: false, text: text, options: refs.options) |
| 1077 | 1070 | if state.current { |
| 1078 | 1071 | if let value = state.value { return value } |
| 1079 | 1072 | useCache = false |
| @@ -1127,17 +1120,41 @@ public enum Babel { |
| 1127 | 1120 | |
| 1128 | 1121 | /// `org-babel-sh-var-to-string`: a table's rows as tab-separated lines, a list's items as |
| 1129 | 1122 | /// lines. |
| 1130 | | static func shellString(_ value: VarValue) -> String { |
| 1131 | | func cell(_ c: Cell) -> String { |
| 1132 | | switch c { |
| 1133 | | case .number(let n): n |
| 1134 | | case .text(let s): s |
| 1135 | | } |
| 1136 | | } |
| 1123 | static func shellString(_ value: VarValue, separator: String? = nil, hline: String? = nil) -> String { |
| 1137 | 1124 | switch value { |
| 1138 | 1125 | case .scalar(let s), .number(let s): return s |
| 1139 | | case .list(let items): return items.map(cell).joined(separator: "\n") |
| 1140 | | case .table(let rows): return rows.compactMap { $0?.map(cell).joined(separator: "\t") }.joined(separator: "\n") |
| 1126 | case .list(let items): return items.map(cellText).joined(separator: "\n") |
| 1127 | case .table(let rows): return rows.compactMap { $0?.map(cellText).joined(separator: separator ?? "\t") ?? hline }.joined(separator: "\n") |
| 1128 | } |
| 1129 | } |
| 1130 | |
| 1131 | /// `org-babel-variable-assignments:shell`, by the shell's name: for bash, an array for a |
| 1132 | /// list and an associative array for a table of two or more columns; `set` for fish; a |
| 1133 | /// quoted string otherwise. |
| 1134 | static func shellAssignments(_ vars: [(String, VarValue)], shell: String, params: Params) -> [String] { |
| 1135 | let separator = params.single[":separator"] |
| 1136 | let hline = params.single[":hlines"] == "yes" ? params.single[":hline-string"] ?? "hline" : nil |
| 1137 | func quoted(_ value: VarValue) -> String { |
| 1138 | "'" + shellString(value, separator: separator, hline: hline).replacingOccurrences(of: "'", with: "'\"'\"'") + "'" |
| 1139 | } |
| 1140 | return vars.map { name, value in |
| 1141 | if shell.hasSuffix("bash") { |
| 1142 | switch value { |
| 1143 | case .table(let rows) where (rows.first ?? nil).map({ $0.count >= 2 }) ?? false: |
| 1144 | let entries = rows.compactMap { $0 }.map { row in |
| 1145 | "\(name)[\(quoted(.scalar(cellText(row[0]))))]=\(quoted(.list(Array(row.dropFirst()))))" |
| 1146 | } |
| 1147 | return (["unset \(name)", "declare -A \(name)"] + entries).joined(separator: "\n") |
| 1148 | case .table(let rows): |
| 1149 | let items = rows.map { $0.map { quoted(.list($0)) } ?? quoted(.scalar("hline")) } |
| 1150 | return "unset \(name)\ndeclare -a \(name)=( \(items.joined(separator: " ")) )" |
| 1151 | case .list(let items): |
| 1152 | return "unset \(name)\ndeclare -a \(name)=( \(items.map { quoted(.scalar(cellText($0))) }.joined(separator: " ")) )" |
| 1153 | case .scalar, .number: |
| 1154 | break |
| 1155 | } |
| 1156 | } |
| 1157 | return (shell.hasSuffix("fish") ? "set \(name) " : "\(name)=") + quoted(value) |
| 1141 | 1158 | } |
| 1142 | 1159 | } |
| 1143 | 1160 | |