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