lib/giblish/treeconverter.rb in giblish-2.0.0 vs lib/giblish/treeconverter.rb in giblish-2.0.1

- old
+ new

@@ -154,24 +154,24 @@ "source-highlighter" => "rouge", "source-linenums-option" => true } # see https://docs.asciidoctor.org/asciidoctor/latest/api/options/ - DEFAULT_ADOC_OPTS = { - backend: "html5", + DEFAULT_ADOC_API_OPTS = { + # backend: "html5", # base_dir: - catalog_assets: false, + # catalog_assets: false, # converter: - doctype: "article", + # doctype: "article", # eruby: # ignore extention stuff - header_only: false, + # header_only: false, # logger: - mkdirs: false, - parse: true, + # mkdirs: false, + # parse: true, safe: :unsafe, - sourcemap: false, + # sourcemap: false, # template stuff TBD, # to_file: # to_dir: standalone: true } @@ -190,18 +190,73 @@ @conv_cb = opts.fetch(:conversion_cb, { success: ->(src, dst, dst_rel_path, doc, logstr) { TreeConverter.on_success(src, dst, dst_rel_path, doc, logstr) }, failure: ->(src, dst, dst_rel_path, ex, logstr) { TreeConverter.on_failure(src, dst, dst_rel_path, ex, logstr) } }) - # merge user's options with the default, giving preference - # to the user - @adoc_api_opts = DEFAULT_ADOC_OPTS.dup - .merge!(opts.fetch(:adoc_api_opts, {})) - @adoc_api_opts[:attributes] = DEFAULT_ADOC_DOC_ATTRIBS.dup - .merge!(opts.fetch(:adoc_doc_attribs, {})) + # cache external configuration + @config_opts = opts.dup end + # Resolve the document attributes according to the precedence. + # + # According to https://docs.asciidoctor.org/asciidoc/latest/attributes/assignment-precedence/ + # The attribute precedence is: + # 1. An attribute passed to the API or CLI whose value does not end in @ + # 2. An attribute defined in the document + # 3. An attribute passed to the API or CLI whose value or name ends in @ + # 4. The default value of the attribute, if applicable + # + # giblish adds the following rules: + # 1.5 An attribute defined in an attribute provider for a specific source node + # 3.5 The default value set by giblish, if applicable + def resolve_doc_attributes(doc_src, node_attr) + # rule 3.5 + doc_attr = DEFAULT_ADOC_DOC_ATTRIBS.dup + + # sort attribs into soft and hard (rule 1 and 3) + soft_attr = {} + hard_attr = {} + @config_opts.fetch(:adoc_doc_attribs, {}).each do |k, v| + ks = k.to_s.strip + vs = v.to_s.strip + + if ks.end_with?("@") + soft_attr[ks[0..]] = vs + next + end + if vs.end_with?("@") + soft_attr[ks] = vs[0..] + next + end + hard_attr[ks] = vs + end + + # rule 3. + doc_attr.merge!(soft_attr) + + # rule 2 + Giblish.process_header_lines(doc_src.lines) do |line| + a = /^:(.+):(.*)$/.match(line) + next unless a + @logger.debug { "got header attr from doc: #{a[1]} : #{a[2]}" } + doc_attr[a[1].strip] = a[2].strip + end + + @logger.debug { "idprefix before: #{doc_attr["idprefix"]}" } + + # rule 1.5 + doc_attr.merge!(node_attr) + + # rule 1. + doc_attr.merge!(hard_attr) + + @logger.debug { "idprefix after: #{doc_attr["idprefix"]}" } + + # @logger&.debug { "Header attribs: #{doc_attr}" } + doc_attr + end + # require the following methods to be available from the src node: # adoc_source # # the following methods will be called if supported: # document_attributes @@ -214,50 +269,60 @@ # under which all converted files are written. def convert(src_node, dst_node, dst_top) @logger&.info { "Converting #{src_node.pathname} and store result under #{dst_node.parent.pathname}" } # merge the common api opts with node specific - api_opts = @adoc_api_opts.dup + api_opts = DEFAULT_ADOC_API_OPTS.dup + api_opts.merge!(@config_opts.fetch(:adoc_api_opts, {})) api_opts.merge!(src_node.api_options(src_node, dst_node, dst_top)) if src_node.respond_to?(:api_options) - api_opts[:attributes].merge!(src_node.document_attributes(src_node, dst_node, dst_top)) if src_node.respond_to?(:document_attributes) # use a new logger instance for each conversion adoc_logger = Giblish::AsciidoctorLogger.new(@logger, @adoc_log_level) begin - # load the source to enable access to doc properties + doc_src = src_node.adoc_source(src_node, dst_node, dst_top) + + node_attr = src_node.respond_to?(:document_attributes) ? + src_node.document_attributes(src_node, dst_node, dst_top) : {} + doc_attr = resolve_doc_attributes(doc_src, node_attr) + # piggy-back our own info on the doc attributes hash so that + # asciidoctor extensions can use this info later on + doc_attr["giblish-info"] = { + src_node: src_node, + dst_node: dst_node, + dst_top: dst_top + } + + # load the source to enable access to doc attributes and properties # - # NOTE: the 'parse: false' is needed to prevent preprocessor extensions to be run as part + # NOTE: 'parse' is set to false to prevent preprocessor extensions to be run as part # of loading the document. We want them to run during the 'convert' call later when # doc attribs have been amended. - doc = Asciidoctor.load(src_node.adoc_source(src_node, dst_node, dst_top), api_opts.merge( + # + # NOTE2: by trial-and-error, it seems that some document attributes must be set when + # calling 'load' and not added after the call and before the 'convert' call to have + # the expected effect (e.g. idprefix). + doc = Asciidoctor.load(doc_src, api_opts.merge( { + attributes: doc_attr, parse: false, logger: adoc_logger } )) - # piggy-back our own info on the doc attributes hash so that - # asciidoctor extensions can use this info later on - doc.attributes["giblish-info"] = { - src_node: src_node, - dst_node: dst_node, - dst_top: dst_top - } - # update the destination node with the correct file suffix. This is dependent # on the type of conversion performed dst_node.name = dst_node.name.sub_ext(doc.attributes["outfilesuffix"]) d = dst_node.pathname # make sure the dst dir exists d.dirname.mkpath - # write the converted doc to the file - output = doc.convert(api_opts.merge({logger: adoc_logger})) + # do the conversion and write the converted doc to file + output = doc.convert(api_opts) doc.write(output, d.to_s) - # give user the opportunity to eg store the result of the conversion + # give the user the opportunity to eg store the result of the conversion # as data in the destination node @conv_cb[:success]&.call(src_node, dst_node, dst_top, doc, adoc_logger.in_mem_storage.string) true rescue => ex @logger&.error { "Conversion failed for #{src_node.pathname}" }