lib/howzit/buildnote.rb in howzit-2.0.9 vs lib/howzit/buildnote.rb in howzit-2.0.10

- old
+ new

@@ -5,67 +5,127 @@ class BuildNote attr_accessor :topics attr_reader :metadata, :title + ## + ## Initialize a build note + ## + ## @param file [String] The path to the build note file + ## @param args [Array] additional args + ## def initialize(file: nil, args: []) @topics = [] - create_note if note_file.nil? - @metadata = Util.read_file(note_file).split(/^#/)[0].strip.get_metadata + if note_file.nil? + res = Prompt.yn('No build notes file found, create one?', default: true) + create_note if res + Process.exit 0 + end + content = Util.read_file(note_file) + if content.nil? || content.empty? + Howzit.console.error("{br}No content found in build note (#{note_file}){x}".c) + Process.exit 1 + else + @metadata = content.split(/^#/)[0].strip.get_metadata + end + read_help(file) end def inspect puts "#<Howzit::BuildNote @topics=[#{@topics.count}]>" end + ## + ## Public method to begin processing the build note based on command line options + ## def run process end + ## + ## Public method to open build note in editor + ## def edit edit_note end + ## + ## Find a topic based on a fuzzy match + ## + ## @param term [String] The search term + ## def find_topic(term) @topics.filter do |topic| rx = term.to_rx topic.title.downcase =~ rx end end + ## + ## Call grep on all topics, filtering out those that don't match + ## + ## @param term [String] The search pattern + ## def grep(term) @topics.filter { |topic| topic.grep(term) } end # Output a list of topic titles + # + # @return [String] formatted list of topics in build note + # def list output = [] output.push("{bg}Topics:{x}\n".c) @topics.each do |topic| output.push("- {bw}#{topic.title}{x}".c) end output.join("\n") end + + ## + ## Return an array of topic titles + ## + ## @return [Array] array of topic titles + ## def list_topics @topics.map { |topic| topic.title } end + ## + ## Return a list of topic titles suitable for shell completion + ## + ## @return [String] newline-separated list of topic titles + ## def list_completions list_topics.join("\n") end + ## + ## Return a list of topics containing @directives, + ## suitable for shell completion + ## + ## @return [String] newline-separated list of topic + ## titles + ## def list_runnable_completions output = [] @topics.each do |topic| output.push(topic.title) if topic.tasks.count.positive? end output.join("\n") end + ## + ## Return a formatted list of topics containing + ## @directives suitable for console output + ## + ## @return [String] formatted list + ## def list_runnable output = [] output.push(%({bg}"Runnable" Topics:{x}\n).c) @topics.each do |topic| s_out = [] @@ -80,10 +140,15 @@ end end output.join("\n") end + ## + ## Read the help file contents + ## + ## @param file [String] The filepath + ## def read_file(file) read_help_file(file) end # Create a buildnotes skeleton @@ -208,18 +273,30 @@ Dir.chdir(home) buildnotes.reverse end + ## + ## Test if the filename matches the conditions to be a build note + ## + ## @param filename [String] The filename to test + ## + ## @return [Boolean] true if filename passes test + ## def build_note?(filename) return false if filename.downcase !~ /^(howzit[^.]*|build[^.]+)/ return false if Howzit.config.should_ignore(filename) true end + ## + ## Glob current directory for valid build note filenames + ## + ## @return [String] file path + ## def glob_note filename = nil # Check for a build note file in the current folder. Filename must start # with "build" and have an extension of txt, md, or markdown. @@ -230,10 +307,17 @@ end end filename end + ## + ## Search for a valid build note, checking current + ## directory, git top level directory, and parent + ## directories + ## + ## @return [String] filepath + ## def find_note_file filename = glob_note if filename.nil? && 'git'.available? proj_dir = `git rev-parse --show-toplevel 2>/dev/null`.strip @@ -251,37 +335,54 @@ return nil if filename.nil? File.expand_path(filename) end + ## + ## Search upstream directories for build notes + ## + ## @return [Array] array of build note paths + ## def read_upstream buildnotes = glob_upstream topics_dict = [] buildnotes.each do |path| topics_dict.concat(read_help_file(path)) end topics_dict end + ## + ## Test to ensure that any `required` metadata in a + ## template is fulfilled by the build note + ## + ## @param template [String] The template to read + ## from + ## def ensure_requirements(template) t_leader = Util.read_file(template).split(/^#/)[0].strip if t_leader.length > 0 t_meta = t_leader.get_metadata if t_meta.key?('required') required = t_meta['required'].strip.split(/\s*,\s*/) required.each do |req| unless @metadata.keys.include?(req.downcase) - Howzit.console.error %({xr}ERROR: Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c - Howzit.console.error %({xr}Please define {by}#{req.downcase}{xr} in build notes{x}).c + Howzit.console.error %({bRw}ERROR:{xbr} Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c + Howzit.console.error %({br}Please define {by}#{req.downcase}{xr} in build notes{x}).c Process.exit 1 end end end end end + ## + ## Read a list of topics from an included template + ## + ## @param content [String] The template contents + ## def get_template_topics(content) leader = content.split(/^#/)[0].strip template_topics = [] @@ -325,14 +426,20 @@ end end template_topics end - def include_file(m) - file = File.expand_path(m[1]) + ## + ## Import the contents of a filename as new topics + ## + ## @param mtch [MatchData] the filename match from + ## the include directive + ## + def include_file(mtch) + file = File.expand_path(mtch[1]) - return m[0] unless File.exist?(file) + return mtch[0] unless File.exist?(file) content = Util.read_file(file) home = ENV['HOME'] short_path = File.dirname(file.sub(/^#{home}/, '~')) prefix = "#{short_path}/#{File.basename(file)}:" @@ -343,10 +450,15 @@ else "## #{parts.join('## ')}".gsub(/^(##+ *)(?=\S)/, "\\1#{prefix}") end end + ## + ## Get the title of the build note (top level header) + ## + ## @param truncate [Integer] Truncate to width + ## def note_title(truncate = 0) help = Util.read_file(note_file) title = help.match(/(?:^(\S.*?)(?=\n==)|^# ?(.*?)$)/) title = if title title[1].nil? ? title[2] : title[1] @@ -355,18 +467,29 @@ end title && truncate.positive? ? title.trunc(truncate) : title end - # Read in the build notes file and output a hash of "Title" => contents + # Read in the build notes file and output a hash of + # "Title" => contents + # + # @param path [String] The build note path + # + # @return [Array] array of Topics + # def read_help_file(path = nil) topics = [] filename = path.nil? ? note_file : path help = Util.read_file(filename) + if help.nil? || help.empty? + Howzit.console.error("{br}No content found in #{filename}{x}".c) + Process.exit 1 + end + @title = note_title help.gsub!(/@include\((.*?)\)/) do include_file(Regexp.last_match) end @@ -401,21 +524,35 @@ end topics end + ## + ## Read build note and include upstream topics + ## + ## @param path [String] The build note path + ## def read_help(path = nil) @topics = read_help_file(path) return unless path.nil? && Howzit.options[:include_upstream] upstream_topics = read_upstream upstream_topics.each do |topic| @topics.push(topic) unless find_topic(title.sub(/^.+:/, '')).count.positive? end + + if note_file && @topics.empty? + Howzit.console.error("{br}Note file found but no topics detected in #{note_file}{x}".c) + Process.exit 1 + end + end + ## + ## Open build note in editor + ## def edit_note editor = Howzit.options.fetch(:editor, ENV['EDITOR']) raise 'No editor defined' if editor.nil? @@ -429,11 +566,20 @@ else `#{editor} "#{note_file}"` end end - def process_topic(topic, run, single = false) + ## + ## Run or print a topic + ## + ## @param topic [Topic] The topic + ## @param run [Boolean] execute directives if + ## true + ## @param single [Boolean] is being output as a + ## single topic + ## + def process_topic(topic, run, single: false) new_topic = topic.dup # Handle variable replacement new_topic.content = new_topic.content.render_arguments @@ -443,10 +589,13 @@ new_topic.print_out({ single: single }) end output.nil? ? '' : output.join("\n") end + ## + ## Search and process the build note + ## def process output = [] unless note_file Process.exit 0 if Howzit.options[:list_runnable_titles] || Howzit.options[:list_topic_titles] @@ -535,13 +684,13 @@ end end if !topic_matches.empty? # If we found a match - topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], true)) } + topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], single: true)) } else # If there's no argument or no match found, output all - topics.each { |k| output.push(process_topic(k, false, false)) } + topics.each { |k| output.push(process_topic(k, false, single: false)) } end Howzit.options[:paginate] = false if Howzit.options[:run] Util.show(output.join("\n").strip, Howzit.options) end end