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,275 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "pathname"
|
|
5
|
+
require_relative "source_registry"
|
|
6
|
+
require "ace/support/fs"
|
|
7
|
+
require "ace/support/config"
|
|
8
|
+
|
|
9
|
+
module Ace
|
|
10
|
+
module Support
|
|
11
|
+
module Nav
|
|
12
|
+
module Molecules
|
|
13
|
+
# Loads configuration from .ace/nav/*.yml files and protocols
|
|
14
|
+
# ADR-022: Uses Ace::Support::Config.create() for gem defaults + user cascade
|
|
15
|
+
class ConfigLoader
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
def initialize(config_dir = nil, source_registry: nil)
|
|
19
|
+
@config_dir = find_config_dir(config_dir)
|
|
20
|
+
@configs = {}
|
|
21
|
+
@protocols = nil # Lazy load protocols
|
|
22
|
+
@source_registry = source_registry || SourceRegistry.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Load main settings configuration
|
|
26
|
+
# ADR-022: Uses Ace::Support::Config.create() for gem defaults + user cascade
|
|
27
|
+
#
|
|
28
|
+
# NOTE: The "nav" namespace is intentionally preserved (not "support/nav") for
|
|
29
|
+
# backward compatibility with existing user configurations in .ace/nav/config.yml.
|
|
30
|
+
# This allows users upgrading from ace-nav to ace-support-nav to keep their config
|
|
31
|
+
# without migration. See PR #152 review discussion for context.
|
|
32
|
+
#
|
|
33
|
+
# @return [Hash] Configuration with defaults and user overrides
|
|
34
|
+
def load_settings
|
|
35
|
+
# Return cached if already loaded
|
|
36
|
+
return @configs["settings"] if @configs.key?("settings")
|
|
37
|
+
|
|
38
|
+
# Load gem defaults via Ace::Support::Config
|
|
39
|
+
# Use centralized gem_root from Nav module (avoids path depth duplication)
|
|
40
|
+
resolver = Ace::Support::Config.create(
|
|
41
|
+
config_dir: ".ace",
|
|
42
|
+
defaults_dir: ".ace-defaults",
|
|
43
|
+
gem_path: Ace::Support::Nav.gem_root
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Get gem defaults first - uses "nav" namespace for backward compatibility
|
|
47
|
+
gem_defaults = begin
|
|
48
|
+
resolver.resolve_namespace("nav").data
|
|
49
|
+
rescue Ace::Support::Config::YamlParseError => e
|
|
50
|
+
warn "Warning: Failed to parse nav config: #{e.message}" if debug?
|
|
51
|
+
load_gem_defaults_only
|
|
52
|
+
rescue => e
|
|
53
|
+
warn "Warning: Could not load ace-support-nav config: #{e.message}" if debug?
|
|
54
|
+
load_gem_defaults_only
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Load user config from @config_dir if explicitly set (for testing/override)
|
|
58
|
+
# or use the cascade from resolver
|
|
59
|
+
user_config = if @config_dir
|
|
60
|
+
load_user_config_from_dir(@config_dir)
|
|
61
|
+
else
|
|
62
|
+
# No explicit config_dir - defaults already include cascade
|
|
63
|
+
{}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Deep merge: user config over gem defaults
|
|
67
|
+
@configs["settings"] = Ace::Support::Config::Atoms::DeepMerger.merge(gem_defaults, user_config)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Load protocol-specific configuration
|
|
71
|
+
def load_protocol_config(protocol)
|
|
72
|
+
protocols = load_protocols
|
|
73
|
+
return protocols[protocol] if protocols[protocol]
|
|
74
|
+
|
|
75
|
+
# Fallback to defaults for backward compatibility
|
|
76
|
+
default_protocol_config(protocol)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Load all available protocols
|
|
80
|
+
def load_protocols
|
|
81
|
+
return @protocols if @protocols
|
|
82
|
+
|
|
83
|
+
@protocols = {}
|
|
84
|
+
|
|
85
|
+
# 0. Load from gem defaults (lowest priority)
|
|
86
|
+
gem_defaults_dir = File.join(Ace::Support::Nav.gem_root, ".ace-defaults", "nav", "protocols")
|
|
87
|
+
load_directory_protocols(gem_defaults_dir).each do |protocol_data|
|
|
88
|
+
key = protocol_data["protocol"]
|
|
89
|
+
@protocols[key] = protocol_data if key
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# 1. Load from user ~/.ace/nav/protocols/
|
|
93
|
+
load_directory_protocols(File.expand_path("~/.ace/nav/protocols")).each do |protocol_data|
|
|
94
|
+
key = protocol_data["protocol"]
|
|
95
|
+
@protocols[key] = protocol_data if key
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 2. Load from project .ace/protocols/ hierarchy (highest priority)
|
|
99
|
+
# Find all .ace/protocols directories from current dir up to project root
|
|
100
|
+
discover_project_protocol_dirs.each do |dir|
|
|
101
|
+
load_directory_protocols(dir).each do |protocol_data|
|
|
102
|
+
key = protocol_data["protocol"]
|
|
103
|
+
@protocols[key] = protocol_data if key
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@protocols
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get list of valid protocol names
|
|
111
|
+
def valid_protocols
|
|
112
|
+
load_protocols.keys
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check if a protocol is valid
|
|
116
|
+
def valid_protocol?(protocol)
|
|
117
|
+
valid_protocols.include?(protocol)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get all available configuration files
|
|
121
|
+
def available_configs
|
|
122
|
+
return [] unless @config_dir && Dir.exist?(@config_dir)
|
|
123
|
+
|
|
124
|
+
Dir.glob(File.join(@config_dir, "*.yml")).map do |path|
|
|
125
|
+
File.basename(path, ".yml")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Get all discovered protocols with their metadata
|
|
130
|
+
def discovered_protocols
|
|
131
|
+
load_protocols
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Get sources for a specific protocol
|
|
135
|
+
def sources_for_protocol(protocol)
|
|
136
|
+
@source_registry.sources_for_protocol(protocol)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Get the type of a protocol (cmd or file)
|
|
140
|
+
# @param protocol_name [String] The protocol name (e.g., "task", "wfi")
|
|
141
|
+
# @return [String] Protocol type: "cmd" for command delegation, "file" for file-based (default)
|
|
142
|
+
def protocol_type(protocol_name)
|
|
143
|
+
protocol_config = load_protocol_config(protocol_name)
|
|
144
|
+
protocol_config["type"] || "file"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# Load user config from explicit config directory
|
|
150
|
+
# @param config_dir [String] Path to .ace/nav directory
|
|
151
|
+
# @return [Hash] User configuration
|
|
152
|
+
def load_user_config_from_dir(config_dir)
|
|
153
|
+
config_path = File.join(config_dir, "config.yml")
|
|
154
|
+
|
|
155
|
+
if File.exist?(config_path)
|
|
156
|
+
load_yaml_file(config_path)
|
|
157
|
+
else
|
|
158
|
+
{}
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def discover_project_protocol_dirs
|
|
163
|
+
dirs = []
|
|
164
|
+
|
|
165
|
+
# Use directory traverser to find all .ace directories up to project root
|
|
166
|
+
traverser = Ace::Support::Fs::Molecules::DirectoryTraverser.new(start_path: Dir.pwd)
|
|
167
|
+
config_dirs = traverser.find_config_directories
|
|
168
|
+
|
|
169
|
+
# Check each .ace directory for a protocols subdirectory
|
|
170
|
+
config_dirs.each do |config_dir|
|
|
171
|
+
protocol_dir = File.join(config_dir, "nav/protocols")
|
|
172
|
+
dirs << protocol_dir if Dir.exist?(protocol_dir)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
dirs
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def load_directory_protocols(dir_path)
|
|
179
|
+
protocols = []
|
|
180
|
+
return protocols unless Dir.exist?(dir_path)
|
|
181
|
+
|
|
182
|
+
Dir.glob(File.join(dir_path, "*.yml")).each do |file|
|
|
183
|
+
protocol_data = load_yaml_file(file)
|
|
184
|
+
protocols << protocol_data if protocol_data.is_a?(Hash)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
protocols
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def find_config_dir(config_dir)
|
|
191
|
+
return config_dir if config_dir
|
|
192
|
+
|
|
193
|
+
# Search for .ace/nav directory in cascade
|
|
194
|
+
search_paths = [
|
|
195
|
+
File.expand_path("./.ace/nav"), # Project level
|
|
196
|
+
File.expand_path("~/.ace/nav") # User level
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
search_paths.find { |path| Dir.exist?(path) }
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def load_yaml_file(path)
|
|
203
|
+
content = File.read(path)
|
|
204
|
+
YAML.safe_load(content, permitted_classes: [Symbol]) || {}
|
|
205
|
+
rescue => e
|
|
206
|
+
warn "Warning: Failed to load config from #{path}: #{e.message}"
|
|
207
|
+
{}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Load only gem defaults (for fallback on config errors)
|
|
211
|
+
# Delegates to module-level method to avoid duplication
|
|
212
|
+
# @return [Hash] Gem defaults or empty hash
|
|
213
|
+
def load_gem_defaults_only
|
|
214
|
+
Ace::Support::Nav.load_gem_defaults_fallback
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Check if debug mode is enabled
|
|
218
|
+
# @return [Boolean] True if debug mode is enabled
|
|
219
|
+
def debug?
|
|
220
|
+
ENV["ACE_DEBUG"] == "1" || ENV["DEBUG"] == "1"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def default_protocol_config(protocol)
|
|
224
|
+
case protocol
|
|
225
|
+
when "wfi"
|
|
226
|
+
{
|
|
227
|
+
"workflows" => {
|
|
228
|
+
"extensions" => [".wfi.md", ".workflow.md"],
|
|
229
|
+
"default_dir" => "workflow-instructions"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
when "tmpl"
|
|
233
|
+
{
|
|
234
|
+
"templates" => {
|
|
235
|
+
"extensions" => [".tmpl.md", ".template.md"],
|
|
236
|
+
"default_dir" => "templates"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
when "guide"
|
|
240
|
+
{
|
|
241
|
+
"guides" => {
|
|
242
|
+
"extensions" => [".guide.md", ".md"],
|
|
243
|
+
"default_dir" => "guides"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
when "sample"
|
|
247
|
+
{
|
|
248
|
+
"samples" => {
|
|
249
|
+
"extensions" => [],
|
|
250
|
+
"default_dir" => "samples"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
when "task"
|
|
254
|
+
{
|
|
255
|
+
"tasks" => {
|
|
256
|
+
"search_paths" => [
|
|
257
|
+
"dev-taskflow/current/*/tasks",
|
|
258
|
+
"dev-taskflow/backlog"
|
|
259
|
+
],
|
|
260
|
+
"extensions" => [".md"],
|
|
261
|
+
"autocorrect" => {
|
|
262
|
+
"enabled" => true,
|
|
263
|
+
"pad_zeros" => true
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else
|
|
268
|
+
{}
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../atoms/gem_resolver"
|
|
4
|
+
require_relative "../atoms/path_normalizer"
|
|
5
|
+
require_relative "../models/handbook_source"
|
|
6
|
+
require_relative "config_loader"
|
|
7
|
+
|
|
8
|
+
module Ace
|
|
9
|
+
module Support
|
|
10
|
+
module Nav
|
|
11
|
+
module Molecules
|
|
12
|
+
# Scans and indexes available handbooks
|
|
13
|
+
class HandbookScanner
|
|
14
|
+
def initialize(gem_resolver: nil, path_normalizer: nil, config_loader: nil)
|
|
15
|
+
@gem_resolver = gem_resolver || Atoms::GemResolver.new
|
|
16
|
+
@path_normalizer = path_normalizer || Atoms::PathNormalizer.new
|
|
17
|
+
@config_loader = config_loader || ConfigLoader.new
|
|
18
|
+
@settings = @config_loader.load_settings
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def scan_all_sources
|
|
22
|
+
sources = []
|
|
23
|
+
|
|
24
|
+
# Priority 1: Project overrides
|
|
25
|
+
sources << scan_project_source
|
|
26
|
+
|
|
27
|
+
# Priority 2: User overrides
|
|
28
|
+
sources << scan_user_source
|
|
29
|
+
|
|
30
|
+
# Priority 3: Gem handbooks
|
|
31
|
+
sources.concat(scan_gem_sources)
|
|
32
|
+
|
|
33
|
+
# Priority 4: Custom configured paths
|
|
34
|
+
sources.concat(scan_custom_sources)
|
|
35
|
+
|
|
36
|
+
# Filter out non-existent sources unless specifically configured
|
|
37
|
+
sources.compact.select { |s| s.exists? || s.custom? }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def scan_source_by_alias(alias_name)
|
|
41
|
+
return scan_project_source if alias_name == "@project"
|
|
42
|
+
return scan_user_source if alias_name == "@user"
|
|
43
|
+
|
|
44
|
+
# Check if it's a gem source
|
|
45
|
+
if alias_name.start_with?("@ace-")
|
|
46
|
+
gem_name = alias_name[1..] # Remove @
|
|
47
|
+
return scan_gem_source(gem_name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check custom sources
|
|
51
|
+
scan_custom_source(alias_name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def find_resources_in_source(source, protocol, pattern = "*")
|
|
55
|
+
return [] unless source&.exists?
|
|
56
|
+
|
|
57
|
+
handbook_path = source.handbook_path
|
|
58
|
+
protocol_dir = protocol_to_directory(protocol)
|
|
59
|
+
search_path = File.join(handbook_path, protocol_dir)
|
|
60
|
+
|
|
61
|
+
return [] unless Dir.exist?(search_path)
|
|
62
|
+
|
|
63
|
+
# Find matching files
|
|
64
|
+
extension = protocol_to_extension(protocol)
|
|
65
|
+
glob_pattern = File.join(search_path, "**", "#{pattern}#{extension}")
|
|
66
|
+
|
|
67
|
+
Dir.glob(glob_pattern).map do |file_path|
|
|
68
|
+
relative_path = file_path.sub("#{search_path}/", "").sub(extension, "")
|
|
69
|
+
{
|
|
70
|
+
path: file_path,
|
|
71
|
+
relative_path: relative_path,
|
|
72
|
+
source: source,
|
|
73
|
+
protocol: protocol
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def scan_project_source
|
|
81
|
+
project_path = File.expand_path("./.ace-handbook")
|
|
82
|
+
return nil unless Dir.exist?(project_path)
|
|
83
|
+
|
|
84
|
+
Models::HandbookSource.new(
|
|
85
|
+
name: "project",
|
|
86
|
+
path: project_path,
|
|
87
|
+
alias_name: "@project",
|
|
88
|
+
type: :project,
|
|
89
|
+
priority: 10
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def scan_user_source
|
|
94
|
+
user_path = File.expand_path("~/.ace-handbook")
|
|
95
|
+
return nil unless Dir.exist?(user_path)
|
|
96
|
+
|
|
97
|
+
Models::HandbookSource.new(
|
|
98
|
+
name: "user",
|
|
99
|
+
path: user_path,
|
|
100
|
+
alias_name: "@user",
|
|
101
|
+
type: :user,
|
|
102
|
+
priority: 20
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def scan_gem_sources
|
|
107
|
+
@gem_resolver.find_ace_gems.map.with_index do |gem_info, index|
|
|
108
|
+
next unless gem_info[:has_handbook]
|
|
109
|
+
|
|
110
|
+
Models::HandbookSource.new(
|
|
111
|
+
name: gem_info[:name],
|
|
112
|
+
path: gem_info[:path],
|
|
113
|
+
alias_name: "@#{gem_info[:name]}",
|
|
114
|
+
type: :gem,
|
|
115
|
+
priority: 100 + index
|
|
116
|
+
)
|
|
117
|
+
end.compact
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def scan_gem_source(gem_name)
|
|
121
|
+
gem_info = @gem_resolver.find_gem_by_name(gem_name)
|
|
122
|
+
return nil unless gem_info
|
|
123
|
+
return nil unless gem_info[:has_handbook]
|
|
124
|
+
|
|
125
|
+
Models::HandbookSource.new(
|
|
126
|
+
name: gem_info[:name],
|
|
127
|
+
path: gem_info[:path],
|
|
128
|
+
alias_name: "@#{gem_info[:name]}",
|
|
129
|
+
type: :gem,
|
|
130
|
+
priority: 100
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def scan_custom_sources
|
|
135
|
+
sources = []
|
|
136
|
+
config_sources = @settings.dig("handbooks", "sources") || []
|
|
137
|
+
|
|
138
|
+
config_sources.each do |source_config|
|
|
139
|
+
next if source_config["gem"] # Skip gem sources (handled elsewhere)
|
|
140
|
+
|
|
141
|
+
if source_config["path"]
|
|
142
|
+
path = File.expand_path(source_config["path"])
|
|
143
|
+
next unless Dir.exist?(path)
|
|
144
|
+
|
|
145
|
+
sources << Models::HandbookSource.new(
|
|
146
|
+
name: source_config["alias"] || File.basename(path),
|
|
147
|
+
path: path,
|
|
148
|
+
alias_name: "@#{source_config["alias"] || File.basename(path)}",
|
|
149
|
+
type: :custom,
|
|
150
|
+
priority: 200
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
sources
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def scan_custom_source(alias_name)
|
|
159
|
+
config_sources = @settings.dig("handbooks", "sources") || []
|
|
160
|
+
|
|
161
|
+
# Remove @ prefix if present
|
|
162
|
+
search_alias = alias_name.start_with?("@") ? alias_name[1..] : alias_name
|
|
163
|
+
|
|
164
|
+
config_sources.each do |source_config|
|
|
165
|
+
next unless source_config["alias"] == search_alias
|
|
166
|
+
|
|
167
|
+
path = File.expand_path(source_config["path"])
|
|
168
|
+
return nil unless Dir.exist?(path)
|
|
169
|
+
|
|
170
|
+
return Models::HandbookSource.new(
|
|
171
|
+
name: source_config["alias"],
|
|
172
|
+
path: path,
|
|
173
|
+
alias_name: "@#{source_config["alias"]}",
|
|
174
|
+
type: :custom,
|
|
175
|
+
priority: 200
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
nil
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def protocol_to_directory(protocol)
|
|
183
|
+
config = @config_loader.load_protocol_config(protocol)
|
|
184
|
+
return protocol unless config
|
|
185
|
+
|
|
186
|
+
# Use the directory specified in the protocol config
|
|
187
|
+
config["directory"] || protocol
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def protocol_to_extension(protocol)
|
|
191
|
+
config = @config_loader.load_protocol_config(protocol)
|
|
192
|
+
return ".md" unless config
|
|
193
|
+
|
|
194
|
+
# Use the extensions specified in the protocol config
|
|
195
|
+
extensions = config["extensions"]
|
|
196
|
+
return "" if extensions.nil? || extensions.empty?
|
|
197
|
+
|
|
198
|
+
extensions.first
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|