gloo-cli 1.0 → 1.2
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/lib/command.rb +217 -0
- data/lib/command_node.rb +24 -0
- data/lib/gloo-cli.rb +7 -14
- data/lib/shell.rb +192 -0
- data/lib/shell_context.rb +108 -0
- data/lib/shell_runner.rb +243 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e85b61ea757d2e59ded7f9271a24394b66bc4ed6da70687257e0a4b8d6f2fcd6
|
|
4
|
+
data.tar.gz: faf2dcf643d24652a1a11be58232f235308878deb5c99b521d495031a40f2f8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc7764a58cd890fe2100f4bd45ebf188d41726d583e0f86f5596ca1e0e958a9153de83a268d6fd14ad6ab78163e953a84bcfbe39aea2a829b2d54079ed3c4423
|
|
7
|
+
data.tar.gz: cc93483adce46c83815080802cab6fe1cc471a30f22fb52ef6d5a77b6153de27871717a71882c83a7d5ab4eb90cbf7cb3396419ee643b0fc76bd209eb0c47b02
|
data/lib/command.rb
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
2
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# A CLI command.
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
class Command < Gloo::Core::Obj
|
|
8
|
+
|
|
9
|
+
KEYWORD = 'command'.freeze
|
|
10
|
+
KEYWORD_SHORT = 'command'.freeze
|
|
11
|
+
DESCRIPTION = 'description'.freeze
|
|
12
|
+
ACTION = 'action'.freeze
|
|
13
|
+
DYNAMIC = 'dynamic'.freeze
|
|
14
|
+
NODES = 'nodes'.freeze
|
|
15
|
+
|
|
16
|
+
CONTEXT = 'context'.freeze
|
|
17
|
+
OPTIONS = 'options'.freeze
|
|
18
|
+
OPTIONS_KEY = 'options_key'.freeze
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# The name of the object type.
|
|
22
|
+
#
|
|
23
|
+
def self.typename
|
|
24
|
+
return KEYWORD
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# The short name of the object type.
|
|
29
|
+
#
|
|
30
|
+
def self.short_typename
|
|
31
|
+
return KEYWORD_SHORT
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#
|
|
35
|
+
# Get the description of the command.
|
|
36
|
+
#
|
|
37
|
+
def description
|
|
38
|
+
o = find_child DESCRIPTION
|
|
39
|
+
return '' unless o
|
|
40
|
+
|
|
41
|
+
return o.value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# Is this a dynamic command?
|
|
46
|
+
# It is dynamic if there is a dynamic child.
|
|
47
|
+
# If so, it has contextual data.
|
|
48
|
+
#
|
|
49
|
+
def dynamic?
|
|
50
|
+
o = find_child DYNAMIC
|
|
51
|
+
return true if o
|
|
52
|
+
return false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
# Get the dynamic key for this command.
|
|
57
|
+
#
|
|
58
|
+
def dynamic_key
|
|
59
|
+
o = find_child DYNAMIC
|
|
60
|
+
return o ? o.value : nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def context
|
|
64
|
+
o = find_child CONTEXT
|
|
65
|
+
return o ? o : nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
#
|
|
69
|
+
# Get the options key for this command.
|
|
70
|
+
#
|
|
71
|
+
def options_key
|
|
72
|
+
o = find_child OPTIONS_KEY
|
|
73
|
+
return o ? o.value : nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#
|
|
77
|
+
# Get the options for this command.
|
|
78
|
+
#
|
|
79
|
+
def options
|
|
80
|
+
arr = []
|
|
81
|
+
o = find_child OPTIONS
|
|
82
|
+
|
|
83
|
+
if o
|
|
84
|
+
arr = o.children.map { |child| child.value }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
return arr
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
#
|
|
91
|
+
# Get the child nodes for this command.
|
|
92
|
+
#
|
|
93
|
+
def nodes
|
|
94
|
+
o = find_child NODES
|
|
95
|
+
return o ? o : nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# Run the action script.
|
|
100
|
+
#
|
|
101
|
+
def run_action
|
|
102
|
+
o = find_child ACTION
|
|
103
|
+
return unless o
|
|
104
|
+
|
|
105
|
+
Gloo::Exec::Dispatch.message( @engine, 'run', o )
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def run_action_with_context( context )
|
|
109
|
+
o = find_child ACTION
|
|
110
|
+
return unless o
|
|
111
|
+
|
|
112
|
+
ctx = find_child CONTEXT
|
|
113
|
+
ctx.set_value( context ) if ctx
|
|
114
|
+
|
|
115
|
+
Gloo::Exec::Dispatch.message( @engine, 'run', o )
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------
|
|
120
|
+
# Children
|
|
121
|
+
# ---------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
#
|
|
124
|
+
# Does this object have children to add when an object
|
|
125
|
+
# is created in interactive mode?
|
|
126
|
+
# This does not apply during obj load, etc.
|
|
127
|
+
#
|
|
128
|
+
def add_children_on_create?
|
|
129
|
+
return true
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
#
|
|
133
|
+
# Add children to this object.
|
|
134
|
+
# This is used by containers to add children needed
|
|
135
|
+
# for default configurations.
|
|
136
|
+
#
|
|
137
|
+
def add_default_children
|
|
138
|
+
fac = @engine.factory
|
|
139
|
+
fac.create_string NAME, '', self
|
|
140
|
+
fac.create_string DESCRIPTION, '', self
|
|
141
|
+
fac.create_script ACTION, '', self
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------
|
|
146
|
+
# Messages
|
|
147
|
+
# ---------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
#
|
|
150
|
+
# Get a list of message names that this object receives.
|
|
151
|
+
#
|
|
152
|
+
def self.messages
|
|
153
|
+
return super + [ 'register' ]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
#
|
|
157
|
+
# Register the command with the shell.
|
|
158
|
+
#
|
|
159
|
+
def msg_register
|
|
160
|
+
if @params&.token_count&.positive?
|
|
161
|
+
pn = Gloo::Core::Pn.new( @engine, @params.first )
|
|
162
|
+
shell = pn.resolve
|
|
163
|
+
shell.add_command( self, get_command_data )
|
|
164
|
+
|
|
165
|
+
# Are there options to add?
|
|
166
|
+
if options_key
|
|
167
|
+
shell.set_context( options_key, options )
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------
|
|
174
|
+
# Helpers
|
|
175
|
+
# ---------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
#
|
|
178
|
+
# Get the child nodes for this command.
|
|
179
|
+
#
|
|
180
|
+
def get_child_nodes
|
|
181
|
+
data = nodes.children.map do |child|
|
|
182
|
+
child.get_command_data
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
return data
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
#
|
|
189
|
+
# Get the command data for this command.
|
|
190
|
+
#
|
|
191
|
+
def get_command_data
|
|
192
|
+
if dynamic?
|
|
193
|
+
return {
|
|
194
|
+
name: name,
|
|
195
|
+
description: description,
|
|
196
|
+
dynamic: true,
|
|
197
|
+
obj: pn,
|
|
198
|
+
method: "cmd_obj_action_with_context",
|
|
199
|
+
source: dynamic_key
|
|
200
|
+
}
|
|
201
|
+
elsif nodes
|
|
202
|
+
return {
|
|
203
|
+
name: name,
|
|
204
|
+
description: description,
|
|
205
|
+
children: get_child_nodes
|
|
206
|
+
}
|
|
207
|
+
else
|
|
208
|
+
return {
|
|
209
|
+
name: name,
|
|
210
|
+
description: description,
|
|
211
|
+
method: "cmd_obj_action",
|
|
212
|
+
obj: pn
|
|
213
|
+
}
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
end
|
data/lib/command_node.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
3
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# A command node.
|
|
6
|
+
# Represents a command in the command tree.
|
|
7
|
+
#
|
|
8
|
+
class CommandNode
|
|
9
|
+
|
|
10
|
+
attr_reader :name, :description, :method, :obj
|
|
11
|
+
|
|
12
|
+
def initialize( name, description: "", method: nil, obj: nil, &children_block )
|
|
13
|
+
@name = name
|
|
14
|
+
@description = description
|
|
15
|
+
@method = method
|
|
16
|
+
@obj = obj
|
|
17
|
+
@children_block = children_block
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def children( context )
|
|
21
|
+
return [] unless @children_block
|
|
22
|
+
@children_block.call( context )
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/gloo-cli.rb
CHANGED
|
@@ -9,6 +9,11 @@ require 'menu'
|
|
|
9
9
|
require 'menu_item'
|
|
10
10
|
require 'prompt'
|
|
11
11
|
require 'select'
|
|
12
|
+
require 'shell'
|
|
13
|
+
require 'shell_runner'
|
|
14
|
+
require 'shell_context'
|
|
15
|
+
require 'command_node'
|
|
16
|
+
require 'command'
|
|
12
17
|
|
|
13
18
|
#
|
|
14
19
|
# Registers the extension.
|
|
@@ -25,20 +30,8 @@ class CliInit < Gloo::Plugin::Base
|
|
|
25
30
|
callback.register_obj( MenuItem )
|
|
26
31
|
callback.register_obj( Prompt )
|
|
27
32
|
callback.register_obj( Select )
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
#
|
|
33
|
-
# Registers the extension.
|
|
34
|
-
#
|
|
35
|
-
class MdInit < Gloo::Plugin::Base
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
# Register verbs and objects.
|
|
39
|
-
#
|
|
40
|
-
def register( callback )
|
|
41
|
-
callback.register_obj( Md )
|
|
33
|
+
callback.register_obj( Shell )
|
|
34
|
+
callback.register_obj( Command )
|
|
42
35
|
end
|
|
43
36
|
|
|
44
37
|
end
|
data/lib/shell.rb
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
2
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# A CLI shell.
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
class Shell < Gloo::Core::Obj
|
|
8
|
+
|
|
9
|
+
KEYWORD = 'shell'.freeze
|
|
10
|
+
KEYWORD_SHORT = 'shell'.freeze
|
|
11
|
+
PROMPT = 'prompt'.freeze
|
|
12
|
+
DEFAULT_ACTION = 'default_action'.freeze
|
|
13
|
+
INCLUDE_QUIT = 'include_quit'.freeze
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# The name of the object type.
|
|
17
|
+
#
|
|
18
|
+
def self.typename
|
|
19
|
+
return KEYWORD
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# The short name of the object type.
|
|
24
|
+
#
|
|
25
|
+
def self.short_typename
|
|
26
|
+
return KEYWORD_SHORT
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Get the value of the prompt child object.
|
|
31
|
+
# Returns nil if there is none.
|
|
32
|
+
#
|
|
33
|
+
def prompt
|
|
34
|
+
o = find_child PROMPT
|
|
35
|
+
return '' unless o
|
|
36
|
+
|
|
37
|
+
return o.value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# Get the value of the include_quit child object.
|
|
42
|
+
# Returns false if there is none.
|
|
43
|
+
#
|
|
44
|
+
def include_quit?
|
|
45
|
+
o = find_child INCLUDE_QUIT
|
|
46
|
+
return o.value if o
|
|
47
|
+
return false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------
|
|
52
|
+
# Children
|
|
53
|
+
# ---------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
# Does this object have children to add when an object
|
|
57
|
+
# is created in interactive mode?
|
|
58
|
+
# This does not apply during obj load, etc.
|
|
59
|
+
#
|
|
60
|
+
def add_children_on_create?
|
|
61
|
+
return true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Add children to this object.
|
|
66
|
+
# This is used by containers to add children needed
|
|
67
|
+
# for default configurations.
|
|
68
|
+
#
|
|
69
|
+
def add_default_children
|
|
70
|
+
fac = @engine.factory
|
|
71
|
+
fac.create_string PROMPT, '> ', self
|
|
72
|
+
fac.create_script DEFAULT_ACTION, '', self
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------
|
|
77
|
+
# Messages
|
|
78
|
+
# ---------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
#
|
|
81
|
+
# Get a list of message names that this object receives.
|
|
82
|
+
#
|
|
83
|
+
def self.messages
|
|
84
|
+
return super + [ 'start', 'stop' ]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
# Get the shell runner or initialize it if it doesn't exist.
|
|
89
|
+
#
|
|
90
|
+
def get_runner
|
|
91
|
+
return @runner ||= ShellRunner.new( @engine, self )
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
#
|
|
95
|
+
# Start the shell.
|
|
96
|
+
#
|
|
97
|
+
def msg_start
|
|
98
|
+
runner = get_runner
|
|
99
|
+
|
|
100
|
+
# add_test_commands
|
|
101
|
+
add_quit_command
|
|
102
|
+
|
|
103
|
+
runner.start
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
#
|
|
107
|
+
# Stop the shell.
|
|
108
|
+
#
|
|
109
|
+
def msg_stop
|
|
110
|
+
@runner.stop if @runner
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#
|
|
114
|
+
# Add a command to the shell.
|
|
115
|
+
#
|
|
116
|
+
def add_command obj, command_data
|
|
117
|
+
runner = get_runner
|
|
118
|
+
runner.add_command_node( command_data )
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------
|
|
123
|
+
# Context
|
|
124
|
+
# ---------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
def set_context key, value
|
|
127
|
+
@runner.set_context( key, value )
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------------
|
|
132
|
+
# Commands
|
|
133
|
+
# ---------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
#
|
|
136
|
+
# Quit the shell.
|
|
137
|
+
#
|
|
138
|
+
def add_quit_command
|
|
139
|
+
return unless include_quit?
|
|
140
|
+
|
|
141
|
+
@runner.add_command_node({
|
|
142
|
+
name: "quit",
|
|
143
|
+
description: "Quit the application",
|
|
144
|
+
method: "cmd_quit"
|
|
145
|
+
})
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# def add_test_commands
|
|
149
|
+
# @runner.add_command_node({
|
|
150
|
+
# name: "add",
|
|
151
|
+
# description: "add a project",
|
|
152
|
+
# method: "cmd_add"
|
|
153
|
+
# })
|
|
154
|
+
|
|
155
|
+
# @runner.add_command_node({
|
|
156
|
+
# name: "show",
|
|
157
|
+
# description: "show a resource",
|
|
158
|
+
# children: [
|
|
159
|
+
# {
|
|
160
|
+
# name: "project",
|
|
161
|
+
# description: "show a project",
|
|
162
|
+
# dynamic: true,
|
|
163
|
+
# source: "projects"
|
|
164
|
+
# },
|
|
165
|
+
# {
|
|
166
|
+
# name: "task",
|
|
167
|
+
# description: "show a task",
|
|
168
|
+
# dynamic: true,
|
|
169
|
+
# source: "tasks"
|
|
170
|
+
# }
|
|
171
|
+
# ]
|
|
172
|
+
# })
|
|
173
|
+
|
|
174
|
+
# @runner.add_command_node({
|
|
175
|
+
# name: "list",
|
|
176
|
+
# description: "list resources",
|
|
177
|
+
# children: [
|
|
178
|
+
# {
|
|
179
|
+
# name: "projects",
|
|
180
|
+
# description: "list projects",
|
|
181
|
+
# method: "cmd_projects"
|
|
182
|
+
# },
|
|
183
|
+
# {
|
|
184
|
+
# name: "tasks",
|
|
185
|
+
# description: "list tasks",
|
|
186
|
+
# method: "cmd_tasks"
|
|
187
|
+
# }
|
|
188
|
+
# ]
|
|
189
|
+
# })
|
|
190
|
+
# end
|
|
191
|
+
|
|
192
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
3
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# A shell context.
|
|
6
|
+
# Data associated with the shell session.
|
|
7
|
+
#
|
|
8
|
+
class ShellContext
|
|
9
|
+
|
|
10
|
+
attr_accessor :done
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Initialize the shell context
|
|
14
|
+
#
|
|
15
|
+
def initialize
|
|
16
|
+
@done = false
|
|
17
|
+
@properties = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Get a property value
|
|
22
|
+
#
|
|
23
|
+
# @param key [Symbol] The property key
|
|
24
|
+
# @return [Object] The property value
|
|
25
|
+
#
|
|
26
|
+
def get( key )
|
|
27
|
+
key = key.to_sym
|
|
28
|
+
return @properties[key] if @properties.key?(key)
|
|
29
|
+
|
|
30
|
+
# If the property doesn't exist, return an empty array
|
|
31
|
+
return []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#
|
|
35
|
+
# Set a property value
|
|
36
|
+
#
|
|
37
|
+
# @param key [Symbol] The property key
|
|
38
|
+
# @param value [Object] The property value
|
|
39
|
+
#
|
|
40
|
+
def set( key, value )
|
|
41
|
+
@properties[key.to_sym] = value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# Add an item to a property list
|
|
46
|
+
#
|
|
47
|
+
# @param key [Symbol] The property key
|
|
48
|
+
# @param item [Object] The item to add
|
|
49
|
+
#
|
|
50
|
+
def add_to_list( key, item )
|
|
51
|
+
key = key.to_sym
|
|
52
|
+
list = get(key)
|
|
53
|
+
|
|
54
|
+
# Ensure we have an array to work with
|
|
55
|
+
list = [] unless list.is_a?(Array)
|
|
56
|
+
|
|
57
|
+
# Add the item and store the updated list
|
|
58
|
+
list << item
|
|
59
|
+
set(key, list)
|
|
60
|
+
|
|
61
|
+
list
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Check if a property exists
|
|
66
|
+
#
|
|
67
|
+
# @param key [Symbol] The property key
|
|
68
|
+
# @return [Boolean] True if property exists
|
|
69
|
+
#
|
|
70
|
+
def has?( key )
|
|
71
|
+
@properties.key?( key.to_sym )
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# Get all property keys
|
|
76
|
+
#
|
|
77
|
+
# @return [Array<Symbol>] All property keys
|
|
78
|
+
#
|
|
79
|
+
def keys
|
|
80
|
+
@properties.keys
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Dynamic method access to properties
|
|
85
|
+
#
|
|
86
|
+
def method_missing( method_name, *args, &block )
|
|
87
|
+
if method_name.to_s.end_with?('=')
|
|
88
|
+
# Setter method: projects=, tasks=, etc.
|
|
89
|
+
key = method_name.to_s.chomp('=').to_sym
|
|
90
|
+
set(key, args.first)
|
|
91
|
+
else
|
|
92
|
+
# Getter method: projects, tasks, etc.
|
|
93
|
+
key = method_name.to_sym
|
|
94
|
+
get(key)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# Respond to missing methods for property access
|
|
100
|
+
#
|
|
101
|
+
def respond_to_missing?( method_name, include_private = false )
|
|
102
|
+
key = method_name.to_s.chomp('=').to_sym
|
|
103
|
+
has?(key) || super
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
end
|
data/lib/shell_runner.rb
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
3
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# A shell runner.
|
|
6
|
+
#
|
|
7
|
+
require "readline"
|
|
8
|
+
|
|
9
|
+
class ShellRunner
|
|
10
|
+
|
|
11
|
+
DEFAULT_PROMPT = " -> "
|
|
12
|
+
#
|
|
13
|
+
# Initialize the shell runner
|
|
14
|
+
#
|
|
15
|
+
# @param obj [Object] The shell obj.
|
|
16
|
+
#
|
|
17
|
+
def initialize( engine, obj )
|
|
18
|
+
@engine = engine
|
|
19
|
+
@obj = obj
|
|
20
|
+
@context = ShellContext.new
|
|
21
|
+
@root = CommandNode.new( nil )
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------
|
|
26
|
+
# Shell, control, start and stop
|
|
27
|
+
# ---------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Start the shell.
|
|
31
|
+
#
|
|
32
|
+
def start
|
|
33
|
+
repl
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Flag the shell as done, next time through the loop it will stop
|
|
38
|
+
#
|
|
39
|
+
def stop
|
|
40
|
+
@context.done = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Get the prompt string
|
|
45
|
+
#
|
|
46
|
+
def prompt
|
|
47
|
+
p = @obj.prompt
|
|
48
|
+
return p ? p + ' ' : DEFAULT_PROMPT
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
# Quit the shell.
|
|
53
|
+
#
|
|
54
|
+
def cmd_quit( obj, context )
|
|
55
|
+
puts "Quitting…"
|
|
56
|
+
context.done = true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
#
|
|
60
|
+
# Run an action on an object.
|
|
61
|
+
#
|
|
62
|
+
def cmd_obj_action( obj, context )
|
|
63
|
+
pn = Gloo::Core::Pn.new( @engine, obj )
|
|
64
|
+
command = pn.resolve
|
|
65
|
+
if command
|
|
66
|
+
command.run_action
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def cmd_obj_action_with_context( cmd_node, parent_node = nil )
|
|
71
|
+
if parent_node
|
|
72
|
+
pn = Gloo::Core::Pn.new( @engine, parent_node.obj )
|
|
73
|
+
command = pn.resolve
|
|
74
|
+
if command
|
|
75
|
+
command.run_action_with_context( cmd_node.name )
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------
|
|
83
|
+
# Context
|
|
84
|
+
# ---------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# Set a context list.
|
|
88
|
+
#
|
|
89
|
+
def set_context key, value_list
|
|
90
|
+
@context.set( key, value_list )
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------
|
|
95
|
+
# Tree building
|
|
96
|
+
# ---------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# Execute a command.
|
|
100
|
+
#
|
|
101
|
+
def execute_command( command_node, args, parent_node = nil )
|
|
102
|
+
if command_node.respond_to?( :method ) && command_node.method
|
|
103
|
+
send( command_node.method, command_node.obj, @context )
|
|
104
|
+
else
|
|
105
|
+
if command_node.name && command_node.name != "" && !command_node.description.empty?
|
|
106
|
+
puts "#{command_node.description}: #{command_node.name}"
|
|
107
|
+
elsif command_node.name
|
|
108
|
+
# puts "Showing: #{command_node.name}"
|
|
109
|
+
cmd_obj_action_with_context( command_node, parent_node )
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
#
|
|
116
|
+
# Build a command node from data.
|
|
117
|
+
#
|
|
118
|
+
def build_node_from_data( data )
|
|
119
|
+
if data[:dynamic]
|
|
120
|
+
CommandNode.new(data[:name], description: data[:description], obj: data[:obj]) do |ctx|
|
|
121
|
+
ctx.send(data[:source]).map do |item|
|
|
122
|
+
CommandNode.new(item)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
elsif data[:children]
|
|
126
|
+
CommandNode.new(data[:name], description: data[:description], method: data[:method], obj: data[:obj]) do |ctx|
|
|
127
|
+
data[:children].map { |child_data| build_node_from_data(child_data) }
|
|
128
|
+
end
|
|
129
|
+
else
|
|
130
|
+
CommandNode.new(data[:name], description: data[:description], method: data[:method], obj: data[:obj])
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
#
|
|
135
|
+
# Add a command node to the root dynamically
|
|
136
|
+
#
|
|
137
|
+
# @param command_data [Hash] The command data hash
|
|
138
|
+
#
|
|
139
|
+
# Add a single command dynamically
|
|
140
|
+
#
|
|
141
|
+
def add_command_node( command_data)
|
|
142
|
+
node = build_node_from_data(command_data)
|
|
143
|
+
|
|
144
|
+
# Get existing children block or create new one
|
|
145
|
+
existing_block = @root.instance_variable_get(:@children_block)
|
|
146
|
+
|
|
147
|
+
if existing_block
|
|
148
|
+
# Store existing nodes and add new one
|
|
149
|
+
existing_nodes = existing_block.call(@context)
|
|
150
|
+
all_nodes = existing_nodes + [node]
|
|
151
|
+
@root.instance_variable_set(:@children_block, proc { |ctx| all_nodes })
|
|
152
|
+
else
|
|
153
|
+
# Create new children block with just this node
|
|
154
|
+
@root.instance_variable_set(:@children_block, proc { |ctx| [node] })
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
node
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ---------------------------------------------------------------------
|
|
162
|
+
# REPL
|
|
163
|
+
# ---------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
#
|
|
166
|
+
# Traverse the command tree to find the matching node
|
|
167
|
+
#
|
|
168
|
+
# @param node [CommandNode] The current node
|
|
169
|
+
# @param tokens [Array<String>] The tokens to traverse
|
|
170
|
+
#
|
|
171
|
+
# @return [Hash] Hash with :node and :parent keys
|
|
172
|
+
#
|
|
173
|
+
def traverse( node, tokens )
|
|
174
|
+
current = node
|
|
175
|
+
parent = nil
|
|
176
|
+
|
|
177
|
+
tokens.each do |token|
|
|
178
|
+
children = current.children( @context )
|
|
179
|
+
match = children.find { |c| c.name == token }
|
|
180
|
+
return { node: nil, parent: nil } unless match
|
|
181
|
+
|
|
182
|
+
parent = current
|
|
183
|
+
current = match
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
{ node: current, parent: parent }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
#
|
|
190
|
+
# Setup readline completion
|
|
191
|
+
#
|
|
192
|
+
def setup_completion
|
|
193
|
+
Readline.completion_append_character = " "
|
|
194
|
+
Readline.basic_word_break_characters = " \t\n\"\\'`@$><=;|&{("
|
|
195
|
+
|
|
196
|
+
Readline.completion_proc = proc do |input|
|
|
197
|
+
buffer = Readline.line_buffer
|
|
198
|
+
tokens = buffer.split(" ")
|
|
199
|
+
|
|
200
|
+
tokens << "" if buffer.end_with?(" ")
|
|
201
|
+
|
|
202
|
+
result = traverse( @root, tokens[0..-2] )
|
|
203
|
+
current = result[:node]
|
|
204
|
+
options = current.children( @context ).map( &:name )
|
|
205
|
+
|
|
206
|
+
matches = options.grep(/^#{Regexp.escape(input)}/)
|
|
207
|
+
|
|
208
|
+
if matches.length > 1
|
|
209
|
+
puts
|
|
210
|
+
current.children( @context ).each do |child|
|
|
211
|
+
if matches.include?( child.name )
|
|
212
|
+
puts "#{child.name.ljust(15)} #{child.description}"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
print "#{prompt}#{buffer}"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
matches
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
#
|
|
224
|
+
# Run the REPL loop.
|
|
225
|
+
#
|
|
226
|
+
def repl
|
|
227
|
+
setup_completion
|
|
228
|
+
|
|
229
|
+
while ( ! @context.done && (line = Readline.readline(prompt, true)) )
|
|
230
|
+
tokens = line.strip.split(" ")
|
|
231
|
+
next if tokens.empty?
|
|
232
|
+
|
|
233
|
+
result = traverse( @root, tokens )
|
|
234
|
+
|
|
235
|
+
if result[:node]
|
|
236
|
+
execute_command( result[:node], tokens, result[:parent] )
|
|
237
|
+
else
|
|
238
|
+
puts "Unknown command"
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gloo-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.2'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eric Crane
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Adds CLI support to Gloo.
|
|
14
14
|
email:
|
|
@@ -19,11 +19,16 @@ extra_rdoc_files: []
|
|
|
19
19
|
files:
|
|
20
20
|
- lib/cli_colorize.rb
|
|
21
21
|
- lib/cli_confirm.rb
|
|
22
|
+
- lib/command.rb
|
|
23
|
+
- lib/command_node.rb
|
|
22
24
|
- lib/gloo-cli.rb
|
|
23
25
|
- lib/menu.rb
|
|
24
26
|
- lib/menu_item.rb
|
|
25
27
|
- lib/prompt.rb
|
|
26
28
|
- lib/select.rb
|
|
29
|
+
- lib/shell.rb
|
|
30
|
+
- lib/shell_context.rb
|
|
31
|
+
- lib/shell_runner.rb
|
|
27
32
|
homepage: https://gloo.ecrane.us/
|
|
28
33
|
licenses:
|
|
29
34
|
- MIT
|