| @@ -326,7 +326,7 @@ public enum Babel { |
| 326 | if useCache, !inline, params.single[":cache"] == "yes" { |
326 | if useCache, !inline, params.single[":cache"] == "yes" { |
| 327 | do { |
327 | do { |
| 328 | let state = try cacheState(language: language, params: params, tables: tables, body: body, location: location, name: name, |
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 | if state.current { return .failure(.message(cachedMessage(state.value))) } |
330 | if state.current { return .failure(.message(cachedMessage(state.value))) } |
| 331 | // Appended or prepended results accumulate, so they carry no hash. |
331 | // Appended or prepended results accumulate, so they carry no hash. |
| 332 | if params.results.contains("replace") { hash = state.hash } |
332 | if params.results.contains("replace") { hash = state.hash } |
| @@ -342,16 +342,8 @@ public enum Babel { |
| 342 | |
342 | |
| 343 | if shells.contains(language) { |
343 | if shells.contains(language) { |
| 344 | let valueIsExitStatus = params.results.contains("value") |
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 | let executable = language == "shell" ? options.shell : language |
345 | let executable = language == "shell" ? options.shell : language |
| |
346 | let lines = shellAssignments(vars, shell: executable, params: params) |
| 355 | let cmdline = params.single[":cmdline"] |
347 | let cmdline = params.single[":cmdline"] |
| 356 | let shebang = params.single[":shebang"].flatMap { $0.contains(where: { !$0.isWhitespace }) ? $0 : nil } |
348 | let shebang = params.single[":shebang"].flatMap { $0.contains(where: { !$0.isWhitespace }) ? $0 : nil } |
| 357 | if session == nil, stdin != nil || cmdline != nil || shebang != nil { |
349 | if session == nil, stdin != nil || cmdline != nil || shebang != nil { |
| @@ -363,7 +355,7 @@ public enum Babel { |
| 363 | let command = Babel.scriptFileToken + (stdin != nil || cmdline != nil ? " " + (cmdline ?? "") : "") |
355 | let command = Babel.scriptFileToken + (stdin != nil || cmdline != nil ? " " + (cmdline ?? "") : "") |
| 364 | var job = BabelJob( |
356 | var job = BabelJob( |
| 365 | language: language, runner: .process(executable: executable, arguments: ["-c", command]), |
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 | finish: finishBase(.shell(valueIsExitStatus: valueIsExitStatus)) |
359 | finish: finishBase(.shell(valueIsExitStatus: valueIsExitStatus)) |
| 368 | ) |
360 | ) |
| 369 | job.script = script |
361 | job.script = script |
| @@ -682,9 +674,11 @@ public enum Babel { |
| 682 | /// `:cache yes` (`org-babel-execute-src-block`): the block's hash, and whether its results |
674 | /// `:cache yes` (`org-babel-execute-src-block`): the block's hash, and whether its results |
| 683 | /// already carry it, with their value when it can be read. |
675 | /// already carry it, with their value when it can be read. |
| 684 | static func cacheState(language: String, params: Params, tables: Tables, body: String, location: Range<Int>, name: String?, |
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 | let input = try cacheInput(language: language, params: params, tables: tables, |
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 | let hash = Insecure.SHA1.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined() |
682 | let hash = Insecure.SHA1.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined() |
| 689 | guard let line = existingResults(name: name, block: location, text: text), resultsHash(at: line, text: text) == hash else { |
683 | guard let line = existingResults(name: name, block: location, text: text), resultsHash(at: line, text: text) == hash else { |
| 690 | return (hash, false, nil) |
684 | return (hash, false, nil) |
| @@ -697,10 +691,9 @@ public enum Babel { |
| 697 | } |
691 | } |
| 698 | |
692 | |
| 699 | /// The body with its variables, as the language's `org-babel-expand-body:` writes it. |
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 | if shells.contains(language) { |
695 | if shells.contains(language) { |
| 702 | let lines = vars.map { "\($0.0)='" + shellString($0.1).replacingOccurrences(of: "'", with: "'\"'\"'") + "'" } |
696 | return (shellAssignments(vars, shell: shell, params: params) + [body]).joined(separator: "\n") |
| 703 | return (lines + [body]).joined(separator: "\n") |
| |
| 704 | } |
697 | } |
| 705 | if language == "python" { |
698 | if language == "python" { |
| 706 | return (vars.map { "\($0.0)=\(pythonLiteral($0.1))" } + [body]).joined(separator: "\n") |
699 | return (vars.map { "\($0.0)=\(pythonLiteral($0.1))" } + [body]).joined(separator: "\n") |
| @@ -1073,7 +1066,7 @@ public enum Babel { |
| 1073 | if params.single[":cache"] == "yes", let language = block.language { |
1066 | if params.single[":cache"] == "yes", let language = block.language { |
| 1074 | let tables = try disassemble(params.vars.map { ($0.name, try resolve($0.value, model: model, text: text, refs: inner)) }, params: params) |
1067 | let tables = try disassemble(params.vars.map { ($0.name, try resolve($0.value, model: model, text: text, refs: inner)) }, params: params) |
| 1075 | let body = nowebEvaluates(params.single[":noweb"]) ? try expandNoweb(block, params: params, model: model, text: text) : normalizedBody(block) |
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 | if state.current { |
1070 | if state.current { |
| 1078 | if let value = state.value { return value } |
1071 | if let value = state.value { return value } |
| 1079 | useCache = false |
1072 | useCache = false |
| @@ -1127,17 +1120,41 @@ public enum Babel { |
| 1127 | |
1120 | |
| 1128 | /// `org-babel-sh-var-to-string`: a table's rows as tab-separated lines, a list's items as |
1121 | /// `org-babel-sh-var-to-string`: a table's rows as tab-separated lines, a list's items as |
| 1129 | /// lines. |
1122 | /// lines. |
| 1130 | static func shellString(_ value: VarValue) -> String { |
1123 | static func shellString(_ value: VarValue, separator: String? = nil, hline: String? = nil) -> String { |
| 1131 | func cell(_ c: Cell) -> String { |
| |
| 1132 | switch c { |
| |
| 1133 | case .number(let n): n |
| |
| 1134 | case .text(let s): s |
| |
| 1135 | } |
| |
| 1136 | } |
| |
| 1137 | switch value { |
1124 | switch value { |
| 1138 | case .scalar(let s), .number(let s): return s |
1125 | case .scalar(let s), .number(let s): return s |
| 1139 | case .list(let items): return items.map(cell).joined(separator: "\n") |
1126 | case .list(let items): return items.map(cellText).joined(separator: "\n") |
| 1140 | case .table(let rows): return rows.compactMap { $0?.map(cell).joined(separator: "\t") }.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 | |