# Hurry Curry! - a game about cooking
# Copyright (C) 2025 Hurry Curry! contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
#
extends Node
class_name Cli
enum Mode { FLAG, OPTION, MULTI_OPTION, POSITIONAL }
class Option:
var short #: String?
var long: String
var mode: Mode
var help: String
func _init(s, l: String, m: Mode, h: String):
short = s; long = l; mode = m; help = h
static var OPTIONS := [
Option.new("h", "help", Mode.FLAG, "Show help"),
Option.new("s", "setting", Mode.MULTI_OPTION, "Per-launch setting override"),
Option.new("c", "join-command", Mode.OPTION, "Message to send right after initial joining"),
Option.new(null, "render-items", Mode.OPTION, "Render items from text file to images"),
Option.new(null, "render-tiles", Mode.OPTION, "Render tiles from text file to images"),
Option.new(null, "render-resolution", Mode.OPTION, "Resolution for rendering items or tiles"),
Option.new(null, "render-output", Mode.OPTION, "Output directory for rendering items or tiles"),
Option.new(null, "connect_address", Mode.POSITIONAL, "Connect to a server directly without menu interaction")
]
static var opts = {} #: Dictionary[String, Variant]
static func init() -> bool:
if not parse(): return false
if opts.has("help"):
print_help()
return false
return true
static func print_help():
print("OPTIONS:\n")
for opt in OPTIONS:
var line = ""
if opt.mode == Mode.POSITIONAL:
line += "<" + opt.long.to_upper() + ">"
else:
if opt.short: line += "-" + opt.short + ", "
line += "--" + opt.long
if opt.mode == Mode.OPTION or opt.mode == Mode.MULTI_OPTION:
line += " "
while line.length() < 25: line += " "
line += " " + opt.help
print(line)
static func parse() -> bool:
var args := OS.get_cmdline_user_args()
while not args.is_empty():
var arg := args[0]
args.remove_at(0)
if arg.begins_with("--"):
var long = arg.trim_prefix("--")
var opt_index = OPTIONS.find_custom(func(x): return x.long == long)
if opt_index == -1:
push_error("unknown long option \"%s\"" % long)
return false
if not _parse_opt(args, OPTIONS[opt_index]): return false
elif arg.begins_with("-"):
for short in arg.trim_prefix("-"):
var opt_index = OPTIONS.find_custom(func(x): return x.short == short)
if opt_index == -1:
push_error("unknown short option \"%s\"" % short)
return false
if not _parse_opt(args, OPTIONS[opt_index]): return false
else:
var opt_index = OPTIONS.find_custom(func(x): return x.mode == Mode.POSITIONAL)
if opt_index == -1:
push_error("no positional arguments")
return false
var opt = OPTIONS[opt_index]
opts[opt.long] = arg
print("Parsed options: ", opts)
return true
static func _parse_opt(args: Array[String], opt: Option) -> bool:
match opt.mode:
Mode.FLAG:
opts[opt.long] = true
return true
Mode.OPTION:
if args.is_empty():
push_error("missing option value")
return false
opts[opt.long] = trim_apostrophes(args[0])
args.remove_at(0)
return true
Mode.MULTI_OPTION:
if args.is_empty():
push_error("missing option value")
return false
if not opts.has(opt.long): opts[opt.long] = []
opts[opt.long].push_back(trim_apostrophes(args[0]))
args.remove_at(0)
return true
Mode.POSITIONAL:
push_error("positional arg doesnt need flag")
return false
push_error("unreachable")
return false
static func trim_apostrophes(s: String) -> String:
# Godot doesn't remove apostrophes when adding custom arguments with
# customize run instances in the editor. We'll have to remove them manually.
if OS.has_feature("editor_runtime"):
return s.trim_prefix("\"").trim_prefix("\'").trim_suffix("\"").trim_suffix("\'")
return s # Not running in editor