// thriftc: a .thrift file in, MoonBit out.
//
// thriftc [-C dir] file.thrift -o out.g.mbt [--check]
//
// `--check` writes nothing and fails when `out.g.mbt` is not what the input would
// produce, which is how CI keeps a generated file honest.
///|
fn usage(msg : String) -> Unit {
println("thriftc: \{msg}")
println("usage: [-C ] -o [--check]")
@env.exit(1)
}
///|
fn resolve(dir : String, path : String) -> String {
let absolute = path.has_prefix("/") ||
path.has_prefix("\\") ||
(path.length() > 1 && path.get_char(1) is Some(':'))
if dir == "" || absolute {
path
} else {
dir + "/" + path
}
}
///|
/// Read the file and everything it includes, once each. The link step is pure, so the
/// file system is read here instead, before it runs; a file that cannot be read is left
/// out and the link step names it.
async fn collect_sources(root : String, text : String) -> Map[String, String] {
let sources : Map[String, String] = Map([])
sources[root] = text
let pending : Array[String] = [root]
while pending.length() > 0 {
let file = pending.pop().unwrap()
let doc = parse(file, sources[file])
for path in doc.includes {
let resolved = join(dir_of(file), path)
if sources.get(resolved) is None {
match (Some(@fs.read_file(resolved)) catch { _ => None }) {
Some(opened) => {
sources[resolved] = opened.text()
pending.push(resolved)
}
None => ()
}
}
}
}
sources
}
///|
async fn main {
let args = @env.args()
let mut input = ""
let mut output = ""
let mut check = false
let mut dir = ""
let mut i = 1
while i < args.length() {
let arg = args[i]
if arg == "-o" {
if i + 1 >= args.length() {
usage("-o needs a path")
}
output = args[i + 1]
i = i + 2
} else if arg == "-C" {
if i + 1 >= args.length() {
usage("-C needs a directory")
}
dir = args[i + 1]
i = i + 2
} else if arg == "--check" {
check = true
i = i + 1
} else if arg.has_prefix("-") {
usage("there is no option \{arg}")
} else {
if input != "" {
usage("one input file at a time")
}
input = arg
i = i + 1
}
}
if input == "" {
usage("no input file")
}
if output == "" {
usage("-o is required")
}
let source_path = resolve(dir, input)
let output_path = resolve(dir, output)
let source = @fs.read_file(source_path) catch {
_ => {
println("thriftc: cannot read \{source_path}")
@env.exit(1)
}
}
let sources = collect_sources(source_path, source.text()) catch {
e => {
println("thriftc: \{e}")
@env.exit(1)
}
}
let rendered = render_with(source_path, source.text(), path => {
sources.get(path)
}) catch {
e => {
println("thriftc: \{e}")
@env.exit(1)
}
}
if check {
let mut current = ""
if @fs.exists(output_path) {
let file = @fs.read_file(output_path)
current = file.text()
}
let lf : (String) -> String = s => s.replace_all(old="\r\n", new="\n")
if lf(current) != lf(rendered) {
println("thriftc: \{output_path} is out of date with \{source_path}")
@env.exit(1)
}
} else {
@fs.write_file(output_path, rendered) catch {
_ => {
println("thriftc: cannot write \{output_path}")
@env.exit(1)
}
}
println("thriftc: wrote \{output_path}")
}
}