1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
# 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 <https://www.gnu.org/licenses/>.
#
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 += " <VALUE>"
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
|