package cjbind_cli.arg
import std.collection.{ArrayList, HashMap}
import cjbind.utils.sprintAlign
public abstract class Flag {
public func read(_: Iterator<String>): Unit
public let shortArg: ?String
public let longArg: String
public let name: String
public let description: String
public let optSuffix: ?String
public init(shortArg: ?String, longArg: String, name: String, description: String, optSuffix: ?String) {
this.shortArg = shortArg
this.longArg = longArg
this.name = name
this.description = description
this.optSuffix = optSuffix
}
public func getHelp(): (String, String) {
let opt = if (let Some(v) <- this.shortArg) {
"-${v}, --${this.longArg}"
} else {
" --${this.longArg}"
} + if (let Some(v) <- optSuffix) {
" <${v}>"
} else {
""
}
return (opt, this.description)
}
}
public class StringFlag <: Flag {
public var value: ?String = None
public init(shortArg: ?String, longArg: String, name: String, description: String, optSuffix: ?String, defaultValue: ?String) {
super(shortArg, longArg, name, description, optSuffix)
this.value = defaultValue
}
public func read(it: Iterator<String>): Unit {
match (it.next()) {
case Some(v) => value = v
case None => throw Exception("选项 ${this.name} 需要一个参数")
}
}
}
public class BoolFlag <: Flag {
public var value: Bool = false
public init(shortArg: ?String, longArg: String, name: String, description: String) {
super(shortArg, longArg, name, description, None)
}
public func read(_: Iterator<String>): Unit {
this.value = true
}
}
public class ArgsParser {
private let args: Array<Flag>
private let shortMap: HashMap<String, Flag> = HashMap()
private let longMap: HashMap<String, Flag> = HashMap()
private let nameMap: HashMap<String, Flag> = HashMap()
public init(input: Array<Flag>) {
this.args = input
for (a in input) {
if (let Some(v) <- a.shortArg) {
if (shortMap.contains(v)) {
throw Exception("短选项 ${v} 重复")
}
shortMap.add(v, a)
}
let lv = a.longArg
if (longMap.contains(lv)) {
throw Exception("长选项 ${lv} 重复")
}
longMap.add(lv, a)
nameMap.add(a.name, a)
}
}
public func parse(input: Array<String>): (Array<String>, Array<String>) {
let it = input.iterator()
// remove bin (argv[0]); guard against empty input
if (it.next().isNone()) { return ([], []) }
let headers = ArrayList<String>()
var collectingClangArgs = false
let clangArgs = ArrayList<String>()
for (i in it) {
if (i == "--") {
collectingClangArgs = true
continue
}
if (collectingClangArgs) {
clangArgs.add(i)
continue
}
if (i.startsWith("--")) {
let body = i[2..]
let flag = match (longMap.get(body)) {
case Some(v) => v
case None => throw Exception("未知选项 ${i}")
}
flag.read(it)
} else if (i.startsWith("-")) {
let body = i[1..]
let flag = match (shortMap.get(body)) {
case Some(v) => v
case None => throw Exception("未知选项 ${i}")
}
flag.read(it)
} else {
headers.add(i)
}
}
return (headers.toArray(), clangArgs.toArray())
}
public func getHelp(): String {
let sb = StringBuilder()
let header = """
自动生成仓颉到 C 库的 FFI 绑定代码。
用法:cjbind <OPTIONS> <HEADER> -- <CLANG_ARGS>
参数:
<HEADER> C 头文件路径
[CLANG_ARGS]... 会被直接传递给 clang 的参数
选项:
"""
sb.append(header)
let pres = Array<String>(args.size, repeat: "")
let posts = Array<String>(args.size, repeat: "")
for (i in 0..args.size) {
let arg = args[i]
let (pre, post) = arg.getHelp()
pres[i] = pre
posts[i] = post
}
let options = sprintAlign(pres, posts)
sb.append(options)
return sb.toString()
}
}