ace-support-nav 0.25.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 +7 -0
- data/.ace-defaults/nav/config.yml +33 -0
- data/.ace-defaults/nav/protocols/guide-sources/ace-support-nav.yml +7 -0
- data/.ace-defaults/nav/protocols/guide.yml +69 -0
- data/.ace-defaults/nav/protocols/prompt.yml +39 -0
- data/.ace-defaults/nav/protocols/skill-sources/ace-support-nav.yml +19 -0
- data/.ace-defaults/nav/protocols/skill.yml +22 -0
- data/.ace-defaults/nav/protocols/tmpl-sources/ace-support-nav.yml +7 -0
- data/.ace-defaults/nav/protocols/tmpl.yml +55 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-support-nav.yml +7 -0
- data/.ace-defaults/nav/protocols/wfi.yml +61 -0
- data/CHANGELOG.md +231 -0
- data/LICENSE +21 -0
- data/README.md +48 -0
- data/Rakefile +12 -0
- data/docs/demo/ace-support-nav-getting-started.gif +0 -0
- data/docs/demo/ace-support-nav-getting-started.tape.yml +28 -0
- data/exe/ace-nav +49 -0
- data/handbook/workflow-instructions/test.wfi.md +14 -0
- data/lib/ace/support/nav/atoms/extension_inferrer.rb +134 -0
- data/lib/ace/support/nav/atoms/gem_resolver.rb +59 -0
- data/lib/ace/support/nav/atoms/path_normalizer.rb +52 -0
- data/lib/ace/support/nav/atoms/uri_parser.rb +62 -0
- data/lib/ace/support/nav/cli/commands/create.rb +114 -0
- data/lib/ace/support/nav/cli/commands/list.rb +122 -0
- data/lib/ace/support/nav/cli/commands/resolve.rb +187 -0
- data/lib/ace/support/nav/cli/commands/sources.rb +112 -0
- data/lib/ace/support/nav/cli.rb +66 -0
- data/lib/ace/support/nav/models/handbook_source.rb +73 -0
- data/lib/ace/support/nav/models/protocol_source.rb +104 -0
- data/lib/ace/support/nav/models/resource.rb +46 -0
- data/lib/ace/support/nav/models/resource_uri.rb +78 -0
- data/lib/ace/support/nav/molecules/config_loader.rb +275 -0
- data/lib/ace/support/nav/molecules/handbook_scanner.rb +204 -0
- data/lib/ace/support/nav/molecules/protocol_scanner.rb +434 -0
- data/lib/ace/support/nav/molecules/resource_resolver.rb +134 -0
- data/lib/ace/support/nav/molecules/source_registry.rb +133 -0
- data/lib/ace/support/nav/organisms/command_delegator.rb +122 -0
- data/lib/ace/support/nav/organisms/navigation_engine.rb +180 -0
- data/lib/ace/support/nav/version.rb +9 -0
- data/lib/ace/support/nav.rb +104 -0
- metadata +228 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/cli"
|
|
4
|
+
require "ace/core"
|
|
5
|
+
require_relative "../../molecules/config_loader"
|
|
6
|
+
require_relative "../../organisms/navigation_engine"
|
|
7
|
+
require_relative "../../organisms/command_delegator"
|
|
8
|
+
|
|
9
|
+
module Ace
|
|
10
|
+
module Support
|
|
11
|
+
module Nav
|
|
12
|
+
module CLI
|
|
13
|
+
module Commands
|
|
14
|
+
# ace-support-cli Command class for the resolve command
|
|
15
|
+
class Resolve < Ace::Support::Cli::Command
|
|
16
|
+
include Ace::Support::Cli::Base
|
|
17
|
+
|
|
18
|
+
desc <<~DESC.strip
|
|
19
|
+
Resolve resource path or content
|
|
20
|
+
|
|
21
|
+
SYNTAX:
|
|
22
|
+
ace-nav resolve [URI] [OPTIONS]
|
|
23
|
+
|
|
24
|
+
Automatically detects list mode for wildcards, patterns ending with /,
|
|
25
|
+
and protocol-only URIs (e.g., wfi://, tmpl://).
|
|
26
|
+
|
|
27
|
+
Magic Wildcard Routing:
|
|
28
|
+
Wildcard patterns are auto-routed to 'list' command:
|
|
29
|
+
ace-nav resolve wfi://* → ace-nav list wfi://*
|
|
30
|
+
ace-nav resolve tmpl://@ace-*/* → ace-nav list tmpl://@ace-*
|
|
31
|
+
ace-nav resolve wfi:// → ace-nav list wfi:// (protocol-only)
|
|
32
|
+
Recognized patterns: *, ?, trailing /, protocol-only
|
|
33
|
+
|
|
34
|
+
EXAMPLES:
|
|
35
|
+
|
|
36
|
+
# Resolve URI to path
|
|
37
|
+
$ ace-nav resolve wfi://setup
|
|
38
|
+
|
|
39
|
+
# Display content
|
|
40
|
+
$ ace-nav resolve wfi://setup --content
|
|
41
|
+
|
|
42
|
+
# Wildcard patterns auto-route to list
|
|
43
|
+
$ ace-nav resolve wfi://*
|
|
44
|
+
|
|
45
|
+
# Protocol-only URIs auto-route to list
|
|
46
|
+
$ ace-nav resolve tmpl:///
|
|
47
|
+
|
|
48
|
+
CONFIGURATION:
|
|
49
|
+
|
|
50
|
+
Global config: ~/.ace/nav/config.yml
|
|
51
|
+
Project config: .ace/nav/config.yml
|
|
52
|
+
Example: ace-support-nav/.ace-defaults/nav/config.yml
|
|
53
|
+
|
|
54
|
+
Sources configured via nav.sources in config
|
|
55
|
+
|
|
56
|
+
OUTPUT:
|
|
57
|
+
|
|
58
|
+
By default, displays resolved path
|
|
59
|
+
Use --content to display resource content
|
|
60
|
+
Exit codes: 0 (success), 1 (error)
|
|
61
|
+
DESC
|
|
62
|
+
|
|
63
|
+
example [
|
|
64
|
+
"wfi://setup # Resolve workflow",
|
|
65
|
+
"wfi://* # List workflows (auto-routing)",
|
|
66
|
+
"tmpl:// # List templates (auto-routing)",
|
|
67
|
+
"wfi://setup --content # Display resource content"
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
argument :uri, required: true, desc: "Resource URI to resolve"
|
|
71
|
+
|
|
72
|
+
option :path, type: :boolean, desc: "Display resource path"
|
|
73
|
+
option :content, type: :boolean, desc: "Display resource content"
|
|
74
|
+
option :tree, type: :boolean, desc: "Display resources in tree format (passed through to cmd protocols)"
|
|
75
|
+
option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
|
|
76
|
+
option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
|
|
77
|
+
option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
|
|
78
|
+
|
|
79
|
+
def call(uri:, **options)
|
|
80
|
+
# Normalize bare protocol names to protocol:// format for listing
|
|
81
|
+
uri = normalize_protocol_shorthand(uri)
|
|
82
|
+
|
|
83
|
+
# Handle magic patterns (wildcards → list)
|
|
84
|
+
if magic_wildcard_pattern?(uri)
|
|
85
|
+
# Delegate to list command
|
|
86
|
+
require_relative "list"
|
|
87
|
+
list_cmd = List.new
|
|
88
|
+
return list_cmd.call(pattern: uri, **options)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Initialize instance variables for use in private methods
|
|
92
|
+
@uri = uri
|
|
93
|
+
@options = options
|
|
94
|
+
@engine = Organisms::NavigationEngine.new
|
|
95
|
+
|
|
96
|
+
execute
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def execute
|
|
100
|
+
display_config_summary
|
|
101
|
+
|
|
102
|
+
# Check for cmd:// protocol delegation
|
|
103
|
+
if @uri.include?("://")
|
|
104
|
+
protocol = @uri.split("://").first
|
|
105
|
+
if @engine.cmd_protocol?(protocol)
|
|
106
|
+
delegator = Organisms::CommandDelegator.new
|
|
107
|
+
return delegator.delegate(@uri, @options)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
result = @engine.resolve(@uri, content: @options[:content], verbose: @options[:verbose])
|
|
112
|
+
|
|
113
|
+
if result.nil?
|
|
114
|
+
raise Ace::Support::Cli::Error.new("Resource not found: #{@uri}")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if @options[:verbose] && result.is_a?(Hash)
|
|
118
|
+
require "json"
|
|
119
|
+
puts JSON.pretty_generate(result)
|
|
120
|
+
elsif @options[:path]
|
|
121
|
+
# Show path only
|
|
122
|
+
puts result.is_a?(Hash) ? result[:path] || result : result
|
|
123
|
+
else
|
|
124
|
+
puts result
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# Normalize bare protocol names (e.g., "wfi") to protocol:// format
|
|
131
|
+
# This allows users to type "ace-nav wfi" instead of "ace-nav wfi://"
|
|
132
|
+
def normalize_protocol_shorthand(uri)
|
|
133
|
+
return uri if uri.include?("://")
|
|
134
|
+
|
|
135
|
+
# Check if input is a known protocol name
|
|
136
|
+
config_loader = Molecules::ConfigLoader.new
|
|
137
|
+
if config_loader.valid_protocol?(uri)
|
|
138
|
+
"#{uri}://"
|
|
139
|
+
else
|
|
140
|
+
uri
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Check if URI pattern should trigger list mode
|
|
145
|
+
def magic_wildcard_pattern?(uri)
|
|
146
|
+
return true if uri.include?("*") || uri.include?("?")
|
|
147
|
+
return true if uri.match?(%r{/$})
|
|
148
|
+
return true if uri.match?(/^\w+:\/\/$/)
|
|
149
|
+
false
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def display_config_summary
|
|
153
|
+
return if @options[:quiet]
|
|
154
|
+
|
|
155
|
+
require "ace/core"
|
|
156
|
+
Ace::Core::Atoms::ConfigSummary.display(
|
|
157
|
+
command: "resolve",
|
|
158
|
+
config: load_effective_config,
|
|
159
|
+
defaults: default_config,
|
|
160
|
+
options: @options,
|
|
161
|
+
quiet: false
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def load_effective_config
|
|
166
|
+
# Use Ace::Support::Nav.config which already handles the cascade
|
|
167
|
+
require_relative "../../../nav"
|
|
168
|
+
Ace::Support::Nav.config
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def default_config
|
|
172
|
+
# Use centralized gem_root from Nav module (avoids path depth duplication)
|
|
173
|
+
defaults_path = File.join(Ace::Support::Nav.gem_root, ".ace-defaults", "nav", "config.yml")
|
|
174
|
+
|
|
175
|
+
if File.exist?(defaults_path)
|
|
176
|
+
require "yaml"
|
|
177
|
+
YAML.safe_load_file(defaults_path, permitted_classes: [Date], aliases: true) || {}
|
|
178
|
+
else
|
|
179
|
+
{}
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/cli"
|
|
4
|
+
require "ace/core"
|
|
5
|
+
require_relative "../../organisms/navigation_engine"
|
|
6
|
+
|
|
7
|
+
module Ace
|
|
8
|
+
module Support
|
|
9
|
+
module Nav
|
|
10
|
+
module CLI
|
|
11
|
+
module Commands
|
|
12
|
+
# ace-support-cli Command class for the sources command
|
|
13
|
+
class Sources < Ace::Support::Cli::Command
|
|
14
|
+
include Ace::Support::Cli::Base
|
|
15
|
+
|
|
16
|
+
desc <<~DESC.strip
|
|
17
|
+
Show available sources
|
|
18
|
+
|
|
19
|
+
Show all available sources for resources.
|
|
20
|
+
|
|
21
|
+
EXAMPLES:
|
|
22
|
+
|
|
23
|
+
# Show all sources
|
|
24
|
+
$ ace-nav sources
|
|
25
|
+
|
|
26
|
+
# Verbose JSON output
|
|
27
|
+
$ ace-nav sources --verbose
|
|
28
|
+
|
|
29
|
+
CONFIGURATION:
|
|
30
|
+
|
|
31
|
+
Sources configured in: .ace/nav/config.yml
|
|
32
|
+
Global config: ~/.ace/nav/config.yml
|
|
33
|
+
Project config: .ace/nav/config.yml
|
|
34
|
+
|
|
35
|
+
OUTPUT:
|
|
36
|
+
|
|
37
|
+
Table format with source details
|
|
38
|
+
Use --verbose for JSON output
|
|
39
|
+
Exit codes: 0 (success), 1 (error)
|
|
40
|
+
DESC
|
|
41
|
+
|
|
42
|
+
example [
|
|
43
|
+
" # Show all sources",
|
|
44
|
+
"--verbose # Show detailed information (JSON)"
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
|
|
48
|
+
option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
|
|
49
|
+
option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
|
|
50
|
+
|
|
51
|
+
def call(**options)
|
|
52
|
+
# Initialize instance variables for use in private methods
|
|
53
|
+
@options = options
|
|
54
|
+
@engine = Organisms::NavigationEngine.new
|
|
55
|
+
|
|
56
|
+
execute
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def execute
|
|
60
|
+
display_config_summary
|
|
61
|
+
|
|
62
|
+
sources = @engine.sources(verbose: @options[:verbose])
|
|
63
|
+
|
|
64
|
+
if @options[:verbose]
|
|
65
|
+
require "json"
|
|
66
|
+
puts JSON.pretty_generate(sources)
|
|
67
|
+
else
|
|
68
|
+
puts "Available sources:"
|
|
69
|
+
sources.each { |source| puts " #{source}" }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def display_config_summary
|
|
78
|
+
return if @options[:quiet]
|
|
79
|
+
|
|
80
|
+
require "ace/core"
|
|
81
|
+
Ace::Core::Atoms::ConfigSummary.display(
|
|
82
|
+
command: "sources",
|
|
83
|
+
config: load_effective_config,
|
|
84
|
+
defaults: default_config,
|
|
85
|
+
options: @options,
|
|
86
|
+
quiet: false
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def load_effective_config
|
|
91
|
+
# Use Ace::Support::Nav.config which already handles the cascade
|
|
92
|
+
require_relative "../../../nav"
|
|
93
|
+
Ace::Support::Nav.config
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def default_config
|
|
97
|
+
# Use centralized gem_root from Nav module (avoids path depth duplication)
|
|
98
|
+
defaults_path = File.join(Ace::Support::Nav.gem_root, ".ace-defaults", "nav", "config.yml")
|
|
99
|
+
|
|
100
|
+
if File.exist?(defaults_path)
|
|
101
|
+
require "yaml"
|
|
102
|
+
YAML.safe_load_file(defaults_path, permitted_classes: [Date], aliases: true) || {}
|
|
103
|
+
else
|
|
104
|
+
{}
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/cli"
|
|
4
|
+
require "ace/core"
|
|
5
|
+
require_relative "../nav"
|
|
6
|
+
# Commands (Hanami pattern: CLI::Commands::)
|
|
7
|
+
require_relative "cli/commands/resolve"
|
|
8
|
+
require_relative "cli/commands/list"
|
|
9
|
+
require_relative "cli/commands/create"
|
|
10
|
+
require_relative "cli/commands/sources"
|
|
11
|
+
|
|
12
|
+
module Ace
|
|
13
|
+
module Support
|
|
14
|
+
module Nav
|
|
15
|
+
# ace-support-cli based CLI registry for ace-nav
|
|
16
|
+
module CLI
|
|
17
|
+
extend Ace::Support::Cli::RegistryDsl
|
|
18
|
+
|
|
19
|
+
PROGRAM_NAME = "ace-nav"
|
|
20
|
+
|
|
21
|
+
REGISTERED_COMMANDS = [
|
|
22
|
+
["resolve", "Resolve resource path or content"],
|
|
23
|
+
["list", "List matching resources"],
|
|
24
|
+
["create", "Create resource from template"],
|
|
25
|
+
["sources", "Show available sources"]
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
HELP_EXAMPLES = [
|
|
29
|
+
"ace-nav resolve wfi://task/work # Get workflow file path",
|
|
30
|
+
"ace-nav list 'wfi://*' # Browse all workflows",
|
|
31
|
+
"ace-nav sources # Show registered sources"
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
# Register the resolve command (default) - Hanami pattern: CLI::Commands::
|
|
35
|
+
register "resolve", CLI::Commands::Resolve.new
|
|
36
|
+
|
|
37
|
+
# Register the list command
|
|
38
|
+
register "list", CLI::Commands::List.new
|
|
39
|
+
|
|
40
|
+
# Register the create command
|
|
41
|
+
register "create", CLI::Commands::Create.new
|
|
42
|
+
|
|
43
|
+
# Register the sources command
|
|
44
|
+
register "sources", CLI::Commands::Sources.new
|
|
45
|
+
|
|
46
|
+
# Register version command
|
|
47
|
+
version_cmd = Ace::Support::Cli::VersionCommand.build(
|
|
48
|
+
gem_name: "ace-support-nav",
|
|
49
|
+
version: Ace::Support::Nav::VERSION
|
|
50
|
+
)
|
|
51
|
+
register "version", version_cmd
|
|
52
|
+
register "--version", version_cmd
|
|
53
|
+
|
|
54
|
+
help_cmd = Ace::Support::Cli::HelpCommand.build(
|
|
55
|
+
program_name: PROGRAM_NAME,
|
|
56
|
+
version: Ace::Support::Nav::VERSION,
|
|
57
|
+
commands: REGISTERED_COMMANDS,
|
|
58
|
+
examples: HELP_EXAMPLES
|
|
59
|
+
)
|
|
60
|
+
register "help", help_cmd
|
|
61
|
+
register "--help", help_cmd
|
|
62
|
+
register "-h", help_cmd
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Support
|
|
5
|
+
module Nav
|
|
6
|
+
module Models
|
|
7
|
+
# Represents a source of handbook resources
|
|
8
|
+
class HandbookSource
|
|
9
|
+
attr_reader :name, :path, :alias_name, :type, :priority, :resource_root
|
|
10
|
+
|
|
11
|
+
def initialize(name:, path:, alias_name: nil, type: :gem, priority: 100, resource_root: nil)
|
|
12
|
+
@name = name
|
|
13
|
+
@path = path
|
|
14
|
+
@alias_name = alias_name || derive_alias(name, type)
|
|
15
|
+
@type = type # :project, :user, :gem, :custom
|
|
16
|
+
@priority = priority
|
|
17
|
+
@resource_root = resource_root || default_resource_root
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def project?
|
|
21
|
+
type == :project
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def user?
|
|
25
|
+
type == :user
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def gem?
|
|
29
|
+
type == :gem
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def custom?
|
|
33
|
+
type == :custom
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def handbook_path
|
|
37
|
+
resource_root
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def exists?
|
|
41
|
+
Dir.exist?(handbook_path)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_h
|
|
45
|
+
{
|
|
46
|
+
name: name,
|
|
47
|
+
path: path,
|
|
48
|
+
alias: alias_name,
|
|
49
|
+
type: type,
|
|
50
|
+
priority: priority,
|
|
51
|
+
exists: exists?
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def default_resource_root
|
|
58
|
+
gem? ? File.join(path, "handbook") : path
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def derive_alias(name, type)
|
|
62
|
+
case type
|
|
63
|
+
when :project then "@project"
|
|
64
|
+
when :user then "@user"
|
|
65
|
+
when :gem then "@#{name}"
|
|
66
|
+
else "@#{name.downcase.gsub(/[^a-z0-9-]/, "-")}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Support
|
|
5
|
+
module Nav
|
|
6
|
+
module Models
|
|
7
|
+
# Represents a source registration for a protocol
|
|
8
|
+
class ProtocolSource
|
|
9
|
+
attr_reader :name, :type, :path, :priority, :description, :origin, :config_file, :alias_name, :config_dir, :config
|
|
10
|
+
|
|
11
|
+
def initialize(name:, type:, path:, priority:, description: nil, origin: nil, config_file: nil, config_dir: nil, config: nil)
|
|
12
|
+
@name = name
|
|
13
|
+
@type = type
|
|
14
|
+
@path = path
|
|
15
|
+
@priority = priority
|
|
16
|
+
@description = description
|
|
17
|
+
@origin = origin
|
|
18
|
+
@config_file = config_file
|
|
19
|
+
@config_dir = config_dir
|
|
20
|
+
@config = config
|
|
21
|
+
@alias_name = "@#{name}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get the full path for this source
|
|
25
|
+
def full_path
|
|
26
|
+
case @type
|
|
27
|
+
when "gem"
|
|
28
|
+
# For gem type, resolve through RubyGems
|
|
29
|
+
require "rubygems"
|
|
30
|
+
begin
|
|
31
|
+
spec = Gem::Specification.find_by_name(@name)
|
|
32
|
+
gem_dir = spec.gem_dir
|
|
33
|
+
|
|
34
|
+
# Get relative path from config, or use default
|
|
35
|
+
relative = @config&.dig("relative_path") || "handbook/workflow-instructions"
|
|
36
|
+
|
|
37
|
+
File.join(gem_dir, relative)
|
|
38
|
+
rescue Gem::LoadError => e
|
|
39
|
+
# Gem not found, return a placeholder path
|
|
40
|
+
warn "Gem '#{@name}' not found: #{e.message}" if ENV["VERBOSE"]
|
|
41
|
+
"/gem-not-found/#{@name}"
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
# Original path resolution logic for directory/path types
|
|
45
|
+
return path if path&.start_with?("/")
|
|
46
|
+
|
|
47
|
+
if config_dir && path
|
|
48
|
+
# Resolve relative paths from project root (parent of .ace directory)
|
|
49
|
+
project_dir = find_project_root(config_dir)
|
|
50
|
+
if project_dir
|
|
51
|
+
File.expand_path(File.join(project_dir, path))
|
|
52
|
+
else
|
|
53
|
+
File.expand_path(path)
|
|
54
|
+
end
|
|
55
|
+
elsif path
|
|
56
|
+
# Fallback to expand from current directory
|
|
57
|
+
File.expand_path(path)
|
|
58
|
+
else
|
|
59
|
+
# No path specified
|
|
60
|
+
"/no-path-specified"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def exists?
|
|
66
|
+
Dir.exist?(full_path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_h
|
|
70
|
+
{
|
|
71
|
+
name: name,
|
|
72
|
+
type: type,
|
|
73
|
+
path: path,
|
|
74
|
+
full_path: full_path,
|
|
75
|
+
priority: priority,
|
|
76
|
+
description: description,
|
|
77
|
+
origin: origin,
|
|
78
|
+
exists: exists?
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def to_s
|
|
83
|
+
"#{name} (#{type}): #{full_path}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# Walk up from config_dir to find the .ace directory, return its parent (project root)
|
|
89
|
+
# config_dir is like /path/to/project/.ace/protocols/wfi-sources/local.yml
|
|
90
|
+
def find_project_root(dir)
|
|
91
|
+
ace_dir = dir
|
|
92
|
+
while ace_dir && !ace_dir.end_with?("/.ace")
|
|
93
|
+
parent = File.dirname(ace_dir)
|
|
94
|
+
break if parent == ace_dir # Reached filesystem root
|
|
95
|
+
ace_dir = parent
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
File.dirname(ace_dir) if ace_dir&.end_with?("/.ace")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Support
|
|
5
|
+
module Nav
|
|
6
|
+
module Models
|
|
7
|
+
# Represents a resource within a handbook
|
|
8
|
+
class Resource
|
|
9
|
+
attr_reader :uri, :path, :source, :protocol, :resource_path
|
|
10
|
+
|
|
11
|
+
def initialize(uri:, path:, source:, protocol:, resource_path:)
|
|
12
|
+
@uri = uri
|
|
13
|
+
@path = path
|
|
14
|
+
@source = source
|
|
15
|
+
@protocol = protocol
|
|
16
|
+
@resource_path = resource_path
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def content
|
|
20
|
+
return nil unless File.exist?(path)
|
|
21
|
+
File.read(path)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def exists?
|
|
25
|
+
File.exist?(path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def directory?
|
|
29
|
+
File.directory?(path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_h
|
|
33
|
+
{
|
|
34
|
+
uri: uri,
|
|
35
|
+
path: path,
|
|
36
|
+
source: source.to_h,
|
|
37
|
+
protocol: protocol,
|
|
38
|
+
resource_path: resource_path,
|
|
39
|
+
exists: exists?
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../molecules/config_loader"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Support
|
|
7
|
+
module Nav
|
|
8
|
+
module Models
|
|
9
|
+
# Represents a parsed resource URI
|
|
10
|
+
class ResourceUri
|
|
11
|
+
attr_reader :protocol, :source, :path, :raw
|
|
12
|
+
|
|
13
|
+
def initialize(raw_uri, config_loader: nil)
|
|
14
|
+
@raw = raw_uri
|
|
15
|
+
@config_loader = config_loader || Molecules::ConfigLoader.new
|
|
16
|
+
parse_uri(raw_uri)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def valid?
|
|
20
|
+
!protocol.nil? && @config_loader.valid_protocol?(protocol)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def source_specific?
|
|
24
|
+
!source.nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cascade_search?
|
|
28
|
+
source.nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
raw
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_h
|
|
36
|
+
{
|
|
37
|
+
raw: raw,
|
|
38
|
+
protocol: protocol,
|
|
39
|
+
source: source,
|
|
40
|
+
path: path,
|
|
41
|
+
source_specific: source_specific?,
|
|
42
|
+
cascade_search: cascade_search?
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def parse_uri(raw_uri)
|
|
49
|
+
return unless raw_uri.include?("://")
|
|
50
|
+
|
|
51
|
+
parts = raw_uri.split("://", 2)
|
|
52
|
+
@protocol = parts[0]
|
|
53
|
+
|
|
54
|
+
return unless parts[1]
|
|
55
|
+
|
|
56
|
+
# Check for @ prefix indicating source-specific lookup
|
|
57
|
+
if parts[1].start_with?("@")
|
|
58
|
+
# Extract source and path
|
|
59
|
+
# Format: @source/path or just @source
|
|
60
|
+
if parts[1].include?("/")
|
|
61
|
+
source_parts = parts[1].split("/", 2)
|
|
62
|
+
@source = source_parts[0] # includes @
|
|
63
|
+
@path = source_parts[1]
|
|
64
|
+
else
|
|
65
|
+
@source = parts[1] # just @source
|
|
66
|
+
@path = nil
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
# No source specified - cascade search
|
|
70
|
+
@source = nil
|
|
71
|
+
@path = parts[1].empty? ? nil : parts[1]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|