Add asciicast importer

This commit is contained in:
Leroy Hopson 2020-09-22 15:31:46 +07:00
parent 55544de93e
commit ffa8561865
11 changed files with 199 additions and 77 deletions

View file

@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Changelog.
- Asciicast importer plugin. Enables the import of .cast ([asciicast files v2](https://github.com/asciinema/asciinema/blob/master/doc/asciicast-v2.md)) that can be made using the [asciinema](https://asciinema.org/) terminal session recorder. See the [asciicast scene](/examples/asciicast) for example usage.
### Changed
- Implementation of Terminal node from GDScript to GDNative using [Aetf's patched version of libtsm](https://github.com/Aetf/libtsm).

View file

@ -18,6 +18,13 @@ Click the thumbnail to watch a demo video on youtube:
[![Demo video thumbnail](https://img.youtube.com/vi/_Tt4eQEBybo/0.jpg)](https://www.youtube.com/watch?v=_Tt4eQEBybo)
## Usage
### Asciicast (.cast) file importer
[Asciinema](https://asciinema.org) recordings saved with the `.cast` extension will be automatically imported as animations. They can then be added to AnimationPlayer which is a child of the Terminal node. Playing the animation will play the terminal session recording in the parent Terminal.
For an example, see the scene in [/examples/asciicast](/examples/asciicast).
## License
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license.

View file

@ -0,0 +1,69 @@
tool
extends EditorImportPlugin
const Asciicast = preload("res://addons/godot_xterm/resources/asciicast.gd")
func get_importer_name():
return "godot_xterm"
func get_visible_name():
return "asciicast"
func get_recognized_extensions():
return ["cast"]
func get_save_extension():
return "res"
func get_resource_type():
return "Animation"
func get_import_options(preset):
return []
func get_preset_count():
return 0
func import(source_file, save_path, options, r_platform_variant, r_gen_files):
var file = File.new()
var err = file.open(source_file, File.READ)
if err != OK:
return err
var header = file.get_line()
var asciicast = Asciicast.new()
asciicast.add_track(Animation.TYPE_METHOD, 0)
asciicast.track_set_path(0, ".")
var time: float
while not file.eof_reached():
var line = file.get_line()
if line == "":
continue
var p = JSON.parse(line)
if typeof(p.result) != TYPE_ARRAY:
continue
time = p.result[0]
var event_type: String = p.result[1]
var event_data: PoolByteArray = p.result[2].to_utf8()
if event_type == "o":
asciicast.track_insert_key(0, time, {"method": "write",
"args": [event_data]})
asciicast.length = time
return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], asciicast)

View file

@ -1,16 +0,0 @@
extends Node
# Declare member variables here. Examples:
# var a = 2
# var b = "text"
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass

View file

@ -2,7 +2,16 @@ tool
extends EditorPlugin
var asciicast_import_plugin
func _enter_tree():
asciicast_import_plugin = preload("res://addons/godot_xterm/import_plugins/asciicast_import_plugin.gd").new()
add_import_plugin(asciicast_import_plugin)
var asciicast_script = preload("res://addons/godot_xterm/resources/asciicast.gd")
add_custom_type("Asciicast", "Animation", asciicast_script, null)
var terminal_script = preload("res://addons/godot_xterm/nodes/terminal/terminal.gdns")
var terminal_icon = preload("res://addons/godot_xterm/nodes/terminal/terminal_icon.svg")
add_custom_type("Terminal", "Control", terminal_script, terminal_icon)
@ -13,5 +22,9 @@ func _enter_tree():
func _exit_tree():
remove_import_plugin(asciicast_import_plugin)
asciicast_import_plugin = null
remove_custom_type("Asciicast")
remove_custom_type("Terminal")
remove_custom_type("Psuedoterminal")

View file

@ -0,0 +1,19 @@
extends Animation
signal data_written(data)
signal data_read(data)
export(int) var version: int = 2
# Initial terminal width (number of columns).
export(int) var width: int
# Initial terminal height (number of rows).
export(int) var height: int
func get_class() -> String:
return "Asciicast"
func is_class(name) -> bool:
return name == get_class() or .is_class(name)

View file

@ -0,0 +1,53 @@
extends Container
# This Container ensures that the terminal always fills
# the window and/or screen. It also connects the terminal
# to the input/output of the Psuedoterminal.
const ESCAPE = 27
const BACKSPACE = 8
const BEEP = 7
const SPACE = 32
const LEFT_BRACKET = 91
const ENTER = 10
const BACKSPACE_ALT = 127
onready var viewport = get_viewport()
func _ready():
viewport.connect("size_changed", self, "_resize")
_resize()
$Terminal/AnimationPlayer.play("a")
func _input(event):
#return
if event is InputEventKey and event.pressed:
var data = PoolByteArray([])
accept_event()
# TODO: Handle more of these.
if (event.control and event.scancode == KEY_C):
data.append(3)
elif event.unicode:
data.append(event.unicode)
elif event.scancode == KEY_ENTER:
data.append(ENTER)
elif event.scancode == KEY_BACKSPACE:
data.append(BACKSPACE_ALT)
elif event.scancode == KEY_ESCAPE:
data.append(27)
elif event.scancode == KEY_TAB:
data.append(9)
elif OS.get_scancode_string(event.scancode) == "Shift":
pass
elif OS.get_scancode_string(event.scancode) == "Control":
pass
else:
pass
#push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode)))
#emit_signal("output", data)
func _resize():
rect_size = viewport.size
$Terminal.rect_size = rect_size

View file

@ -0,0 +1,24 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://examples/asciicast/asciicast.gd" type="Script" id=1]
[ext_resource path="res://addons/godot_xterm/themes/default.theme" type="Theme" id=2]
[ext_resource path="res://addons/godot_xterm/nodes/terminal/terminal.gdns" type="Script" id=4]
[ext_resource path="res://examples/asciicast/example.cast" type="Animation" id=6]
[node name="Container" type="Container"]
margin_right = 40.0
margin_bottom = 40.0
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Terminal" type="Control" parent="."]
margin_right = 40.0
margin_bottom = 40.0
theme = ExtResource( 2 )
script = ExtResource( 4 )
[node name="AnimationPlayer" type="AnimationPlayer" parent="Terminal"]
method_call_mode = 1
anims/a = ExtResource( 6 )

View file

@ -1,61 +0,0 @@
{"version": 2, "width": 86, "height": 29, "timestamp": 1589772748, "env": {"SHELL": "/run/current-system/sw/bin/bash", "TERM": "xterm"}}
[0.082961, "o", "> "]
[0.798002, "o", "e"]
[0.893414, "o", "c"]
[0.956255, "o", "h"]
[1.008677, "o", "o"]
[1.089472, "o", " "]
[1.189602, "o", "h"]
[1.266892, "o", "e"]
[1.347483, "o", "l"]
[1.46568, "o", "l"]
[1.541039, "o", "o"]
[1.726772, "o", "\r\n"]
[1.727475, "o", "hello\r\n> "]
[2.060109, "o", "#"]
[2.179668, "o", " "]
[2.471941, "o", "T"]
[2.652735, "o", "h"]
[2.746515, "o", "i"]
[2.810578, "o", "s"]
[2.921342, "o", " "]
[2.98886, "o", "i"]
[3.069095, "o", "s"]
[3.31728, "o", " "]
[3.399615, "o", "a"]
[3.513605, "o", " "]
[3.72609, "o", "d"]
[3.811197, "o", "e"]
[3.94649, "o", "m"]
[4.047162, "o", "o"]
[4.225042, "o", "\r\n"]
[4.225402, "o", "> "]
[4.935288, "o", "t"]
[5.163552, "o", "o"]
[5.323205, "o", "i"]
[5.46746, "o", "l"]
[5.561098, "o", "et "]
[6.064937, "o", "-"]
[6.41563, "o", "-"]
[6.60443, "o", "g"]
[6.666621, "o", "a"]
[6.768317, "o", "y"]
[6.848917, "o", " "]
[7.076406, "o", "H"]
[7.250067, "o", "E"]
[7.410878, "o", "L"]
[7.537016, "o", "L"]
[7.604155, "o", "O"]
[7.888992, "o", " "]
[8.193437, "o", "W"]
[8.365871, "o", "O"]
[8.454678, "o", "R"]
[8.525163, "o", "L"]
[8.60286, "o", "D"]
[8.873053, "o", "!"]
[9.216434, "o", "\r\n"]
[9.251462, "o", " \r\n \u001b[0;1;31;91mm\u001b[0m \u001b[0;1;36;96mm\u001b[0m \u001b[0;1;34;94mmm\u001b[0;1;35;95mmm\u001b[0;1;31;91mmm\u001b[0m \u001b[0;1;33;93mm\u001b[0m \u001b[0;1;35;95mm\u001b[0m \u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0m \u001b[0;1;36;96mm\u001b[0m \u001b[0;1;31;91mm\u001b[0m \u001b[0;1;33;93mm\u001b[0;1;32;92mmm\u001b[0;1;36;96mm\u001b[0m \u001b[0;1;34;94mm\u001b[0;1;35;95mmm\u001b[0;1;31;91mmm\u001b[0m \u001b[0;1;32;92mm\u001b[0m \u001b[0;1;35;95mm\u001b[0;1;31;91mmm\u001b[0;1;33;93mm\u001b[0m \r\n \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;36;96mm\u001b[0;1;34;94m\"\u001b[0m \u001b[0;1;35;95m\"\u001b[0;1;31;91mm\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92mm\"\u001b[0m \u001b[0;1;34;94m\"m\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;33;93m\"\u001b[0;1;32;92m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;32;92m\"\u001b[0;1;36;96mm\u001b[0m\r\n \u001b[0;1;32;92m#\u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0;1;35;95m#\u001b[0m \u001b[0;1;31;91m#m\u001b[0;1;33;93mmm\u001b[0;1;32;92mmm\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b"]
[9.251901, "o", "[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;35;95m\"\u001b[0m \u001b[0;1;31;91m#\"\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;31;91m#\u001b[0;1;33;93mmm\u001b[0;1;32;92mmm\u001b[0;1;36;96m\"\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m\r\n \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;31;91m#\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m##\u001b[0;1;36;96m\"\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;36;96m\"\u001b[0;1;34;94mm\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m\r\n \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#m\u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0m \u001b[0;1;35;95m#\u001b[0;1;31;91mmm\u001b[0;1;33;93mmm\u001b[0;1;32;92mm\u001b[0m \u001b[0;1;36;96m#m\u001b[0;1;34;94mmm\u001b[0;1;35;95mmm\u001b[0m \u001b[0;1;33;93m#m\u001b[0;1;32;92mm#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;35;95m#\u001b[0;1;31;91mmm\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m\"\u001b[0m \u001b[0;1;31;91m#m\u001b[0;1;33;93mmm\u001b[0;1"]
[9.251944, "o", ";32;92mmm\u001b[0m \u001b[0;1;36;96m#\u001b[0;1;34;94mmm\u001b[0;1;35;95mm\"\u001b[0m \r\n \r\n \r\n \r\n \u001b[0;1;36;96mm\u001b[0m \r\n \u001b[0;1;34;94m#\u001b[0m \r\n \u001b[0;1;35;95m#\u001b[0m \r\n \u001b[0;1;31;91m\"\u001b[0m \r\n \u001b[0;1;33;93m#\u001b[0m \r\n \r\n \r\n"]
[9.252259, "o", "> "]
[12.56287, "o", "exit\r\n"]

View file

@ -0,0 +1,13 @@
[remap]
importer="godot_xterm"
type="Animation"
path="res://.import/example.cast-9299cc6d12357f676344c3d48e3179e0.res"
[deps]
source_file="res://examples/asciicast/example.cast"
dest_files=[ "res://.import/example.cast-9299cc6d12357f676344c3d48e3179e0.res" ]
[params]