lib/giblish/buildindex.rb in giblish-0.2.12 vs lib/giblish/buildindex.rb in giblish-0.3.0

- old
+ new

@@ -1,504 +1,451 @@ -#!/usr/bin/env ruby -# - require "pathname" require "git" -require_relative "cmdline" + require_relative "pathtree" require_relative "gititf" +require_relative "docinfo" -# Container class for bundling together the data we cache for -# each asciidoc file we come across -class DocInfo - # Cache git info - class DocHistory - attr_accessor :date - attr_accessor :author - attr_accessor :message - end +module Giblish - attr_accessor :converted - attr_accessor :title - attr_accessor :doc_id - attr_accessor :purpose_str - attr_accessor :status - attr_accessor :history - attr_accessor :error_msg - attr_accessor :stderr - # these two members can have encoding issues when - # running in a mixed Windows/Linux setting. - # that is why the explicit utf-8 read methods are - # provided. - attr_accessor :relPath - attr_accessor :srcFile + # Base class with common functionality for all index builders + class BasicIndexBuilder + # set up the basic index building info + def initialize(processed_docs, path_manager, handle_docid = false) + @paths = path_manager + @nof_missing_titles = 0 + @processed_docs = processed_docs + @src_str = "" + @manage_docid = handle_docid + end - def relPath_utf8 - return nil if @relPath.nil? - @relPath.to_s.encode("utf-8") - end + def source(dep_graph_exists = false) + <<~DOC_STR + #{generate_header} + #{generate_tree(dep_graph_exists)} + #{generate_details} + #{generate_footer} + DOC_STR + end - def srcFile_utf8 - return nil if @srcFile.nil? - @srcFile.to_s.encode("utf-8") - end + protected + # return the adoc string for displaying the source file + def display_source_file(doc_info) + <<~SRC_FILE_TXT + Source file:: + #{doc_info.srcFile_utf8} - def initialize - @history = [] - end + SRC_FILE_TXT + end - def to_s - "DocInfo: title: #{@title} srcFile: #{srcFile_utf8}" - end -end + def generate_header + t = Time.now + <<~DOC_HEADER + = Document index + from #{@paths.src_root_abs} -# Base class with common functionality for all index builders -class BasicIndexBuilder - # set up the basic index building info - def initialize(path_manager, handle_docid = false) - @paths = path_manager - @nof_missing_titles = 0 - @added_docs = [] - @src_str = "" - @manage_docid = handle_docid - end + Generated by Giblish at:: - # creates a DocInfo instance, fills it with basic info and - # returns the filled in instance so that derived implementations can - # add more data - def add_doc(adoc, adoc_stderr) - Giblog.logger.debug { "Adding adoc: #{adoc} Asciidoctor stderr: #{adoc_stderr}" } - Giblog.logger.debug {"Doc attributes: #{adoc.attributes}"} + #{t.strftime('%Y-%m-%d %H:%M')} - info = DocInfo.new - info.converted = true - info.stderr = adoc_stderr + DOC_HEADER + end - # Get the purpose info if it exists - info.purpose_str = get_purpose_info adoc + def get_docid_statistics + largest = "" + clash = [] + @processed_docs.each do |d| + # get the lexically largest doc id + largest = d.doc_id if !d.doc_id.nil? && d.doc_id > largest - # Get the relative path beneath the root dir to the doc - d_attr = adoc.attributes - info.relPath = Pathname.new( - "#{d_attr['outdir']}/#{d_attr['docname']}#{d_attr['docfilesuffix']}" - ).relative_path_from( - @paths.dst_root_abs - ) + # collect all ids in an array to find duplicates later on + clash << d.doc_id unless d.doc_id.nil? + end + # find the duplicate doc ids (if any) + duplicates = clash.select { |id| clash.count(id) > 1 }.uniq.sort - # Get the doc id if it exists - info.doc_id = adoc.attributes["docid"] + return largest,duplicates + end - # Get the source file path - info.srcFile = adoc.attributes["docfile"] + def generate_doc_id_info(dep_graph_exists) + largest,duplicates = get_docid_statistics + docid_info_str = if ! @manage_docid + "" + else + "The 'largest' document id found when resolving :docid: tags is *#{largest}*." + end - # If a docid exists, set titel to docid - title if we care about - # doc ids. - info.title = if !info.doc_id.nil? && @manage_docid - "#{info.doc_id} - #{adoc.doctitle}" - else - adoc.doctitle - end + docid_warn_str = if duplicates.length.zero? + "" + else + "WARNING: The following document ids are used for more than one document. " + + "_#{duplicates.map {|id| id.to_s}.join(",") }_" + end - # Cache the created DocInfo - @added_docs << info - info - end + # include link to dependency graph if it exists + dep_graph_str = if dep_graph_exists + "_(a visual graph of document dependencies can be found " \ + "<<./graph.adoc#,here>>)_" + else + "" + end - def add_doc_fail(filepath, exception) - info = DocInfo.new + if @manage_docid + <<~DOC_ID_INFO + Document id numbers:: + The generation of this repository uses document id numbers. #{docid_info_str} #{dep_graph_str} + + #{docid_warn_str} - # the only info we have is the source file name - info.converted = false - info.srcFile = filepath - info.error_msg = exception.message + DOC_ID_INFO + else + "" + end + end - # Cache the DocInfo - @added_docs << info - info - end + def generate_tree(dep_graph_exists) + # output tree intro + tree_string = <<~DOC_HEADER + == Document Overview - def index_source - <<~DOC_STR - #{generate_header} - #{generate_tree} - #{generate_details} - #{generate_footer} - DOC_STR - end + _Click on the title to open the document or on `details` to see more + info about the document. A `(warn)` label indicates that there were + warnings while converting the document from its asciidoc format._ - protected + #{generate_doc_id_info dep_graph_exists} - def generate_header - t = Time.now - <<~DOC_HEADER - = Document index - from #{@paths.src_root_abs} + [subs=\"normal\"] + ---- + DOC_HEADER - Generated by Giblish at:: + # build up tree of paths + root = PathTree.new + @processed_docs.each do |d| + root.add_path(d.rel_path.to_s, d) + end - #{t.strftime('%Y-%m-%d %H:%M')} + # generate each tree entry string + root.traverse_top_down do |level, node| + tree_string << tree_entry_string(level, node) + end - DOC_HEADER - end + # generate the tree footer + tree_string << "\n----\n" + end - def generate_footer - "" - end - - private - - def get_purpose_info(adoc) - # Get the 'Purpose' section if it exists - purpose_str = "" - adoc.blocks.each do |section| - next unless section.is_a?(Asciidoctor::Section) && - (section.level == 1) && - (section.name =~ /^Purpose$/) - purpose_str = "Purpose::\n\n" - - # filter out 'odd' text, such as lists etc... - section.blocks.each do |bb| - next unless bb.is_a?(Asciidoctor::Block) - purpose_str << "#{bb.source}\n+\n" - end + def generate_footer + "" end - purpose_str - end - def generate_conversion_info(d) - return "" if d.stderr.empty? - # extract conversion warnings from asciddoctor std err - conv_warnings = d.stderr.gsub("asciidoctor:", "\n * asciidoctor:") - Giblog.logger.warn { "Conversion warnings: #{conv_warnings}" } + private - # assemble info to index page - <<~CONV_INFO - Conversion info:: + def generate_conversion_info(d) + return "" if d.stderr.empty? + # extract conversion warnings from asciddoctor std err + conv_warnings = d.stderr.gsub("asciidoctor:", "\n * asciidoctor:") + Giblog.logger.warn {"Conversion warnings: #{conv_warnings}"} - #{conv_warnings} - CONV_INFO - end + # assemble info to index page + <<~CONV_INFO + Conversion info:: - # Private: Return adoc elements for displaying a clickable title - # and a 'details' ref that points to a section that uses the title as an id. - # - # Returns [ clickableTitleStr, clickableDetailsStr ] - def format_title_and_ref(doc_info) - unless doc_info.title - @nof_missing_titles += 1 - doc_info.title = "NO TITLE FOUND (#{@nof_missing_titles}) !" + #{conv_warnings} + CONV_INFO end - return "<<#{doc_info.relPath_utf8}#,#{doc_info.title}>>".encode("utf-8"), - "<<#{Giblish.to_valid_id(doc_info.title)},details>>\n".encode("utf-8") - end - # Generate an adoc string that will display as - # DocTitle (warn) details - # Where the DocTitle and details are links to the doc itself and a section - # identified with the doc's title respectively. - def tree_entry_converted(prefix_str, doc_info) - # Get the elements of the entry - doc_title, doc_details = format_title_and_ref doc_info - warning_label = doc_info.stderr.empty? ? "" : "(warn)" + # Private: Return adoc elements for displaying a clickable title + # and a 'details' ref that points to a section that uses the title as an id. + # + # Returns [ title, clickableTitleStr, clickableDetailsStr ] + def format_title_and_ref(doc_info) + unless doc_info.title + @nof_missing_titles += 1 + doc_info.title = "NO TITLE FOUND (#{@nof_missing_titles}) !" + end - # Calculate padding to get (warn) and details aligned between entries - padding = 80 - [doc_info.title, prefix_str, warning_label].each { |p| padding -= p.length } - padding = 0 unless padding.positive? - "#{prefix_str} #{doc_title}#{' ' * padding}#{warning_label} #{doc_details}" - end + # Manipulate the doc title if we have a doc id + title = if !doc_info.doc_id.nil? && @manage_docid + "#{doc_info.doc_id} - #{doc_info.title}" + else + doc_info.title + end - def tree_entry_string(level, node) - # indent 2 * level - prefix_str = " " * (level + 1) + [title, "<<#{doc_info.rel_path}#,#{title}>>".encode("utf-8"), + "<<#{Giblish.to_valid_id(doc_info.title)},details>>\n".encode("utf-8")] + end - # return only name for directories - return "#{prefix_str} #{node.name}\n" unless node.leaf? + # Generate an adoc string that will display as + # DocTitle (warn) details + # Where the DocTitle and details are links to the doc itself and a section + # identified with the doc's title respectively. + def tree_entry_converted(prefix_str, doc_info) + # Get the elements of the entry + doc_title, doc_link, doc_details = format_title_and_ref doc_info + warning_label = doc_info.stderr.empty? ? "" : "(warn)" - # return links to content and details for files - d = node.data - if d.converted - tree_entry_converted prefix_str, d - else - # no converted file exists, show what we know - "#{prefix_str} FAIL: #{d.srcFile_utf8} <<#{d.srcFile_utf8},details>>\n" + # Calculate padding to get (warn) and details aligned between entries + padding = 70 + [doc_title, prefix_str, warning_label].each {|p| padding -= p.length} + padding = 0 unless padding.positive? + "#{prefix_str} #{doc_link}#{' ' * padding}#{warning_label} #{doc_details}" end - end - def generate_tree - # build up tree of paths - root = PathTree.new - @added_docs.each do |d| - root.add_path(d.relPath.to_s, d) - end + def tree_entry_string(level, node) + # indent 2 * level + prefix_str = " " * (level + 1) - # output tree intro - tree_string = <<~DOC_HEADER - == Document Overview + # return only name for directories + return "#{prefix_str} #{node.name}\n" unless node.leaf? - _Click on the title to open the document or on `details` to see more - info about the document. A `(warn)` label indicates that there were - warnings while converting the document._ + # return links to content and details for files + # node.data is a DocInfo instance + d = node.data + if d.converted + tree_entry_converted prefix_str, d + else + # no converted file exists, show what we know + "#{prefix_str} FAIL: #{d.srcFile_utf8} <<#{d.srcFile_utf8},details>>\n" + end + end - [subs=\"normal\"] - ---- - DOC_HEADER - - # generate each tree entry string - root.traverse_top_down do |level, node| - tree_string << tree_entry_string(level, node) + # Derived classes can override this with useful info + def generate_history_info(_d) + "" end - # generate the tree footer - tree_string << "\n----\n" - end + def generate_detail_fail(d) + <<~FAIL_INFO + === #{d.srcFile_utf8} - # Derived classes can override this with useful info - def generate_history_info(_d) - "" - end + #{display_source_file(d)} - def generate_detail_fail(d) - <<~FAIL_INFO - === #{d.srcFile_utf8} + Error detail:: + #{d.stderr} - Source file:: + '''' - #{d.srcFile_utf8} + FAIL_INFO + end - Error detail:: - #{d.stderr} + def generate_detail(d) + # Generate detail info + purpose_str = if d.purpose_str.nil? + "" + else + "Purpose::\n#{d.purpose_str}" + end - '''' + doc_id_str = if !d.doc_id.nil? && @manage_docid + "Doc id::\n_#{d.doc_id}_" + else + "" + end - FAIL_INFO - end + <<~DETAIL_SRC + [[#{Giblish.to_valid_id(d.title.encode("utf-8"))}]] + === #{d.title.encode("utf-8")} - def generate_detail(d) - # Generate detail info - <<~DETAIL_SRC - [[#{Giblish.to_valid_id(d.title.encode("utf-8"))}]] - === #{d.title.encode("utf-8")} + #{doc_id_str} - #{d.purpose_str} + #{purpose_str} - #{generate_conversion_info d} + #{generate_conversion_info d} - Source file:: - #{d.srcFile_utf8} + #{display_source_file(d)} - #{generate_history_info d} + #{generate_history_info d} - '''' + '''' - DETAIL_SRC - end - - def generate_details - root = PathTree.new - @added_docs.each do |d| - root.add_path(d.relPath.to_s, d) + DETAIL_SRC end - details_str = "== Document details\n\n" + def generate_details + root = PathTree.new + @processed_docs.each do |d| + root.add_path(d.rel_path.to_s, d) + end - root.traverse_top_down do |_level, node| - details_str << if node.leaf? - d = node.data - if d.converted - generate_detail(d) + details_str = "== Document details\n\n" + + root.traverse_top_down do |_level, node| + details_str << if node.leaf? + d = node.data + if d.converted + generate_detail(d) + else + generate_detail_fail(d) + end else - generate_detail_fail(d) + "" end - else - "" - end + end + details_str end - details_str end -end -# A simple index generator that shows a table with the generated documents -class SimpleIndexBuilder < BasicIndexBuilder - def initialize(path_manager, manage_docid = false) - super path_manager, manage_docid + # A simple index generator that shows a table with the generated documents + class SimpleIndexBuilder < BasicIndexBuilder + def initialize(processed_docs, path_manager, manage_docid = false) + super processed_docs, path_manager, manage_docid + end end - def add_doc(adoc, adoc_stderr) - super(adoc, adoc_stderr) - end -end + # Builds an index of the generated documents and includes some git metadata + # from the repository + class GitRepoIndexBuilder < BasicIndexBuilder + def initialize(processed_docs, path_manager, manage_docid, git_repo_root) + super processed_docs, path_manager, manage_docid -# Builds an index of the generated documents and includes some git metadata -# repository -class GitRepoIndexBuilder < BasicIndexBuilder - def initialize(path_manager, manage_docid, git_repo_root) - super path_manager, manage_docid + # no repo root given... + return unless git_repo_root - # initialize state variables - @git_repo_root = git_repo_root - - # no repo root given... - return unless @git_repo_root - - begin - # Make sure that we can "talk" to git if user feeds us - # a git repo root - @git_repo = Git.open(@git_repo_root) - rescue Exception => e - Giblog.logger.error { "No git repo! exception: #{e.message}" } + begin + # Make sure that we can "talk" to git if user feeds us + # a git repo root + @git_repo = Git.open(git_repo_root) + @git_repo_root = git_repo_root + rescue Exception => e + Giblog.logger.error {"No git repo! exception: #{e.message}"} + end end - end - def add_doc(adoc, adoc_stderr) - info = super(adoc, adoc_stderr) + protected + # override basic version and use the relative path to the + # git repo root instead + def display_source_file(doc_info) + # Use the path relative to the git repo root as display + src_file = Pathname. + new(doc_info.src_file). + relative_path_from(@git_repo_root).to_s + <<~SRC_FILE_TXT + Source file:: + #{src_file} - # Redefine the srcFile to mean the relative path to the git repo root - info.srcFile = Pathname.new(info.srcFile).relative_path_from(@git_repo_root).to_s - - # Get the commit history of the doc - # (use a homegrown git log to get 'follow' flag) - gi = Giblish::GitItf.new(@git_repo_root) - gi.file_log(info.srcFile_utf8).each do |i| - h = DocInfo::DocHistory.new - h.date = i["date"] - h.message = i["message"] - h.author = i["author"] - info.history << h + SRC_FILE_TXT end - end - protected - def generate_header - t = Time.now - <<~DOC_HEADER - = Document index - #{@git_repo.current_branch} + def generate_header + t = Time.now + <<~DOC_HEADER + = Document index + #{@git_repo.current_branch} - Generated by Giblish at:: + Generated by Giblish at:: + #{t.strftime('%Y-%m-%d %H:%M')} - #{t.strftime('%Y-%m-%d %H:%M')} + DOC_HEADER + end - DOC_HEADER - end + def generate_history_info(d) + str = <<~HISTORY_HEADER + File history:: - def generate_history_info(d) - str = <<~HISTORY_HEADER - File history:: + [cols=\"2,3,8\",options=\"header\"] + |=== + |Date |Author |Message + HISTORY_HEADER - [cols=\"2,3,8\",options=\"header\"] - |=== - |Date |Author |Message - HISTORY_HEADER + # Generate table rows of history information + d.history.each do |h| + str << <<~HISTORY_ROW + |#{h.date.strftime('%Y-%m-%d')} + |#{h.author} + |#{h.message} - # Generate table rows of history information - d.history.each do |h| - str << <<~HISTORY_ROW - |#{h.date.strftime('%Y-%m-%d')} - |#{h.author} - |#{h.message} - - HISTORY_ROW + HISTORY_ROW + end + str << "|===\n\n" end - str << "|===\n\n" end -end -# Builds an index page with a summary of what branches have -# been generated -class GitSummaryIndexBuilder - def initialize(repo) - @branches = [] - @tags = [] - @git_repo = repo - @repo_url = repo.remote.url - end + # Builds an index page with a summary of what branches have + # been generated + class GitSummaryIndexBuilder + def initialize(repo, branches, tags) + @branches = branches + @tags = tags + @git_repo = repo + @repo_url = repo.remote.url + end - def add_branch(b) - @branches << b - end + def source + <<~ADOC_SRC + #{generate_header} + #{generate_branch_info} + #{generate_tag_info} + #{generate_footer} + ADOC_SRC + end - def add_tag(t) - @tags << t - end + private - def index_source - <<~ADOC_SRC - #{generate_header} - #{generate_branch_info} - #{generate_tag_info} - #{generate_footer} - ADOC_SRC - end + def generate_header + t = Time.now + <<~DOC_HEADER + = Document repository + From #{@repo_url} - private + Generated by Giblish at:: + #{t.strftime('%Y-%m-%d %H:%M')} - def generate_header - t = Time.now - <<~DOC_HEADER - = Document repository - From #{@repo_url} + DOC_HEADER + end - Generated by Giblish at:: + def generate_footer + "" + end - #{t.strftime('%Y-%m-%d %H:%M')} + def generate_branch_info + return "" if @branches.empty? - DOC_HEADER - end + # get the branch-unique dst-dir + str = <<~BRANCH_INFO + == Branches - def generate_footer - "" - end + BRANCH_INFO - def generate_branch_info - return "" if @branches.empty? - - # get the branch-unique dst-dir - str = <<~BRANCH_INFO - == Branches - - BRANCH_INFO - - @branches.each do |b| - dirname = b.name.tr "/", "_" - str << " * link:#{dirname}/index.html[#{b.name}]\n" + @branches.each do |b| + dirname = b.name.tr "/", "_" + str << " * link:#{dirname}/index.html[#{b.name}]\n" + end + str end - str - end - def generate_tag_info - return "" if @tags.empty? + def generate_tag_info + return "" if @tags.empty? - # get the branch-unique dst-dir - str = <<~TAG_INFO - == Tags + # get the branch-unique dst-dir + str = <<~TAG_INFO + == Tags + + |=== + |Tag |Tag comment |Creator |Tagged commit - |=== - |Tag |Tag comment |Creator |Tagged commit + TAG_INFO - TAG_INFO + str << @tags.collect do |t| + dirname = t.name.tr "/", "_" + c = @git_repo.gcommit(t.sha) - str << @tags.collect do |t| - dirname = t.name.tr "/", "_" - c = @git_repo.gcommit(t.sha) + <<~A_ROW + |link:#{dirname}/index.html[#{t.name}] + |#{t.annotated? ? t.message : "-"} + |#{t.annotated? ? t.tagger.name : "-"} + |#{t.sha[0, 8]}... committed at #{c.author.date} + A_ROW + end.join("\n") - <<~A_ROW - |link:#{dirname}/index.html[#{t.name}] - |#{t.annotated? ? t.message : "-"} - |#{t.annotated? ? t.tagger.name : "-"} - |#{t.sha[0,8]}... committed at #{c.author.date} - A_ROW - end.join("\n") + str << "|===\n" - str << "|===\n" - - # @tags.each do |t| - # dirname = t.name.tr "/", "_" - # str << " * link:#{dirname}/index.html[#{t.name}]" - # if t.annotated? - # str << "created at #{t.tagger.date} by #{t.tagger.name} with message: #{t.message}" - # end - # end - str + # @tags.each do |t| + # dirname = t.name.tr "/", "_" + # str << " * link:#{dirname}/index.html[#{t.name}]" + # if t.annotated? + # str << "created at #{t.tagger.date} by #{t.tagger.name} with message: #{t.message}" + # end + # end + str + end end -end +end \ No newline at end of file