gloo 6.0.1 → 6.2.0
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/CLAUDE.md +9 -3
- data/README.md +39 -7
- data/docs/application.md +184 -0
- data/docs/getting_started.md +110 -0
- data/docs/iterators.md +294 -0
- data/docs/language_objects.md +190 -0
- data/docs/language_scripting.md +62 -0
- data/docs/language_syntax.md +334 -0
- data/docs/objects.md +77 -0
- data/docs/operators.md +62 -0
- data/docs/plugins.md +376 -0
- data/docs/verbs.md +64 -0
- data/docs/web_app.md +211 -0
- data/gloo.gemspec +3 -1
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +16 -0
- data/lib/gloo/app/engine.rb +17 -2
- data/lib/gloo/app/log.rb +5 -4
- data/lib/gloo/app/platform.rb +34 -2
- data/lib/gloo/app/prompt.rb +2 -1
- data/lib/gloo/app/settings.rb +25 -15
- data/lib/gloo/app/table.rb +7 -3
- data/lib/gloo/app/theme.rb +144 -0
- data/lib/gloo/core/gloo_system.rb +52 -1
- data/lib/gloo/core/invoker.rb +102 -0
- data/lib/gloo/core/parser.rb +48 -7
- data/lib/gloo/core/tokens.rb +81 -1
- data/lib/gloo/docs/doc_data.rb +160 -0
- data/lib/gloo/docs/help_shell.rb +332 -0
- data/lib/gloo/docs/markdown_renderer.rb +39 -0
- data/lib/gloo/expr/call.rb +54 -0
- data/lib/gloo/expr/expression.rb +3 -0
- data/lib/gloo/objs/basic/alias.rb +53 -0
- data/lib/gloo/objs/basic/boolean.rb +30 -0
- data/lib/gloo/objs/basic/container.rb +38 -0
- data/lib/gloo/objs/basic/decimal.rb +30 -0
- data/lib/gloo/objs/basic/integer.rb +58 -0
- data/lib/gloo/objs/basic/script.rb +31 -0
- data/lib/gloo/objs/basic/string.rb +31 -4
- data/lib/gloo/objs/basic/string_msgs.rb +212 -2
- data/lib/gloo/objs/basic/text.rb +30 -4
- data/lib/gloo/objs/basic/untyped.rb +21 -0
- data/lib/gloo/objs/ctrl/each.rb +63 -1
- data/lib/gloo/objs/ctrl/function.rb +86 -4
- data/lib/gloo/objs/ctrl/repeat.rb +41 -0
- data/lib/gloo/objs/dt/date.rb +43 -0
- data/lib/gloo/objs/dt/datetime.rb +50 -0
- data/lib/gloo/objs/dt/time.rb +43 -0
- data/lib/gloo/objs/str_utils/cipher.rb +59 -0
- data/lib/gloo/objs/str_utils/outline.rb +65 -1
- data/lib/gloo/objs/str_utils/password.rb +56 -0
- data/lib/gloo/objs/system/erb.rb +39 -0
- data/lib/gloo/objs/system/file_handle.rb +56 -0
- data/lib/gloo/objs/system/system.rb +29 -0
- data/lib/gloo/objs/web/http_get.rb +35 -0
- data/lib/gloo/objs/web/http_post.rb +33 -0
- data/lib/gloo/objs/web/json.rb +40 -0
- data/lib/gloo/objs/web/uri.rb +40 -0
- data/lib/gloo/shell/command_node.rb +39 -0
- data/lib/gloo/shell/context.rb +93 -0
- data/lib/gloo/shell/runner.rb +315 -0
- data/lib/gloo/verbs/break.rb +32 -0
- data/lib/gloo/verbs/check.rb +49 -0
- data/lib/gloo/verbs/cls.rb +18 -0
- data/lib/gloo/verbs/context.rb +47 -0
- data/lib/gloo/verbs/create.rb +42 -0
- data/lib/gloo/verbs/eval.rb +27 -0
- data/lib/gloo/verbs/execute.rb +27 -0
- data/lib/gloo/verbs/exists.rb +54 -2
- data/lib/gloo/verbs/files.rb +22 -0
- data/lib/gloo/verbs/help.rb +46 -178
- data/lib/gloo/verbs/if.rb +52 -0
- data/lib/gloo/verbs/invoke.rb +88 -33
- data/lib/gloo/verbs/list.rb +37 -4
- data/lib/gloo/verbs/load.rb +55 -2
- data/lib/gloo/verbs/log.rb +44 -0
- data/lib/gloo/verbs/move.rb +36 -1
- data/lib/gloo/verbs/put.rb +35 -0
- data/lib/gloo/verbs/quit.rb +19 -0
- data/lib/gloo/verbs/redirect.rb +52 -2
- data/lib/gloo/verbs/reload.rb +27 -0
- data/lib/gloo/verbs/run.rb +39 -0
- data/lib/gloo/verbs/save.rb +26 -0
- data/lib/gloo/verbs/show.rb +54 -0
- data/lib/gloo/verbs/tell.rb +32 -0
- data/lib/gloo/verbs/throw.rb +29 -0
- data/lib/gloo/verbs/unless.rb +50 -0
- data/lib/gloo/verbs/unload.rb +23 -0
- data/lib/gloo/verbs/version.rb +37 -3
- data/lib/gloo/verbs/wait.rb +26 -0
- data/test.gloo/objs/string.test.gloo +54 -0
- data/test.gloo/verbs/invoke.test.gloo +73 -0
- metadata +26 -10
- data/.DS_Store +0 -0
- data/.travis.yml +0 -5
data/gloo.gemspec
CHANGED
|
@@ -11,9 +11,11 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
|
|
12
12
|
spec.summary = %q{Gloo scripting language. A scripting language built on ruby.}
|
|
13
13
|
spec.description = %q{A scripting languge to keep it all together.}
|
|
14
|
-
spec.homepage = "
|
|
14
|
+
spec.homepage = "https://github.com/ecrane/gloo"
|
|
15
15
|
spec.license = 'MIT'
|
|
16
16
|
|
|
17
|
+
spec.metadata['documentation_uri'] = 'https://github.com/ecrane/gloo'
|
|
18
|
+
|
|
17
19
|
# Specify which files should be added to the gem when it is released.
|
|
18
20
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
19
21
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
data/lib/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.0
|
|
1
|
+
6.2.0
|
data/lib/VERSION_NOTES
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
6.2.0 - 2026.08.18
|
|
2
|
+
- Adds light | dark mode option for terminal output.
|
|
3
|
+
- Adds inline function invocation.
|
|
4
|
+
- Adds split messages for strings and text objects.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
6.1.0 - 2026.08.04
|
|
8
|
+
- Adds narrative documentation: 10 pages in `docs/`, covering getting started, application, language objects/syntax/scripting, operators, iterators, objects, verbs, and plugins
|
|
9
|
+
- Adds an in-app interactive help shell (`help`/`?`) to look up verbs, objects, settings, libraries, extensions, and doc pages, with tab completion
|
|
10
|
+
- Adds `doc_data` to every verb and object type, in the interpreter and its core library gems, rendered by the help shell
|
|
11
|
+
- Fixes gemspecs so docs and READMEs are actually packaged with the gem and its core libraries
|
|
12
|
+
- Adds a table of contents and reading order to the README's documentation list
|
|
13
|
+
- Fixes the rubygems.org "Documentation" link
|
|
14
|
+
- Adds page message to string and text
|
|
15
|
+
|
|
16
|
+
|
|
1
17
|
6.0.1 - 2026.07.16
|
|
2
18
|
- Update dependencies to newer versions for better security.
|
|
3
19
|
|
data/lib/gloo/app/engine.rb
CHANGED
|
@@ -31,10 +31,14 @@ module Gloo
|
|
|
31
31
|
@args = Args.new( self, context.params )
|
|
32
32
|
@settings = Settings.new( self, context.user_root )
|
|
33
33
|
|
|
34
|
+
# Platform (and its theme) needs to be ready before Log is
|
|
35
|
+
# constructed - Log reads engine.theme, which reads through
|
|
36
|
+
# to platform.theme, in its own initialize.
|
|
37
|
+
@platform = context.platform
|
|
38
|
+
@platform.theme = Gloo::App::Theme.new( @settings.theme )
|
|
39
|
+
|
|
34
40
|
@log = context.log.new( self, @args.quiet? )
|
|
35
41
|
@log.debug "log (class: #{@log.class.name}) in use ..."
|
|
36
|
-
|
|
37
|
-
@platform = context.platform
|
|
38
42
|
@log.debug "platform (class: #{@platform.class.name}) in use ..."
|
|
39
43
|
|
|
40
44
|
@handling_exception = false
|
|
@@ -42,6 +46,17 @@ module Gloo
|
|
|
42
46
|
@log.debug 'engine intialized...'
|
|
43
47
|
end
|
|
44
48
|
|
|
49
|
+
#
|
|
50
|
+
# Get the active theme. Platform is the single source of
|
|
51
|
+
# truth for it (Prompt/Table only hold a @platform reference,
|
|
52
|
+
# not an @engine one) - this just reads through to that,
|
|
53
|
+
# rather than keeping a second ivar that could drift out of
|
|
54
|
+
# sync if something reassigns platform.theme directly.
|
|
55
|
+
#
|
|
56
|
+
def theme
|
|
57
|
+
return @platform.theme
|
|
58
|
+
end
|
|
59
|
+
|
|
45
60
|
#
|
|
46
61
|
# Start the engine.
|
|
47
62
|
# Load object and verb definitions and setup engine elements.
|
data/lib/gloo/app/log.rb
CHANGED
|
@@ -38,6 +38,7 @@ module Gloo
|
|
|
38
38
|
@engine = engine
|
|
39
39
|
@quiet = quiet
|
|
40
40
|
@debug = engine.settings.debug
|
|
41
|
+
@theme = engine.theme
|
|
41
42
|
|
|
42
43
|
create_loggers
|
|
43
44
|
|
|
@@ -160,7 +161,7 @@ module Gloo
|
|
|
160
161
|
def warn( msg )
|
|
161
162
|
@logger.warn msg
|
|
162
163
|
@error.warn msg
|
|
163
|
-
puts
|
|
164
|
+
puts @theme.warn( msg ) unless @quiet
|
|
164
165
|
end
|
|
165
166
|
|
|
166
167
|
#
|
|
@@ -175,11 +176,11 @@ module Gloo
|
|
|
175
176
|
if ex
|
|
176
177
|
@error.error ex.message
|
|
177
178
|
@error.error ex.backtrace
|
|
178
|
-
puts
|
|
179
|
-
puts ex.message
|
|
179
|
+
puts @theme.error( msg ) unless @quiet
|
|
180
|
+
puts @theme.error( ex.message ) unless @quiet
|
|
180
181
|
puts ex.backtrace unless @quiet
|
|
181
182
|
else
|
|
182
|
-
puts
|
|
183
|
+
puts @theme.error( msg ) unless @quiet
|
|
183
184
|
end
|
|
184
185
|
end
|
|
185
186
|
|
data/lib/gloo/app/platform.rb
CHANGED
|
@@ -13,8 +13,12 @@ module Gloo
|
|
|
13
13
|
|
|
14
14
|
DEFAULT_TMP_FILE = 'tmp.txt'.freeze
|
|
15
15
|
RETURN = "\n".freeze
|
|
16
|
+
DEFAULT_LINES = 24
|
|
17
|
+
DEFAULT_COLS = 80
|
|
18
|
+
PAGER_CMD = 'less -R -F -X'.freeze
|
|
16
19
|
|
|
17
20
|
attr_reader :prompt, :table
|
|
21
|
+
attr_accessor :theme
|
|
18
22
|
|
|
19
23
|
#
|
|
20
24
|
# Set up Platform.
|
|
@@ -22,6 +26,10 @@ module Gloo
|
|
|
22
26
|
def initialize
|
|
23
27
|
@prompt = Gloo::App::Prompt.new( self )
|
|
24
28
|
@table = Gloo::App::Table.new( self )
|
|
29
|
+
|
|
30
|
+
# Default theme, in case this platform is ever used
|
|
31
|
+
# standalone, without an Engine to set the real one.
|
|
32
|
+
@theme = Gloo::App::Theme.new
|
|
25
33
|
end
|
|
26
34
|
|
|
27
35
|
#
|
|
@@ -31,6 +39,22 @@ module Gloo
|
|
|
31
39
|
puts msg
|
|
32
40
|
end
|
|
33
41
|
|
|
42
|
+
#
|
|
43
|
+
# Show a message in a pager (less), for long content.
|
|
44
|
+
# Falls back to a plain puts when there is no real terminal
|
|
45
|
+
# to page in (piped output, captured test output, etc) or
|
|
46
|
+
# when less isn't available on the system.
|
|
47
|
+
#
|
|
48
|
+
def page( msg )
|
|
49
|
+
return show( msg ) unless $stdout.tty?
|
|
50
|
+
|
|
51
|
+
IO.popen( PAGER_CMD, 'w' ) { |less| less.puts msg }
|
|
52
|
+
rescue Errno::ENOENT
|
|
53
|
+
show( msg )
|
|
54
|
+
rescue Errno::EPIPE
|
|
55
|
+
# The user quit the pager early. Nothing more to do.
|
|
56
|
+
end
|
|
57
|
+
|
|
34
58
|
#
|
|
35
59
|
# Clear the screen.
|
|
36
60
|
#
|
|
@@ -64,18 +88,26 @@ module Gloo
|
|
|
64
88
|
# ---------------------------------------------------------------------
|
|
65
89
|
#
|
|
66
90
|
# Get the number of vertical lines on screen.
|
|
91
|
+
# Falls back to a default when stdout has no real screen behind it
|
|
92
|
+
# (piped/captured output, no controlling TTY, etc).
|
|
67
93
|
#
|
|
68
94
|
def lines
|
|
69
|
-
rows,
|
|
95
|
+
rows, _columns = $stdout.winsize
|
|
70
96
|
return rows
|
|
97
|
+
rescue StandardError
|
|
98
|
+
return DEFAULT_LINES
|
|
71
99
|
end
|
|
72
100
|
|
|
73
101
|
#
|
|
74
102
|
# Get the number of horizontal columns on screen.
|
|
103
|
+
# Falls back to a default when stdout has no real screen behind it
|
|
104
|
+
# (piped/captured output, no controlling TTY, etc).
|
|
75
105
|
#
|
|
76
106
|
def cols
|
|
77
|
-
|
|
107
|
+
_rows, columns = $stdout.winsize
|
|
78
108
|
return columns
|
|
109
|
+
rescue StandardError
|
|
110
|
+
return DEFAULT_COLS
|
|
79
111
|
end
|
|
80
112
|
|
|
81
113
|
# ---------------------------------------------------------------------
|
data/lib/gloo/app/prompt.rb
CHANGED
|
@@ -87,7 +87,8 @@ module Gloo
|
|
|
87
87
|
dt = DateTime.now
|
|
88
88
|
d = dt.strftime( '%Y.%m.%d' )
|
|
89
89
|
t = dt.strftime( '%I:%M:%S' )
|
|
90
|
-
|
|
90
|
+
theme = @platform.theme
|
|
91
|
+
return "#{theme.heading( 'gloo' )} #{theme.accent( d )} #{theme.emphasis( t )} >"
|
|
91
92
|
end
|
|
92
93
|
|
|
93
94
|
end
|
data/lib/gloo/app/settings.rb
CHANGED
|
@@ -10,10 +10,13 @@ module Gloo
|
|
|
10
10
|
module App
|
|
11
11
|
class Settings
|
|
12
12
|
|
|
13
|
+
DEFAULT_THEME = 'dark'.freeze
|
|
14
|
+
THEMES = %w[dark light].freeze
|
|
15
|
+
|
|
13
16
|
attr_reader :user_root, :log_path,
|
|
14
17
|
:config_path, :project_path, :ext_path,
|
|
15
18
|
:start_with, :list_indent, :list_levels, :tmp_path,
|
|
16
|
-
:debug_path, :debug
|
|
19
|
+
:debug_path, :debug, :theme
|
|
17
20
|
|
|
18
21
|
#
|
|
19
22
|
# Load setting from the yml file.
|
|
@@ -33,26 +36,29 @@ module Gloo
|
|
|
33
36
|
# Can be seen in app with 'help settings'
|
|
34
37
|
#
|
|
35
38
|
def show
|
|
36
|
-
|
|
37
|
-
puts
|
|
38
|
-
puts '
|
|
39
|
-
puts '
|
|
40
|
-
puts '
|
|
41
|
-
puts '
|
|
42
|
-
puts '
|
|
39
|
+
theme = @engine.theme
|
|
40
|
+
puts theme.heading( "\n Application Settings:" )
|
|
41
|
+
puts theme.accent( ' Startup with: ' ) + theme.emphasis( @start_with.to_s )
|
|
42
|
+
puts theme.accent( ' Indent in Listing: ' ) + theme.emphasis( @list_indent.to_s )
|
|
43
|
+
puts theme.accent( ' List Levels: ' ) + theme.emphasis( @list_levels.to_s )
|
|
44
|
+
puts theme.accent( ' Debug? ' ) + theme.emphasis( @debug.to_s )
|
|
45
|
+
puts theme.accent( ' Theme: ' ) + theme.emphasis( @theme )
|
|
46
|
+
puts theme.accent( ' Screen Lines: ' ) + theme.emphasis( Gloo::App::Settings.lines( @engine ).to_s )
|
|
47
|
+
puts theme.accent( ' Page Size: ' ) + theme.emphasis( Gloo::App::Settings.page_size( @engine ).to_s )
|
|
43
48
|
self.show_paths
|
|
44
49
|
end
|
|
45
|
-
|
|
50
|
+
|
|
46
51
|
#
|
|
47
52
|
# Show path settings
|
|
48
53
|
#
|
|
49
54
|
def show_paths
|
|
50
|
-
|
|
51
|
-
puts
|
|
52
|
-
puts '
|
|
53
|
-
puts '
|
|
54
|
-
puts '
|
|
55
|
-
puts '
|
|
55
|
+
theme = @engine.theme
|
|
56
|
+
puts theme.heading( "\n Application Paths:" )
|
|
57
|
+
puts theme.accent( ' User Root Path is here: ' ) + theme.emphasis( @user_root )
|
|
58
|
+
puts theme.accent( ' Projects Path: ' ) + theme.emphasis( @project_path )
|
|
59
|
+
puts theme.accent( ' Extensions Path: ' ) + theme.emphasis( @ext_path )
|
|
60
|
+
puts theme.accent( ' Tmp Path: ' ) + theme.emphasis( @tmp_path )
|
|
61
|
+
puts theme.accent( ' Debug Path: ' ) + theme.emphasis( @debug_path )
|
|
56
62
|
puts "\n"
|
|
57
63
|
end
|
|
58
64
|
|
|
@@ -154,6 +160,9 @@ module Gloo
|
|
|
154
160
|
@list_levels = settings[ 'gloo' ][ 'list_levels' ]
|
|
155
161
|
|
|
156
162
|
@debug = settings[ 'gloo' ][ 'debug' ]
|
|
163
|
+
|
|
164
|
+
@theme = settings[ 'gloo' ][ 'theme' ]&.strip&.downcase
|
|
165
|
+
@theme = DEFAULT_THEME unless THEMES.include?( @theme )
|
|
157
166
|
end
|
|
158
167
|
|
|
159
168
|
#
|
|
@@ -184,6 +193,7 @@ module Gloo
|
|
|
184
193
|
list_indent: 2
|
|
185
194
|
list_levels: 3
|
|
186
195
|
debug: false
|
|
196
|
+
theme: #{DEFAULT_THEME}
|
|
187
197
|
TEXT
|
|
188
198
|
return str
|
|
189
199
|
end
|
data/lib/gloo/app/table.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
#
|
|
4
4
|
# CLI input.
|
|
5
5
|
#
|
|
6
|
-
require 'colorize'
|
|
7
6
|
require 'terminal-table'
|
|
8
7
|
|
|
9
8
|
module Gloo
|
|
@@ -28,14 +27,19 @@ module Gloo
|
|
|
28
27
|
#
|
|
29
28
|
# Show the given table data.
|
|
30
29
|
#
|
|
30
|
+
# Deliberately left uncolored: box-drawing borders and text read
|
|
31
|
+
# fine against the terminal's own default colors, and forcing a
|
|
32
|
+
# fixed foreground/background (as this used to do) fought
|
|
33
|
+
# whatever theme the terminal was actually running.
|
|
34
|
+
#
|
|
31
35
|
def show( headers, data, title = nil )
|
|
32
36
|
unless title.blank?
|
|
33
|
-
table = Terminal::Table.new(
|
|
37
|
+
table = Terminal::Table.new(
|
|
34
38
|
:title => title, :headings => headers, :rows => data )
|
|
35
39
|
else
|
|
36
40
|
table = Terminal::Table.new( :headings => headers, :rows => data )
|
|
37
41
|
end
|
|
38
|
-
puts table.to_s
|
|
42
|
+
puts table.to_s
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
2
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Maps semantic color roles (heading, emphasis, etc) onto actual
|
|
5
|
+
# ANSI colors for the app's UI chrome (prompt, settings, log,
|
|
6
|
+
# tables, listings, help). Callers ask for a role, never a literal
|
|
7
|
+
# color, so a single palette swap (dark/light) fixes every call
|
|
8
|
+
# site at once.
|
|
9
|
+
#
|
|
10
|
+
# NOT used by the user-facing `show {target} (color)` verb or the
|
|
11
|
+
# `[colorize]` core-lib object - those are explicit color choices
|
|
12
|
+
# made in gloo scripts, not app chrome, and are intentionally left
|
|
13
|
+
# alone.
|
|
14
|
+
#
|
|
15
|
+
# Design note: most of the base 8 ANSI colors (red/green/yellow/
|
|
16
|
+
# blue/magenta/cyan) are exactly what a well-built terminal color
|
|
17
|
+
# scheme remaps per-background to stay legible - that's what
|
|
18
|
+
# switching a terminal profile from dark to light is for. `white`
|
|
19
|
+
# is the exception: by convention it stays "lightest foreground"
|
|
20
|
+
# regardless of background, so text forced to `.white` goes
|
|
21
|
+
# near-invisible on a light background no matter how good the
|
|
22
|
+
# terminal theme is. That's why :emphasis flips color between the
|
|
23
|
+
# two palettes below.
|
|
24
|
+
#
|
|
25
|
+
# `yellow` and `cyan` turned out to be further exceptions in
|
|
26
|
+
# practice: tested against a real light-background terminal, both
|
|
27
|
+
# were washed out and hard to read - the terminal's own theme
|
|
28
|
+
# wasn't remapping them as dark/saturated as hoped. So the light
|
|
29
|
+
# palette diverges from dark on more than just :emphasis, using
|
|
30
|
+
# explicit 256-color values (:ansi, raw SGR codes - none of these
|
|
31
|
+
# have a true equivalent in the base 16 ANSI colors) picked by eye
|
|
32
|
+
# against a real light terminal:
|
|
33
|
+
# - :warn uses orange instead of yellow
|
|
34
|
+
# - :accent uses a dark green instead of yellow
|
|
35
|
+
# - :subheading uses navy instead of cyan
|
|
36
|
+
#
|
|
37
|
+
require 'colorize'
|
|
38
|
+
require 'colorized_string'
|
|
39
|
+
|
|
40
|
+
module Gloo
|
|
41
|
+
module App
|
|
42
|
+
class Theme
|
|
43
|
+
|
|
44
|
+
# 256-color values picked by eye against a real light-background
|
|
45
|
+
# terminal - see design note above. 256-color support has been
|
|
46
|
+
# effectively universal in terminals for well over a decade.
|
|
47
|
+
ORANGE = '38;5;208'.freeze
|
|
48
|
+
NAVY = '38;5;18'.freeze
|
|
49
|
+
DARK_GREEN = '38;5;22'.freeze
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
# Dark-background palette. This matches the colors gloo
|
|
53
|
+
# has always used, so it's the default and non-breaking.
|
|
54
|
+
#
|
|
55
|
+
DARK = {
|
|
56
|
+
:heading => { :color => :blue, :mode => :bold },
|
|
57
|
+
:subheading => { :color => :cyan, :mode => :bold },
|
|
58
|
+
:accent => { :color => :yellow },
|
|
59
|
+
:emphasis => { :color => :white },
|
|
60
|
+
:muted => { :color => :light_black },
|
|
61
|
+
:warn => { :color => :yellow },
|
|
62
|
+
:error => { :color => :red }
|
|
63
|
+
}.freeze
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Light-background palette.
|
|
67
|
+
#
|
|
68
|
+
LIGHT = {
|
|
69
|
+
:heading => { :color => :blue, :mode => :bold },
|
|
70
|
+
:subheading => { :ansi => NAVY },
|
|
71
|
+
:accent => { :ansi => DARK_GREEN },
|
|
72
|
+
:emphasis => { :color => :black },
|
|
73
|
+
:muted => { :color => :light_black },
|
|
74
|
+
:warn => { :ansi => ORANGE },
|
|
75
|
+
:error => { :color => :red }
|
|
76
|
+
}.freeze
|
|
77
|
+
|
|
78
|
+
PALETTES = { 'dark' => DARK, 'light' => LIGHT }.freeze
|
|
79
|
+
ROLES = DARK.keys.freeze
|
|
80
|
+
|
|
81
|
+
attr_reader :name
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Set up the theme for the given theme name ('dark' or 'light').
|
|
85
|
+
# Falls back to the default (dark) for anything else.
|
|
86
|
+
#
|
|
87
|
+
def initialize( name = nil )
|
|
88
|
+
@name = PALETTES.key?( name ) ? name : Gloo::App::Settings::DEFAULT_THEME
|
|
89
|
+
@palette = PALETTES[ @name ]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#
|
|
93
|
+
# Build a Theme from the engine's settings.
|
|
94
|
+
#
|
|
95
|
+
def self.for_engine( engine )
|
|
96
|
+
return new( engine&.settings&.theme )
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# Colorize the given string for the given semantic role.
|
|
101
|
+
# Unknown roles are returned unchanged rather than raising,
|
|
102
|
+
# so a typo'd role degrades to plain text instead of crashing.
|
|
103
|
+
#
|
|
104
|
+
# Named colors (:color/:mode) go through the colorize gem;
|
|
105
|
+
# :ansi is a raw SGR parameter string for colors the gem's
|
|
106
|
+
# named palette doesn't have (eg. 256-color orange).
|
|
107
|
+
#
|
|
108
|
+
def apply( str, role )
|
|
109
|
+
style = @palette[ role ]
|
|
110
|
+
return str.to_s unless style
|
|
111
|
+
return ansi_wrap( str.to_s, style ) if style[ :ansi ]
|
|
112
|
+
|
|
113
|
+
params = {}
|
|
114
|
+
params[ :color ] = style[ :color ] if style[ :color ]
|
|
115
|
+
params[ :mode ] = style[ :mode ] if style[ :mode ]
|
|
116
|
+
return ColorizedString[ str.to_s ].colorize( params ).to_s
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# Define a convenience method per role, eg. theme.heading( str ).
|
|
121
|
+
#
|
|
122
|
+
ROLES.each do |role|
|
|
123
|
+
define_method( role ) do |str|
|
|
124
|
+
apply( str, role )
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
#
|
|
131
|
+
# Wrap a string in a raw SGR escape sequence - used for :ansi
|
|
132
|
+
# styles (eg. 256-color orange) that the colorize gem's named
|
|
133
|
+
# 16-color palette can't express.
|
|
134
|
+
#
|
|
135
|
+
def ansi_wrap( str, style )
|
|
136
|
+
codes = []
|
|
137
|
+
codes << String.mode_codes[ style[ :mode ] ] if style[ :mode ]
|
|
138
|
+
codes << style[ :ansi ]
|
|
139
|
+
return "\e[#{codes.join( ';' )}m#{str}\e[0m"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -278,13 +278,64 @@ module Gloo
|
|
|
278
278
|
def self.open_for_platform
|
|
279
279
|
return 'open' if OS.mac?
|
|
280
280
|
return 'xdg-open' if OS.posix?
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
return 'Start-Process' if OS.windows?
|
|
283
283
|
return 'explorer.exe' if self.wsl?
|
|
284
284
|
|
|
285
285
|
return nil
|
|
286
286
|
end
|
|
287
287
|
|
|
288
|
+
# ---------------------------------------------------------------------
|
|
289
|
+
# Object Documentation
|
|
290
|
+
# ---------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
#
|
|
293
|
+
# Get the object's documentation data.
|
|
294
|
+
#
|
|
295
|
+
def self.doc_data
|
|
296
|
+
{
|
|
297
|
+
:name => KEYWORD,
|
|
298
|
+
:shortcut => KEYWORD_SHORT,
|
|
299
|
+
:description => 'The gloo system objects are virtual objects — ' \
|
|
300
|
+
"accessed like other objects (via 'gloo' or the shortcut " \
|
|
301
|
+
'$), but their values are set by the system and cannot be ' \
|
|
302
|
+
'updated. They also do not show up in the object heap. Some ' \
|
|
303
|
+
'names include an underscore to separate words; a period ' \
|
|
304
|
+
'can be used instead, so gloo.working_dir and ' \
|
|
305
|
+
'gloo.working.dir are identical.',
|
|
306
|
+
:children => [
|
|
307
|
+
'app — Path of the running app (same as gloo_projects).',
|
|
308
|
+
'hostname — Get the system hostname.',
|
|
309
|
+
'user — Get the logged in user.',
|
|
310
|
+
'line — A carriage return (line feed) character.',
|
|
311
|
+
"user_home — Get the user's home directory.",
|
|
312
|
+
'working_dir — Get the working directory.',
|
|
313
|
+
'gloo_home — Get the gloo home directory.',
|
|
314
|
+
'gloo_config — Get the gloo configuration directory.',
|
|
315
|
+
'gloo_projects — Get the gloo projects directory.',
|
|
316
|
+
'gloo_log — Get the gloo logging directory.',
|
|
317
|
+
'screen_lines — Get the number of lines on screen.',
|
|
318
|
+
'screen_cols — Get the number of columns on screen.',
|
|
319
|
+
'platform_cpu — Get the platform CPU.',
|
|
320
|
+
'platform_os — Get the platform operating system.',
|
|
321
|
+
'platform_version — Get the platform version.',
|
|
322
|
+
'platform_windows? — Is the platform Windows?',
|
|
323
|
+
'platform_unix? — Is the platform Unix?',
|
|
324
|
+
'platform_linux? — Is the platform Linux?',
|
|
325
|
+
'platform_mac? — Is the platform Mac?'
|
|
326
|
+
],
|
|
327
|
+
:examples => <<~EXAMPLES.strip
|
|
328
|
+
> show gloo.user
|
|
329
|
+
> show $.user
|
|
330
|
+
|
|
331
|
+
> show gloo.working_dir
|
|
332
|
+
> show gloo.working.dir
|
|
333
|
+
> show $.working_dir
|
|
334
|
+
> show $.working.dir
|
|
335
|
+
EXAMPLES
|
|
336
|
+
}
|
|
337
|
+
end
|
|
338
|
+
|
|
288
339
|
end
|
|
289
340
|
end
|
|
290
341
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
2
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Resolves, validates and invokes a function. Shared by the
|
|
5
|
+
# `invoke`/`~>` verb (lib/gloo/verbs/invoke.rb) and inline calls
|
|
6
|
+
# inside any expression (lib/gloo/expr/call.rb), so both go through
|
|
7
|
+
# the same error handling instead of maintaining two copies of it.
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
module Gloo
|
|
11
|
+
module Core
|
|
12
|
+
class Invoker
|
|
13
|
+
|
|
14
|
+
NO_TARGET_ERR = 'Missing function reference!'.freeze
|
|
15
|
+
NOT_FOUND_ERR = 'Object was not found: '.freeze
|
|
16
|
+
NOT_FUNCTION_ERR = 'Not a function: '.freeze
|
|
17
|
+
PARAM_COUNT_ERR = 'Wrong number of parameters for function: '.freeze
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# Resolve, validate and invoke the function at the given
|
|
21
|
+
# target path, with the given raw (unevaluated) arg tokens.
|
|
22
|
+
# Reports an error and returns nil for any failure - including
|
|
23
|
+
# a failure inside the function itself (see Function#invoke,
|
|
24
|
+
# which leaves `it` alone rather than setting it to an
|
|
25
|
+
# unreliable result when that happens).
|
|
26
|
+
#
|
|
27
|
+
def self.invoke( engine, target, arg_tokens )
|
|
28
|
+
if target.nil? || target.to_s.strip.empty?
|
|
29
|
+
engine.err NO_TARGET_ERR
|
|
30
|
+
return nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
func = resolve_function( engine, target )
|
|
34
|
+
return nil unless func
|
|
35
|
+
|
|
36
|
+
args = evaluate_arg_tokens( engine, arg_tokens )
|
|
37
|
+
return nil unless params_count_ok?( engine, func, args )
|
|
38
|
+
|
|
39
|
+
return invoke_function( engine, func, args )
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# Resolve the function object at the target path. Reports an
|
|
44
|
+
# error and returns nil if the path doesn't resolve to
|
|
45
|
+
# anything, or resolves to an object that isn't a function.
|
|
46
|
+
#
|
|
47
|
+
def self.resolve_function( engine, target )
|
|
48
|
+
pn = Gloo::Core::Pn.new( engine, target )
|
|
49
|
+
func = pn.resolve
|
|
50
|
+
|
|
51
|
+
unless func
|
|
52
|
+
engine.err "#{NOT_FOUND_ERR}#{target}"
|
|
53
|
+
return nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
unless func.is_function?
|
|
57
|
+
engine.err "#{NOT_FUNCTION_ERR}#{target}"
|
|
58
|
+
return nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return func
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Evaluate a list of raw tokens, each as its own single-token
|
|
66
|
+
# expression (a literal or object reference) - the standalone
|
|
67
|
+
# invoke verb's long-standing per-token semantics, now shared
|
|
68
|
+
# with the inline-call form too.
|
|
69
|
+
#
|
|
70
|
+
def self.evaluate_arg_tokens( engine, tokens )
|
|
71
|
+
return ( tokens || [] ).map do |t|
|
|
72
|
+
Gloo::Expr::Expression.new( engine, [ t ] ).evaluate
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#
|
|
77
|
+
# Confirm the number of args given matches the number the
|
|
78
|
+
# function declares. Reports an error and returns false if
|
|
79
|
+
# they don't match.
|
|
80
|
+
#
|
|
81
|
+
def self.params_count_ok?( engine, func, args )
|
|
82
|
+
expected = func.params_hash&.keys&.length || 0
|
|
83
|
+
return true if args.count == expected
|
|
84
|
+
|
|
85
|
+
engine.err "#{PARAM_COUNT_ERR}#{func.pn} " \
|
|
86
|
+
"(expected #{expected}, got #{args.count})"
|
|
87
|
+
return false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
#
|
|
91
|
+
# Invoke the function and return its result.
|
|
92
|
+
#
|
|
93
|
+
def self.invoke_function( engine, func, args )
|
|
94
|
+
engine.log.debug "invoking function: #{func.pn}"
|
|
95
|
+
result = func.invoke( args )
|
|
96
|
+
engine.log.debug "function returned: #{result}"
|
|
97
|
+
return result
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/gloo/core/parser.rb
CHANGED
|
@@ -39,17 +39,58 @@ module Gloo
|
|
|
39
39
|
# If additional params were provided, split them out
|
|
40
40
|
# from the token list.
|
|
41
41
|
#
|
|
42
|
+
# A trailing (...) is normally the optional-param convention
|
|
43
|
+
# (eg. show "text" (color)) and gets split off here. But a
|
|
44
|
+
# trailing (...) can also be an inline function call that
|
|
45
|
+
# happens to be the last thing in the command (eg.
|
|
46
|
+
# show invoke( functions.add 3 4 )) - that must be left alone
|
|
47
|
+
# so it flows into tokenizing as part of the main command.
|
|
48
|
+
#
|
|
42
49
|
def split_params( cmd )
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
i = matching_open_paren_index( cmd )
|
|
51
|
+
return cmd, nil unless i
|
|
52
|
+
return cmd, nil if inline_call_opener?( cmd, i )
|
|
53
|
+
|
|
54
|
+
pstr = cmd[ i + 1..-1 ]
|
|
55
|
+
params = pstr.strip[ 0..-2 ] if pstr
|
|
56
|
+
cmd = cmd[ 0, i ].strip
|
|
50
57
|
return cmd, params
|
|
51
58
|
end
|
|
52
59
|
|
|
60
|
+
#
|
|
61
|
+
# Find the index of the '(' that balances the command's final
|
|
62
|
+
# ')', counting depth from the end so an inline call earlier
|
|
63
|
+
# in the command (or nested parens) doesn't confuse the match.
|
|
64
|
+
# Returns nil if the command doesn't end with ')', or the
|
|
65
|
+
# parens aren't balanced.
|
|
66
|
+
#
|
|
67
|
+
def matching_open_paren_index( cmd )
|
|
68
|
+
return nil unless cmd.strip.end_with?( ')' )
|
|
69
|
+
|
|
70
|
+
depth = 0
|
|
71
|
+
( cmd.length - 1 ).downto( 0 ) do |idx|
|
|
72
|
+
case cmd[ idx ]
|
|
73
|
+
when ')' then depth += 1
|
|
74
|
+
when '('
|
|
75
|
+
depth -= 1
|
|
76
|
+
return idx if depth.zero?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
return nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
#
|
|
83
|
+
# Is the '(' at the given index immediately preceded (no
|
|
84
|
+
# space) by an inline-call keyword, eg. invoke( or ~>( ?
|
|
85
|
+
# Shares its keyword list with Gloo::Core::Tokens, which is
|
|
86
|
+
# where such a call actually gets recognized as one token.
|
|
87
|
+
#
|
|
88
|
+
def inline_call_opener?( cmd, paren_index )
|
|
89
|
+
before = cmd[ 0...paren_index ]
|
|
90
|
+
word = before[ /\S*\z/ ]
|
|
91
|
+
return Gloo::Core::Tokens::CALL_OPENERS.include?( word )
|
|
92
|
+
end
|
|
93
|
+
|
|
53
94
|
#
|
|
54
95
|
# Parse a command and then run it if it parsed correctly.
|
|
55
96
|
#
|