demotape 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +9 -0
- data/Makefile +4 -2
- data/README.md +101 -10
- data/demotape.gemspec +1 -1
- data/demotape.y +224 -505
- data/demotape_new.y.backup +160 -0
- data/demotape_simple_test.y +32 -0
- data/editors/sublime-text/DemoTape.sublime-completions +4 -0
- data/editors/sublime-text/DemoTape.sublime-syntax +24 -2
- data/editors/vim/syntax/demotape.vim +16 -2
- data/editors/vscode/demotape.tmLanguage.json +31 -3
- data/lib/demo_tape/cli.rb +36 -0
- data/lib/demo_tape/command.rb +63 -103
- data/lib/demo_tape/exporter.rb +3 -0
- data/lib/demo_tape/formatter.rb +142 -0
- data/lib/demo_tape/lexer.rb +118 -34
- data/lib/demo_tape/parser/helpers.rb +363 -0
- data/lib/demo_tape/parser/rules.rb +396 -0
- data/lib/demo_tape/parser.rb +381 -592
- data/lib/demo_tape/runner.rb +82 -27
- data/lib/demo_tape/token.rb +86 -0
- data/lib/demo_tape/version.rb +1 -1
- data/lib/demo_tape.rb +5 -2
- metadata +10 -527
- data/examples/alt.mp4 +0 -0
- data/examples/alt.png +0 -0
- data/examples/alt.tape +0 -9
- data/examples/arrow.mp4 +0 -0
- data/examples/arrow.png +0 -0
- data/examples/arrow.tape +0 -7
- data/examples/backspace.mp4 +0 -0
- data/examples/backspace.png +0 -0
- data/examples/backspace.tape +0 -4
- data/examples/bash.mp4 +0 -0
- data/examples/bash.png +0 -0
- data/examples/bash.tape +0 -4
- data/examples/bg.jpg +0 -0
- data/examples/clear.mp4 +0 -0
- data/examples/clear.png +0 -0
- data/examples/clear.tape +0 -6
- data/examples/cli_ui_interactive_prompt.mp4 +0 -0
- data/examples/cli_ui_interactive_prompt.png +0 -0
- data/examples/cli_ui_interactive_prompt.tape +0 -16
- data/examples/cli_ui_nested_frames.mp4 +0 -0
- data/examples/cli_ui_nested_frames.png +0 -0
- data/examples/cli_ui_nested_frames.tape +0 -15
- data/examples/cli_ui_progress.mp4 +0 -0
- data/examples/cli_ui_progress.png +0 -0
- data/examples/cli_ui_progress.tape +0 -6
- data/examples/cli_ui_spinner.mp4 +0 -0
- data/examples/cli_ui_spinner.png +0 -0
- data/examples/cli_ui_spinner.tape +0 -6
- data/examples/cli_ui_status_widget.mp4 +0 -0
- data/examples/cli_ui_status_widget.png +0 -0
- data/examples/cli_ui_status_widget.tape +0 -6
- data/examples/cli_ui_symbols.mp4 +0 -0
- data/examples/cli_ui_symbols.png +0 -0
- data/examples/cli_ui_symbols.tape +0 -6
- data/examples/cli_ui_text_prompt.mp4 +0 -0
- data/examples/cli_ui_text_prompt.png +0 -0
- data/examples/cli_ui_text_prompt.tape +0 -10
- data/examples/clipboard.mp4 +0 -0
- data/examples/clipboard.png +0 -0
- data/examples/clipboard.tape +0 -5
- data/examples/ctrl.mp4 +0 -0
- data/examples/ctrl.png +0 -0
- data/examples/ctrl.tape +0 -5
- data/examples/ctrl_arrows.mp4 +0 -0
- data/examples/ctrl_arrows.png +0 -0
- data/examples/ctrl_arrows.tape +0 -14
- data/examples/demotape.mp4 +0 -0
- data/examples/demotape.png +0 -0
- data/examples/demotape.tape +0 -6
- data/examples/demotape.txt +0 -13
- data/examples/enter.mp4 +0 -0
- data/examples/enter.png +0 -0
- data/examples/enter.tape +0 -3
- data/examples/errors/include_itself.tape +0 -1
- data/examples/fastfetch.gif +0 -0
- data/examples/fastfetch.mp4 +0 -0
- data/examples/fastfetch.png +0 -0
- data/examples/fastfetch.tape +0 -11
- data/examples/fish.mp4 +0 -0
- data/examples/fish.png +0 -0
- data/examples/fish.tape +0 -4
- data/examples/format.mp4 +0 -0
- data/examples/format.png +0 -0
- data/examples/format.tape +0 -15
- data/examples/gh-cli.mp4 +0 -0
- data/examples/gh-cli.png +0 -0
- data/examples/gh-cli.tape +0 -13
- data/examples/gif_loop.gif +0 -0
- data/examples/gif_loop.png +0 -0
- data/examples/gif_loop.tape +0 -5
- data/examples/include.mp4 +0 -0
- data/examples/include.png +0 -0
- data/examples/include.tape +0 -1
- data/examples/include_with_set.mp4 +0 -0
- data/examples/include_with_set.png +0 -0
- data/examples/include_with_set.tape +0 -2
- data/examples/meta/cli.tape +0 -10
- data/examples/nerdfonts.mp4 +0 -0
- data/examples/nerdfonts.png +0 -0
- data/examples/nerdfonts.tape +0 -9
- data/examples/output.avi +0 -0
- data/examples/output.gif +0 -0
- data/examples/output.mov +0 -0
- data/examples/output.mp4 +0 -0
- data/examples/output.png +0 -0
- data/examples/output.tape +0 -9
- data/examples/output.webm +0 -0
- data/examples/products.json +0 -1803
- data/examples/screenshot.mp4 +0 -0
- data/examples/screenshot.png +0 -0
- data/examples/screenshot.tape +0 -9
- data/examples/script.rb +0 -3
- data/examples/set_cursor_bar.mp4 +0 -0
- data/examples/set_cursor_bar.png +0 -0
- data/examples/set_cursor_bar.tape +0 -5
- data/examples/set_cursor_blink.mp4 +0 -0
- data/examples/set_cursor_blink.png +0 -0
- data/examples/set_cursor_blink.tape +0 -4
- data/examples/set_cursor_underline.mp4 +0 -0
- data/examples/set_cursor_underline.png +0 -0
- data/examples/set_cursor_underline.tape +0 -5
- data/examples/set_dimensions.mp4 +0 -0
- data/examples/set_dimensions.png +0 -0
- data/examples/set_dimensions.tape +0 -5
- data/examples/set_font_family.mp4 +0 -0
- data/examples/set_font_family.png +0 -0
- data/examples/set_font_family.tape +0 -4
- data/examples/set_font_size.mp4 +0 -0
- data/examples/set_font_size.png +0 -0
- data/examples/set_font_size.tape +0 -4
- data/examples/set_line_height.mp4 +0 -0
- data/examples/set_line_height.png +0 -0
- data/examples/set_line_height.tape +0 -8
- data/examples/set_margin.mp4 +0 -0
- data/examples/set_margin.png +0 -0
- data/examples/set_margin.tape +0 -7
- data/examples/set_margin_with_border_radius.mp4 +0 -0
- data/examples/set_margin_with_border_radius.png +0 -0
- data/examples/set_margin_with_border_radius.tape +0 -8
- data/examples/set_margin_with_image.mp4 +0 -0
- data/examples/set_margin_with_image.png +0 -0
- data/examples/set_margin_with_image.tape +0 -7
- data/examples/set_padding.mp4 +0 -0
- data/examples/set_padding.png +0 -0
- data/examples/set_padding.tape +0 -7
- data/examples/set_theme.mp4 +0 -0
- data/examples/set_theme.png +0 -0
- data/examples/set_theme.tape +0 -9
- data/examples/set_theme_property.mp4 +0 -0
- data/examples/set_theme_property.png +0 -0
- data/examples/set_theme_property.tape +0 -10
- data/examples/set_typing_speed.mp4 +0 -0
- data/examples/set_typing_speed.png +0 -0
- data/examples/set_typing_speed.tape +0 -6
- data/examples/slides.mp4 +0 -0
- data/examples/slides.png +0 -0
- data/examples/slides.tape +0 -14
- data/examples/space.mp4 +0 -0
- data/examples/space.png +0 -0
- data/examples/space.tape +0 -3
- data/examples/stdin.mp4 +0 -0
- data/examples/stdin.png +0 -0
- data/examples/stdin.tape +0 -7
- data/examples/tab.mp4 +0 -0
- data/examples/tab.png +0 -0
- data/examples/tab.tape +0 -5
- data/examples/themes/3024_day.png +0 -0
- data/examples/themes/3024_night.png +0 -0
- data/examples/themes/aardvark_blue.png +0 -0
- data/examples/themes/abernathy.png +0 -0
- data/examples/themes/adventure.png +0 -0
- data/examples/themes/adventure_time.png +0 -0
- data/examples/themes/afterglow.png +0 -0
- data/examples/themes/alabaster.png +0 -0
- data/examples/themes/alien_blood.png +0 -0
- data/examples/themes/andromeda.png +0 -0
- data/examples/themes/apple_classic.png +0 -0
- data/examples/themes/apple_system_colors.png +0 -0
- data/examples/themes/arcoiris.png +0 -0
- data/examples/themes/argonaut.png +0 -0
- data/examples/themes/arthur.png +0 -0
- data/examples/themes/atelier_sulphurpool.png +0 -0
- data/examples/themes/atom.png +0 -0
- data/examples/themes/atom_one_light.png +0 -0
- data/examples/themes/aurora.png +0 -0
- data/examples/themes/ayu.png +0 -0
- data/examples/themes/ayu_light.png +0 -0
- data/examples/themes/ayu_mirage.png +0 -0
- data/examples/themes/banana_blueberry.png +0 -0
- data/examples/themes/batman.png +0 -0
- data/examples/themes/belafonte_day.png +0 -0
- data/examples/themes/belafonte_night.png +0 -0
- data/examples/themes/birds_of_paradise.png +0 -0
- data/examples/themes/blazer.png +0 -0
- data/examples/themes/blue_berry_pie.png +0 -0
- data/examples/themes/blue_dolphin.png +0 -0
- data/examples/themes/blue_matrix.png +0 -0
- data/examples/themes/bluloco_dark.png +0 -0
- data/examples/themes/bluloco_light.png +0 -0
- data/examples/themes/borland.png +0 -0
- data/examples/themes/breeze.png +0 -0
- data/examples/themes/bright_lights.png +0 -0
- data/examples/themes/broadcast.png +0 -0
- data/examples/themes/brogrammer.png +0 -0
- data/examples/themes/bubbles.png +0 -0
- data/examples/themes/builtin_dark.png +0 -0
- data/examples/themes/builtin_light.png +0 -0
- data/examples/themes/builtin_pastel_dark.png +0 -0
- data/examples/themes/builtin_solarized_dark.png +0 -0
- data/examples/themes/builtin_solarized_light.png +0 -0
- data/examples/themes/builtin_tango_dark.png +0 -0
- data/examples/themes/builtin_tango_light.png +0 -0
- data/examples/themes/c64.png +0 -0
- data/examples/themes/calamity.png +0 -0
- data/examples/themes/catppuccin_frappe.png +0 -0
- data/examples/themes/catppuccin_latte.png +0 -0
- data/examples/themes/catppuccin_macchiato.png +0 -0
- data/examples/themes/catppuccin_mocha.png +0 -0
- data/examples/themes/cga.png +0 -0
- data/examples/themes/chalk.png +0 -0
- data/examples/themes/chalkboard.png +0 -0
- data/examples/themes/challenger_deep.png +0 -0
- data/examples/themes/chester.png +0 -0
- data/examples/themes/ciapre.png +0 -0
- data/examples/themes/clrs.png +0 -0
- data/examples/themes/cobalt2.png +0 -0
- data/examples/themes/cobalt_neon.png +0 -0
- data/examples/themes/coffee_theme.png +0 -0
- data/examples/themes/contrast_light.png +0 -0
- data/examples/themes/coolnight.png +0 -0
- data/examples/themes/crayon_pony_fish.png +0 -0
- data/examples/themes/crystal_violet.png +0 -0
- data/examples/themes/cutie_pro.png +0 -0
- data/examples/themes/cyber_cube.png +0 -0
- data/examples/themes/cyber_punk2077.png +0 -0
- data/examples/themes/cyberdyne.png +0 -0
- data/examples/themes/cyberpunk.png +0 -0
- data/examples/themes/dark_pastel.png +0 -0
- data/examples/themes/dark_plus.png +0 -0
- data/examples/themes/darkermatrix.png +0 -0
- data/examples/themes/darkmatrix.png +0 -0
- data/examples/themes/darkside.png +0 -0
- data/examples/themes/dayfox.png +0 -0
- data/examples/themes/deep.png +0 -0
- data/examples/themes/desert.png +0 -0
- data/examples/themes/dimmed_monokai.png +0 -0
- data/examples/themes/django.png +0 -0
- data/examples/themes/django_reborn_again.png +0 -0
- data/examples/themes/django_smooth.png +0 -0
- data/examples/themes/doom_one.png +0 -0
- data/examples/themes/doom_peacock.png +0 -0
- data/examples/themes/dot_gov.png +0 -0
- data/examples/themes/dracula.png +0 -0
- data/examples/themes/dracula_plus.png +0 -0
- data/examples/themes/duckbones.png +0 -0
- data/examples/themes/duotone_dark.png +0 -0
- data/examples/themes/earthsong.png +0 -0
- data/examples/themes/elemental.png +0 -0
- data/examples/themes/elementary.png +0 -0
- data/examples/themes/encom.png +0 -0
- data/examples/themes/espresso.png +0 -0
- data/examples/themes/espresso_libre.png +0 -0
- data/examples/themes/everblush.png +0 -0
- data/examples/themes/fahrenheit.png +0 -0
- data/examples/themes/fairyfloss.png +0 -0
- data/examples/themes/farmhouse_dark.png +0 -0
- data/examples/themes/farmhouse_light.png +0 -0
- data/examples/themes/fideloper.png +0 -0
- data/examples/themes/firefly_traditional.png +0 -0
- data/examples/themes/firefox_dev.png +0 -0
- data/examples/themes/firewatch.png +0 -0
- data/examples/themes/fish_tank.png +0 -0
- data/examples/themes/flat.png +0 -0
- data/examples/themes/flatland.png +0 -0
- data/examples/themes/flexoki_dark.png +0 -0
- data/examples/themes/flexoki_light.png +0 -0
- data/examples/themes/floraverse.png +0 -0
- data/examples/themes/forest_blue.png +0 -0
- data/examples/themes/framer.png +0 -0
- data/examples/themes/front_end_delight.png +0 -0
- data/examples/themes/fun_forrest.png +0 -0
- data/examples/themes/galaxy.png +0 -0
- data/examples/themes/galizur.png +0 -0
- data/examples/themes/ganyu.png +0 -0
- data/examples/themes/git_hub_dark.png +0 -0
- data/examples/themes/github.png +0 -0
- data/examples/themes/glacier.png +0 -0
- data/examples/themes/glorious.png +0 -0
- data/examples/themes/grape.png +0 -0
- data/examples/themes/grass.png +0 -0
- data/examples/themes/grey_green.png +0 -0
- data/examples/themes/gruvbox_dark.png +0 -0
- data/examples/themes/gruvbox_dark_hard.png +0 -0
- data/examples/themes/gruvbox_light.png +0 -0
- data/examples/themes/guezwhoz.png +0 -0
- data/examples/themes/h4rithd.png +0 -0
- data/examples/themes/h4rithd_com.png +0 -0
- data/examples/themes/ha_x0_r_blue.png +0 -0
- data/examples/themes/ha_x0_r_gr33_n.png +0 -0
- data/examples/themes/ha_x0_r_r3_d.png +0 -0
- data/examples/themes/hacktober.png +0 -0
- data/examples/themes/hardcore.png +0 -0
- data/examples/themes/harper.png +0 -0
- data/examples/themes/highway.png +0 -0
- data/examples/themes/hipster_green.png +0 -0
- data/examples/themes/hivacruz.png +0 -0
- data/examples/themes/homebrew.png +0 -0
- data/examples/themes/hopscotch.png +0 -0
- data/examples/themes/hopscotch_256.png +0 -0
- data/examples/themes/horizon.png +0 -0
- data/examples/themes/hurtado.png +0 -0
- data/examples/themes/hybrid.png +0 -0
- data/examples/themes/hyper.png +0 -0
- data/examples/themes/ic_green_ppl.png +0 -0
- data/examples/themes/ic_orange_ppl.png +0 -0
- data/examples/themes/iceberg_dark.png +0 -0
- data/examples/themes/iceberg_light.png +0 -0
- data/examples/themes/idea.png +0 -0
- data/examples/themes/idle_toes.png +0 -0
- data/examples/themes/ir_black.png +0 -0
- data/examples/themes/iterm2_dark_background.png +0 -0
- data/examples/themes/iterm2_default.png +0 -0
- data/examples/themes/iterm2_light_background.png +0 -0
- data/examples/themes/iterm2_pastel_dark_background.png +0 -0
- data/examples/themes/iterm2_smoooooth.png +0 -0
- data/examples/themes/iterm2_solarized_dark.png +0 -0
- data/examples/themes/iterm2_solarized_light.png +0 -0
- data/examples/themes/iterm2_tango_dark.png +0 -0
- data/examples/themes/iterm2_tango_light.png +0 -0
- data/examples/themes/jackie_brown.png +0 -0
- data/examples/themes/japanesque.png +0 -0
- data/examples/themes/jellybeans.png +0 -0
- data/examples/themes/jet_brains_darcula.png +0 -0
- data/examples/themes/jubi.png +0 -0
- data/examples/themes/juicy_colors.png +0 -0
- data/examples/themes/kanagawa.png +0 -0
- data/examples/themes/kanagawabones.png +0 -0
- data/examples/themes/kibble.png +0 -0
- data/examples/themes/kolorit.png +0 -0
- data/examples/themes/konsolas.png +0 -0
- data/examples/themes/lab_fox.png +0 -0
- data/examples/themes/laser.png +0 -0
- data/examples/themes/later_this_evening.png +0 -0
- data/examples/themes/lavandula.png +0 -0
- data/examples/themes/liquid_carbon.png +0 -0
- data/examples/themes/liquid_carbon_transparent.png +0 -0
- data/examples/themes/liquid_carbon_transparent_inverse.png +0 -0
- data/examples/themes/lovelace.png +0 -0
- data/examples/themes/man_page.png +0 -0
- data/examples/themes/mariana.png +0 -0
- data/examples/themes/material.png +0 -0
- data/examples/themes/material_dark.png +0 -0
- data/examples/themes/material_darker.png +0 -0
- data/examples/themes/material_design_colors.png +0 -0
- data/examples/themes/material_ocean.png +0 -0
- data/examples/themes/mathias.png +0 -0
- data/examples/themes/matrix.png +0 -0
- data/examples/themes/medallion.png +0 -0
- data/examples/themes/mellifluous.png +0 -0
- data/examples/themes/midnight_in_mojave.png +0 -0
- data/examples/themes/mirage.png +0 -0
- data/examples/themes/misterioso.png +0 -0
- data/examples/themes/molokai.png +0 -0
- data/examples/themes/mona_lisa.png +0 -0
- data/examples/themes/monokai_cmder.png +0 -0
- data/examples/themes/monokai_pro.png +0 -0
- data/examples/themes/monokai_pro_filter_octagon.png +0 -0
- data/examples/themes/monokai_pro_filter_ristretto.png +0 -0
- data/examples/themes/monokai_remastered.png +0 -0
- data/examples/themes/monokai_soda.png +0 -0
- data/examples/themes/monokai_vivid.png +0 -0
- data/examples/themes/moonlight_ii.png +0 -0
- data/examples/themes/n0tch2k.png +0 -0
- data/examples/themes/neobones_dark.png +0 -0
- data/examples/themes/neobones_light.png +0 -0
- data/examples/themes/neon.png +0 -0
- data/examples/themes/neopolitan.png +0 -0
- data/examples/themes/neutron.png +0 -0
- data/examples/themes/night_city.png +0 -0
- data/examples/themes/night_lion_v1.png +0 -0
- data/examples/themes/night_lion_v2.png +0 -0
- data/examples/themes/night_owlish_light.png +0 -0
- data/examples/themes/nightfox.png +0 -0
- data/examples/themes/niji.png +0 -0
- data/examples/themes/nocturnal_winter.png +0 -0
- data/examples/themes/nord.png +0 -0
- data/examples/themes/nord_light.png +0 -0
- data/examples/themes/novel.png +0 -0
- data/examples/themes/nvim_dark.png +0 -0
- data/examples/themes/nvim_light.png +0 -0
- data/examples/themes/obsidian.png +0 -0
- data/examples/themes/ocean.png +0 -0
- data/examples/themes/oceanic_material.png +0 -0
- data/examples/themes/oceanic_next.png +0 -0
- data/examples/themes/ollie.png +0 -0
- data/examples/themes/one_dark.png +0 -0
- data/examples/themes/one_half_dark.png +0 -0
- data/examples/themes/one_half_light.png +0 -0
- data/examples/themes/one_star.png +0 -0
- data/examples/themes/operator_mono_dark.png +0 -0
- data/examples/themes/overnight_slumber.png +0 -0
- data/examples/themes/pale_night_hc.png +0 -0
- data/examples/themes/pandora.png +0 -0
- data/examples/themes/paraiso_dark.png +0 -0
- data/examples/themes/paul_millr.png +0 -0
- data/examples/themes/pencil_dark.png +0 -0
- data/examples/themes/pencil_light.png +0 -0
- data/examples/themes/peppermint.png +0 -0
- data/examples/themes/piatto_light.png +0 -0
- data/examples/themes/pnevma.png +0 -0
- data/examples/themes/popping_and_locking.png +0 -0
- data/examples/themes/primary.png +0 -0
- data/examples/themes/primer.png +0 -0
- data/examples/themes/pro.png +0 -0
- data/examples/themes/pro_light.png +0 -0
- data/examples/themes/purple_rain.png +0 -0
- data/examples/themes/purplepeter.png +0 -0
- data/examples/themes/qb64_super_dark_blue.png +0 -0
- data/examples/themes/rapture.png +0 -0
- data/examples/themes/raycast_dark.png +0 -0
- data/examples/themes/raycast_light.png +0 -0
- data/examples/themes/rebecca.png +0 -0
- data/examples/themes/red_alert.png +0 -0
- data/examples/themes/red_planet.png +0 -0
- data/examples/themes/red_sands.png +0 -0
- data/examples/themes/relaxed.png +0 -0
- data/examples/themes/retro.png +0 -0
- data/examples/themes/retrowave.png +0 -0
- data/examples/themes/rippedcasts.png +0 -0
- data/examples/themes/rose_pine.png +0 -0
- data/examples/themes/rose_pine_dawn.png +0 -0
- data/examples/themes/rose_pine_moon.png +0 -0
- data/examples/themes/rouge_2.png +0 -0
- data/examples/themes/royal.png +0 -0
- data/examples/themes/ryuuko.png +0 -0
- data/examples/themes/sakura.png +0 -0
- data/examples/themes/scarlet_protocol.png +0 -0
- data/examples/themes/sea_shells.png +0 -0
- data/examples/themes/seafoam_pastel.png +0 -0
- data/examples/themes/seoulbones_dark.png +0 -0
- data/examples/themes/seoulbones_light.png +0 -0
- data/examples/themes/serendipity_midnight.png +0 -0
- data/examples/themes/serendipity_morning.png +0 -0
- data/examples/themes/serendipity_sunset.png +0 -0
- data/examples/themes/seti.png +0 -0
- data/examples/themes/shades_of_purple.png +0 -0
- data/examples/themes/shaman.png +0 -0
- data/examples/themes/slate.png +0 -0
- data/examples/themes/sleepy_hollow.png +0 -0
- data/examples/themes/smyck.png +0 -0
- data/examples/themes/snazzy.png +0 -0
- data/examples/themes/soft_server.png +0 -0
- data/examples/themes/solarized_darcula.png +0 -0
- data/examples/themes/solarized_dark_higher_contrast.png +0 -0
- data/examples/themes/solarized_dark_patched.png +0 -0
- data/examples/themes/sonoran_gothic.png +0 -0
- data/examples/themes/sonoran_sunrise.png +0 -0
- data/examples/themes/space_gray.png +0 -0
- data/examples/themes/space_gray_eighties.png +0 -0
- data/examples/themes/space_gray_eighties_dull.png +0 -0
- data/examples/themes/spacedust.png +0 -0
- data/examples/themes/spiderman.png +0 -0
- data/examples/themes/spring.png +0 -0
- data/examples/themes/square.png +0 -0
- data/examples/themes/sublette.png +0 -0
- data/examples/themes/subliminal.png +0 -0
- data/examples/themes/sundried.png +0 -0
- data/examples/themes/symfonic.png +0 -0
- data/examples/themes/synthwave.png +0 -0
- data/examples/themes/synthwave_alpha.png +0 -0
- data/examples/themes/synthwave_everything.png +0 -0
- data/examples/themes/tango_adapted.png +0 -0
- data/examples/themes/tango_half_adapted.png +0 -0
- data/examples/themes/teerb.png +0 -0
- data/examples/themes/terafox.png +0 -0
- data/examples/themes/terminal_basic.png +0 -0
- data/examples/themes/thayer_bright.png +0 -0
- data/examples/themes/the_hulk.png +0 -0
- data/examples/themes/theme.tape +0 -13
- data/examples/themes/tinacious_design_dark.png +0 -0
- data/examples/themes/tinacious_design_light.png +0 -0
- data/examples/themes/tokyo_night.png +0 -0
- data/examples/themes/tokyo_night_light.png +0 -0
- data/examples/themes/tokyo_night_storm.png +0 -0
- data/examples/themes/tokyonight.png +0 -0
- data/examples/themes/tokyonight_day.png +0 -0
- data/examples/themes/tokyonight_storm.png +0 -0
- data/examples/themes/tomorrow.png +0 -0
- data/examples/themes/tomorrow_night.png +0 -0
- data/examples/themes/tomorrow_night_blue.png +0 -0
- data/examples/themes/tomorrow_night_bright.png +0 -0
- data/examples/themes/tomorrow_night_burns.png +0 -0
- data/examples/themes/tomorrow_night_eighties.png +0 -0
- data/examples/themes/toy_chest.png +0 -0
- data/examples/themes/treehouse.png +0 -0
- data/examples/themes/twilight.png +0 -0
- data/examples/themes/ubuntu.png +0 -0
- data/examples/themes/ultra_dark.png +0 -0
- data/examples/themes/ultra_violent.png +0 -0
- data/examples/themes/under_the_sea.png +0 -0
- data/examples/themes/unholy.png +0 -0
- data/examples/themes/unikitty.png +0 -0
- data/examples/themes/urple.png +0 -0
- data/examples/themes/vaughn.png +0 -0
- data/examples/themes/vesper.png +0 -0
- data/examples/themes/vibrant_ink.png +0 -0
- data/examples/themes/vimbones.png +0 -0
- data/examples/themes/violet_dark.png +0 -0
- data/examples/themes/violet_light.png +0 -0
- data/examples/themes/warm_neon.png +0 -0
- data/examples/themes/wez.png +0 -0
- data/examples/themes/whimsy.png +0 -0
- data/examples/themes/wild_cherry.png +0 -0
- data/examples/themes/wilmersdorf.png +0 -0
- data/examples/themes/wombat.png +0 -0
- data/examples/themes/wryan.png +0 -0
- data/examples/themes/zenbones.png +0 -0
- data/examples/themes/zenbones_dark.png +0 -0
- data/examples/themes/zenbones_light.png +0 -0
- data/examples/themes/zenburn.png +0 -0
- data/examples/themes/zenburned.png +0 -0
- data/examples/themes/zenwritten_dark.png +0 -0
- data/examples/themes/zenwritten_light.png +0 -0
- data/examples/themes/zeonica.png +0 -0
- data/examples/tmux.mp4 +0 -0
- data/examples/tmux.png +0 -0
- data/examples/tmux.tape +0 -28
- data/examples/type.mp4 +0 -0
- data/examples/type.png +0 -0
- data/examples/type.tape +0 -11
- data/examples/type_file.mp4 +0 -0
- data/examples/type_file.png +0 -0
- data/examples/type_file.tape +0 -23
- data/examples/variable_typing.mp4 +0 -0
- data/examples/variable_typing.png +0 -0
- data/examples/variable_typing.tape +0 -41
- data/examples/wait_until.mp4 +0 -0
- data/examples/wait_until.png +0 -0
- data/examples/wait_until.tape +0 -8
- data/examples/zsh.mp4 +0 -0
- data/examples/zsh.png +0 -0
- data/examples/zsh.tape +0 -4
data/lib/demo_tape/command.rb
CHANGED
|
@@ -62,6 +62,7 @@ module DemoTape
|
|
|
62
62
|
"F12" => :f12,
|
|
63
63
|
"Meta" => :meta,
|
|
64
64
|
"Command" => :command,
|
|
65
|
+
"Cmd" => :command,
|
|
65
66
|
"Slash" => "/",
|
|
66
67
|
"BackSlash" => "\\"
|
|
67
68
|
}.freeze
|
|
@@ -69,12 +70,14 @@ module DemoTape
|
|
|
69
70
|
VALID_COMMANDS = KEY_MAPPING.keys + %w[
|
|
70
71
|
Clear
|
|
71
72
|
Copy
|
|
73
|
+
Group
|
|
72
74
|
Include
|
|
73
75
|
Output
|
|
74
76
|
Pause
|
|
75
77
|
Paste
|
|
76
78
|
Require
|
|
77
79
|
Resume
|
|
80
|
+
Run
|
|
78
81
|
Screenshot
|
|
79
82
|
Send
|
|
80
83
|
Set
|
|
@@ -85,9 +88,10 @@ module DemoTape
|
|
|
85
88
|
WaitUntil
|
|
86
89
|
].freeze
|
|
87
90
|
|
|
88
|
-
META_COMMANDS = %w[
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
META_COMMANDS = %w[Group Include Output Require Set].freeze
|
|
92
|
+
COMMANDS_WITH_DURATION = KEY_MAPPING.keys +
|
|
93
|
+
%w[Run Type TypeFile WaitUntil Wait Sleep].freeze
|
|
94
|
+
COMMANDS_WITH_COUNT = KEY_MAPPING.keys + %w[Wait Sleep Set]
|
|
91
95
|
VALID_TIME_UNITS = %w[ms s m h].freeze
|
|
92
96
|
|
|
93
97
|
# Valid keys that can be used in key combos
|
|
@@ -103,21 +107,21 @@ module DemoTape
|
|
|
103
107
|
typing_speed variable_typing width
|
|
104
108
|
].freeze
|
|
105
109
|
|
|
106
|
-
attr_reader :type, :args, :options
|
|
110
|
+
attr_reader :type, :args, :options, :children
|
|
107
111
|
attr_accessor :column, :duration_column, :file, :line, :line_content,
|
|
108
|
-
:
|
|
112
|
+
:tokens
|
|
109
113
|
|
|
110
114
|
def initialize(type, args = "", **options)
|
|
111
115
|
@type = type
|
|
112
116
|
@args = args
|
|
113
117
|
@options = options
|
|
118
|
+
@children = options.delete(:children) || []
|
|
119
|
+
@group_invocation = options.delete(:group_invocation) || false
|
|
114
120
|
@line = nil
|
|
115
121
|
@column = nil
|
|
116
122
|
@line_content = nil
|
|
117
123
|
@file = nil
|
|
118
124
|
@duration_column = nil
|
|
119
|
-
@speed_column = nil
|
|
120
|
-
@timeout_column = nil
|
|
121
125
|
@tokens = []
|
|
122
126
|
end
|
|
123
127
|
|
|
@@ -126,6 +130,10 @@ module DemoTape
|
|
|
126
130
|
VALID_KEYS.include?(type)
|
|
127
131
|
end
|
|
128
132
|
|
|
133
|
+
def group?
|
|
134
|
+
type == "Group"
|
|
135
|
+
end
|
|
136
|
+
|
|
129
137
|
def keys
|
|
130
138
|
return [] unless key?
|
|
131
139
|
|
|
@@ -153,22 +161,14 @@ module DemoTape
|
|
|
153
161
|
.to_sym
|
|
154
162
|
end
|
|
155
163
|
|
|
156
|
-
def
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
raise_error "Unknown command: #{type.inspect}"
|
|
164
|
+
def find_token(klass, list = tokens)
|
|
165
|
+
list.find {|token| token.is_a?(klass) }
|
|
160
166
|
end
|
|
161
167
|
|
|
162
|
-
def
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def validate_timeout!
|
|
169
|
-
return unless options[:timeout] && !COMMANDS_WITH_TIMEOUT.include?(type)
|
|
170
|
-
|
|
171
|
-
raise_error "Command #{type.inspect} does not accept timeout option"
|
|
168
|
+
def group_invocation?
|
|
169
|
+
# Group invocations are marked as such
|
|
170
|
+
# AND don't start with uppercase letter
|
|
171
|
+
@group_invocation && type[0].to_s.match?(/[^A-Z]/)
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
def validate_set_options!
|
|
@@ -180,96 +180,35 @@ module DemoTape
|
|
|
180
180
|
return if SET_OPTIONS.include?(base_option)
|
|
181
181
|
|
|
182
182
|
raise_error "Unknown option: #{options[:option].inspect} #{self}",
|
|
183
|
-
column_override: tokens[
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def validate_keys!
|
|
187
|
-
return unless options[:keys]
|
|
188
|
-
|
|
189
|
-
all_keys = options[:keys] + [type]
|
|
190
|
-
|
|
191
|
-
all_keys.each do |key|
|
|
192
|
-
next if VALID_KEYS.include?(key)
|
|
193
|
-
|
|
194
|
-
unless VALID_COMMANDS.include?(key)
|
|
195
|
-
raise_error "Invalid key in combo: #{key.inspect}"
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
raise_error "Command #{key.inspect} doesn't support key combos"
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def validate_regex!
|
|
203
|
-
return unless type == "WaitUntil"
|
|
204
|
-
|
|
205
|
-
raise_error "WaitUntil command requires a regex pattern" if args.empty?
|
|
206
|
-
|
|
207
|
-
begin
|
|
208
|
-
options[:pattern] = Regexp.new(args)
|
|
209
|
-
rescue RegexpError => error
|
|
210
|
-
raise_error "Invalid regex pattern: #{error.message}"
|
|
211
|
-
end
|
|
183
|
+
column_override: tokens[2].column
|
|
212
184
|
end
|
|
213
185
|
|
|
214
186
|
def prepare!
|
|
215
|
-
validate_command!
|
|
216
|
-
validate_speed!
|
|
217
|
-
validate_timeout!
|
|
218
187
|
normalize_theme_options!
|
|
219
188
|
validate_set_options!
|
|
220
|
-
validate_keys!
|
|
221
|
-
validate_regex!
|
|
222
|
-
validate_duration!
|
|
223
|
-
validate_type_file!
|
|
224
|
-
|
|
225
189
|
normalize_spacing_values!
|
|
226
190
|
|
|
227
191
|
self
|
|
228
192
|
end
|
|
229
193
|
|
|
230
|
-
private def validate_type_file!
|
|
231
|
-
return unless type == "TypeFile"
|
|
232
|
-
|
|
233
|
-
raise_error "TypeFile command requires a file path" if args.empty?
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
private def validate_duration!
|
|
237
|
-
[args, :duration] => [duration, source] if %w[Sleep Wait].include?(type)
|
|
238
|
-
[options[:speed], :speed] => [duration, source] if options[:speed]
|
|
239
|
-
[options[:timeout], :timeout] => [duration, source] if options[:timeout]
|
|
240
|
-
|
|
241
|
-
return unless duration
|
|
242
|
-
|
|
243
|
-
unit = duration[/[a-z]+$/i]
|
|
244
|
-
return if VALID_TIME_UNITS.include?(unit)
|
|
245
|
-
|
|
246
|
-
col = case source
|
|
247
|
-
when :speed then speed_column
|
|
248
|
-
when :timeout then timeout_column
|
|
249
|
-
else duration_column
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
raise_error "Invalid time unit: #{unit.inspect}", column_override: col
|
|
253
|
-
end
|
|
254
|
-
|
|
255
194
|
private def normalize_theme_options!
|
|
256
195
|
return unless type == "Set"
|
|
257
196
|
return unless options[:option]&.include?(".")
|
|
258
197
|
|
|
259
|
-
option,
|
|
198
|
+
option, property = *options[:option].split(".", 2)
|
|
260
199
|
|
|
261
200
|
unless option == "theme"
|
|
262
201
|
raise_error "Unexpected attribute #{options[:option].inspect}",
|
|
263
|
-
column_override: tokens[
|
|
202
|
+
column_override: tokens[2].column
|
|
264
203
|
end
|
|
265
204
|
|
|
266
|
-
unless Theme.valid_options.include?(
|
|
205
|
+
unless Theme.valid_options.include?(property.to_sym)
|
|
267
206
|
raise_error "Invalid theme property",
|
|
268
|
-
column_override: tokens[
|
|
207
|
+
column_override: tokens[2].column + option.length + 1
|
|
269
208
|
end
|
|
270
209
|
|
|
271
210
|
@options[:option] = option
|
|
272
|
-
@options[:
|
|
211
|
+
@options[:property] = property
|
|
273
212
|
end
|
|
274
213
|
|
|
275
214
|
private def normalize_spacing_values!
|
|
@@ -286,12 +225,23 @@ module DemoTape
|
|
|
286
225
|
|
|
287
226
|
def raise_error(message, column_override: nil)
|
|
288
227
|
col = column_override || column
|
|
228
|
+
line_content = tokens
|
|
229
|
+
# Remove "end" keyword from line content
|
|
230
|
+
# This is required so we can properly point to errors
|
|
231
|
+
# in commands that have blocks (e.g. Run do..end)
|
|
232
|
+
.reject { it.is_a?(Token::Keyword) && it.value == "end" }
|
|
233
|
+
.map(&:raw).join
|
|
289
234
|
|
|
290
|
-
raise DemoTape::ParseError, message unless line && col
|
|
235
|
+
raise DemoTape::ParseError, message unless line && col
|
|
291
236
|
|
|
292
237
|
error_msg = "#{message} at #{location(column_override:)}:\n"
|
|
293
|
-
error_msg += " #{line_content}\n"
|
|
294
|
-
|
|
238
|
+
error_msg += " #{line_content.strip}\n"
|
|
239
|
+
|
|
240
|
+
# Calculate pointer position: col is absolute position in original line
|
|
241
|
+
# line_content.strip removes leading spaces, so we need to adjust
|
|
242
|
+
leading_spaces = line_content[/^\s*/].size
|
|
243
|
+
pointer_col = col - leading_spaces - 1
|
|
244
|
+
error_msg += " #{' ' * pointer_col}^"
|
|
295
245
|
|
|
296
246
|
raise DemoTape::ParseError, error_msg
|
|
297
247
|
end
|
|
@@ -308,28 +258,38 @@ module DemoTape
|
|
|
308
258
|
|
|
309
259
|
tokens.each_with_index do |token, index|
|
|
310
260
|
previous_token = tokens[index - 1]
|
|
311
|
-
|
|
312
|
-
|
|
261
|
+
preceded_by_number = previous_token.is_a?(Token::Number)
|
|
262
|
+
preceded_by_group_name = tokens[index - 2]&.value == "Group"
|
|
263
|
+
preceded_by_command_with_duration =
|
|
264
|
+
%w[Sleep Wait].include?(tokens[index - 2]&.value) # rubocop:disable Performance/CollectionLiteralInLoop
|
|
313
265
|
|
|
314
266
|
case token
|
|
267
|
+
when Token::Duration
|
|
268
|
+
duration = token.value[:raw].to_s.strip
|
|
269
|
+
duration = "#{duration}s" if token.value[:unit].to_s.strip == ""
|
|
270
|
+
values << thor.set_color(duration, :cyan)
|
|
315
271
|
when Token::String
|
|
316
|
-
values << " "
|
|
317
272
|
values << thor.set_color(token.raw, :yellow)
|
|
318
273
|
when Token::Operator
|
|
319
274
|
values << thor.set_color(token.value, :white)
|
|
320
275
|
when Token::Number
|
|
321
|
-
values <<
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
values << thor.set_color(token.value, :magenta)
|
|
276
|
+
values << if preceded_by_command_with_duration
|
|
277
|
+
thor.set_color("#{token.value}s", :cyan)
|
|
278
|
+
else
|
|
279
|
+
thor.set_color(token.value, :magenta)
|
|
280
|
+
end
|
|
327
281
|
when Token::Regex
|
|
328
|
-
values << " "
|
|
329
282
|
values << thor.set_color(token.raw, :green)
|
|
330
283
|
when Token::Identifier
|
|
331
|
-
values <<
|
|
332
|
-
|
|
284
|
+
values << if preceded_by_group_name
|
|
285
|
+
thor.set_color(token.value, :cyan)
|
|
286
|
+
else
|
|
287
|
+
thor.set_color(token.value, :blue)
|
|
288
|
+
end
|
|
289
|
+
when Token::Space
|
|
290
|
+
values << " " unless preceded_by_number
|
|
291
|
+
when Token::Keyword
|
|
292
|
+
# do nothing
|
|
333
293
|
else
|
|
334
294
|
raise "Unexpected token type: #{token.class.name}"
|
|
335
295
|
end
|
data/lib/demo_tape/exporter.rb
CHANGED
|
@@ -214,6 +214,9 @@ module DemoTape
|
|
|
214
214
|
end
|
|
215
215
|
|
|
216
216
|
def get_png_dimensions(file_path)
|
|
217
|
+
return [0, 0] unless file_path
|
|
218
|
+
return [0, 0] unless File.file?(file_path)
|
|
219
|
+
|
|
217
220
|
File.open(file_path, "rb") do |f|
|
|
218
221
|
f.read(8) # Skip PNG signature
|
|
219
222
|
f.read(4) # Skip chunk length
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DemoTape
|
|
4
|
+
class Formatter
|
|
5
|
+
attr_accessor :buffered_newlines
|
|
6
|
+
|
|
7
|
+
def initialize(input)
|
|
8
|
+
@input = input
|
|
9
|
+
@buffered_newlines = 0
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def newline?(token)
|
|
13
|
+
token.instance_of?(Token::Newline)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
parser = Parser.new
|
|
18
|
+
tree = parser.parse(@input)
|
|
19
|
+
|
|
20
|
+
meta = extract_meta!(tree)
|
|
21
|
+
|
|
22
|
+
output = [
|
|
23
|
+
collect(meta).flatten.compact.join.strip,
|
|
24
|
+
collect(tree).flatten.compact.join.strip
|
|
25
|
+
].reject(&:empty?).join("\n\n")
|
|
26
|
+
|
|
27
|
+
"#{output}\n"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def extract_meta!(tree, meta = [])
|
|
31
|
+
tree.each do |node|
|
|
32
|
+
next unless node.is_a?(Hash)
|
|
33
|
+
|
|
34
|
+
if node[:type] == :command
|
|
35
|
+
first_token = node[:tokens].reject(&:any_space?).first
|
|
36
|
+
next unless first_token.meta?
|
|
37
|
+
|
|
38
|
+
meta << node
|
|
39
|
+
meta << Token::Newline.new("\n")
|
|
40
|
+
tree.delete(node)
|
|
41
|
+
elsif node[:type] == :group
|
|
42
|
+
extract_meta!(node[:children], meta)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
meta
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def collect(tree, output = [])
|
|
50
|
+
query = Query.new(tree)
|
|
51
|
+
|
|
52
|
+
tree.each_with_index do |node, _index|
|
|
53
|
+
# Whenever we hit a non-newline, flush buffered newlines (first)
|
|
54
|
+
# and then process the node. Newlines are clamped to max 2.
|
|
55
|
+
unless newline?(node)
|
|
56
|
+
newlines = buffered_newlines.clamp(0, 2)
|
|
57
|
+
output << ("\n" * newlines)
|
|
58
|
+
self.buffered_newlines = 0
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
case node
|
|
62
|
+
when Token::Keyword
|
|
63
|
+
output << node.value unless node.keyword?("end")
|
|
64
|
+
when Token::Identifier
|
|
65
|
+
output << if node.group?
|
|
66
|
+
"\n#{node.value}"
|
|
67
|
+
else
|
|
68
|
+
node.value
|
|
69
|
+
end
|
|
70
|
+
when Token::Duration, Token::Comment
|
|
71
|
+
output << node.value
|
|
72
|
+
when Token::Newline
|
|
73
|
+
self.buffered_newlines += 1
|
|
74
|
+
when Token::Number
|
|
75
|
+
value = [node.value.to_s]
|
|
76
|
+
value << "s" if query.previous_identifier?(node, /Sleep|Wait/)
|
|
77
|
+
output << value.join
|
|
78
|
+
when Token::String
|
|
79
|
+
output << normalize_string(node)
|
|
80
|
+
when Token::MultilineString
|
|
81
|
+
output << %["""\n#{node.value.chomp}\n"""\n]
|
|
82
|
+
when Token::Space
|
|
83
|
+
output << " "
|
|
84
|
+
when Token::LeadingSpace
|
|
85
|
+
next if output.empty?
|
|
86
|
+
next unless within_group?
|
|
87
|
+
|
|
88
|
+
output << " "
|
|
89
|
+
when Hash
|
|
90
|
+
output = [output.flatten.compact.join]
|
|
91
|
+
output << collect(node[:tokens])
|
|
92
|
+
|
|
93
|
+
if node[:type] == :group
|
|
94
|
+
within_group do
|
|
95
|
+
output << "\n "
|
|
96
|
+
output << collect(node[:children]).join.strip
|
|
97
|
+
output << "\nend"
|
|
98
|
+
self.buffered_newlines += 1
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
output << node.raw
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
output
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def within_group?
|
|
110
|
+
@within_group
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def within_group
|
|
114
|
+
@within_group = true
|
|
115
|
+
yield
|
|
116
|
+
ensure
|
|
117
|
+
@within_group = false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def normalize_string(token)
|
|
121
|
+
%["#{token.value}"]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class Query
|
|
125
|
+
attr_reader :nodes
|
|
126
|
+
|
|
127
|
+
def initialize(nodes)
|
|
128
|
+
@nodes = nodes
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def previous_identifier?(current_node, value)
|
|
132
|
+
index = nodes.index(current_node)
|
|
133
|
+
return false if index.nil? || index.zero?
|
|
134
|
+
|
|
135
|
+
previous_node = nodes[index - 2]
|
|
136
|
+
|
|
137
|
+
previous_node.is_a?(Token::Identifier) &&
|
|
138
|
+
previous_node.value.match?(value)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
data/lib/demo_tape/lexer.rb
CHANGED
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module DemoTape
|
|
4
4
|
class Lexer
|
|
5
|
-
def self.tokenize(content)
|
|
6
|
-
new.tokenize(content)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
5
|
attr_reader :line_map
|
|
10
6
|
|
|
11
7
|
def tokenize(content)
|
|
@@ -39,7 +35,7 @@ module DemoTape
|
|
|
39
35
|
content: '"""',
|
|
40
36
|
raw: '"""..."""'
|
|
41
37
|
}
|
|
42
|
-
tokens << [:
|
|
38
|
+
tokens << [:MULTILINE_STRING, multiline_text]
|
|
43
39
|
token_index += 1
|
|
44
40
|
|
|
45
41
|
# Add NEWLINE token after multiline string
|
|
@@ -59,25 +55,99 @@ module DemoTape
|
|
|
59
55
|
next
|
|
60
56
|
end
|
|
61
57
|
|
|
62
|
-
#
|
|
63
|
-
|
|
58
|
+
# Handle comments - emit as tokens
|
|
59
|
+
if stripped_line.start_with?("#")
|
|
60
|
+
# Emit LEADING_SPACE if there's any
|
|
61
|
+
leading_space = original_line[/^[ \t]*/]
|
|
62
|
+
if leading_space && !leading_space.empty?
|
|
63
|
+
@line_map[token_index] = {
|
|
64
|
+
line: @line_number,
|
|
65
|
+
column: 1,
|
|
66
|
+
content: original_line.chomp,
|
|
67
|
+
raw: leading_space
|
|
68
|
+
}
|
|
69
|
+
tokens << [:LEADING_SPACE, leading_space]
|
|
70
|
+
token_index += 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Emit COMMENT token
|
|
74
|
+
@line_map[token_index] = {
|
|
75
|
+
line: @line_number,
|
|
76
|
+
column: leading_space.length + 1,
|
|
77
|
+
content: original_line.chomp,
|
|
78
|
+
raw: stripped_line
|
|
79
|
+
}
|
|
80
|
+
tokens << [:COMMENT, stripped_line]
|
|
81
|
+
token_index += 1
|
|
82
|
+
|
|
83
|
+
@line_map[token_index] = {
|
|
84
|
+
line: @line_number,
|
|
85
|
+
column: 1,
|
|
86
|
+
content: original_line.chomp
|
|
87
|
+
}
|
|
88
|
+
tokens << [:NEWLINE, "\n"]
|
|
89
|
+
token_index += 1
|
|
90
|
+
next
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Handle empty lines - emit just NEWLINE
|
|
94
|
+
if stripped_line.empty?
|
|
95
|
+
@line_map[token_index] = {
|
|
96
|
+
line: @line_number,
|
|
97
|
+
column: 1,
|
|
98
|
+
content: ""
|
|
99
|
+
}
|
|
100
|
+
tokens << [:NEWLINE, "\n"]
|
|
101
|
+
token_index += 1
|
|
102
|
+
next
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Tokenize the original line (preserving all spaces)
|
|
106
|
+
line_tokens = tokenize_line(line.chomp)
|
|
107
|
+
|
|
108
|
+
# Convert first SPACE token to LEADING_SPACE if it exists
|
|
109
|
+
if line_tokens.any? && line_tokens[0][0] == :SPACE
|
|
110
|
+
line_tokens[0][0] = :LEADING_SPACE
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Special case: if the line is just whitespace followed by 'end', skip
|
|
114
|
+
# the LEADING_SPACE. This helps the parser avoid shift/reduce conflicts
|
|
115
|
+
# with group_body rules
|
|
116
|
+
if line_tokens.length >= 2 &&
|
|
117
|
+
line_tokens[0][0] == :LEADING_SPACE &&
|
|
118
|
+
line_tokens[1][0] == :END &&
|
|
119
|
+
(line_tokens.length == 2 || line_tokens[2][0] == :SPACE)
|
|
120
|
+
# Skip the leading space token for 'end' keyword
|
|
121
|
+
line_tokens.shift
|
|
122
|
+
end
|
|
64
123
|
|
|
65
|
-
|
|
124
|
+
# Convert last SPACE token (before end) to TRAILING_SPACE if it exists
|
|
125
|
+
last_non_newline = line_tokens.length - 1
|
|
126
|
+
|
|
127
|
+
while last_non_newline >= 0 &&
|
|
128
|
+
line_tokens[last_non_newline][0] == :NEWLINE
|
|
129
|
+
last_non_newline -= 1
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
if last_non_newline >= 0 && line_tokens[last_non_newline][0] == :SPACE
|
|
133
|
+
line_tokens[last_non_newline][0] = :TRAILING_SPACE
|
|
134
|
+
end
|
|
66
135
|
|
|
67
136
|
# Check if any token starts a multiline string
|
|
68
|
-
|
|
137
|
+
multiline_index = line_tokens.find_index do |t|
|
|
69
138
|
t[0] == :TRIPLE_QUOTE_START
|
|
70
139
|
end
|
|
71
|
-
|
|
140
|
+
|
|
141
|
+
if multiline_index
|
|
72
142
|
# Process tokens before the triple quote
|
|
73
|
-
line_tokens[0...
|
|
143
|
+
line_tokens[0...multiline_index].each do |token|
|
|
74
144
|
col = token[2] || 1
|
|
75
145
|
raw = token[3] || token[1].to_s
|
|
76
146
|
|
|
77
147
|
@line_map[token_index] = {
|
|
78
148
|
line: @line_number,
|
|
79
149
|
column: col,
|
|
80
|
-
content: original_line.
|
|
150
|
+
content: original_line.chomp,
|
|
81
151
|
raw: raw
|
|
82
152
|
}
|
|
83
153
|
|
|
@@ -86,12 +156,12 @@ module DemoTape
|
|
|
86
156
|
token_index += 1
|
|
87
157
|
end
|
|
88
158
|
|
|
89
|
-
tokens.concat(line_tokens[0...
|
|
159
|
+
tokens.concat(line_tokens[0...multiline_index])
|
|
90
160
|
|
|
91
161
|
# Start multiline mode
|
|
92
162
|
in_multiline = true
|
|
93
163
|
multiline_start_line = @line_number
|
|
94
|
-
multiline_start_col = line_tokens[
|
|
164
|
+
multiline_start_col = line_tokens[multiline_index][2] || 1
|
|
95
165
|
multiline_content = []
|
|
96
166
|
next
|
|
97
167
|
end
|
|
@@ -104,7 +174,7 @@ module DemoTape
|
|
|
104
174
|
@line_map[token_index] = {
|
|
105
175
|
line: @line_number,
|
|
106
176
|
column: col,
|
|
107
|
-
content: original_line.
|
|
177
|
+
content: original_line.chomp,
|
|
108
178
|
raw: raw
|
|
109
179
|
}
|
|
110
180
|
|
|
@@ -119,7 +189,7 @@ module DemoTape
|
|
|
119
189
|
@line_map[token_index] = {
|
|
120
190
|
line: @line_number,
|
|
121
191
|
column: 1,
|
|
122
|
-
content: original_line.
|
|
192
|
+
content: original_line.chomp
|
|
123
193
|
}
|
|
124
194
|
|
|
125
195
|
tokens << [:NEWLINE, "\n"]
|
|
@@ -157,22 +227,36 @@ module DemoTape
|
|
|
157
227
|
|
|
158
228
|
# Regex pattern (between forward slashes)
|
|
159
229
|
elsif scanner.scan(%r{/((?:[^/\\]|\\.)*)/})
|
|
160
|
-
|
|
230
|
+
result = begin
|
|
231
|
+
{pattern: Regexp.new(scanner[1])}
|
|
232
|
+
rescue RegexpError => error
|
|
233
|
+
{error: error.message.gsub("/#{scanner[1]}/", "").strip.chomp(":")}
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
tokens << [:REGEX, result, col, scanner[0]]
|
|
161
237
|
|
|
162
238
|
# Duration (number + time unit - any identifier)
|
|
163
|
-
elsif scanner.scan(/(\d+(?:\.\d+)
|
|
239
|
+
elsif scanner.scan(/(\d+(?:\.\d+)?|-?\.\d+)([a-zA-Z]+)/)
|
|
240
|
+
number_part = scanner[1]
|
|
241
|
+
unit_part = scanner[2]
|
|
242
|
+
full_match = scanner[0]
|
|
164
243
|
tokens << [
|
|
165
|
-
:
|
|
166
|
-
|
|
244
|
+
:DURATION,
|
|
245
|
+
{
|
|
246
|
+
number: if number_part.include?(".")
|
|
247
|
+
number_part.to_f
|
|
248
|
+
else
|
|
249
|
+
number_part.to_i
|
|
250
|
+
end,
|
|
251
|
+
unit: unit_part,
|
|
252
|
+
raw: full_match
|
|
253
|
+
},
|
|
167
254
|
col,
|
|
168
|
-
|
|
255
|
+
full_match
|
|
169
256
|
]
|
|
170
|
-
# TIME_UNIT starts after the number
|
|
171
|
-
time_unit_col = col + scanner[1].length
|
|
172
|
-
tokens << [:TIME_UNIT, scanner[2], time_unit_col, scanner[2]]
|
|
173
257
|
|
|
174
|
-
# Number
|
|
175
|
-
elsif scanner.scan(
|
|
258
|
+
# Number (including negative numbers)
|
|
259
|
+
elsif scanner.scan(/-?(?:\d+(?:\.\d+)?|\.\d+)/)
|
|
176
260
|
value = scanner[0].include?(".") ? scanner[0].to_f : scanner[0].to_i
|
|
177
261
|
tokens << [:NUMBER, value, col, scanner[0]]
|
|
178
262
|
|
|
@@ -190,14 +274,14 @@ module DemoTape
|
|
|
190
274
|
|
|
191
275
|
# Identifier (including dot notation for nested options)
|
|
192
276
|
elsif scanner.scan(/[a-zA-Z_][\w.]*/)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
277
|
+
# Check for keywords
|
|
278
|
+
value = scanner[0]
|
|
279
|
+
token_type = case value
|
|
280
|
+
when "do" then :DO
|
|
281
|
+
when "end" then :END
|
|
282
|
+
else :IDENTIFIER
|
|
283
|
+
end
|
|
284
|
+
tokens << [token_type, value, col, value]
|
|
201
285
|
end
|
|
202
286
|
end
|
|
203
287
|
|