// Command line front end for moon-pdf417-core.
//
// Usage:
// moon run cmd/main --target native -- encode --data "HELLO 1234" --level 2 --columns 4
// moon run cmd/main --target native -- inspect --data "HELLO 1234"
// moon run cmd/main --target native -- capacity --level 2
// moon run cmd/main --target native -- structure --columns 4 --rows 6 --level 2
// moon run cmd/main --target native -- render --data "HELLO 1234" --format svg
//
// `--data` reads the text literally; use `--codes` for a comma separated list
// of code units (0..255) when the payload is not printable ASCII. The runner
// passes the module path as the first argument, so the parser starts at the
// second one and falls back to the first when only one argument is present.
///|
/// Command line parsing error.
suberror CliError {
BadArguments
}
///|
fn usage() -> Unit {
println("moon-pdf417-core command line")
println("")
println(" encode --data [--level 0-8] [--columns 1-30]")
println(" inspect --data [--level 0-8] [--columns 1-30]")
println(" capacity [--level 0-8]")
println(" structure --columns 1-30 --rows 3-90 [--level 0-8]")
println(
" render --data [--format svg|ascii] [--scale n] [--quiet n]",
)
println("")
println(
"The render command writes the document to stdout; redirect it to save a",
)
println("file, for example: render --data X --format svg > symbol.svg")
println("")
println("Options:")
println(" --data UTF-8 text payload")
println(" --codes payload as comma separated code units (0..255)")
println(" --level error correction level, default 2")
println(" --columns data columns, default 4")
println(" --rows rows for the structure command")
println(" --format render output: svg (default) or ascii")
println(" --scale modules per unit, default 4")
println(" --quiet quiet zone in modules, default 2")
}
///|
/// Parsed command line.
struct Options {
command : String
data : String
has_codes : Bool
codes : Array[Int]
level : Int
columns : Int
rows : Int
format : String
scale : Int
quiet : Int
}
///|
/// Parse a non-negative decimal integer.
fn parse_int(text : StringView) -> Int raise CliError {
if text.length() == 0 {
raise BadArguments
}
let mut value = 0
for character in text.iter() {
if character < '0' || character > '9' {
raise BadArguments
}
value = value * 10 + (character.to_int() - 48)
}
value
}
///|
/// Drop the leading module path that the MoonBit runner passes through.
fn arguments_tail(args : Array[String]) -> Array[String] {
if args.length() >= 2 &&
(args[0].has_suffix(".wasm") || args[0].has_suffix(".exe")) {
let tail : Array[String] = []
for index in 1.. Options raise CliError {
if args.length() == 0 {
raise BadArguments
}
let command = args[0]
let mut data = ""
let mut has_codes = false
let mut codes : Array[Int] = []
let mut level = 2
let mut columns = 4
let mut rows = 6
let mut format = "svg"
let mut scale = 4
let mut quiet = 2
let mut index = 1
while index < args.length() {
if index + 1 >= args.length() {
raise BadArguments
}
let flag = args[index]
let value = args[index + 1]
if flag == "--data" {
data = value
} else if flag == "--codes" {
has_codes = true
let parsed : Array[Int] = []
for part in value.split(",") {
parsed.push(parse_int(part))
}
codes = parsed
} else if flag == "--level" {
level = parse_int(value)
} else if flag == "--columns" {
columns = parse_int(value)
} else if flag == "--rows" {
rows = parse_int(value)
} else if flag == "--format" {
format = value
} else if flag == "--scale" {
scale = parse_int(value)
} else if flag == "--quiet" {
quiet = parse_int(value)
} else {
raise BadArguments
}
index += 2
}
{
command,
data,
has_codes,
codes,
level,
columns,
rows,
format,
scale,
quiet,
}
}
///|
/// Payload of an options record: comma separated code units win over text.
fn payload(options : Options) -> Array[Int] {
if options.has_codes {
options.codes
} else {
@moon-pdf417-core.utf8_bytes(options.data)
}
}
///|
/// Render a compaction mode as a single letter.
fn mode_letter(mode : @moon-pdf417-core.Compaction) -> String {
match mode {
Text => "T"
Numeric => "N"
Byte => "B"
Auto => "A"
}
}
///|
/// Render a high level result as one line of modes.
fn mode_summary(high : @moon-pdf417-core.HighLevel) -> String {
let mut summary = ""
for mode in high.modes {
summary = summary + mode_letter(mode)
}
summary
}
///|
fn print_symbol(symbol : @moon-pdf417-core.Symbol) -> Unit {
println("columns: " + symbol.columns.to_string())
println("rows: " + symbol.rows.to_string())
println("error_correction_level: " + symbol.ec_level.to_string())
println("data_codewords: " + symbol.data_count.to_string())
println("pad_codewords: " + symbol.pad_count.to_string())
println("total_codewords: " + symbol.codewords.length().to_string())
println("codewords: " + repr(symbol.codewords))
println(
"parity_verified: " + @moon-pdf417-core.verify_symbol(symbol).to_string(),
)
}
///|
fn run_encode(options : Options) -> Unit {
let data = payload(options)
match @moon-pdf417-core.encode_high_level_opt(data) {
None => println("error: the payload cannot be encoded")
Some(high) => {
println("payload_bytes: " + data.length().to_string())
println("modes: " + mode_summary(high))
println("data_codewords: " + high.codewords.length().to_string())
println("codewords: " + repr(high.codewords))
match
@moon-pdf417-core.finalize_opt(
high.codewords,
options.level,
options.columns,
) {
None => println("error: the codewords do not fit the requested layout")
Some(symbol) => print_symbol(symbol)
}
}
}
}
///|
fn run_inspect(options : Options) -> Unit {
let data = payload(options)
match @moon-pdf417-core.encode_high_level_opt(data) {
None => println("error: the payload cannot be encoded")
Some(high) => {
println("payload_bytes: " + data.length().to_string())
println("segments: " + high.modes.length().to_string())
println("modes: " + mode_summary(high))
let mut index = 0
while index < high.codewords.length() {
println(
" [" +
index.to_string() +
"] " +
high.codewords[index].to_string() +
" (segment " +
high.segments[index].to_string() +
")",
)
index += 1
}
println(
"round_trip: " +
@moon-pdf417-core.round_trip_matches(data, high.codewords).to_string(),
)
}
}
}
///|
fn run_capacity(options : Options) -> Unit {
println("level: " + options.level.to_string())
println(
"parity_codewords: " +
@moon-pdf417-core.error_correction_codeword_count(options.level).to_string(),
)
println("columns:max_data_codewords")
for columns in 1..<31 {
println(
columns.to_string() +
":" +
@moon-pdf417-core.data_capacity(columns, options.level).to_string(),
)
}
}
///|
fn run_structure(options : Options) -> Unit {
let columns = options.columns
let rows = options.rows
println("columns: " + columns.to_string())
println("rows: " + rows.to_string())
println("error_correction_level: " + options.level.to_string())
println(
"width_modules: " + @moon-pdf417-core.symbol_modules(columns).to_string(),
)
println(
"codeword_capacity: " +
@moon-pdf417-core.symbol_codeword_capacity(columns, rows).to_string(),
)
println("row_cluster,left_row_indicator,right_row_indicator")
for row in 0.. symbol.svg` produces a file a browser can open
/// without any cleanup.
fn run_render(options : Options) -> Unit {
let data = payload(options)
let symbol = match @moon-pdf417-core.encode_high_level_opt(data) {
None => {
eprintln("error: the payload cannot be encoded")
return
}
Some(high) =>
match
@moon-pdf417-core.finalize_opt(
high.codewords,
options.level,
options.columns,
) {
None => {
eprintln("error: the codewords do not fit the requested layout")
return
}
Some(value) => value
}
}
let matrix = try @moon-pdf417-core.symbol_matrix(symbol) catch {
error => {
eprintln("error: cannot build the module matrix: " + repr(error))
return
}
} noraise {
value => value
}
let is_ascii = options.format == "ascii" || options.format == "txt"
let body = if is_ascii {
@moon-pdf417-core.matrix_to_ascii(matrix, options.scale, options.quiet)
} else {
@moon-pdf417-core.matrix_to_svg(matrix, options.scale, options.quiet)
}
eprintln("columns: " + symbol.columns.to_string())
eprintln("rows: " + symbol.rows.to_string())
eprintln(
"width_modules: " +
@moon-pdf417-core.symbol_modules(symbol.columns).to_string(),
)
eprintln("codewords: " + symbol.codewords.length().to_string())
eprintln(
"matrix_verified: " +
@moon-pdf417-core.verify_matrix(matrix, symbol.columns).to_string(),
)
eprintln("format: " + (if is_ascii { "ascii" } else { "svg" }))
println(body)
}
///|
fn main {
let args : Array[String] = []
for argument in @env.args() {
args.push(argument)
}
if args.length() == 0 {
usage()
return
}
let options = try parse_arguments(arguments_tail(args)) catch {
_ => {
println("error: invalid arguments")
usage()
return
}
} noraise {
value => value
}
if options.command == "encode" {
run_encode(options)
} else if options.command == "inspect" {
run_inspect(options)
} else if options.command == "capacity" {
run_capacity(options)
} else if options.command == "structure" {
run_structure(options)
} else if options.command == "render" {
run_render(options)
} else if options.command == "help" {
usage()
} else {
println("error: unknown command " + options.command)
usage()
}
}