giblish 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2e06197e632acf984fd627c5bd9ed60934a6ba4f
4
+ data.tar.gz: 2cafd5a3eaf191f4bcf74bb7121f6c370c8a5518
5
+ SHA512:
6
+ metadata.gz: ab5f2e8b5a2e218385fbad17447cd29379a2f28cda64bcf6cf972b38c7da3a49ced8586d6bd2763b23fa05b102400d1c9a871d016621a786b52b39559983d78a
7
+ data.tar.gz: db3ebc6969d4abb83db8345f2201ff992bbd88ebe3f049d838fdec3c234e930348a3cc2c6a5eabd75d64374b267ef25101d9a67d65ccd10ecb9e5aecec30764e
data/.gitignore ADDED
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in giblish.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 rillbert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.adoc ADDED
@@ -0,0 +1,67 @@
1
+ = Giblish: Generate docs from asciidoc files in a git repo
2
+
3
+ == Purpose
4
+ giblish is used to convert a source directory tree containing AsciiDoc files to
5
+ a destination directory tree containing the corresponding html or pdf files
6
+ and add a handy index page for the converted files.
7
+
8
+ If the source directory tree is part of a git repository, giblish can generate
9
+ separate html/pdf trees for branches and/or tags that match a user specified
10
+ regexp (see examples below).
11
+
12
+ == Dependencies and credits
13
+
14
+ Giblish is basically a convenience wrapper around the awesome asciidoctor and
15
+ asciidoctor-pdf projects. Thank you @mojavelinux and others for making these
16
+ brilliant tools!!
17
+
18
+ == Installation
19
+
20
+ gem install giblish
21
+
22
+ == Usage Examples
23
+
24
+ .Get available options
25
+ ====
26
+ giblish -h
27
+ ====
28
+
29
+ .Giblish 'hello world'
30
+ ====
31
+ giblish my_src_root my_dst_root
32
+
33
+ The above will convert all .adoc or .ADOC files under the dir `my_src_root` to
34
+ html and place the resulting files under the `my_dst_root` dir. An index page
35
+ named `index.html` is generated in the `my_dst_root` dir containing links and
36
+ some info about the converted files.
37
+
38
+ The default asciidoctor css will be used in the html conversion.
39
+ ====
40
+
41
+ .Using a different css
42
+ ====
43
+ giblish -r ./path/to/my/resources -s mylayout my_src_root my_dst_root
44
+
45
+ The above will convert all .adoc or .ADOC files under the dir `my_src_root` to
46
+ html and place the resulting files under the `my_dst_root` dir. An index page
47
+ named `index.html` is generated in the `my_dst_root` dir containing links and
48
+ some info about the converted files.
49
+
50
+ A css named mylayout.css must be found in the dir
51
+ `<working_dir/path/to/my/resources/css`. The resulting html files will link
52
+ to this css. Fonts and images used from the css must be found under
53
+ `<working_dir/path/to/my/resources/fonts` and
54
+ `<working_dir/path/to/my/resources/images` respectively.
55
+ ====
56
+
57
+ .Generate docs from multiple git branches
58
+ ====
59
+ giblish -g "feature" my_src_root my_dst_root
60
+
61
+ The above will check-out all branches matching the regexp "feature" and convert
62
+ the .adoc or .ADOC files under the dir `my_src_root` to html and place the
63
+ resulting files under the `my_dst_root/<branch_name>` dir. An index page named
64
+ `index.html` is generated in each `my_dst_root/<branch_name` dir containing links and
65
+ some info about the converted files. A summary page containing links to all
66
+ branches will be generated directly in the `my_dst_root` dir.
67
+ ====
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ # task :default => :spec
11
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "giblish"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/giblish ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #
4
+
5
+ require "giblish"
6
+
7
+ Giblish.application.run
data/lib/giblish.rb ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "giblish/version"
4
+ require_relative "giblish/utils"
5
+ require_relative "giblish/core"
6
+ require_relative "giblish/buildindex"
7
+ require_relative "giblish/cmdline"
8
+ require_relative "giblish/pathtree"
9
+ require_relative "giblish/application"
10
+
11
+ module Giblish
12
+ class << self
13
+
14
+ def application
15
+ @application ||= Giblish::Application.new
16
+ end
17
+ end
18
+ end
19
+
20
+ if __FILE__ == $PROGRAM_NAME
21
+ Giblish.application.run
22
+ end
@@ -0,0 +1,34 @@
1
+
2
+ require_relative "cmdline"
3
+ require_relative "core"
4
+ require_relative "utils"
5
+
6
+ module Giblish
7
+ class Application
8
+ def run
9
+ # setup logging
10
+ Giblog.setup
11
+
12
+ # Parse cmd line
13
+ cmdline = CmdLineParser.new ARGV
14
+
15
+ Giblog.logger.debug { "cmd line args: #{cmdline.args.to_s}" }
16
+
17
+ # Convert using given args
18
+ begin
19
+ if cmdline.args[:gitRepoRoot]
20
+ Giblog.logger.info {"User asked to parse a git repo"}
21
+ GitRepoParser.new cmdline.args
22
+ else
23
+ tc = TreeConverter.new cmdline.args
24
+ tc.walk_dirs
25
+ end
26
+ rescue Exception => e
27
+ puts "Error: #{e.message}"
28
+ puts "\n"
29
+ puts "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
30
+ puts cmdline.usage
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,454 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ require "pathname"
5
+ require "git"
6
+ require_relative "cmdline"
7
+ require_relative "pathtree"
8
+
9
+ # Container class for bundling together the data we cache for
10
+ # each asciidoc file we come across
11
+ class DocInfo
12
+ # Cache git info
13
+ class DocHistory
14
+ attr_accessor :date
15
+ attr_accessor :author
16
+ attr_accessor :message
17
+ end
18
+
19
+ attr_accessor :converted
20
+ attr_accessor :title
21
+ attr_accessor :doc_id
22
+ attr_accessor :purpose_str
23
+ attr_accessor :status
24
+ attr_accessor :relPath
25
+ attr_accessor :srcFile
26
+ attr_accessor :history
27
+ attr_accessor :error_msg
28
+ attr_accessor :stderr
29
+
30
+ def initialize
31
+ @history = []
32
+ end
33
+
34
+ def to_s
35
+ "DocInfo: title: #{@title} srcFile: #{@srcFile}"
36
+ end
37
+ end
38
+
39
+ # Base class with common functionality for all index builders
40
+ class BasicIndexBuilder
41
+ # set up the basic index building info
42
+ def initialize(path_manager)
43
+ @paths = path_manager
44
+ @added_docs = []
45
+ @src_str = ""
46
+ end
47
+
48
+ # creates a DocInfo instance, fills it with basic info and
49
+ # returns the filled in instance so that derived implementations can
50
+ # add more data
51
+ def add_doc(adoc, adoc_stderr)
52
+ Giblog.logger.debug { "Adding adoc: #{adoc} Asciidoctor stderr: #{adoc_stderr}" }
53
+
54
+ info = DocInfo.new
55
+ info.converted = true
56
+ info.stderr = adoc_stderr
57
+
58
+ # Get the doc id if it exists
59
+ info.doc_id = adoc.attributes["docid"]
60
+
61
+ # Get the purpose info if it exists
62
+ info.purpose_str = get_purpose_info adoc
63
+
64
+ # Get the relative path beneath the root dir to the doc
65
+ info.relPath = Pathname.new(
66
+ adoc.attributes["outfile"]
67
+ ).relative_path_from(
68
+ @paths.dst_root_abs
69
+ )
70
+
71
+ # Get the source file path and title
72
+ info.srcFile = adoc.attributes["docfile"]
73
+ info.title = adoc.doctitle
74
+
75
+ # Cache the created DocInfo
76
+ @added_docs << info
77
+ info
78
+ end
79
+
80
+ def add_doc_fail(filepath, exception)
81
+ info = DocInfo.new
82
+
83
+ # the only info we have is the source file name
84
+ info.converted = false
85
+ info.srcFile = filepath
86
+ info.error_msg = exception.message
87
+
88
+ # Cache the DocInfo
89
+ @added_docs << info
90
+ info
91
+ end
92
+
93
+ def index_source
94
+ <<~DOC_STR
95
+ #{generate_header}
96
+ #{generate_tree}
97
+ #{generate_details}
98
+ #{generate_footer}
99
+ DOC_STR
100
+ end
101
+
102
+ protected
103
+
104
+ def generate_header
105
+ t = Time.now
106
+ <<~DOC_HEADER
107
+ = Document index
108
+ from #{@paths.src_root_abs}
109
+
110
+ Generated by Giblish at::
111
+
112
+ #{t.strftime('%Y-%m-%d %H:%M')}
113
+
114
+ DOC_HEADER
115
+ end
116
+
117
+ def generate_footer
118
+ ""
119
+ end
120
+
121
+ private
122
+
123
+ def get_purpose_info(adoc)
124
+ # Get the 'Purpose' section if it exists
125
+ purpose_str = ""
126
+ adoc.blocks.each do |section|
127
+ next unless section.is_a?(Asciidoctor::Section) &&
128
+ (section.level == 1) &&
129
+ (section.name =~ /^Purpose$/)
130
+ purpose_str = "Purpose::\n\n"
131
+ section.blocks.each do |bb|
132
+ next unless bb.is_a?(Asciidoctor::Block)
133
+ purpose_str << "#{bb.source}\n+\n"
134
+ end
135
+ end
136
+ purpose_str
137
+ end
138
+
139
+ def generate_conversion_info(d)
140
+ return "" if d.stderr.empty?
141
+ # extract conversion warnings from asciddoctor std err
142
+ conv_warnings = d.stderr.gsub("asciidoctor:", "\n * asciidoctor:")
143
+ Giblog.logger.warn { "Conversion warnings: #{conv_warnings}" }
144
+
145
+ # assemble info to index page
146
+ <<~CONV_INFO
147
+ Conversion info::
148
+
149
+ #{conv_warnings}
150
+ CONV_INFO
151
+ end
152
+
153
+ # Private: Return adoc elements for displaying a clickable title
154
+ # and a 'details' ref that points to a section that uses the title as an id.
155
+ #
156
+ # Returns [ clickableTitleStr, clickableDetailsStr ]
157
+ def format_title_and_ref(doc_info)
158
+ unless doc_info.title
159
+ @nof_missing_titles += 1
160
+ doc_info.title = "NO TITLE FOUND (#{@nof_missing_titles}) !"
161
+ end
162
+ return "<<#{doc_info.relPath}#,#{doc_info.title}>>",
163
+ "<<#{Giblish.to_valid_id(doc_info.title)},details>>\n"
164
+ end
165
+
166
+ # Generate an adoc string that will display as
167
+ # DocTitle (warn) details
168
+ # Where the DocTitle and details are links to the doc itself and a section
169
+ # identified with the doc's title respectively.
170
+ def tree_entry_converted(prefix_str, doc_info)
171
+ # Get the elements of the entry
172
+ doc_title, doc_details = format_title_and_ref doc_info
173
+ warning_label = doc_info.stderr.empty? ? "" : "(warn)"
174
+
175
+ # Calculate padding to get (warn) and details aligned between entries
176
+ padding = 80
177
+ [doc_info.title, prefix_str, warning_label].each { |p| padding -= p.length }
178
+ padding = 0 unless padding.positive?
179
+ "#{prefix_str} #{doc_title}#{' ' * padding}#{warning_label} #{doc_details}"
180
+ end
181
+
182
+ def tree_entry_string(level, node)
183
+ # indent 2 * level
184
+ prefix_str = " " * (level + 1)
185
+
186
+ # return only name for directories
187
+ return "#{prefix_str} #{node.name}\n" unless node.leaf?
188
+
189
+ # return links to content and details for files
190
+ d = node.data
191
+ if d.converted
192
+ tree_entry_converted prefix_str, d
193
+ else
194
+ # no converted file exists, show what we know
195
+ "#{prefix_str} FAIL: #{d.srcFile} <<#{d.srcFile},details>>\n"
196
+ end
197
+ end
198
+
199
+ def generate_tree
200
+ # build up tree of paths
201
+ root = PathTree.new
202
+ @added_docs.each do |d|
203
+ root.add_path(d.relPath.to_s, d)
204
+ end
205
+
206
+ # output tree intro
207
+ tree_string = <<~DOC_HEADER
208
+ == Document Overview
209
+
210
+ _Click on the title to open the document or on `details` to see more
211
+ info about the document. A `(warn)` label indicates that there were
212
+ warnings while converting the document._
213
+
214
+ [subs=\"normal\"]
215
+ ----
216
+ DOC_HEADER
217
+
218
+ # generate each tree entry string
219
+ root.traverse_top_down do |level, node|
220
+ tree_string << tree_entry_string(level, node)
221
+ end
222
+
223
+ # generate the tree footer
224
+ tree_string << "\n----\n"
225
+ end
226
+
227
+ # Derived classes can override this with useful info
228
+ def generate_history_info(_d)
229
+ ""
230
+ end
231
+
232
+ def generate_detail_fail(d)
233
+ <<~FAIL_INFO
234
+ === #{d.srcFile}
235
+
236
+ Source file::
237
+
238
+ #{d.srcFile}
239
+
240
+ Error detail::
241
+ #{d.stderr}
242
+
243
+ ''''
244
+
245
+ FAIL_INFO
246
+ end
247
+
248
+ def generate_detail(d)
249
+ # Generate detail info
250
+ <<~DETAIL_SRC
251
+ [[#{Giblish.to_valid_id(d.title)}]]
252
+ === #{d.title}
253
+
254
+ #{d.purpose_str}
255
+
256
+ #{generate_conversion_info d}
257
+
258
+ Source file::
259
+ #{d.srcFile}
260
+
261
+ #{generate_history_info d}
262
+
263
+ ''''
264
+
265
+ DETAIL_SRC
266
+ end
267
+
268
+ def generate_details
269
+ root = PathTree.new
270
+ @added_docs.each do |d|
271
+ root.add_path(d.relPath.to_s, d)
272
+ end
273
+
274
+ details_str = "== Document details\n\n"
275
+
276
+ root.traverse_top_down do |_level, node|
277
+ details_str << if node.leaf?
278
+ d = node.data
279
+ if d.converted
280
+ generate_detail(d)
281
+ else
282
+ generate_detail_fail(d)
283
+ end
284
+ else
285
+ ""
286
+ end
287
+ end
288
+ details_str
289
+ end
290
+ end
291
+
292
+ # A simple index generator that shows a table with the generated documents
293
+ class SimpleIndexBuilder < BasicIndexBuilder
294
+ def initialize(path_manager)
295
+ super path_manager
296
+ end
297
+
298
+ def add_doc(adoc, adoc_stderr)
299
+ super(adoc, adoc_stderr)
300
+ end
301
+ end
302
+
303
+ # Builds an index of the generated documents and includes some git metadata
304
+ # repository
305
+ class GitRepoIndexBuilder < BasicIndexBuilder
306
+ def initialize(path_manager, git_repo_root)
307
+ super path_manager
308
+ @git_repo_root = git_repo_root
309
+
310
+ # no repo root given...
311
+ return unless @git_repo_root
312
+
313
+ begin
314
+ # Make sure that we can "talk" to git if user feeds us
315
+ # a git repo root
316
+ @git_repo = Git.open(@git_repo_root)
317
+
318
+ # initialize state variables
319
+ @nof_missing_titles = 0
320
+ rescue Exception => e
321
+ Giblog.logger.error { "No git repo! exception: #{e.message}" }
322
+ end
323
+ end
324
+
325
+ def add_doc(adoc, adoc_stderr)
326
+ info = super(adoc, adoc_stderr)
327
+
328
+ # Redefine the srcFile to mean the relative path to the git repo root
329
+ info.srcFile = Pathname.new(info.srcFile).relative_path_from(@git_repo_root).to_s
330
+
331
+ # Get the commit history of the doc
332
+ @git_repo.log(50).object("*#{info.srcFile}").each do |l|
333
+ h = DocInfo::DocHistory.new
334
+ h.date = l.date
335
+ h.message = l.message
336
+ h.author = l.author.name
337
+ info.history << h
338
+ end
339
+ end
340
+
341
+ protected
342
+
343
+ def generate_header
344
+ t = Time.now
345
+ <<~DOC_HEADER
346
+ = Document index
347
+ #{@git_repo.current_branch}
348
+
349
+ Generated by Giblish at::
350
+
351
+ #{t.strftime('%Y-%m-%d %H:%M')}
352
+
353
+ DOC_HEADER
354
+ end
355
+
356
+ def generate_history_info(d)
357
+ str = <<~HISTORY_HEADER
358
+ File history::
359
+
360
+ [cols=\"2,3,8\",options=\"header\"]
361
+ |===
362
+ |Date |Author |Message
363
+ HISTORY_HEADER
364
+
365
+ # Generate table rows of history information
366
+ d.history.each do |h|
367
+ str << <<~HISTORY_ROW
368
+ |#{h.date.strftime('%Y-%m-%d')}
369
+ |#{h.author}
370
+ |#{h.message}
371
+
372
+ HISTORY_ROW
373
+ end
374
+ str << "|===\n\n"
375
+ end
376
+ end
377
+
378
+ # Builds an index page with a summary of what branches have
379
+ # been generated
380
+ class GitSummaryIndexBuilder
381
+ def initialize(repo)
382
+ @branches = []
383
+ @tags = []
384
+ @repo_url = repo.remote.url
385
+ end
386
+
387
+ def add_branch(b)
388
+ @branches << b
389
+ end
390
+
391
+ def add_tag(t)
392
+ @tags << t
393
+ end
394
+
395
+ def index_source
396
+ <<~ADOC_SRC
397
+ #{generate_header}
398
+ #{generate_branch_info}
399
+ #{generate_tag_info}
400
+ #{generate_footer}
401
+ ADOC_SRC
402
+ end
403
+
404
+ private
405
+
406
+ def generate_header
407
+ t = Time.now
408
+ <<~DOC_HEADER
409
+ = Document repository
410
+ From #{@repo_url}
411
+
412
+ Generated by Giblish at::
413
+
414
+ #{t.strftime('%Y-%m-%d %H:%M')}
415
+
416
+ DOC_HEADER
417
+ end
418
+
419
+ def generate_footer
420
+ ""
421
+ end
422
+
423
+ def generate_branch_info
424
+ return "" if @branches.empty?
425
+
426
+ # get the branch-unique dst-dir
427
+ str = <<~BRANCH_INFO
428
+ == Branches
429
+
430
+ BRANCH_INFO
431
+
432
+ @branches.each do |b|
433
+ dirname = b.name.tr "/", "_"
434
+ str << " * link:#{dirname}/index.html[#{b.name}]\n"
435
+ end
436
+ str
437
+ end
438
+
439
+ def generate_tag_info
440
+ return "" if @tags.empty?
441
+
442
+ # get the branch-unique dst-dir
443
+ str = <<~TAG_INFO
444
+ == Tags
445
+
446
+ TAG_INFO
447
+
448
+ @tags.each do |t|
449
+ dirname = t.name.tr "/", "_"
450
+ str << " * link:#{dirname}/index.html[#{t.name}]\n"
451
+ end
452
+ str
453
+ end
454
+ end