aboutsummaryrefslogtreecommitdiff
path: root/client/system/cli.gd
blob: 7a312127fc89c468f094ee1c17727d252212dfb7 (plain)
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
# 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, "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