giblish 0.8.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/unit_tests.yml +30 -0
- data/.gitignore +7 -3
- data/.ruby-version +1 -1
- data/Changelog.adoc +59 -0
- data/README.adoc +261 -0
- data/docs/concepts/text_search.adoc +213 -0
- data/docs/concepts/text_search_im/cgi-search_request.puml +35 -0
- data/docs/concepts/text_search_im/cgi-search_request.svg +397 -0
- data/docs/concepts/text_search_im/search_request.puml +40 -0
- data/docs/concepts/text_search_im/search_request.svg +408 -0
- data/docs/howtos/trigger_generation.adoc +180 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/Render Documents.png +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/View Documents.png +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/deploy_with_hooks.graphml +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/deploy_with_hooks.svg +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/deploy_with_jenkins.graphml +0 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/deploy_with_jenkins.svg +0 -0
- data/docs/howtos/trigger_generation_im/docgen_github.puml +51 -0
- data/docs/{setup_server_assets → howtos/trigger_generation_im}/giblish_deployment.graphml +0 -0
- data/docs/howtos/trigger_generation_im/post-receive-example.sh +50 -0
- data/docs/reference/box_flow_spec.adoc +22 -0
- data/docs/reference/search_spec.adoc +185 -0
- data/giblish.gemspec +50 -29
- data/lib/giblish/adocsrc_providers.rb +23 -0
- data/lib/giblish/application.rb +214 -41
- data/lib/giblish/cmdline.rb +273 -259
- data/lib/giblish/config_utils.rb +41 -0
- data/lib/giblish/configurator.rb +163 -0
- data/lib/giblish/conversion_info.rb +120 -0
- data/lib/giblish/docattr_providers.rb +125 -0
- data/lib/giblish/docid/docid.rb +181 -0
- data/lib/giblish/github_trigger/webhook_manager.rb +64 -0
- data/lib/giblish/gitrepos/checkoutmanager.rb +124 -0
- data/lib/giblish/{gititf.rb → gitrepos/gititf.rb} +30 -4
- data/lib/giblish/gitrepos/gitsummary.erb +61 -0
- data/lib/giblish/gitrepos/gitsummaryprovider.rb +78 -0
- data/lib/giblish/gitrepos/history_pb.rb +41 -0
- data/lib/giblish/indexbuilders/d3treegraph.rb +88 -0
- data/lib/giblish/indexbuilders/depgraphbuilder.rb +109 -0
- data/lib/giblish/indexbuilders/dotdigraphadoc.rb +174 -0
- data/lib/giblish/indexbuilders/standard_index.erb +10 -0
- data/lib/giblish/indexbuilders/subtree_indices.rb +132 -0
- data/lib/giblish/indexbuilders/templates/circles.html.erb +111 -0
- data/lib/giblish/indexbuilders/templates/flame.html.erb +61 -0
- data/lib/giblish/indexbuilders/templates/tree.html.erb +366 -0
- data/lib/giblish/indexbuilders/templates/treemap.html.erb +127 -0
- data/lib/giblish/indexbuilders/verbatimtree.rb +94 -0
- data/lib/giblish/pathtree.rb +473 -74
- data/lib/giblish/resourcepaths.rb +150 -0
- data/lib/giblish/search/expand_adoc.rb +55 -0
- data/lib/giblish/search/headingindexer.rb +312 -0
- data/lib/giblish/search/request_manager.rb +110 -0
- data/lib/giblish/search/searchquery.rb +68 -0
- data/lib/giblish/search/textsearcher.rb +349 -0
- data/lib/giblish/subtreeinfobuilder.rb +77 -0
- data/lib/giblish/treeconverter.rb +272 -0
- data/lib/giblish/utils.rb +142 -294
- data/lib/giblish/version.rb +1 -1
- data/lib/giblish.rb +10 -7
- data/scripts/hooks/post-receive.example +66 -0
- data/{docgen/scripts/githook_examples → scripts/hooks}/post-update.example +0 -0
- data/{docgen → scripts}/resources/css/adoc-colony.css +0 -0
- data/scripts/resources/css/giblish-serif.css +419 -0
- data/scripts/resources/css/giblish.css +1979 -419
- data/{docgen → scripts}/resources/fonts/Ubuntu-B.ttf +0 -0
- data/{docgen → scripts}/resources/fonts/Ubuntu-BI.ttf +0 -0
- data/{docgen → scripts}/resources/fonts/Ubuntu-R.ttf +0 -0
- data/{docgen → scripts}/resources/fonts/Ubuntu-RI.ttf +0 -0
- data/{docgen → scripts}/resources/fonts/mplus1p-regular-fallback.ttf +0 -0
- data/{docgen → scripts}/resources/images/giblish_logo.png +0 -0
- data/{docgen → scripts}/resources/images/giblish_logo.svg +0 -0
- data/{docgen → scripts}/resources/themes/giblish.yml +0 -0
- data/scripts/wserv_development.rb +32 -0
- data/web_apps/cgi_search/gibsearch.rb +43 -0
- data/web_apps/gh_webhook_trigger/config.ru +2 -0
- data/web_apps/gh_webhook_trigger/gh_webhook_trigger.rb +73 -0
- data/web_apps/gh_webhook_trigger/public/dummy.txt +3 -0
- data/web_apps/sinatra_search/config.ru +2 -0
- data/web_apps/sinatra_search/public/dummy.txt +3 -0
- data/web_apps/sinatra_search/sinatra_search.rb +34 -0
- data/web_apps/sinatra_search/tmp/restart.txt +0 -0
- metadata +180 -71
- data/.rubocop.yml +0 -7
- data/.travis.yml +0 -3
- data/Changelog +0 -16
- data/Gemfile +0 -4
- data/README.adoc +0 -1
- data/Rakefile +0 -41
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/data/testdocs/malformed/no_header.adoc +0 -5
- data/data/testdocs/toplevel.adoc +0 -19
- data/data/testdocs/wellformed/adorned_purpose.adoc +0 -17
- data/data/testdocs/wellformed/docidtest/docid_1.adoc +0 -24
- data/data/testdocs/wellformed/docidtest/docid_2.adoc +0 -8
- data/data/testdocs/wellformed/simple.adoc +0 -14
- data/data/testdocs/wellformed/source_highlighting/highlight_source.adoc +0 -38
- data/docgen/resources/css/giblish.css +0 -1979
- data/docgen/scripts/Jenkinsfile +0 -18
- data/docgen/scripts/gen_adoc_org.sh +0 -58
- data/docs/README.adoc +0 -387
- data/docs/setup_server.adoc +0 -202
- data/lib/giblish/buildgraph.rb +0 -216
- data/lib/giblish/buildindex.rb +0 -459
- data/lib/giblish/core.rb +0 -451
- data/lib/giblish/docconverter.rb +0 -308
- data/lib/giblish/docid.rb +0 -180
- data/lib/giblish/docinfo.rb +0 -75
- data/lib/giblish/indexheadings.rb +0 -251
- data/lib/giblish-search.cgi +0 -459
- data/scripts/hooks/post-receive +0 -57
- data/scripts/publish_html.sh +0 -99
@@ -0,0 +1,163 @@
|
|
1
|
+
require_relative "config_utils"
|
2
|
+
require_relative "resourcepaths"
|
3
|
+
require_relative "docattr_providers"
|
4
|
+
require_relative "adocsrc_providers"
|
5
|
+
require_relative "subtreeinfobuilder"
|
6
|
+
require_relative "docid/docid"
|
7
|
+
require_relative "search/headingindexer"
|
8
|
+
require_relative "indexbuilders/depgraphbuilder"
|
9
|
+
require_relative "indexbuilders/subtree_indices"
|
10
|
+
require_relative "gitrepos/history_pb"
|
11
|
+
|
12
|
+
module Giblish
|
13
|
+
class HtmlLayoutConfig
|
14
|
+
attr_reader :pre_builders, :post_builders, :adoc_extensions, :adoc_api_opts, :docattr_providers
|
15
|
+
|
16
|
+
def initialize(config_opts)
|
17
|
+
@adoc_api_opts = {backend: "html"}
|
18
|
+
@pre_builders = []
|
19
|
+
@post_builders = []
|
20
|
+
@adoc_extensions = {}
|
21
|
+
@docattr_providers = []
|
22
|
+
case config_opts
|
23
|
+
in resource_dir:
|
24
|
+
# copy local resources to dst and link the generated html with
|
25
|
+
# the given css
|
26
|
+
@pre_builders << CopyResourcesPreBuild.new(config_opts)
|
27
|
+
|
28
|
+
# make sure generated html has relative link to the copied css
|
29
|
+
@docattr_providers << RelativeCssDocAttr.new(ResourcePaths.new(config_opts).dst_style_path_rel)
|
30
|
+
in server_css:
|
31
|
+
# do not copy any local resources, use the given web path to link to css
|
32
|
+
@docattr_providers << AbsoluteLinkedCss.new(config_opts.server_css)
|
33
|
+
else
|
34
|
+
4 == 5 # workaround for bug in standardrb formatting
|
35
|
+
end
|
36
|
+
|
37
|
+
if config_opts.make_searchable
|
38
|
+
# enabling text search
|
39
|
+
search_provider = HeadingIndexer.new(config_opts.srcdir)
|
40
|
+
@adoc_extensions[:tree_processor] = search_provider
|
41
|
+
@post_builders << search_provider
|
42
|
+
|
43
|
+
# add search form to all docs
|
44
|
+
@adoc_extensions[:docinfo_processor] = AddSearchForm.new(config_opts.search_action_path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class PdfLayoutConfig
|
50
|
+
attr_reader :pre_builders, :post_builders, :adoc_extensions, :adoc_api_opts, :docattr_providers
|
51
|
+
|
52
|
+
def initialize(config_opts)
|
53
|
+
@adoc_api_opts = {backend: "pdf"}
|
54
|
+
@pre_builders = []
|
55
|
+
@post_builders = []
|
56
|
+
@adoc_extensions = {}
|
57
|
+
@docattr_providers = []
|
58
|
+
|
59
|
+
unless config_opts.resource_dir.nil?
|
60
|
+
# generate pdf using asciidoctor-pdf with custom styling
|
61
|
+
rp = ResourcePaths.new(config_opts)
|
62
|
+
@docattr_providers << PdfCustomStyle.new(rp.src_style_path_abs, *rp.font_dirs_abs.to_a)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# configure all parts needed to execute the options specified by
|
68
|
+
# the user
|
69
|
+
class Configurator
|
70
|
+
attr_reader :build_options, :doc_attr, :config_opts
|
71
|
+
|
72
|
+
# config_opts:: a Cmdline::Options instance with config info
|
73
|
+
def initialize(config_opts)
|
74
|
+
@config_opts = config_opts
|
75
|
+
@build_options = {
|
76
|
+
pre_builders: [],
|
77
|
+
post_builders: [],
|
78
|
+
adoc_api_opts: {},
|
79
|
+
# add a hash where all values are initiated as empty arrays
|
80
|
+
adoc_extensions: Hash.new { |h, k| h[k] = [] }
|
81
|
+
}
|
82
|
+
|
83
|
+
# Initiate the doc attribute repo used during 'run-time'
|
84
|
+
@doc_attr = DocAttrBuilder.new(
|
85
|
+
GiblishDefaultDocAttribs.new
|
86
|
+
)
|
87
|
+
|
88
|
+
layout_config = case config_opts
|
89
|
+
in format: "html" then HtmlLayoutConfig.new(config_opts)
|
90
|
+
in format: "pdf" then PdfLayoutConfig.new(config_opts)
|
91
|
+
else
|
92
|
+
raise OptionParser::InvalidArgument, "The given cmd line flags are not supported: #{config_opts.inspect}"
|
93
|
+
end
|
94
|
+
|
95
|
+
# setup all options from the chosen layout configuration but
|
96
|
+
# override doc attributes with ones from the supplied configuration to
|
97
|
+
# ensure they have highest pref
|
98
|
+
@doc_attr.add_doc_attr_providers(
|
99
|
+
*layout_config.docattr_providers, CmdLineDocAttribs.new(config_opts)
|
100
|
+
)
|
101
|
+
|
102
|
+
setup_docid(config_opts, @build_options, @doc_attr)
|
103
|
+
setup_index_generation(config_opts, @build_options, @doc_attr)
|
104
|
+
|
105
|
+
# setup all pre,post, and build options
|
106
|
+
@build_options[:adoc_api_opts] = layout_config.adoc_api_opts
|
107
|
+
@build_options[:pre_builders] += layout_config.pre_builders
|
108
|
+
@build_options[:post_builders] += layout_config.post_builders
|
109
|
+
layout_config.adoc_extensions.each do |type, instance|
|
110
|
+
@build_options[:adoc_extensions][type] << instance
|
111
|
+
end
|
112
|
+
|
113
|
+
# add copy of asset dirs if options stipulates this
|
114
|
+
@build_options[:post_builders] << CopyAssetDirsPostBuild.new(@config_opts) unless @config_opts.copy_asset_folders.nil?
|
115
|
+
end
|
116
|
+
|
117
|
+
protected
|
118
|
+
|
119
|
+
def setup_index_generation(config_opts, build_options, doc_attr)
|
120
|
+
return if config_opts.no_index
|
121
|
+
|
122
|
+
# setup index generation
|
123
|
+
idx = SubtreeInfoBuilder.new(doc_attr, nil, SubtreeIndexBase, config_opts.index_basename)
|
124
|
+
build_options[:post_builders] << idx
|
125
|
+
end
|
126
|
+
|
127
|
+
def setup_docid(config_opts, build_options, doc_attr)
|
128
|
+
return unless config_opts.resolve_docid
|
129
|
+
|
130
|
+
# setup docid resolution
|
131
|
+
d = DocIdExtension::DocidPreBuilder.new
|
132
|
+
build_options[:pre_builders] << d
|
133
|
+
docid_pp = DocIdExtension::DocidProcessor.new({id_2_node: d.id_2_node})
|
134
|
+
build_options[:adoc_extensions][:preprocessor] << docid_pp
|
135
|
+
|
136
|
+
# early exit if user does not want indices
|
137
|
+
return if config_opts.no_index
|
138
|
+
|
139
|
+
# generate dep graph if graphviz is available
|
140
|
+
dg = DependencyGraphPostBuilder.new(docid_pp.node_2_ids, doc_attr, nil, nil, config_opts.graph_basename)
|
141
|
+
build_options[:post_builders] << dg
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# swap standard index generation from the base class to ones including
|
146
|
+
# git history.
|
147
|
+
class GitRepoConfigurator < Configurator
|
148
|
+
def initialize(config_opts, git_repo_dir)
|
149
|
+
@git_repo_dir = git_repo_dir
|
150
|
+
config_opts.search_action_path ||= "/gibsearch.cgi"
|
151
|
+
super(config_opts)
|
152
|
+
end
|
153
|
+
|
154
|
+
protected
|
155
|
+
|
156
|
+
def setup_index_generation(config_opts, build_options, doc_attr)
|
157
|
+
return if config_opts.no_index
|
158
|
+
|
159
|
+
build_options[:post_builders] << AddHistoryPostBuilder.new(@git_repo_dir)
|
160
|
+
build_options[:post_builders] << SubtreeInfoBuilder.new(doc_attr, nil, SubtreeIndexGit, config_opts.index_basename)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module Giblish
|
4
|
+
class FileHistory
|
5
|
+
# History info from git
|
6
|
+
LogEntry = Struct.new(:date, :author, :message, :sha1)
|
7
|
+
|
8
|
+
attr_accessor :history, :branch
|
9
|
+
|
10
|
+
def initialize(branch)
|
11
|
+
@branch = branch
|
12
|
+
@history = []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Base class for bundling together data we always cache for
|
17
|
+
# each asciidoc file we come across.
|
18
|
+
#
|
19
|
+
# Users are expected to use the derived classes.
|
20
|
+
class ConversionInfo
|
21
|
+
attr_reader :converted, :src_node, :dst_node, :dst_top
|
22
|
+
|
23
|
+
# The relative Pathname from the root dir to the src doc
|
24
|
+
# Ex Pathname("my/subdir/file1.adoc")
|
25
|
+
attr_reader :src_rel_path
|
26
|
+
|
27
|
+
@@nof_missing_titles = 0
|
28
|
+
|
29
|
+
def initialize(converted:, src_node:, dst_node:, dst_top:)
|
30
|
+
@converted = converted
|
31
|
+
@src_node = src_node
|
32
|
+
@dst_node = dst_node
|
33
|
+
@dst_top = dst_top
|
34
|
+
@title = nil
|
35
|
+
|
36
|
+
@src_rel_path = dst_node.relative_path_from(dst_top).dirname / src_node.pathname.basename
|
37
|
+
end
|
38
|
+
|
39
|
+
# return:: a String with the basename of the source file
|
40
|
+
def src_basename
|
41
|
+
@src_node.pathname.basename.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def title
|
45
|
+
return @title if @title
|
46
|
+
|
47
|
+
"NO TITLE FOUND (#{@@nof_missing_titles += 1}) !"
|
48
|
+
end
|
49
|
+
|
50
|
+
def docid
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"Conversion #{@converted ? "succeeded" : "failed"} - src: #{@src_node.pathname} dst: #{@dst_node.pathname}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Provide data and access methods available when a conversion has
|
60
|
+
# succeeded
|
61
|
+
class SuccessfulConversion < ConversionInfo
|
62
|
+
attr_reader :stderr, :adoc
|
63
|
+
|
64
|
+
# The relative Pathname from the root dir to the dst file
|
65
|
+
# Ex Pathname("my/subdir/file1.html")
|
66
|
+
attr_reader :dst_rel_path
|
67
|
+
|
68
|
+
attr_reader :purpose_str
|
69
|
+
|
70
|
+
def initialize(src_node:, dst_node:, dst_top:, adoc:, adoc_stderr: "")
|
71
|
+
super(converted: true, src_node: src_node, dst_node: dst_node, dst_top: dst_top)
|
72
|
+
|
73
|
+
@adoc = adoc
|
74
|
+
@stderr = adoc_stderr
|
75
|
+
@title = @adoc.doctitle
|
76
|
+
|
77
|
+
@dst_rel_path = dst_node.relative_path_from(dst_top)
|
78
|
+
|
79
|
+
# Cach the purpose info if it exists
|
80
|
+
@purpose_str = get_purpose_info adoc
|
81
|
+
end
|
82
|
+
|
83
|
+
def docid
|
84
|
+
@adoc.attributes["docid"]
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# TODO: Move this somewhere else
|
90
|
+
def get_purpose_info(adoc)
|
91
|
+
# Get the 'Purpose' section if it exists
|
92
|
+
purpose_str = +""
|
93
|
+
adoc.blocks.each do |section|
|
94
|
+
next unless section.is_a?(Asciidoctor::Section) &&
|
95
|
+
(section.level == 1) &&
|
96
|
+
(section.name =~ /^Purpose$/)
|
97
|
+
|
98
|
+
# filter out 'odd' text, such as lists etc...
|
99
|
+
section.blocks.each do |bb|
|
100
|
+
next unless bb.is_a?(Asciidoctor::Block)
|
101
|
+
|
102
|
+
purpose_str << "#{bb.source}\n+\n"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
purpose_str
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Provide data and access methods available when a conversion has
|
110
|
+
# failed
|
111
|
+
class FailedConversion < ConversionInfo
|
112
|
+
attr_reader :error_msg
|
113
|
+
|
114
|
+
def initialize(src_node:, dst_node:, dst_top:, error_msg: nil)
|
115
|
+
super(converted: false, src_node: src_node, dst_node: dst_node, dst_top: dst_top)
|
116
|
+
|
117
|
+
@error_msg = error_msg
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Giblish
|
2
|
+
class DocAttributesBase
|
3
|
+
def document_attributes(src_node, dst_node, dst)
|
4
|
+
raise NotImplementedError
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class AbsoluteLinkedCss < DocAttributesBase
|
9
|
+
def initialize(css_path)
|
10
|
+
@css_path = Pathname.new(css_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def document_attributes(src_node, dst_node, dst_top)
|
14
|
+
{
|
15
|
+
"stylesdir" => @css_path.dirname.to_s,
|
16
|
+
"stylesheet" => @css_path.basename.to_s,
|
17
|
+
"linkcss" => true,
|
18
|
+
"copycss" => nil
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class RelativeCssDocAttr < DocAttributesBase
|
24
|
+
# dst_css_path_rel:: the relative path to the dst top to the location of the
|
25
|
+
# css file to use
|
26
|
+
def initialize(dst_css_path_rel)
|
27
|
+
@css_path = Pathname.new(dst_css_path_rel)
|
28
|
+
end
|
29
|
+
|
30
|
+
def document_attributes(src_node, dst_node, dst_top)
|
31
|
+
href_path = dst_top.relative_path_from(dst_node).dirname / @css_path
|
32
|
+
{
|
33
|
+
"stylesdir" => href_path.dirname.to_s,
|
34
|
+
"stylesheet" => href_path.basename.to_s,
|
35
|
+
"linkcss" => true,
|
36
|
+
"copycss" => nil
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class PdfCustomStyle < DocAttributesBase
|
42
|
+
# pdf_style_path:: the path name (preferable absolute) to the yml file
|
43
|
+
# pdf_font_dirs:: one or more Pathnames to each font dir that shall be
|
44
|
+
# checked for fonts
|
45
|
+
def initialize(pdf_style_path, *pdf_font_dirs)
|
46
|
+
@pdf_style_path = pdf_style_path
|
47
|
+
# one can specify multiple font dirs as:
|
48
|
+
# -a pdf-fontsdir="path/to/fonts;path/to/more-fonts"
|
49
|
+
# Always use the GEM_FONTS_DIR token to load the adoc-pdf gem's font dirs as well
|
50
|
+
@pdf_fontsdir = (Array(pdf_font_dirs) << "GEM_FONTS_DIR").collect { |d| d.to_s }&.join(";")
|
51
|
+
end
|
52
|
+
|
53
|
+
def document_attributes(src_node, dst_node, dst_top)
|
54
|
+
result = {
|
55
|
+
"pdf-style" => @pdf_style_path.basename.to_s,
|
56
|
+
"pdf-stylesdir" => @pdf_style_path.dirname.to_s,
|
57
|
+
"icons" => "font"
|
58
|
+
}
|
59
|
+
result["pdf-fontsdir"] = @pdf_fontsdir unless @pdf_fontsdir.nil?
|
60
|
+
result
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# provides the default, opinionated doc attributes used
|
65
|
+
# when nothing else is set
|
66
|
+
class GiblishDefaultDocAttribs
|
67
|
+
def document_attributes(src_node, dst_node, dst_top)
|
68
|
+
{
|
69
|
+
"source-highlighter" => "rouge",
|
70
|
+
"rouge-css" => "style",
|
71
|
+
"rouge-style" => "github",
|
72
|
+
"xrefstyle" => "short",
|
73
|
+
"icons" => "font"
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class CmdLineDocAttribs
|
79
|
+
def initialize(cmd_opts)
|
80
|
+
@cmdline_attrs = cmd_opts.doc_attributes.dup
|
81
|
+
end
|
82
|
+
|
83
|
+
def document_attributes(src_node, dst_node, dst_top)
|
84
|
+
@cmdline_attrs
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# a builder class that can be setup with one or more document
|
89
|
+
# attribute providers and then used as the sole doc attrib provider
|
90
|
+
# where the merged sum of all added providers will be presented.
|
91
|
+
#
|
92
|
+
# If more than one added provider set the same doc attrib, the last
|
93
|
+
# added has preference.
|
94
|
+
#
|
95
|
+
# Each added provider must conform to the itf defined in
|
96
|
+
# the DocAttributesBase class.
|
97
|
+
class DocAttrBuilder
|
98
|
+
attr_reader :providers
|
99
|
+
|
100
|
+
def initialize(*attr_providers)
|
101
|
+
@providers = []
|
102
|
+
add_doc_attr_providers(*attr_providers)
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_doc_attr_providers(*attr_providers)
|
106
|
+
return if attr_providers.empty?
|
107
|
+
|
108
|
+
# check itf compliance of added providers
|
109
|
+
attr_providers.each do |ap|
|
110
|
+
unless ap.respond_to?(:document_attributes) &&
|
111
|
+
ap.method(:document_attributes).arity == 3
|
112
|
+
raise ArgumentError, "The supplied doc attribute provider of type: #{ap.class} did not conform to the interface"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
@providers += attr_providers
|
117
|
+
end
|
118
|
+
|
119
|
+
def document_attributes(src_node, dst_node, dst_top)
|
120
|
+
result = {}
|
121
|
+
@providers.each { |p| result.merge!(p.document_attributes(src_node, dst_node, dst_top)) }
|
122
|
+
result
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require "asciidoctor"
|
2
|
+
require_relative "../pathtree"
|
3
|
+
|
4
|
+
module Giblish
|
5
|
+
module DocIdExtension
|
6
|
+
# Build a hash of {docid => src_node} that can be used to resolve
|
7
|
+
# doc id references to valid dst paths
|
8
|
+
class DocidPreBuilder
|
9
|
+
attr_accessor :id_2_node
|
10
|
+
|
11
|
+
# The minimum number of characters required for a valid doc id
|
12
|
+
ID_MIN_LENGTH = 2
|
13
|
+
|
14
|
+
# The maximum number of characters required for a valid doc id
|
15
|
+
ID_MAX_LENGTH = 10
|
16
|
+
|
17
|
+
# the regex used to find :docid: entries in the doc header
|
18
|
+
DOCID_REGEX = /^:docid: +(.*)$/
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@id_2_node = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
# called during the pre-build phase
|
25
|
+
def on_prebuild(src_tree, dst_tree, converter)
|
26
|
+
src_tree.traverse_preorder do |level, src_node|
|
27
|
+
next unless src_node.leaf?
|
28
|
+
|
29
|
+
parse_node(src_node)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Check if a :docid: <id> entry exists in the header of the doc
|
36
|
+
# denoted by 'node'.
|
37
|
+
# According to http://www.methods.co.nz/asciidoc/userguide.html#X95
|
38
|
+
# the header is optional, but if it exists it:
|
39
|
+
# - must start with a titel (=+ <My Title>)
|
40
|
+
# - ends with one or more blank lines
|
41
|
+
# - does not contain any blank line
|
42
|
+
def parse_node(node)
|
43
|
+
p = node.pathname
|
44
|
+
Giblog.logger.debug { "parsing file #{p} for docid..." }
|
45
|
+
Giblish.process_header_lines_from_file(p) do |line|
|
46
|
+
next unless DOCID_REGEX.match(line)
|
47
|
+
|
48
|
+
# There is a docid defined, cache the path and doc id
|
49
|
+
id = Regexp.last_match(1).strip
|
50
|
+
Giblog.logger.debug { "found possible docid: #{id}" }
|
51
|
+
if doc_id_ok?(id, p)
|
52
|
+
@id_2_node[id] = node
|
53
|
+
else
|
54
|
+
Giblog.logger.error { "Invalid docid: #{id} in file #{p}, this will be ignored!" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# make sure the id is within the designated length and
|
60
|
+
# does not contain a '#' symbol
|
61
|
+
def doc_id_ok?(doc_id, path)
|
62
|
+
if @id_2_node.key? doc_id
|
63
|
+
Giblog.logger.warn { "Found same doc id twice: (#{doc_id}). This id will be associated with '#{path}' and _not_ with '#{@id_2_node[id]}'." }
|
64
|
+
end
|
65
|
+
(doc_id.length.between?(ID_MIN_LENGTH, ID_MAX_LENGTH) && !doc_id.include?("#"))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# A preprocessor extension to the Asciidoctor engine that transforms all
|
70
|
+
# <<:docid:>> references found in the adoc source into the matching
|
71
|
+
# file reference.
|
72
|
+
#
|
73
|
+
# It requiers a populated 'id_2_node' with {docid => src_node} before
|
74
|
+
# the first invokation of the 'process' method via Asciidoctor.
|
75
|
+
#
|
76
|
+
# When running, it builds a publicly available 'node_2_ids' map.
|
77
|
+
class DocidProcessor < Asciidoctor::Extensions::Preprocessor
|
78
|
+
# {src_node => [referenced doc_id's]}
|
79
|
+
attr_reader :node_2_ids
|
80
|
+
|
81
|
+
# required options:
|
82
|
+
#
|
83
|
+
# id_2_node:: {docid => src_node }
|
84
|
+
def initialize(opts)
|
85
|
+
raise ArgumentError, "Missing required option: :id_2_node!" unless opts.key?(:id_2_node)
|
86
|
+
|
87
|
+
super(opts)
|
88
|
+
@id_2_node = opts[:id_2_node]
|
89
|
+
|
90
|
+
# init new keys in the hash with an empty array
|
91
|
+
@node_2_ids = Hash.new { |h, k| h[k] = [] }
|
92
|
+
end
|
93
|
+
|
94
|
+
# The regex that matches docid references in files
|
95
|
+
DOCID_REF_REGEX = /<<\s*:docid:\s*(.*?)>>/
|
96
|
+
PASS_MACRO_REGEX = /pass:\[.*\]/
|
97
|
+
|
98
|
+
# This hook is called by Asciidoctor once for each document _before_
|
99
|
+
# Asciidoctor processes the adoc content.
|
100
|
+
#
|
101
|
+
# It replaces references of the format <<:docid: ID-1234,Hello >> with
|
102
|
+
# references to a resolved relative path.
|
103
|
+
def process(document, reader)
|
104
|
+
# Add doc as a source dependency for doc ids
|
105
|
+
gib_data = document.attributes["giblish-info"]
|
106
|
+
if gib_data.nil?
|
107
|
+
Giblog.logger.error "The docid preprocessor did not find required info in the doc attribute. (Doc title: #{document.title}"
|
108
|
+
return reader
|
109
|
+
end
|
110
|
+
|
111
|
+
src_node = gib_data[:src_node]
|
112
|
+
|
113
|
+
# Convert all docid refs to valid relative refs
|
114
|
+
reader.lines.each do |line|
|
115
|
+
# remove commented lines
|
116
|
+
next if line.start_with?("//")
|
117
|
+
|
118
|
+
@node_2_ids[src_node] += parse_line(line, src_node)
|
119
|
+
end
|
120
|
+
|
121
|
+
# we only care for one ref to a specific target, remove duplicates
|
122
|
+
@node_2_ids[src_node] = @node_2_ids[src_node].uniq
|
123
|
+
|
124
|
+
# the asciidoctor engine wants the reader back
|
125
|
+
reader
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# substitutes docid references with the corresponding relative path
|
131
|
+
# references.
|
132
|
+
#
|
133
|
+
# returns:: Array of found docid references
|
134
|
+
def parse_line(line, src_node)
|
135
|
+
refs = []
|
136
|
+
# remove all content within a 'pass:[]' macro from the parser
|
137
|
+
line.gsub!(PASS_MACRO_REGEX)
|
138
|
+
|
139
|
+
line.gsub!(DOCID_REF_REGEX) do |_m|
|
140
|
+
# parse the ref
|
141
|
+
target_id, section, display_str = parse_doc_id_ref(Regexp.last_match(1))
|
142
|
+
Giblog.logger.debug { "Found docid ref to #{target_id} in file: #{src_node.pathname}..." }
|
143
|
+
|
144
|
+
# make sure it exists in the cache
|
145
|
+
unless @id_2_node.key?(target_id)
|
146
|
+
Giblog.logger.warn { "Could not resolve ref to #{target_id} from file: #{src_node.pathname}..." }
|
147
|
+
break "<<UNKNOWN_DOC, Could not resolve doc id reference !!!>>"
|
148
|
+
end
|
149
|
+
|
150
|
+
# add the referenced doc id as a target dependency of this document
|
151
|
+
refs << target_id
|
152
|
+
|
153
|
+
# get the relative path from this file to the target file
|
154
|
+
target_node = @id_2_node[target_id]
|
155
|
+
rel_path = target_node.pathname.relative_path_from(src_node.pathname.dirname)
|
156
|
+
|
157
|
+
# return the resolved reference
|
158
|
+
"<<#{rel_path}##{section}#{display_str}>>"
|
159
|
+
end
|
160
|
+
refs
|
161
|
+
end
|
162
|
+
|
163
|
+
# input_str shall be the expression between
|
164
|
+
# <<:docid:<input_str>>> where the <input_str> is in the form
|
165
|
+
# <id>[#section][,display_str]
|
166
|
+
#
|
167
|
+
# returns an array with [id, section, display_str]
|
168
|
+
def parse_doc_id_ref(input_str)
|
169
|
+
ref, display_str = input_str.split(",").each(&:strip)
|
170
|
+
id, section = ref.split "#"
|
171
|
+
|
172
|
+
display_str = id.dup if display_str.nil?
|
173
|
+
display_str.prepend ","
|
174
|
+
|
175
|
+
section = "" if section.nil?
|
176
|
+
|
177
|
+
[id, section, display_str]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "git"
|
2
|
+
require_relative "../application"
|
3
|
+
|
4
|
+
module Giblish
|
5
|
+
# Generate documentation using giblish based on the supplied parameters
|
6
|
+
class GenerateFromRefs
|
7
|
+
STANDARD_GIBLISH_FLAGS = %W[-f html -m --copy-asset-folders _assets$ --server-search-path /cgi-bin/gibsearch.cgi]
|
8
|
+
|
9
|
+
# doc_repo_url:: the url of the repo hosting the docs to be generated. this repo will be cloned
|
10
|
+
# ref_regexp:: a regexp that both defines what git refs that trigger a document generation and what refs will be
|
11
|
+
# generated.
|
12
|
+
# clone_dir_parent:: path to the local directory under which the doc_repo_url will be cloned.
|
13
|
+
# clone_name:: the name of the local clone of the doc_repo_url
|
14
|
+
# doc_src_rel:: the relative path from the repo root to the directory where the docs reside
|
15
|
+
# doc_dst_abs:: the absolute path to the target location for the generated docs
|
16
|
+
# logger:: a ruby Logger instance that will receive log messages
|
17
|
+
def initialize(doc_repo_url, ref_regexp, clone_dir_parent, clone_name, giblish_args, doc_src_rel, doc_dst_abs, logger)
|
18
|
+
@doc_repo_url = doc_repo_url
|
19
|
+
@ref_regexp = ref_regexp
|
20
|
+
@giblish_args = STANDARD_GIBLISH_FLAGS
|
21
|
+
@giblish_args += giblish_args unless giblish_args.nil?
|
22
|
+
@doc_src_rel = doc_src_rel
|
23
|
+
@dstdir = doc_dst_abs
|
24
|
+
@logger = logger
|
25
|
+
|
26
|
+
@repo_root = clone(doc_repo_url, clone_dir_parent, clone_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generate documents from the git refs found in the GitHub
|
30
|
+
# webhook payload.
|
31
|
+
def docs_from_gh_webhook(github_hash)
|
32
|
+
# TODO Implement support for other refs than branches
|
33
|
+
ref = github_hash.fetch(:ref).sub("refs/heads/", "")
|
34
|
+
if ref.empty? || !(@ref_regexp =~ ref)
|
35
|
+
@logger&.info { "Ref '#{ref}' does not match the document generation trigger -> No document generation triggereed" }
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
generate_docs(ref)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def clone(doc_repo_url, dst_dir, clone_name)
|
45
|
+
p = Pathname.new(dst_dir).join(clone_name)
|
46
|
+
|
47
|
+
@logger&.info { "Cloning #{doc_repo_url} to #{p}..." }
|
48
|
+
repo = Git.clone(doc_repo_url, clone_name, path: dst_dir.to_s, logger: @logger)
|
49
|
+
repo.config("user.name", "Giblish Webhook Manager")
|
50
|
+
repo.config("user.email", "dummy@giblish.com")
|
51
|
+
@logger&.info { "Cloning done" }
|
52
|
+
p
|
53
|
+
end
|
54
|
+
|
55
|
+
def generate_docs(ref)
|
56
|
+
srcdir = @repo_root.join(@doc_src_rel)
|
57
|
+
args = @giblish_args + %W[-g #{@ref_regexp} #{srcdir} #{@dstdir}]
|
58
|
+
|
59
|
+
@logger&.info { "Generate docs using parameters: #{args}" }
|
60
|
+
# run giblish with all args
|
61
|
+
EntryPoint.run(args, @logger)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|