oh-my-git/scenes/shell.gd

127 lines
3.9 KiB
GDScript

extends Node
class_name Shell
var exit_code
var _cwd
var _os = OS.get_name()
var web_shell = JavaScriptBridge.get_interface("web_shell")
func _init():
# Create required directories and move into the tmp directory.
_cwd = "/tmp"
await run("mkdir -p '%s/repos'" % game.tmp_prefix)
_cwd = game.tmp_prefix
func cd(dir):
_cwd = dir
# Run a shell command given as a string. Run this if you're interested in the
# output of the command.
func run(command, crash_on_fail=true):
var shell_command = ShellCommand.new()
shell_command.command = command
shell_command.crash_on_fail = crash_on_fail
run_async_thread(shell_command)
await shell_command.done
print("output of (" +command+ "): >>" + shell_command.output + "<<")
exit_code = shell_command.exit_code
return shell_command.output
func run_async_web(command, crash_on_fail=true):
var shell_command = ShellCommand.new()
shell_command.command = command
shell_command.crash_on_fail = crash_on_fail
run_async_thread(shell_command)
return shell_command
func run_async_thread(shell_command):
var debug = false
var command = shell_command.command
var crash_on_fail = shell_command.crash_on_fail
if debug:
print("$ %s" % command)
var env = {}
env["HOME"] = game.tmp_prefix
var hacky_command = ""
for variable in env:
hacky_command += "export %s='%s';" % [variable, env[variable]]
hacky_command += "export PATH=\'"+game.tmp_prefix+":'\"$PATH\";"
hacky_command += "cd '%s' || exit 1;" % _cwd
hacky_command += command
print("running >>" + hacky_command + "<<")
var result
if _os == "Linux" or _os == "OSX":
# Godot's OS.execute wraps each argument in double quotes before executing
# on Linux and macOS.
# Because we want to be in a single-quote context, where nothing is evaluated,
# we end those double quotes and start a single quoted string. For each single
# quote appearing in our string, we close the single quoted string, and add
# a double quoted string containing the single quote. Ooooof!
#
# Example: The string
#
# test 'fu' "bla" blubb
#
# becomes
#
# "'test '"'"'fu'"'"' "bla" blubb"
hacky_command = '"\''+hacky_command.replace("'", "'\"'\"'")+'\'"'
result = helpers.exec(_shell_binary(), ["-c", hacky_command], crash_on_fail)
elif _os == "Windows":
# On Windows, if the command contains a newline (even if inside a string),
# execution will end. To avoid that, we first write the command to a file,
# and run that file with bash.
var script_path = game.tmp_prefix + "command" + str(randi())
helpers.write_file(script_path, hacky_command)
result = helpers.exec(_shell_binary(), [script_path], crash_on_fail)
elif _os == "Web":
#hacky_command = hacky_command.replace("\\", "\\\\").replace("'", "\\'").replace("\n", "\\n")
#var js_code = "await run_in_vm('" + hacky_command + "')"
#print(js_code)
#var output = JavaScriptBridge.eval(js_code, true)
#var output = JavaScriptBridge.eval("testy()")
#print(hacky_command)
shell_command.js_callback = JavaScriptBridge.create_callback(Callable(shell_command, "callback"))
web_shell.run(hacky_command).then(shell_command.js_callback)
else:
helpers.crash("Unimplemented OS: %s" % _os)
if _os != "Web":
if debug:
print(result["output"])
shell_command.output = result["output"]
shell_command.exit_code = result["exit_code"]
shell_command.emit_signal("done")
func _shell_binary():
if _os == "Linux" or _os == "OSX":
return "bash"
elif _os == "Windows":
return "dependencies\\windows\\git\\bin\\bash.exe"
else:
helpers.crash("Unsupported OS: %s" % _os)
func read_from(c):
var total_available = c.get_available_bytes()
print(str(total_available)+" bytes available")
while total_available > 0:
var available = min(1024, total_available)
total_available -= available
print("reading "+str(available))
var data = c.get_utf8_string(available)
#emit_signal("output", data)
print(data.size())