ascii_binder_gabriel_rh 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +56 -0
  3. data/Dockerfile +19 -0
  4. data/Gemfile +3 -0
  5. data/Guardfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.adoc +21 -0
  8. data/README.md +1 -0
  9. data/Rakefile +1 -0
  10. data/ascii_binder_gabriel_rh.gemspec +41 -0
  11. data/bin/ascii_binder_gabriel_rh +1 -0
  12. data/bin/asciibinder_gabriel_rh +350 -0
  13. data/features/command_help.feature +8 -0
  14. data/features/command_version.feature +8 -0
  15. data/features/repo_build.feature +39 -0
  16. data/features/repo_clean.feature +20 -0
  17. data/features/repo_clone.feature +22 -0
  18. data/features/repo_create.feature +13 -0
  19. data/features/repo_package.feature +15 -0
  20. data/features/step_definitions/steps.rb +189 -0
  21. data/features/support/_clone_distro_map.yml +14 -0
  22. data/features/support/_invalid_alias_topic_map.yml +50 -0
  23. data/features/support/_invalid_distro_map.yml +14 -0
  24. data/features/support/env.rb +518 -0
  25. data/features/support/test_distro/.gitignore +9 -0
  26. data/features/support/test_distro/_distro_map.yml +29 -0
  27. data/features/support/test_distro/_images/asciibinder-logo-horizontal.png +0 -0
  28. data/features/support/test_distro/_images/asciibinder_web_logo.svg +125 -0
  29. data/features/support/test_distro/_images/book_pages_bg.jpg +0 -0
  30. data/features/support/test_distro/_images/favicon.ico +0 -0
  31. data/features/support/test_distro/_images/favicon32x32.png +0 -0
  32. data/features/support/test_distro/_javascripts/.gitkeep +0 -0
  33. data/features/support/test_distro/_stylesheets/asciibinder.css +568 -0
  34. data/features/support/test_distro/_templates/_css.html.erb +3 -0
  35. data/features/support/test_distro/_templates/_nav.html.erb +31 -0
  36. data/features/support/test_distro/_templates/page.html.erb +92 -0
  37. data/features/support/test_distro/_topic_map.yml +50 -0
  38. data/features/support/test_distro/index-main.html +10 -0
  39. data/features/support/test_distro/index-test.html +10 -0
  40. data/features/support/test_distro/main_only_topic_group/index.adoc +17 -0
  41. data/features/support/test_distro/test_only_topic_group/index.adoc +17 -0
  42. data/features/support/test_distro/welcome/aliased.adoc +9 -0
  43. data/features/support/test_distro/welcome/index.adoc +17 -0
  44. data/features/support/test_distro/welcome/subtopics/index.adoc +17 -0
  45. data/features/support/test_distro/welcome/subtopics/main_only_topic.adoc +17 -0
  46. data/features/support/test_distro/welcome/subtopics/test_only_topic.adoc +17 -0
  47. data/features/support/test_distro/welcome/subtopics/wildcard_all.adoc +17 -0
  48. data/lib/ascii_binder_gabriel_rh/distro.rb +111 -0
  49. data/lib/ascii_binder_gabriel_rh/distro_branch.rb +97 -0
  50. data/lib/ascii_binder_gabriel_rh/distro_map.rb +67 -0
  51. data/lib/ascii_binder_gabriel_rh/engine.rb +690 -0
  52. data/lib/ascii_binder_gabriel_rh/helpers.rb +172 -0
  53. data/lib/ascii_binder_gabriel_rh/site.rb +52 -0
  54. data/lib/ascii_binder_gabriel_rh/site_info.rb +22 -0
  55. data/lib/ascii_binder_gabriel_rh/site_map.rb +24 -0
  56. data/lib/ascii_binder_gabriel_rh/tasks/guards.rb +15 -0
  57. data/lib/ascii_binder_gabriel_rh/tasks/tasks.rb +41 -0
  58. data/lib/ascii_binder_gabriel_rh/template_renderer.rb +29 -0
  59. data/lib/ascii_binder_gabriel_rh/topic_entity.rb +324 -0
  60. data/lib/ascii_binder_gabriel_rh/topic_map.rb +112 -0
  61. data/lib/ascii_binder_gabriel_rh/version.rb +3 -0
  62. data/lib/ascii_binder_gabriel_rh.rb +5 -0
  63. data/templates/.gitignore +9 -0
  64. data/templates/LICENSE.txt +4 -0
  65. data/templates/README.adoc +11 -0
  66. data/templates/_distro_map.yml +11 -0
  67. data/templates/_images/asciibinder-logo-horizontal.png +0 -0
  68. data/templates/_images/asciibinder_web_logo.svg +125 -0
  69. data/templates/_images/book_pages_bg.jpg +0 -0
  70. data/templates/_images/favicon.ico +0 -0
  71. data/templates/_images/favicon32x32.png +0 -0
  72. data/templates/_javascripts/.gitkeep +0 -0
  73. data/templates/_javascripts/bootstrap-offcanvas.js +6 -0
  74. data/templates/_stylesheets/asciibinder.css +568 -0
  75. data/templates/_templates/_breadcrumb.html.erb +14 -0
  76. data/templates/_templates/_css.html.erb +3 -0
  77. data/templates/_templates/_nav.html.erb +14 -0
  78. data/templates/_templates/_title.html.erb +8 -0
  79. data/templates/_templates/page.html.erb +88 -0
  80. data/templates/_topic_map.yml +29 -0
  81. data/templates/index-main.html +89 -0
  82. data/templates/welcome/index.adoc +14 -0
  83. metadata +423 -0
@@ -0,0 +1,172 @@
1
+ require 'logger'
2
+ require 'stringio'
3
+
4
+ module AsciiBinderGabrielRH
5
+ module Helpers
6
+ BUILD_FILENAME = '_build_cfg.yml'
7
+ TOPIC_MAP_FOLDER = '_topic_maps'
8
+ TOPIC_MAP_FILENAME = '_topic_map.yml'
9
+ DISTRO_MAP_FILENAME = '_distro_map.yml'
10
+ PREVIEW_DIRNAME = '_preview'
11
+ PACKAGE_DIRNAME = '_package'
12
+ STYLESHEET_DIRNAME = '_stylesheets'
13
+ JAVASCRIPT_DIRNAME = '_javascripts'
14
+ IMAGE_DIRNAME = '_images'
15
+ BLANK_STRING_RE = Regexp.new('^\s*$')
16
+ ID_STRING_RE = Regexp.new('^[A-Za-z0-9\-\_]+$')
17
+ URL_STRING_RE = Regexp.new('^https?:\/\/[\S]+$')
18
+
19
+ def valid_id?(check_id)
20
+ return false unless check_id.is_a?(String)
21
+ return false unless check_id.match ID_STRING_RE
22
+ return true
23
+ end
24
+
25
+ def valid_string?(check_string)
26
+ return false unless check_string.is_a?(String)
27
+ return false unless check_string.length > 0
28
+ return false if check_string.match BLANK_STRING_RE
29
+ return true
30
+ end
31
+
32
+ def valid_url?(check_string)
33
+ return false unless valid_string?(check_string)
34
+ return false unless check_string.match URL_STRING_RE
35
+ return true
36
+ end
37
+
38
+ def camelize(text)
39
+ text.gsub(/[^0-9a-zA-Z ]/i, '').split(' ').map{ |t| t.capitalize }.join
40
+ end
41
+
42
+ def git_root_dir
43
+ @git_root_dir ||= `git rev-parse --show-toplevel`.chomp
44
+ end
45
+
46
+ def gem_root_dir
47
+ @gem_root_dir ||= File.expand_path("../../../", __FILE__)
48
+ end
49
+
50
+ def set_docs_root_dir(docs_root_dir)
51
+ AsciiBinderGabrielRH.const_set("DOCS_ROOT_DIR", docs_root_dir)
52
+ end
53
+
54
+ def docs_root_dir
55
+ AsciiBinderGabrielRH::DOCS_ROOT_DIR
56
+ end
57
+
58
+ def set_depth(user_depth)
59
+ AsciiBinderGabrielRH.const_set("DEPTH", user_depth)
60
+ end
61
+
62
+ def set_log_level(user_log_level)
63
+ AsciiBinderGabrielRH.const_set("LOG_LEVEL", log_levels[user_log_level])
64
+ end
65
+
66
+ def log_levels
67
+ @log_levels ||= {
68
+ :debug => Logger::DEBUG.to_i,
69
+ :error => Logger::ERROR.to_i,
70
+ :fatal => Logger::FATAL.to_i,
71
+ :info => Logger::INFO.to_i,
72
+ :warn => Logger::WARN.to_i,
73
+ }
74
+ end
75
+
76
+ def logerr
77
+ @logerr ||= begin
78
+ logger = Logger.new(STDERR, level: AsciiBinderGabrielRH::LOG_LEVEL)
79
+ logger.formatter = proc do |severity, datetime, progname, msg|
80
+ "#{severity}: #{msg}\n"
81
+ end
82
+ logger
83
+ end
84
+ end
85
+
86
+ def logstd
87
+ @logstd ||= begin
88
+ logger = Logger.new(STDOUT, level: AsciiBinderGabrielRH::LOG_LEVEL)
89
+ logger.formatter = proc do |severity, datetime, progname, msg|
90
+ severity == 'ANY' ? "#{msg}\n" : "#{severity}: #{msg}\n"
91
+ end
92
+ logger
93
+ end
94
+ end
95
+
96
+ def log_info(text)
97
+ logstd.info(text)
98
+ end
99
+
100
+ def log_warn(text)
101
+ logstd.warn(text)
102
+ end
103
+
104
+ def log_error(text)
105
+ logerr.error(text)
106
+ end
107
+
108
+ def log_fatal(text)
109
+ logerr.fatal(text)
110
+ end
111
+
112
+ def log_debug(text)
113
+ logstd.debug(text)
114
+ end
115
+
116
+ def log_unknown(text)
117
+ logstd.unknown(text)
118
+ end
119
+
120
+ def without_warnings
121
+ verboseness_level = $VERBOSE
122
+ $VERBOSE = nil
123
+ yield
124
+ ensure
125
+ $VERBOSE = verboseness_level
126
+ end
127
+
128
+ def template_renderer
129
+ @template_renderer ||= TemplateRenderer.new(docs_root_dir, template_dir)
130
+ end
131
+
132
+ def template_dir
133
+ @template_dir ||= File.join(docs_root_dir,'_templates')
134
+ end
135
+
136
+ def preview_dir
137
+ @preview_dir ||= begin
138
+ lpreview_dir = File.join(docs_root_dir,PREVIEW_DIRNAME)
139
+ if not File.exists?(lpreview_dir)
140
+ Dir.mkdir(lpreview_dir)
141
+ end
142
+ lpreview_dir
143
+ end
144
+ end
145
+
146
+ def package_dir
147
+ @package_dir ||= begin
148
+ lpackage_dir = File.join(docs_root_dir,PACKAGE_DIRNAME)
149
+ if not File.exists?(lpackage_dir)
150
+ Dir.mkdir(lpackage_dir)
151
+ end
152
+ lpackage_dir
153
+ end
154
+ end
155
+
156
+ def stylesheet_dir
157
+ @stylesheet_dir ||= File.join(docs_root_dir,STYLESHEET_DIRNAME)
158
+ end
159
+
160
+ def javascript_dir
161
+ @javascript_dir ||= File.join(docs_root_dir,JAVASCRIPT_DIRNAME)
162
+ end
163
+
164
+ def image_dir
165
+ @image_dir ||= File.join(docs_root_dir,IMAGE_DIRNAME)
166
+ end
167
+
168
+ def alias_text(target)
169
+ "<!DOCTYPE html><html><head><title>#{target}</title><link rel=\"canonical\" href=\"#{target}\"/><meta name=\"robots\" content=\"noindex\"><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" /><meta http-equiv=\"refresh\" content=\"0; url=#{target}\" /></head></html>"
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,52 @@
1
+ require 'ascii_binder_gabriel_rh/helpers'
2
+
3
+ include AsciiBinderGabrielRH::Helpers
4
+
5
+ module AsciiBinderGabrielRH
6
+ class Site
7
+ attr_reader :id, :name, :url
8
+
9
+ def initialize(distro_config)
10
+ @id = distro_config['site']
11
+ @name = distro_config['site_name']
12
+ @url = distro_config['site_url']
13
+ end
14
+
15
+ def is_valid?
16
+ validate
17
+ end
18
+
19
+ def errors
20
+ validate(true)
21
+ end
22
+
23
+ private
24
+
25
+ def validate(verbose=false)
26
+ errors = []
27
+ unless valid_id?(@id)
28
+ if verbose
29
+ errors << "Site ID '#{@id}' is not a valid ID."
30
+ else
31
+ return false
32
+ end
33
+ end
34
+ unless valid_string?(@name)
35
+ if verbose
36
+ errors << "Site name '#{@name}' for site ID '#{@id}' is not a valid string."
37
+ else
38
+ return false
39
+ end
40
+ end
41
+ unless valid_string?(@url)
42
+ if verbose
43
+ errors << "Site URL '#{@url}' for site ID '#{@id}' is not a valid string."
44
+ else
45
+ return false
46
+ end
47
+ end
48
+ return errors if verbose
49
+ return true
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ module AsciiBinderGabrielRH
2
+ class SiteInfo
3
+ attr_reader :id, :name, :url, :distros, :branches
4
+
5
+ def initialize(distro)
6
+ @id = distro.site.id
7
+ @name = distro.site.name
8
+ @url = distro.site.url
9
+ @distros = {}
10
+ @branches = ['main']
11
+ add_distro(distro)
12
+ end
13
+
14
+ def add_distro(distro)
15
+ @distros[distro.id] = distro.branches
16
+ distro.branches.each do |branch|
17
+ next if @branches.include?(branch.id)
18
+ @branches << branch.id
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ require 'ascii_binder_gabriel_rh/site_info'
2
+
3
+ module AsciiBinderGabrielRH
4
+ class SiteMap
5
+ def initialize(distro_map)
6
+ @site_map = {}
7
+ distro_map.distros.each do |distro|
8
+ unless @site_map.has_key?(distro.site.id)
9
+ @site_map[distro.site.id] = AsciiBinderGabrielRH::SiteInfo.new(distro)
10
+ else
11
+ @site_map[distro.site.id].add_distro(distro)
12
+ end
13
+ end
14
+ end
15
+
16
+ def sites
17
+ return @site_map.values
18
+ end
19
+
20
+ def ids
21
+ return @site_map.keys
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ guard 'shell' do
2
+ watch(/^.*\.adoc$/) { |m|
3
+ if not m[0].start_with?('_preview') and not m[0].start_with?('_package')
4
+ full_path = m[0].split('/')
5
+ src_group_path = full_path.length == 1 ? '' : full_path[0..-2].join('/')
6
+ filename = full_path[-1][0..-6]
7
+ system("asciibinder build --page='#{src_group_path}:#{filename}'")
8
+ end
9
+ }
10
+ end
11
+
12
+ guard 'livereload' do
13
+ watch(%r{^_preview/.+\.(css|js|html)$})
14
+ watch(%r{^_preview/.+\/.+\/.+\.(css|js|html)$})
15
+ end
@@ -0,0 +1,41 @@
1
+ require 'rake'
2
+ require 'ascii_binder_gabriel_rh'
3
+
4
+ include AsciiBinderGabrielRH::Engine
5
+ include AsciiBinderGabrielRH::Helpers
6
+
7
+ desc "Build the documentation"
8
+ task :build, :build_distro do |task,args|
9
+ # Figure out which distros we are building.
10
+ # A blank value here == all distros
11
+ set_docs_root_dir(git_root_dir)
12
+ set_log_level(:warn)
13
+ build_distro = args[:build_distro] || ''
14
+ generate_docs(:all,build_distro,nil)
15
+ end
16
+
17
+ desc "Package the documentation"
18
+ task :package, :package_site do |task,args|
19
+ set_docs_root_dir(git_root_dir)
20
+ set_log_level(:warn)
21
+ package_site = args[:package_site] || ''
22
+ Rake::Task["clean"].invoke
23
+ Rake::Task["build"].invoke
24
+ package_docs(package_site)
25
+ end
26
+
27
+ desc "Build the documentation and refresh the page"
28
+ task :refresh_page, :single_page do |task,args|
29
+ set_docs_root_dir(git_root_dir)
30
+ set_log_level(:warn)
31
+ generate_docs(:working_only,'',args[:single_page])
32
+ end
33
+
34
+ desc "Clean all build artifacts"
35
+ task :clean do
36
+ sh "rm -rf _preview/* _package/*" do |ok,res|
37
+ if ! ok
38
+ puts "Nothing to clean."
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'tilt'
2
+
3
+ module AsciiBinderGabrielRH
4
+ class TemplateRenderer
5
+ attr_reader :source_dir, :template_cache
6
+
7
+ def initialize(source_dir,template_directory)
8
+ @source_dir = source_dir
9
+ @template_cache = {}
10
+ Dir.glob(File.join(template_directory, "**/*")).each do |file|
11
+ @template_cache[file] = Tilt.new(file, :trim => "-")
12
+ end
13
+ end
14
+
15
+ def render(template, args = {})
16
+ # Inside erb files, template path is local to repo
17
+ if not template.start_with?(source_dir)
18
+ template = File.join(source_dir, template)
19
+ end
20
+ renderer_for(template).render(self, args).chomp
21
+ end
22
+
23
+ private
24
+
25
+ def renderer_for(template)
26
+ template_cache.fetch(File.expand_path(template))
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,324 @@
1
+ require 'ascii_binder_gabriel_rh/helpers'
2
+ require 'trollop'
3
+
4
+ include AsciiBinderGabrielRH::Helpers
5
+
6
+ module AsciiBinderGabrielRH
7
+ class TopicEntity
8
+ attr_reader :name, :dir, :file, :topic_alias, :distro_keys, :subitems, :raw, :parent, :depth
9
+
10
+ def initialize(topic_entity,actual_distro_keys,dir_path='',parent_group=nil,depth=0)
11
+ @raw = topic_entity
12
+ @parent = parent_group
13
+ @dir_path = dir_path
14
+ @name = topic_entity['Name']
15
+ @dir = topic_entity['Dir']
16
+ @file = topic_entity['File']
17
+ @topic_alias = topic_entity['Alias']
18
+ @depth = depth
19
+ @actual_distro_keys = actual_distro_keys
20
+ @distro_keys = topic_entity.has_key?('Distros') ? parse_distros(topic_entity['Distros']) : actual_distro_keys
21
+ @nav_trees = {}
22
+ @alias_lists = {}
23
+ @path_lists = {}
24
+ @subitems = []
25
+ if topic_entity.has_key?('Topics')
26
+ entity_dir = @dir.nil? ? '<nil_dir>' : @dir
27
+ subdir_path = dir_path == '' ? entity_dir : File.join(dir_path,entity_dir)
28
+ topic_entity['Topics'].each do |sub_entity|
29
+ @subitems << AsciiBinderGabrielRH::TopicEntity.new(sub_entity,actual_distro_keys,subdir_path,self,depth+1)
30
+ end
31
+ end
32
+ end
33
+
34
+ def repo_path
35
+ @repo_path ||= begin
36
+ this_step = '<nil_item>'
37
+ if is_group?
38
+ this_step = dir
39
+ elsif is_topic?
40
+ this_step = file.end_with?('.adoc') ? file : "#{file}.adoc"
41
+ end
42
+ @dir_path == '' ? this_step : File.join(@dir_path,this_step)
43
+ end
44
+ end
45
+
46
+ def basename_path
47
+ @basename_path ||= File.join(File.dirname(repo_path),File.basename(repo_path,'.adoc'))
48
+ end
49
+
50
+ def repo_path_html
51
+ @repo_path_html ||= is_topic? ? File.join(File.dirname(repo_path),File.basename(repo_path,'.adoc')) + ".html" : repo_path
52
+ end
53
+
54
+ def source_path
55
+ @source_path ||= File.join(docs_root_dir,repo_path)
56
+ end
57
+
58
+ def preview_path(distro_key,branch_dir)
59
+ File.join(preview_dir,distro_key,branch_dir,repo_path_html)
60
+ end
61
+
62
+ def topic_publish_url(distro_url,branch_dir)
63
+ File.join(distro_url,branch_dir,repo_path_html)
64
+ end
65
+
66
+ def package_path(site_id,branch_dir)
67
+ File.join(package_dir,site_id,branch_dir,repo_path_html)
68
+ end
69
+
70
+ def group_filepaths
71
+ @group_filepaths ||= begin
72
+ group_filepaths = []
73
+ if is_topic? and not is_alias?
74
+ group_filepaths << File.join(File.dirname(repo_path),File.basename(repo_path,'.adoc'))
75
+ elsif is_group?
76
+ subitems.each do |subitem|
77
+ group_filepaths.concat(subitem.group_filepaths)
78
+ end
79
+ group_filepaths.uniq!
80
+ end
81
+ group_filepaths
82
+ end
83
+ end
84
+
85
+ def nav_tree(distro_key)
86
+ @nav_trees[distro_key] ||= begin
87
+ nav_tree = {}
88
+ if distro_keys.include?(distro_key) and not is_alias?
89
+ nav_tree[:id] = id
90
+ nav_tree[:name] = name
91
+ if is_topic?
92
+ nav_tree[:path] = "../" + repo_path_html
93
+ elsif is_group?
94
+ sub_nav_items = []
95
+ subitems.each do |subitem|
96
+ sub_nav = subitem.nav_tree(distro_key)
97
+ next if sub_nav.empty?
98
+ sub_nav_items << sub_nav
99
+ end
100
+ if sub_nav_items.empty?
101
+ nav_tree = {}
102
+ else
103
+ nav_tree[:topics] = sub_nav_items
104
+ end
105
+ end
106
+ end
107
+ nav_tree
108
+ end
109
+ end
110
+
111
+ def alias_list(distro_key)
112
+ @alias_lists[distro_key] ||= begin
113
+ sub_aliases = []
114
+ if distro_keys.include?(distro_key)
115
+ if is_group?
116
+ subitems.each do |subitem|
117
+ sub_list = subitem.alias_list(distro_key)
118
+ sub_list.each do |sub_list_alias|
119
+ sub_aliases << sub_list_alias
120
+ end
121
+ end
122
+ elsif is_alias?
123
+ sub_aliases << { :alias_path => basename_path, :redirect_path => topic_alias }
124
+ end
125
+ end
126
+ sub_aliases
127
+ end
128
+ end
129
+
130
+ def path_list(distro_key)
131
+ @path_lists[distro_key] ||= begin
132
+ sub_paths = []
133
+ if distro_keys.include?(distro_key)
134
+ if is_group?
135
+ subitems.each do |subitem|
136
+ sub_list = subitem.path_list(distro_key)
137
+ sub_list.each do |sub_list_path|
138
+ sub_paths << sub_list_path
139
+ end
140
+ end
141
+ elsif is_topic? and not is_alias?
142
+ sub_paths << basename_path
143
+ end
144
+ end
145
+ sub_paths
146
+ end
147
+ end
148
+
149
+ # Is this topic entity or any of its children used in
150
+ # the specified distro / single page chain
151
+ def include?(distro_key,single_page_path)
152
+ # If this entity isn't for this distro, bail out
153
+ return false unless distro_keys.include?(distro_key)
154
+
155
+ # If we're building a single page, check if we're on the right track.
156
+ if single_page_path.length > 0 and not single_page_path[depth].nil?
157
+ if is_group?
158
+ return false unless single_page_path[depth] == dir
159
+ elsif is_topic?
160
+ return false unless single_page_path[depth] == file
161
+ else
162
+ return false
163
+ end
164
+ elsif is_group?
165
+ # If this is a topic group that -is- okay for this distro, but
166
+ # none of its subitems are okay for this distro, then bail out.
167
+ subitems_for_distro = false
168
+ subitems.each do |subitem|
169
+ if subitem.include?(distro_key,[])
170
+ subitems_for_distro = true
171
+ break
172
+ end
173
+ end
174
+ return false unless subitems_for_distro
175
+ end
176
+
177
+ return true
178
+ end
179
+
180
+ def breadcrumb
181
+ @breadcrumb ||= hierarchy.map{ |entity| { :id => entity.id, :name => entity.name, :url => entity.repo_path_html } }
182
+ end
183
+
184
+ def id
185
+ @id ||= hierarchy.map{ |entity| camelize(entity.name) }.join('::')
186
+ end
187
+
188
+ def is_group?
189
+ @is_group ||= file.nil? and not name.nil? and not dir.nil? and subitems.length > 0
190
+ end
191
+
192
+ def is_topic?
193
+ @is_topic ||= dir.nil? and not name.nil? and not file.nil? and subitems.length == 0
194
+ end
195
+
196
+ def is_alias?
197
+ @is_alias ||= is_topic? and not topic_alias.nil?
198
+ end
199
+
200
+ def is_valid?
201
+ validate
202
+ end
203
+
204
+ def errors
205
+ validate(true)
206
+ end
207
+
208
+ private
209
+
210
+ def parse_distros(entity_distros)
211
+ values = entity_distros.split(',').map(&:strip)
212
+ # Don't bother with glob expansion if 'all' is in the list.
213
+ return @actual_distro_keys if values.include?('all')
214
+
215
+ # Expand globs and return the list
216
+ values.flat_map do |value|
217
+ value_regex = Regexp.new("\\A#{value.gsub("*", ".*")}\\z")
218
+ @actual_distro_keys.select { |k| value_regex.match(k) }
219
+ end.uniq
220
+ end
221
+
222
+ def validate(verbose=false)
223
+ errors = []
224
+
225
+ # Check common fields - Name and Distros
226
+ if not valid_string?(name)
227
+ if verbose
228
+ errors << "Topic entity with missing or invalid 'Name' value: '#{raw.inspect}'"
229
+ else
230
+ return false
231
+ end
232
+ end
233
+ distro_keys.each do |distro_key|
234
+ next if @actual_distro_keys.include?(distro_key)
235
+ if verbose
236
+ errors << "#{entity_id} 'Distros' filter includes nonexistent distro key '#{distro_key}'"
237
+ else
238
+ return false
239
+ end
240
+ end
241
+
242
+ # Check the depth.
243
+ if (depth > AsciiBinderGabrielRH::DEPTH) and (AsciiBinderGabrielRH::DEPTH != 0)
244
+ if verbose
245
+ errors << "#{entity_id} exceeds the maximum nested depth."
246
+ else
247
+ return false
248
+ end
249
+ end
250
+
251
+ # For groups, test the 'Dir' value and the sub-items.
252
+ if is_group?
253
+ if not valid_string?(dir)
254
+ if verbose
255
+ errors << "#{entity_id} has invalid 'Dir' value."
256
+ else
257
+ return false
258
+ end
259
+ end
260
+ if not topic_alias.nil?
261
+ if verbose
262
+ errors << "#{entity_id} is a topic group with an Alias entry. Aliases are only supported for topic items."
263
+ else
264
+ return false
265
+ end
266
+ end
267
+ subitems.each do |subitem|
268
+ next if subitem.is_valid?
269
+ if verbose
270
+ errors = errors.concat(subitem.errors)
271
+ else
272
+ return false
273
+ end
274
+ end
275
+ elsif is_topic?
276
+ if not valid_string?(file)
277
+ if verbose
278
+ errors << "#{entity_id} has invalid 'File' value."
279
+ else
280
+ return false
281
+ end
282
+ end
283
+ # We can do basic validation of the 'Alias' string here, but real validation has
284
+ # to be done after the whole topic map is loaded.
285
+ if not topic_alias.nil? and not valid_string?(topic_alias)
286
+ if verbose
287
+ errors << "#{entity_id} has invalid 'Alias' value."
288
+ else
289
+ return false
290
+ end
291
+ end
292
+ else
293
+ if verbose
294
+ errors << "#{entity_id} is not parseable as a group or a topic: '#{raw.inspect}'"
295
+ else
296
+ return false
297
+ end
298
+ end
299
+ return errors if verbose
300
+ return true
301
+ end
302
+
303
+ def hierarchy
304
+ @hierarchy ||= begin
305
+ entity = self
306
+ ancestry = []
307
+ loop do
308
+ ancestry << entity
309
+ break if entity.parent.nil?
310
+ entity = entity.parent
311
+ end
312
+ ancestry.reverse
313
+ end
314
+ end
315
+
316
+ def entity_id
317
+ if hierarchy.length == 1
318
+ return "Top level topic entity '#{name}'"
319
+ else
320
+ return "Topic entity at '#{breadcrumb.map{ |node| node[:name] }.join(' -> ')}'"
321
+ end
322
+ end
323
+ end
324
+ end