スクリプティングとCLIの例¶
Onionはコマンドラインスクリプトや小さな自動化タスクにも適しています。このページでは、引数解析、プロセス実行、ファイル入出力の実用的なパターンを紹介します。
コマンドライン引数の解析¶
Args モジュールを使ってフラグ、オプション、位置引数を解析します。
val parsed = Args::parse(args)
val name: String = parsed.option("name", "World")
val count: Int = parsed.intOption("count", 1)
val verbose: Boolean = parsed.flag("verbose")
val rest: List[String] = parsed.positional()
if verbose {
println("name=" + name + " count=" + count)
}
for var i: Int = 0; i < count; i = i + 1 {
println("Hello, " + name + "!")
}
実行:
ファイルの読み書き¶
file"..." リテラル、または動的パスの場合は file(...) 関数を使います。
val content: String = file("input.txt").text()
println("Read " + content.length() + " characters")
file("output.txt").write("Hello from Onion\n")
CSVファイルもサポートされています。
val rows: List[Map[String, String]] = file("data.csv").csvRows()
foreach row: Object in rows {
val m = row as Map
println("name=" + m.get("name") + " age=" + m.get("age"))
}
シェルコマンドの実行¶
Proc モジュールを使うと、外部プログラムを実行して出力を取得するのが簡単です。
val result = Proc::capture("git", "status")
if result.succeeded() {
println(result.stdout())
} else {
println("failed: " + result.stderr())
}
シェル経由でパイプラインを実行することもできます。
スクリプトをすっきりさせる static インポート¶
特定の static メソッドだけをインポートして、クラス名を繰り返さずに済ませられます:
クラス全体の static メンバーをインポートすることもできます:
完全な例: CLI + 設定ファイル¶
ConfigApp.on は引数解析とYAML設定ファイルを組み合わせます。
record ServerConfig(host: String, port: Int, debug: Boolean) derive!(Yaml)
def defaultConfig(): ServerConfig {
return new ServerConfig("localhost", 8080, false)
}
val parsed = Args::parse(args)
val configPath: String = parsed.option("config", "")
val portOverride: Int = parsed.intOption("port", -1)
val debugFlag: Boolean = parsed.flag("debug")
val base: ServerConfig =
if configPath.length() > 0 {
val loaded = ServerConfig::fromYaml(file(configPath).text())
if loaded != null { loaded } else { defaultConfig() }
} else {
defaultConfig()
}
val port = if portOverride >= 0 { portOverride } else { base.port() }
val debug = if debugFlag { true } else { base.debug() }
println("host=" + base.host())
println("port=" + port)
println("debug=" + debug)
実行:
プロセスパイプラインの例¶
ShellPipeline.on は wc、sort、head をパイプラインとして実行します。
val inputPath = "words.txt"
val countResult = Proc::capture("wc", "-l", inputPath)
println("wc exit=" + countResult.status() + " out=" + countResult.stdout().trim())
val pipelineResult = Proc::capture("sh", "-c", "sort " + inputPath + " | head -n 3")
println(pipelineResult.stdout())
拡張メソッドを使った単位変換¶
UnitConverter.on は Double に単位変換メソッドを追加する拡張メソッドを使います。
extension Double {
def celsiusToFahrenheit(): Double {
return self * 9.0 / 5.0 + 32.0
}
def kilometersToMiles(): Double {
return self * 0.621371
}
def rounded(decimals: Int): Double {
val factor = Math::pow(10.0, decimals as Double)
return (Math::round(self * factor) as Double) / factor
}
}
val celsius = 25.0
println(celsius + "C = " + celsius.celsiusToFahrenheit().rounded(2) + "F")
次のステップ¶
- JSONとHTTPの例 - ネットワークとデータ形式のスクリプト
- エラーハンドリングの例 - 入力の検証と失敗の処理
- ツール: スクリプトランナー - スクリプトを直接実行
ログを読んで、読めなかった行も報告する¶
parseAll は壊れた行を痕跡なく捨てます。shape は行ごとに Outcome を返すので、
読めた行と読めなかった行の両方が、行番号つきで手に入ります。
LogLines.on
record Access(ip: String, method: String, path: String, status: Int)
shape common = re"(\S+) (\w+) (\S+) (\d+)"
def main(): void {
val log = "10.0.0.1 GET /a 200\nbroken line\n10.0.0.2 GET /b 404"
val each = Access::common().eachLine(log, Origin::atLine("access.log", 1))
val rows = Outcome::values(each)
val bad = Outcome::defects(each)
println(rows.size + " read, " + bad.size + " not read")
foreach d: Defect in bad {
println(" line " + d.origin().line() + ": " + d.expected())
}
val first = rows[0] as Access
println(Access::common().print(first))
}
設定ファイルを壊さずに編集する¶
ConfigEdit.on
record Server(host: String, port: Int, debug: Boolean)
shape cfg = config
tool setport(path: String, port: Int): Int
requires { read(path), write(path), console }
{
val read = file(path).readLossless(Server::cfg())
if read.isBad() { return 1 }
val out = read.get().edit { v => v.copy(port = port) }.render()
Files::writeText(path, out)
IO::println("port -> " + port)
return 0
}
コメント・空行・キー順・スペーシング・未知キーはすべて生き残り、diff は1行だけを
示します。--plan は実行前に読み書きを見せてくれます。