ascii_binder 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2712179d57dd943569d10c85468d8c29695778a4
4
+ data.tar.gz: ff3209d960df6d6fb660396de2e9ece06a6d5e80
5
+ SHA512:
6
+ metadata.gz: cb44c1f68095c9d03d3cdc595d1b110ea6e99b5bdff5acb831ac318e0aae1f397594e7ed1c36842a0b124c6b9c169ea8a01ac18c52250c7832f53f4c43993d90
7
+ data.tar.gz: 4a8ff2bde4269cb0b71818d7338f2b5694cc3c19635dbe69226824687a0012073ebd5bf41798a1e02c316b5c5b1de396e17f486b36e232a39e938d8f3066289f
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Red Hat, Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,221 @@
1
+ = AsciiBinder
2
+
3
+ This repo contains the documentation for
4
+
5
+ * http://origin.openshift.com/[OpenShift Origin]
6
+ * http://openshift.com/[OpenShift Online]
7
+ * http://www.redhat.com/products/cloud-computing/openshift-enterprise/[OpenShift Enterprise]
8
+
9
+ The documentation is sourced in http://www.methods.co.nz/asciidoc/[AsciiDoc] and transformed into HTML/CSS and other formats through http://asciidoctor.org/[AsciiDoctor]-based automation.
10
+
11
+ == Repo Organization
12
+
13
+ Each directory of the repo represents a different collection of topics (you can think of directories as books). The exceptions to this rule are directories whose names start with an underscore (like `_builder_lib` and `_javascripts`), which contain the assets used to generate the finished documentation. Within each 'book' directory, topics exist as separate asciidoc files and an `images` directory contains any images that are included in the topics.
14
+
15
+ ----
16
+ /
17
+ /book1
18
+ /book1/topic1.adoc
19
+ /book1/topicN.adoc
20
+ /book1/images
21
+ /book1/images/img1.png
22
+ /book1/images/imgN.png
23
+ ...
24
+ /bookN
25
+ ----
26
+
27
+ == Version Management
28
+ The overlap of documentation across OpenShift Origin, Online and Enterprise is no less than 80%. In many cases, this means that individual topics may need to include or exclude individual paragraphs with respect to a specific OpenShift distribution. While it is _possible_ to accomplish this solely by using git branches to maintain slightly different versions of a given topic, doing so would make the task of maintaining internal consistency extremely difficult for content contributors.
29
+
30
+ Git branching is still extremely valuable, and serves the important role of tracking the release versions of documentation for the various OpenShift distributions.
31
+
32
+ === Distribution-Specific Conditionals
33
+ OpenShift documentation uses AsciiDoc's `ifdef/endif` macro to conditionalize document segments for specific OpenShift distributions down to the single-line level.
34
+
35
+ The supported distribution attributes used in the OpenShift document generator are:
36
+
37
+ * `openshift-origin`
38
+ * `openshift-online`
39
+ * `openshift-enterprise`
40
+
41
+ These attributes can be used alone or together to conditionalize text within a topic document.
42
+
43
+ Here is an example of this concept in use:
44
+
45
+ ----
46
+ This first line is unconditionalized, and will appear for all versions.
47
+
48
+ \ifdef::openshift-online[]
49
+ This line will only appear for OpenShift Online.
50
+ \endif::[]
51
+
52
+ \ifdef::openshift-enterprise[]
53
+ This line will only appear for OpenShift Enterprise.
54
+ \endif::[]
55
+
56
+ \ifdef::openshift-origin,openshift-enterprise[]
57
+ This line will appear for OpenShift Origin and Enterprise, but not for OpenShift Online.
58
+ \endif::[]
59
+ ----
60
+
61
+ Two important points to keep in mind:
62
+
63
+ * The `ifdef/endif` blocks have no size limit, however they should _not_ be used to conditionalize an entire topic. If an entire topic file is specific to a given OpenShift distribution, refer to the link:#document-set-metadata[Document Set Metadata] section for information on how to conditionalize at the whole-topic level.
64
+
65
+ * The `ifdef/endif` blocks _cannot be nested_. In other words, one conditional block cannot contain other conditional blocks.
66
+
67
+ === Release Branches
68
+ Through the use of link:#distribution-specific-conditionals[Distribution-Specific Conditionals] and link:#document-set-metadata[Document Set Metadata], the master branch of this repository always contains a complete set of documentation that includes all of the OpenShift distributions. However, when and as new versions of the OpenShift distros are released, the master branch is merged down to new or existing release branches. Here is the general naming scheme used in the branches:
69
+
70
+ * `master` - OpenShift Origin latest code
71
+ * `origin-N.N` - OpenShift Origin most recent stable release
72
+ * `online` - OpenShift Online most recent release
73
+ * `enterprise-N.N` - OpenShift Enterprise support releases
74
+
75
+ On a nightly basis, the documentation web sites are rebuilt for each of these branches. In this manner, documentation for released versions of OpenShift will remain the same even as development continues on master. Additionally, any corrections or additions that are "cherry-picked" into the release branches will show up in the release documentation the next day.
76
+
77
+ == Document Set Metadata
78
+ In order to construct the documentation site from these sources, the build system looks at the `_build_cfg.yml` metadata file. The build system _only_ looks in this file for information on which files to include, so any new file submissions must be accompanied by an update to this metadata file.
79
+
80
+ === File Format
81
+ The format of this file is as indicated:
82
+
83
+ ----
84
+ --- <1>
85
+ Name: Origin of the Species <2>
86
+ Dir: origin_of_the_species <3>
87
+ Distros: all <4>
88
+ Topics:
89
+ - Name: The Majestic Marmoset <5>
90
+ File: the_majestic_marmoset <6>
91
+ Distros: all
92
+ - Name: The Curious Crocodile
93
+ File: the_curious_crocodile
94
+ Distros: openshift-online,openshift-enterprise <7>
95
+ - Name: The Numerous Nematodes
96
+ Dir: the_numerous_nematodes <8>
97
+ Topics:
98
+ - Name: The Wily Worm <9>
99
+ File: the_wily_worm
100
+ - Name: The Acrobatic Ascarid <= Sub-topic 2 name
101
+ File: the_acrobatic_ascarid <= Sub-topic 2 file under <group dir>/<subtopic dir>
102
+ ----
103
+ <1> Record separator at the top of each topic group
104
+ <2> Display name of topic group
105
+ <3> Directory name of topic group
106
+ <4> Which OpenShift versions this topic group is part of
107
+ <5> Topic name
108
+ <6> Topic file under the topic group dir without '.adoc'
109
+ <7> Which OpenShift versions this topic is part of
110
+ <8> This topic is actually a subtopic group. Instead of a `File` path it has a `Dir` path and `Topics`, just like a top-level topic group.
111
+ <9> Topics belonging to a subtopic group are listed just like regular topics with a `Name` and `File`.
112
+
113
+ === Notes on "Distros"
114
+
115
+ * The "Distros" setting is optional for topic groups and topic items. When the "Distros" setting is absent, the system treats the topic group or topic as though the user had set "Distros: all".
116
+ * The "all" value for "Distros" is a synonym for "openshift-origin,openshift-enterprise,openshift-online".
117
+ * The "all" value trumps other values, so "openshift-online,all" is treated as "all"
118
+
119
+ == Understanding the Complete Distribution Condition Chain
120
+ It is important to understand the ordering of distribution conditionals in determining whether or not a specific piece of content appears in the documentation set. The hierarchy is fairly straightforward:
121
+
122
+ 1. Topic group "Distros" setting from `_build_cfg.yml`
123
+ 2. Topic item "Distros" setting from `_build_cfg.yml`
124
+ 3. Document-level `ifdef/endif` blocks
125
+
126
+ In this manner:
127
+
128
+ * If a topic group is configured with "Distros: openshift-online", the entire group will be skipped for OpenShift Enterprise and OpenShift Origin, regardless of the Topic-level and document-level content rules within that group.
129
+
130
+ * When a topic group is available to all Distros, but a specific topic item is limited, the topic group will appear for all distros and the specific topic item will only appear for the indicated distros.
131
+
132
+ == Live Editing
133
+ If you would like to work on one of the documentation files in an editing environment that automatically redraws the resulting HTML, follow these steps.
134
+
135
+ === Prerequisites
136
+ You will need the following tools in your editing environment:
137
+
138
+ * A bash shell environment (Linux distributions and OS X include these out of the box, for Windows consider http://cygwin.com/[Cygwin])
139
+ * https://www.ruby-lang.org/en/[Ruby]
140
+ * http://www.git-scm.com/[git]
141
+ * A web browser (Firefox, Chrome or Safari) with the http://livereload.com/[LiveReload] extension
142
+
143
+ With these tools available, first perform a one-time setup:
144
+
145
+ 1. Clone the https://github.com/openshift/openshift-docs[openshift-docs] repo from GitHub:
146
+ +
147
+ ----
148
+ $ git clone https://github.com/openshift/openshift-docs.git
149
+ ----
150
+ 2. From the cloned directory, run a bundle install:
151
+ +
152
+ ----
153
+ $ cd openshift-docs
154
+ $ bundle install
155
+ ----
156
+ +
157
+ TIP: If you don't have bundler installed, you can get it by running `gem install bundler`
158
+
159
+ That's it for setup, the next section explains how to run the LiveReload system.
160
+
161
+ === Running with LiveReload
162
+ Once you've installed the link:#prerequisites[prerequisites] you can fire up the LiveReload setup as follows:
163
+
164
+ 1. From the `openshift-docs` directory, run a preliminary build:
165
+ +
166
+ ----
167
+ $ cd openshift-docs
168
+ $ bundle exec rake build
169
+ ----
170
+ 2. Now open the generated HTML file in your browser. It will be under `openshift-docs/_preview/<distro>/<branch>` with the same path and filename as the original file. The only difference will be the name ending in '.html' instead of '.adoc'.
171
+ 3. Now start up the `guard` utility:
172
+ +
173
+ ----
174
+ $ bundle exec guard
175
+ ----
176
+ +
177
+ TIP: This utility will run in the terminal where you started it, so you should leave it running off to the side and use other terminals for regular tasks.
178
+ 4. Finally, back in your browser, enable the LiveReload plugin in the same tab where the preview file is displayed. You will know this step succeeded if the LiveReload icon changes, and if you see output similar to the following in the terminal where `guard` is running:
179
+ +
180
+ ----
181
+ [1] guard(main)> 17:29:22 - INFO - Browser connected.
182
+ ----
183
+
184
+ That's it. Now any changes that you make to the source file will automatically trigger a rebuild of the target HTML file.
185
+
186
+ === Clean Up
187
+ The `.gitignore` file is set up to prevent anything under `_preview` and `_package` from being committed. However, you can reset the environment manually by running:
188
+
189
+ ----
190
+ $ bundle exec rake clean
191
+ ----
192
+
193
+ == Creating New Topic Pages
194
+ The layout and style rules for new documentation are largely described in an upcoming style guide (delivery date TBD). However, a few important rules are listed here because they affect the way that the pages are rendered.
195
+
196
+ The top matter of any new topic page must have the following format:
197
+
198
+ ----
199
+ = Human-Readable Topic Title
200
+ {product-author}
201
+ {product-version}
202
+ :data-uri:
203
+ :icons:
204
+ ----
205
+
206
+ * The article title goes on the first line with a level 1 header markup (=)
207
+ * The [x-]`{product-author}` and [x-]`{product-version}` are AsciiDoc attributes that get replaced dynamically when the docs are generated.
208
+ * The `:data-uri:` attribute tells AsciiDoctor to embed any images directly in the HTML.
209
+ * The `:icons:` attribute tells AsciiDoctor to use cool icons for admonition blocks.
210
+
211
+ After the heading block and a single whitespace line, you can include any content for the topic.
212
+
213
+ NOTE: Any section headers within the article must be level 2 (==) or lower. Try to be consistent about level-nesting; it won't break AsciiDoctor to jump from a level 1 section header down to level 3, but it isn't good form.
214
+
215
+
216
+ == Contacts
217
+
218
+ For questions or comments about the documentation system:
219
+
220
+ * OpenShift team members can be found on the http://webchat.freenode.net/?randomnick=1&channels=openshift&uio=d4[#openshift] and http://webchat.freenode.net/?randomnick=1&channels=openshift-dev&uio=d4[#openshift-dev channels] on http://www.freenode.net/[FreeNode].
221
+ * You can also join the http://lists.openshift.redhat.com/openshiftmm/listinfo/users[Users] or http://lists.openshift.redhat.com/openshiftmm/listinfo/dev[Developers] mailing list.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ascii_binder/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ascii_binder"
8
+ spec.version = AsciiBinder::VERSION
9
+ spec.authors = ["N. Harrison Ripps"]
10
+ spec.email = ["nhr@redhat.com"]
11
+ spec.summary = %q{Builder for multi product documention websites.}
12
+ spec.description = %q{Builder for multi product documention websites.}
13
+ spec.homepage = "http://github.com/redhataccess/ascii_binder"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_dependency 'asciidoctor'
25
+ spec.add_dependency 'asciidoctor-diagram'
26
+ spec.add_dependency 'git'
27
+ spec.add_dependency 'guard'
28
+ spec.add_dependency 'guard-shell'
29
+ spec.add_dependency 'guard-livereload'
30
+ spec.add_dependency 'haml'
31
+ spec.add_dependency 'json'
32
+ spec.add_dependency 'pandoc-ruby'
33
+ spec.add_dependency 'sitemap_generator', '~> 5.1.0'
34
+ spec.add_dependency 'yajl-ruby'
35
+ spec.add_dependency 'tilt'
36
+ end
@@ -0,0 +1,3 @@
1
+ require "ascii_binder/version"
2
+ require "ascii_binder/template_renderer"
3
+ require "ascii_binder/helpers"
@@ -0,0 +1,708 @@
1
+ require 'asciidoctor'
2
+ require 'asciidoctor/cli'
3
+ require 'asciidoctor-diagram'
4
+ require 'find'
5
+ require 'git'
6
+ require 'logger'
7
+ require 'pandoc-ruby'
8
+ require 'pathname'
9
+ require 'sitemap_generator'
10
+ require 'yaml'
11
+ require 'forwardable'
12
+
13
+ module AsciiBinder
14
+ module Helpers
15
+ extend Forwardable
16
+
17
+ def self.source_dir
18
+ @source_dir ||= `git rev-parse --show-toplevel`.chomp
19
+ end
20
+
21
+ def self.template_dir
22
+ @template_dir ||= File.join(source_dir,'_templates')
23
+ end
24
+
25
+ def self.preview_dir
26
+ @preview_dir ||= begin
27
+ lpreview_dir = File.join(source_dir,PREVIEW_DIRNAME)
28
+ if not File.exists?(lpreview_dir)
29
+ Dir.mkdir(lpreview_dir)
30
+ end
31
+ lpreview_dir
32
+ end
33
+ end
34
+
35
+ def self.package_dir
36
+ @package_dir ||= begin
37
+ lpackage_dir = File.join(source_dir,PACKAGE_DIRNAME)
38
+ if not File.exists?(lpackage_dir)
39
+ Dir.mkdir(lpackage_dir)
40
+ end
41
+ lpackage_dir
42
+ end
43
+ end
44
+
45
+ def_delegators self, :source_dir, :template_dir, :preview_dir, :package_dir
46
+
47
+ TemplateRenderer.initialize_cache(template_dir)
48
+
49
+ BUILD_FILENAME = '_build_cfg.yml'
50
+ DISTRO_MAP_FILENAME = '_distro_map.yml'
51
+ BUILDER_DIRNAME = '_build_system'
52
+ PREVIEW_DIRNAME = '_preview'
53
+ PACKAGE_DIRNAME = '_package'
54
+ BLANK_STRING_RE = Regexp.new('^\s*$')
55
+
56
+ def build_date
57
+ Time.now.utc
58
+ end
59
+
60
+ def git
61
+ @git ||= Git.open(source_dir)
62
+ end
63
+
64
+ def git_checkout branch_name
65
+ target_branch = git.branches.local.select{ |b| b.name == branch_name }[0]
66
+ if not target_branch.nil? and not target_branch.current
67
+ target_branch.checkout
68
+ end
69
+ end
70
+
71
+ def git_stash_all
72
+ # See if there are any changes in need of stashing
73
+ @stash_needed = `git status --porcelain` !~ /^\s*$/
74
+ if @stash_needed
75
+ puts "\nNOTICE: Stashing uncommited changes and files in working branch."
76
+ `git stash -u`
77
+ end
78
+ end
79
+
80
+ def git_apply_and_drop
81
+ return unless @stash_needed
82
+ puts "\nNOTE: Re-applying uncommitted changes and files to working branch."
83
+ if system("git stash pop")
84
+ puts "NOTE: Stash application successful."
85
+ else
86
+ puts "ERROR: Could not apply stashed code. Run `git stash apply` manually."
87
+ end
88
+ @stash_needed = false
89
+ end
90
+
91
+ # Returns the local git branches; current branch is always first
92
+ def local_branches
93
+ @local_branches ||= begin
94
+ branches = []
95
+ branches << git.branches.local.select{ |b| b.current }[0].name
96
+ branches << git.branches.local.select{ |b| not b.current }.map{ |b| b.name }
97
+ branches.flatten
98
+ end
99
+ end
100
+
101
+ def working_branch
102
+ @working_branch ||= local_branches[0]
103
+ end
104
+
105
+ def build_config_file
106
+ @build_config_file ||= File.join(source_dir,BUILD_FILENAME)
107
+ end
108
+
109
+ def distro_map_file
110
+ @distro_map_file ||= File.join(source_dir, DISTRO_MAP_FILENAME)
111
+ end
112
+
113
+ # Protip: Don't cache this! It needs to be reread every time we change branches.
114
+ def build_config
115
+ validate_config(YAML.load_stream(open(build_config_file)))
116
+ end
117
+
118
+ def find_topic_files
119
+ file_list = Find.find('.').select{ |path| not path.nil? and path =~ /.*\.adoc$/ and not path =~ /README/ and not path =~ /\/old\// and not path.split('/').length < 3 }
120
+ file_list.map{ |path|
121
+ parts = path.split('/').slice(1..-1);
122
+ parts.slice(0..-2).join('/') + '/' + parts[-1].split('.')[0]
123
+ }
124
+ end
125
+
126
+ def remove_found_config_files(branch,branch_build_config,branch_topic_files)
127
+ nonexistent_topics = []
128
+ branch_build_config.each do |topic_group|
129
+ tg_dir = topic_group['Dir']
130
+ topic_group['Topics'].each do |topic|
131
+ if topic.has_key?('File')
132
+ topic_path = tg_dir + '/' + topic['File']
133
+ result = branch_topic_files.delete(topic_path)
134
+ if result.nil?
135
+ nonexistent_topics << topic_path
136
+ end
137
+ elsif topic.has_key?('Dir')
138
+ topic_path = tg_dir + '/' + topic['Dir'] + '/'
139
+ topic['Topics'].each do |subtopic|
140
+ result = branch_topic_files.delete(topic_path + subtopic['File'])
141
+ if result.nil?
142
+ nonexistent_topics << topic_path + subtopic['File']
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ if nonexistent_topics.length > 0
149
+ puts "\nWARNING: The _build_cfg.yml file on branch '#{branch}' references nonexistant topics:\n" + nonexistent_topics.map{ |topic| "- #{topic}" }.join("\n")
150
+ end
151
+ end
152
+
153
+ def distro_map
154
+ @distro_map ||= YAML.load_file(distro_map_file)
155
+ end
156
+
157
+ def site_map
158
+ site_map = {}
159
+ distro_map.each do |distro,distro_config|
160
+ if not site_map.has_key?(distro_config["site"])
161
+ site_map[distro_config["site"]] = { :distros => {}, :name => distro_config['site_name'], :url => distro_config['site_url'] }
162
+ end
163
+ site_map[distro_config["site"]][:distros][distro] = distro_config["branches"]
164
+ end
165
+ site_map
166
+ end
167
+
168
+ def distro_branches(use_distro='')
169
+ @distro_branches ||= begin
170
+ use_distro_list = use_distro == '' ? distro_map.keys : [use_distro]
171
+ distro_map.select{ |dkey,dval| use_distro_list.include?(dkey) }.map{ |distro,dconfig| dconfig["branches"].keys }.flatten
172
+ end
173
+ end
174
+
175
+ def page(args)
176
+ # TODO: This process of rebuilding the entire nav for every page will not scale well.
177
+ # As the doc set increases, we will need to think about refactoring this.
178
+ args[:breadcrumb_root], args[:breadcrumb_group], args[:breadcrumb_subgroup], args[:breadcrumb_topic] = extract_breadcrumbs(args)
179
+
180
+ args[:breadcrumb_subgroup_block] = ''
181
+ args[:subtopic_shim] = ''
182
+ if args[:breadcrumb_subgroup]
183
+ args[:breadcrumb_subgroup_block] = "<li class=\"hidden-xs active\">#{args[:breadcrumb_subgroup]}</li>"
184
+ args[:subtopic_shim] = '../'
185
+ end
186
+
187
+ TemplateRenderer.new.render("_templates/page.html.erb", args)
188
+ end
189
+
190
+ def extract_breadcrumbs(args)
191
+ breadcrumb_root = breadcrumb_group = breadcrumb_subgroup = breadcrumb_topic = nil
192
+
193
+ root_group = args[:navigation].first
194
+ selected_group = args[:navigation].detect { |group| group[:id] == args[:group_id] }
195
+ selected_subgroup = selected_group[:topics].detect { |subgroup| subgroup[:id] == args[:subgroup_id] }
196
+ current_is_subtopic = selected_subgroup ? true : false
197
+
198
+ if root_group
199
+ root_topic = root_group[:topics].first
200
+ breadcrumb_root = linkify_breadcrumb(root_topic[:path], "#{args[:distro]} #{args[:version]}", current_is_subtopic) if root_topic
201
+ end
202
+
203
+ if selected_group
204
+ group_topic = selected_group[:topics].first
205
+ breadcrumb_group = linkify_breadcrumb(group_topic[:path], selected_group[:name], current_is_subtopic) if group_topic
206
+
207
+ if selected_subgroup
208
+ subgroup_topic = selected_subgroup[:topics].first
209
+ breadcrumb_subgroup = linkify_breadcrumb(subgroup_topic[:path], selected_subgroup[:name], current_is_subtopic) if subgroup_topic
210
+
211
+ selected_topic = selected_subgroup[:topics].detect { |topic| topic[:id] == args[:topic_id] }
212
+ breadcrumb_topic = linkify_breadcrumb(nil, selected_topic[:name], current_is_subtopic) if selected_topic
213
+ else
214
+ selected_topic = selected_group[:topics].detect { |topic| topic[:id] == args[:topic_id] }
215
+ breadcrumb_topic = linkify_breadcrumb(nil, selected_topic[:name], current_is_subtopic) if selected_topic
216
+ end
217
+ end
218
+
219
+ return breadcrumb_root, breadcrumb_group, breadcrumb_subgroup, breadcrumb_topic
220
+ end
221
+
222
+ def linkify_breadcrumb(href, text, extra_level)
223
+ addl_level = extra_level ? '../' : ''
224
+ href ? "<a href=\"#{addl_level}#{href}\">#{text}</a>" : text
225
+ end
226
+
227
+ def parse_distros distros_string, for_validation=false
228
+ values = distros_string.split(',').map{ |v| v.strip }
229
+ return values if for_validation
230
+ return distro_map.keys if values.include?('all')
231
+ return values.uniq
232
+ end
233
+
234
+ def validate_distros distros_string
235
+ return false if not distros_string.is_a?(String)
236
+ values = parse_distros(distros_string, true)
237
+ values.each do |v|
238
+ return false if not v == 'all' and not distro_map.keys.include?(v)
239
+ end
240
+ return true
241
+ end
242
+
243
+ def validate_topic_group group, info
244
+ # Check for presence of topic group keys
245
+ ['Name','Dir','Topics'].each do |group_key|
246
+ if not group.has_key?(group_key)
247
+ raise "One of the topic groups in #{build_config_file} is missing the '#{group_key}' key."
248
+ end
249
+ end
250
+ # Check for right format of topic group values
251
+ ['Name','Dir'].each do |group_key|
252
+ if not group[group_key].is_a?(String)
253
+ raise "One of the topic groups in #{build_config_file} is not using a string for the #{group_key} setting; current value is #{group[group_key].inspect}"
254
+ end
255
+ if group[group_key].empty? or group[group_key].match BLANK_STRING_RE
256
+ raise "One of the topic groups in #{build_config_file} is using a blank value for the #{group_key} setting."
257
+ end
258
+ end
259
+ if not File.exists?(File.join(source_dir,info[:path]))
260
+ raise "In #{build_config_file}, the directory path '#{info[:path]}' for topic group #{group['Name']} does not exist under #{source_dir}"
261
+ end
262
+ # Validate the Distros setting
263
+ if group.has_key?('Distros')
264
+ if not validate_distros(group['Distros'])
265
+ key_list = distro_map.keys.map{ |k| "'#{k}'" }.sort.join(', ')
266
+ raise "In #{build_config_file}, the Distros value #{group['Distros'].inspect} for topic group #{group['Name']} is not valid. Legal values are 'all', #{key_list}, or a comma-separated list of legal values."
267
+ end
268
+ group['Distros'] = parse_distros(group['Distros'])
269
+ else
270
+ group['Distros'] = parse_distros('all')
271
+ end
272
+ if not group['Topics'].is_a?(Array)
273
+ raise "The #{group['Name']} topic group in #{build_config_file} is malformed; the build system is expecting an array of 'Topic' definitions."
274
+ end
275
+ # Generate an ID for this topic group
276
+ group['ID'] = camelize group['Name']
277
+ if info.has_key?(:parent_id)
278
+ group['ID'] = "#{info[:parent_id]}::#{group['ID']}"
279
+ end
280
+ end
281
+
282
+ def validate_topic_item item, info
283
+ ['Name','File'].each do |topic_key|
284
+ if not item[topic_key].is_a?(String)
285
+ raise "In #{build_config_file}, topic group #{info[:group]}, one of the topics is not using a string for the '#{topic_key}' setting; current value is #{item[topic_key].inspect}"
286
+ end
287
+ if item[topic_key].empty? or item[topic_key].match BLANK_STRING_RE
288
+ raise "In #{build_config_file}, topic group #{topic_group['Name']}, one of the topics is using a blank value for the '#{topic_key}' setting"
289
+ end
290
+ end
291
+ # Normalize the filenames
292
+ if item['File'].end_with?('.adoc')
293
+ item['File'] = item['File'][0..-6]
294
+ end
295
+ if not File.exists?(File.join(source_dir,info[:path],"#{item['File']}.adoc"))
296
+ raise "In #{build_config_file}, could not find file #{item['File']} under directory #{info[:path]} for topic #{item['Name']} in topic group #{info[:group]}."
297
+ end
298
+ if item.has_key?('Distros')
299
+ if not validate_distros(item['Distros'])
300
+ key_list = distro_map.keys.map{ |k| "'#{k}'" }.sort.join(', ')
301
+ raise "In #{build_config_file}, the Distros value #{item['Distros'].inspect} for topic item #{item['Name']} in topic group #{info[:group]} is not valid. Legal values are 'all', #{key_list}, or a comma-separated list of legal values."
302
+ end
303
+ item['Distros'] = parse_distros(item['Distros'])
304
+ else
305
+ item['Distros'] = parse_distros('all')
306
+ end
307
+ # Generate an ID for this topic
308
+ item['ID'] = "#{info[:group_id]}::#{camelize(item['Name'])}"
309
+ end
310
+
311
+ def validate_config config_data
312
+ # Validate/normalize the config file straight away
313
+ if not config_data.is_a?(Array)
314
+ raise "The configuration in #{build_config_file} is malformed; the build system is expecting an array of topic groups."
315
+ end
316
+ config_data.each do |topic_group|
317
+ validate_topic_group(topic_group, { :path => topic_group['Dir'] })
318
+ # Now buzz through the topics
319
+ topic_group['Topics'].each do |topic|
320
+ # Is this an actual topic or a subtopic group?
321
+ is_subtopic_group = topic.has_key?('Dir') and topic.has_key?('Topics') and not topic.has_key?('File')
322
+ is_topic_item = topic.has_key?('File') and not topic.has_key?('Dir') and not topic.has_key?('Topics')
323
+ if not is_subtopic_group and not is_topic_item
324
+ raise "This topic could not definitively be determined to be a topic item or a subtopic group:\n#{topic.inspect}"
325
+ end
326
+ if is_topic_item
327
+ validate_topic_item(topic, { :group => topic_group['Name'], :group_id => topic_group['ID'], :path => topic_group['Dir'] })
328
+ elsif is_subtopic_group
329
+ topic_path = "#{topic_group['Dir']}/#{topic['Dir']}"
330
+ validate_topic_group(topic, { :path => topic_path, :parent_id => topic_group['ID'] })
331
+ topic['Topics'].each do |subtopic|
332
+ validate_topic_item(subtopic, { :group => "#{topic_group['Name']}/#{topic['Name']}", :group_id => topic['ID'], :path => topic_path })
333
+ end
334
+ end
335
+ end
336
+ end
337
+ config_data
338
+ end
339
+
340
+ def camelize text
341
+ text.gsub(/[^0-9a-zA-Z ]/i, '').split(' ').map{ |t| t.capitalize }.join
342
+ end
343
+
344
+ def nav_tree distro, branch_build_config
345
+ navigation = []
346
+ branch_build_config.each do |topic_group|
347
+ next if not topic_group['Distros'].include?(distro)
348
+ next if topic_group['Topics'].select{ |t| t['Distros'].include?(distro) }.length == 0
349
+ topic_list = []
350
+ topic_group['Topics'].each do |topic|
351
+ next if not topic['Distros'].include?(distro)
352
+ if topic.has_key?('File')
353
+ topic_list << {
354
+ :path => "../#{topic_group['Dir']}/#{topic['File']}.html",
355
+ :name => topic['Name'],
356
+ :id => topic['ID'],
357
+ }
358
+ elsif topic.has_key?('Dir')
359
+ next if topic['Topics'].select{ |t| t['Distros'].include?(distro) }.length == 0
360
+ subtopic_list = []
361
+ topic['Topics'].each do |subtopic|
362
+ next if not subtopic['Distros'].include?(distro)
363
+ subtopic_list << {
364
+ :path => "../#{topic_group['Dir']}/#{topic['Dir']}/#{subtopic['File']}.html",
365
+ :name => subtopic['Name'],
366
+ :id => subtopic['ID'],
367
+ }
368
+ end
369
+ topic_list << { :name => topic['Name'], :id => topic['ID'], :topics => subtopic_list }
370
+ end
371
+ end
372
+ navigation << { :name => topic_group['Name'], :id => topic_group['ID'], :topics => topic_list }
373
+ end
374
+ navigation
375
+ end
376
+
377
+ def asciidoctor_page_attrs(more_attrs=[])
378
+ [
379
+ 'source-highlighter=coderay',
380
+ 'coderay-css=style',
381
+ 'linkcss!',
382
+ 'icons=font',
383
+ 'idprefix=',
384
+ 'idseparator=-',
385
+ 'sectanchors',
386
+ ].concat(more_attrs)
387
+ end
388
+
389
+ def generate_docs(build_distro,single_page=nil)
390
+ single_page_dir = []
391
+ single_page_file = nil
392
+ if not single_page.nil?
393
+ single_page_dir = single_page.split(':')[0].split('/')
394
+ single_page_file = single_page.split(':')[1]
395
+ puts "Rebuilding '#{single_page_dir.join('/')}/#{single_page_file}' on branch '#{working_branch}'."
396
+ end
397
+
398
+ if not build_distro == ''
399
+ if not distro_map.has_key?(build_distro)
400
+ exit
401
+ else
402
+ puts "Building only the #{distro_map[build_distro]["name"]} distribution."
403
+ end
404
+ elsif single_page.nil?
405
+ puts "Building all distributions."
406
+ end
407
+
408
+ # First, notify the user of missing local branches
409
+ missing_branches = []
410
+ distro_branches(build_distro).sort.each do |dbranch|
411
+ next if local_branches.include?(dbranch)
412
+ missing_branches << dbranch
413
+ end
414
+ if missing_branches.length > 0 and single_page.nil?
415
+ puts "\nNOTE: The following branches do not exist in your local git repo:"
416
+ missing_branches.each do |mbranch|
417
+ puts "- #{mbranch}"
418
+ end
419
+ puts "The build will proceed but these branches will not be generated."
420
+ end
421
+
422
+ # Generate all distros for every local branch
423
+ local_branches.each do |local_branch|
424
+ # Single-page regen only occurs for the working branch
425
+ if not local_branch == working_branch
426
+ if single_page.nil?
427
+ # Checkout the branch
428
+ puts "\nCHANGING TO BRANCH '#{local_branch}'"
429
+ git_checkout(local_branch)
430
+ else
431
+ next
432
+ end
433
+ end
434
+
435
+ first_branch = single_page.nil?
436
+
437
+ if local_branch =~ /^\(detached from .*\)/
438
+ local_branch = 'detached'
439
+ end
440
+
441
+ # The branch_orphan_files list starts with the set of all
442
+ # .adoc files found in the repo, and will be whittled
443
+ # down from there.
444
+ branch_orphan_files = find_topic_files
445
+ branch_build_config = build_config
446
+ remove_found_config_files(local_branch,branch_build_config,branch_orphan_files)
447
+
448
+ if branch_orphan_files.length > 0 and single_page.nil?
449
+ puts "\nWARNING: Branch '#{local_branch}' includes the following .adoc files that are not referenced in the _build_cfg.yml file:\n" + branch_orphan_files.map{ |file| "- #{file}" }.join("\n")
450
+ end
451
+
452
+ # Run all distros.
453
+ distro_map.each do |distro,distro_config|
454
+ # Only building a single distro; skip the others.
455
+ if not build_distro == '' and not build_distro == distro
456
+ next
457
+ end
458
+
459
+ site_name = distro_config["site_name"]
460
+
461
+ branch_config = { "name" => "Branch Build", "dir" => local_branch }
462
+ dev_branch = true
463
+ if distro_config["branches"].has_key?(local_branch)
464
+ branch_config = distro_config["branches"][local_branch]
465
+ dev_branch = false
466
+ end
467
+
468
+ if first_branch
469
+ puts "\nBuilding #{distro_config["name"]} for branch '#{local_branch}'"
470
+ first_branch = false
471
+ end
472
+
473
+ # Create the target dir
474
+ branch_path = File.join(preview_dir,distro,branch_config["dir"])
475
+ system("mkdir -p #{branch_path}/stylesheets")
476
+ system("mkdir -p #{branch_path}/javascripts")
477
+ system("mkdir -p #{branch_path}/images")
478
+
479
+ # Copy stylesheets into preview area
480
+ system("cp -r _stylesheets/*css #{branch_path}/stylesheets")
481
+
482
+ # Copy javascripts into preview area
483
+ system("cp -r _javascripts/*js #{branch_path}/javascripts")
484
+
485
+ # Copy images into preview area
486
+ system("cp -r _images/* #{branch_path}/images")
487
+
488
+ # Build the landing page
489
+ navigation = nav_tree(distro,branch_build_config)
490
+
491
+ # Build the topic files for this branch & distro
492
+ branch_build_config.each do |topic_group|
493
+ next if not topic_group['Distros'].include?(distro)
494
+ next if topic_group['Topics'].select{ |t| t['Distros'].include?(distro) }.length == 0
495
+ next if not single_page.nil? and not single_page_dir[0] == topic_group['Dir']
496
+ topic_group['Topics'].each do |topic|
497
+ src_group_path = File.join(source_dir,topic_group['Dir'])
498
+ tgt_group_path = File.join(branch_path,topic_group['Dir'])
499
+ if not File.exists?(tgt_group_path)
500
+ Dir.mkdir(tgt_group_path)
501
+ end
502
+ next if not topic['Distros'].include?(distro)
503
+ if topic.has_key?('File')
504
+ next if not single_page.nil? and not topic['File'] == single_page_file
505
+ topic_path = File.join(topic_group['Dir'],topic['File'])
506
+ configure_and_generate_page({
507
+ :distro => distro,
508
+ :distro_config => distro_config,
509
+ :branch_config => branch_config,
510
+ :navigation => navigation,
511
+ :topic => topic,
512
+ :topic_group => topic_group,
513
+ :topic_path => topic_path,
514
+ :src_group_path => src_group_path,
515
+ :tgt_group_path => tgt_group_path,
516
+ :single_page => single_page,
517
+ :site_name => site_name,
518
+ })
519
+ elsif topic.has_key?('Dir')
520
+ next if not single_page.nil? and not single_page_dir.join('/') == topic_group['Dir'] + '/' + topic['Dir']
521
+ topic['Topics'].each do |subtopic|
522
+ next if not subtopic['Distros'].include?(distro)
523
+ next if not single_page.nil? and not subtopic['File'] == single_page_file
524
+ src_group_path = File.join(source_dir,topic_group['Dir'],topic['Dir'])
525
+ tgt_group_path = File.join(branch_path,topic_group['Dir'],topic['Dir'])
526
+ if not File.exists?(tgt_group_path)
527
+ Dir.mkdir(tgt_group_path)
528
+ end
529
+ topic_path = File.join(topic_group['Dir'],topic['Dir'],subtopic['File'])
530
+ configure_and_generate_page({
531
+ :distro => distro,
532
+ :distro_config => distro_config,
533
+ :branch_config => branch_config,
534
+ :navigation => navigation,
535
+ :topic => subtopic,
536
+ :topic_group => topic_group,
537
+ :topic_subgroup => topic,
538
+ :topic_path => topic_path,
539
+ :src_group_path => src_group_path,
540
+ :tgt_group_path => tgt_group_path,
541
+ :single_page => single_page,
542
+ :site_name => site_name,
543
+ })
544
+ end
545
+ end
546
+ end
547
+ end
548
+
549
+ if not single_page.nil?
550
+ next
551
+ end
552
+
553
+ # Create a distro landing page
554
+ # This is backwards compatible code. We can remove it when no
555
+ # official repo uses index.adoc. We are moving to flat HTML
556
+ # files for index.html
557
+ src_file_path = File.join(source_dir,'index.adoc')
558
+ if File.exists?(src_file_path)
559
+ topic_adoc = File.open(src_file_path,'r').read
560
+ page_attrs = asciidoctor_page_attrs([
561
+ "imagesdir=#{File.join(source_dir,'_site_images')}",
562
+ distro,
563
+ "product-title=#{distro_config["name"]}",
564
+ "product-version=Updated #{build_date}",
565
+ "product-author=#{distro_config["author"]}"
566
+ ])
567
+ topic_html = Asciidoctor.render topic_adoc, :header_footer => true, :safe => :unsafe, :attributes => page_attrs
568
+ File.write(File.join(preview_dir,distro,'index.html'),topic_html)
569
+ end
570
+ end
571
+
572
+ if not single_page.nil?
573
+ return
574
+ end
575
+
576
+ if local_branch == working_branch
577
+ # We're moving away from the working branch, so save off changed files
578
+ git_stash_all
579
+ end
580
+ end
581
+
582
+ # Return to the original branch
583
+ git_checkout(working_branch)
584
+
585
+ # If necessary, restore temporarily stashed files
586
+ git_apply_and_drop
587
+
588
+ puts "\nAll builds completed."
589
+ end
590
+
591
+ def configure_and_generate_page options
592
+ distro = options[:distro]
593
+ distro_config = options[:distro_config]
594
+ branch_config = options[:branch_config]
595
+ navigation = options[:navigation]
596
+ topic = options[:topic]
597
+ topic_group = options[:topic_group]
598
+ topic_subgroup = options[:topic_subgroup]
599
+ topic_path = options[:topic_path]
600
+ src_group_path = options[:src_group_path]
601
+ tgt_group_path = options[:tgt_group_path]
602
+ single_page = options[:single_page]
603
+ site_name = options[:site_name]
604
+
605
+ src_file_path = File.join(src_group_path,"#{topic['File']}.adoc")
606
+ tgt_file_path = File.join(tgt_group_path,"#{topic['File']}.html")
607
+ if single_page.nil?
608
+ puts " - #{topic_path}"
609
+ end
610
+ topic_adoc = File.open(src_file_path,'r').read
611
+ page_attrs = asciidoctor_page_attrs([
612
+ "imagesdir=#{src_group_path}/images",
613
+ distro,
614
+ "product-title=#{distro_config["name"]}",
615
+ "product-version=#{branch_config["name"]}",
616
+ "product-author=#{distro_config["author"]}"
617
+ ])
618
+
619
+ # Because we render only the body of the article with AsciiDoctor, the full article title
620
+ # would be lost in conversion. So, use the _build_cfg.yml 'Name' as a fallback but try
621
+ # to read the full article title out of the file itself.
622
+ file_lines = topic_adoc.split("\n")
623
+ article_title = topic['Name']
624
+ if file_lines.length > 0
625
+ article_title = file_lines[0].gsub(/^\=\s+/, '').gsub(/\s+$/, '').gsub(/\{product-title\}/, distro_config["name"]).gsub(/\{product-version\}/, branch_config["name"])
626
+ end
627
+
628
+ topic_html = Asciidoctor.render topic_adoc, :header_footer => false, :safe => :unsafe, :attributes => page_attrs
629
+ dir_depth = ''
630
+ if branch_config['dir'].split('/').length > 1
631
+ dir_depth = '../' * (branch_config['dir'].split('/').length - 1)
632
+ end
633
+ if not topic_subgroup.nil?
634
+ dir_depth = '../' + dir_depth
635
+ end
636
+ page_args = {
637
+ :distro_key => distro,
638
+ :distro => distro_config["name"],
639
+ :site_name => site_name,
640
+ :version => branch_config["name"],
641
+ :group_title => topic_group['Name'],
642
+ :subgroup_title => topic_subgroup && topic_subgroup['Name'],
643
+ :topic_title => topic['Name'],
644
+ :article_title => article_title,
645
+ :content => topic_html,
646
+ :navigation => navigation,
647
+ :group_id => topic_group['ID'],
648
+ :subgroup_id => topic_subgroup && topic_subgroup['ID'],
649
+ :topic_id => topic['ID'],
650
+ :css_path => "../../#{dir_depth}#{branch_config["dir"]}/stylesheets/",
651
+ :javascripts_path => "../../#{dir_depth}#{branch_config["dir"]}/javascripts/",
652
+ :images_path => "../../#{dir_depth}#{branch_config["dir"]}/images/",
653
+ :site_home_path => "../../#{dir_depth}index.html",
654
+ :css => ['docs.css'],
655
+ }
656
+ full_file_text = page(page_args)
657
+ File.write(tgt_file_path,full_file_text)
658
+ end
659
+
660
+ # package_docs
661
+ # This method generates the docs and then organizes them the way they will be arranged
662
+ # for the production websites.
663
+ def package_docs(package_site)
664
+ site_map.each do |site,site_config|
665
+ next if not package_site == '' and not package_site == site
666
+ puts "\nBuilding #{site} site."
667
+ site_config[:distros].each do |distro,branches|
668
+ branches.each do |branch,branch_config|
669
+ src_dir = File.join(preview_dir,distro,branch_config["dir"])
670
+ tgt_tdir = branch_config["dir"].split('/')
671
+ tgt_tdir.pop
672
+ tgt_dir = ''
673
+ if tgt_tdir.length > 0
674
+ tgt_dir = File.join(package_dir,site,tgt_tdir.join('/'))
675
+ else
676
+ tgt_dir = File.join(package_dir,site)
677
+ end
678
+ next if not File.directory?(src_dir)
679
+ FileUtils.mkdir_p(tgt_dir)
680
+ FileUtils.cp_r(src_dir,tgt_dir)
681
+ end
682
+ site_dir = File.join(package_dir,site)
683
+ if File.directory?(site_dir)
684
+ # With this update, site index files will always come from the master branch
685
+ working_branch_site_index = File.join(source_dir,'index-' + site + '.html')
686
+ if File.exists?(working_branch_site_index)
687
+ FileUtils.cp(working_branch_site_index,File.join(package_dir,site,'index.html'))
688
+ ['_images','_stylesheets'].each do |support_dir|
689
+ FileUtils.cp_r(File.join(source_dir,support_dir),File.join(package_dir,site,support_dir))
690
+ end
691
+ else
692
+ FileUtils.cp(File.join(preview_dir,distro,'index.html'),File.join(package_dir,site,'index.html'))
693
+ end
694
+ end
695
+ # Now build a sitemap
696
+ site_dir_path = Pathname.new(site_dir)
697
+ SitemapGenerator::Sitemap.default_host = site_config[:url]
698
+ SitemapGenerator::Sitemap.create( :compress => false, :filename => File.join(site_dir,'sitemap') ) do
699
+ file_list = Find.find(site_dir).select{ |path| not path.nil? and path =~ /.*\.html$/ }.map{ |path| '/' + Pathname.new(path).relative_path_from(site_dir_path).to_s }
700
+ file_list.each do |file|
701
+ add(file, :changefreq => 'daily')
702
+ end
703
+ end
704
+ end
705
+ end
706
+ end
707
+ end
708
+ end