Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ let package = Package(
name: "BashEvalRunner",
dependencies: [
"Bash",
"BashGit",
"BashPython",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Yams", package: "Yams"),
]
Expand Down
1 change: 1 addition & 0 deletions Sources/Bash/Commands/DefaultCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extension BashSession {
RgCommand.self,
HeadCommand.self,
TailCommand.self,
NlCommand.self,
WcCommand.self,
SortCommand.self,
UniqCommand.self,
Expand Down
99 changes: 99 additions & 0 deletions Sources/Bash/Commands/Text/LineCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,70 @@ struct WcCommand: BuiltinCommand {
}
}

struct NlCommand: BuiltinCommand {
struct Options: ParsableArguments {
@Option(name: .short, help: "Select numbering style")
var b: String = "t"

@Argument(help: "Optional files")
var files: [String] = []
}

static let name = "nl"
static let overview = "Number lines of files"

static func _toAnyBuiltinCommand() -> AnyBuiltinCommand {
makeNormalizedLineCommand(Self.self) { args in
normalizeNlArguments(args)
}
}

static func run(context: inout CommandContext, options: Options) async -> Int32 {
guard let mode = NumberingMode(rawValue: options.b) else {
context.writeStderr("nl: unsupported -b style '\(options.b)'\n")
return 1
}

let inputs = await CommandFS.readInputs(paths: options.files, context: &context)
for content in inputs.contents {
writeNumbered(content: content, mode: mode, context: &context)
}
return inputs.hadError ? 1 : 0
}

private enum NumberingMode: String {
case all = "a"
case nonEmpty = "t"

func includes(_ line: String) -> Bool {
switch self {
case .all:
return true
case .nonEmpty:
return !line.isEmpty
}
}
}

private static func writeNumbered(
content: String,
mode: NumberingMode,
context: inout CommandContext
) {
let lines = CommandIO.splitLines(content)
var lineNumber = 1

for line in lines {
if mode.includes(line) {
context.writeStdout(String(format: "%6d\t%@\n", lineNumber, line))
lineNumber += 1
} else {
context.writeStdout(" \t\(line)\n")
}
}
}
}

private func shouldShowHeader(totalFiles: Int, quiet: Bool, verbose: Bool) -> Bool {
if quiet {
return false
Expand Down Expand Up @@ -364,6 +428,41 @@ private func normalizeAttachedValueOption(
return value.isEmpty ? nil : value
}

private func normalizeNlArguments(_ args: [String]) -> [String] {
var normalized: [String] = []
var passthrough = false
var expectsValue = false

for arg in args {
if passthrough {
normalized.append(arg)
continue
}

if arg == "--" {
passthrough = true
normalized.append(arg)
continue
}

if expectsValue {
normalized.append(arg)
expectsValue = false
continue
}

if let value = normalizeAttachedValueOption(arg, option: "b") {
normalized.append(contentsOf: ["-b", value])
continue
}

normalized.append(arg)
expectsValue = arg == "-b" || arg == "--b"
}

return normalized
}

private func normalizeLegacyBareLineCount(
_ arg: String,
command: LegacyLineCountCommand
Expand Down
Loading
Loading