giblish 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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