module HelpParser
Constants
- HELP_DESCRIPTION_PATTERN
- SKIPPED_COMMENT_PATTERN
- USAGE_MESSAGE_PATTERN
Public Class Methods
Source
# File lib/help_parser.rb, line 220 def self.command_from_pattern(pattern) body = pattern.to_s.sub(/\A\//, "").sub(/\/[imx]*\z/i, "") if (match = body.match(/\A([a-z][a-z0-9]*(?:\s+[a-z][a-z0-9]*)*)\s+\(([^)]+)\)/i)) command = match[1] alternatives = match[2] if alternatives.include?("|") return "#{command} #{alternatives.split("|").join("|")}" end end if (match = body.match(/\A([a-z][a-z0-9]*(?:\s+[a-z][a-z0-9]*)*)/i)) return match[1] end if (match = body.match(/\A([a-z]+)\$/i)) return match[1] end body.split(/\s|\(|\[|\$/, 2).first.to_s end
Source
# File lib/help_parser.rb, line 182 def self.dedupe_entries(entries) entries .group_by { |entry| entry[:command].downcase } .values .map { |group| group.max_by { |entry| description_quality(entry[:description]) } } end
Source
# File lib/help_parser.rb, line 73 def self.description_from_usage_text(text) stripped = text.to_s.strip.gsub(/\s+/, " ") return "" if stripped.empty? if (match = stripped.match(/\AUsage:\s*!\S+.*?\s*[—–]\s*(.+)\z/i)) return match[1].split(/\s*\|\s*!/, 2).first.to_s.strip end if stripped.match?(/\AUsage:/i) remainder = stripped.sub(/\AUsage:\s*/i, "").strip return remainder unless remainder.empty? end stripped end
Source
# File lib/help_parser.rb, line 174 def self.description_quality(description) text = description.to_s.strip score = text.length score -= 100 if text.match?(/\A[A-Z][a-zA-Z]*\z/) score -= 50 if text.start_with?("Usage:") score end
Source
# File lib/help_parser.rb, line 140 def self.extract_class_name(content) content[/class\s+(\w+)/, 1].to_s end
Source
# File lib/help_parser.rb, line 50 def self.extract_comment_block(lines, start_index) collected = [] index = start_index while index >= 0 line = lines[index] if line =~ /\A\s*#/ collected.unshift(line) unless skipped_comment?(line) index -= 1 elsif line.strip.empty? index -= 1 else break end end join_comment_lines(collected) end
Source
# File lib/help_parser.rb, line 189 def self.extract_file_header_description(content) lines = content.lines class_index = lines.index { |line| line =~ /\Aclass\s+\w+/ } return "" unless class_index collected = [] lines[0...class_index].each do |line| collected << line if line =~ /\A\s*#/ && !skipped_comment?(line) end join_comment_lines(collected) end
Source
# File lib/help_parser.rb, line 109 def self.extract_help_description_method(content) match = content.match(HELP_DESCRIPTION_PATTERN) return "" unless match (match[1] || match[2]).to_s.strip end
Source
# File lib/help_parser.rb, line 96 def self.extract_help_line_description(content) content.lines.each do |line| stripped = line.chomp next unless stripped =~ /\A\s*#/ if (match = stripped.match(/\A\s*#\s*Help:\s*(.+)\z/i)) return match[1].strip end end "" end
Source
# File lib/help_parser.rb, line 126 def self.extract_in_class_description(content, match_line_index) lines = content.lines class_index = lines[0...match_line_index].rindex { |line| line =~ /\Aclass\s+\w+/ } return "" unless class_index collected = [] ((class_index + 1)...match_line_index).each do |index| line = lines[index] collected << line if line =~ /\A\s*#/ && !skipped_comment?(line) end join_comment_lines(collected) end
Source
# File lib/help_parser.rb, line 69 def self.extract_match_description(content, match_line_index) extract_comment_block(content.lines, match_line_index - 1) end
Source
# File lib/help_parser.rb, line 89 def self.extract_usage_message_description(content) match = content.match(USAGE_MESSAGE_PATTERN) return "" unless match description_from_usage_text(match[1] || match[2]) end
Source
# File lib/help_parser.rb, line 116 def self.file_description_for(content) description = extract_help_line_description(content) return description unless description.empty? description = extract_help_description_method(content) return description unless description.empty? extract_file_header_description(content) end
Source
# File lib/help_parser.rb, line 144 def self.humanize_class_name(name) name.to_s .gsub(/([A-Z]+)([A-Z][a-z])/, '\1 \2') .gsub(/([a-z\d])([A-Z])/, '\1 \2') .strip end
Source
# File lib/help_parser.rb, line 43 def self.join_comment_lines(lines) lines .map { |line| line.to_s.sub(/\A\s*#\s?/, "").strip } .reject(&:empty?) .join(" ") end
Source
# File lib/help_parser.rb, line 283 def self.list_commands(plugins_dir:) Dir.glob(File.join(plugins_dir, "*.rb")).flat_map do |path| parse_plugin_file(path) end end
Source
# File lib/help_parser.rb, line 242 def self.parse_plugin_content(basename, content) class_name = extract_class_name(content) file_description = file_description_for(content) usage_description = extract_usage_message_description(content) entries = [] content.lines.each_with_index do |line, index| next unless line =~ /\A\s*match(?:\s+|\s*\()/ pattern = resolve_match_pattern(line, content) next unless pattern command = command_from_pattern(pattern) next if command.empty? description = resolve_description( content, index, file_description: file_description, usage_description: usage_description, class_name: class_name ) description = file_description if weak_description?(description, class_name) && !file_description.empty? if weak_description?(description, class_name) && !usage_description.empty? description = usage_description end entries << { command: command, description: description, plugin: basename } end dedupe_entries(entries) end
Source
# File lib/help_parser.rb, line 275 def self.parse_plugin_file(path) basename = File.basename(path) content = File.read(path) parse_plugin_content(basename, content) rescue StandardError [] end
Source
# File lib/help_parser.rb, line 151 def self.resolve_description(content, match_line_index, file_description: "", usage_description: "", class_name: nil) class_name ||= extract_class_name(content) description = extract_match_description(content, match_line_index) return description unless description.empty? return file_description unless file_description.empty? return usage_description unless usage_description.empty? description = extract_in_class_description(content, match_line_index) return description unless description.empty? humanize_class_name(class_name) end
Source
# File lib/help_parser.rb, line 202 def self.resolve_match_pattern(match_line, content) if (match = match_line.match(%r{\A\s*match\s*\(\s*(/.+?/[imx]*)(?:\s*,.*)?\)\s*\z})) return match[1] end if (match = match_line.match(/\A\s*match\s+(\w+)(?:\s*,.*)?\s*\z/)) constant = Regexp.escape(match[1]) if (pattern_match = content.match(/def self\.#{constant}\s*\n\s*(\/.+\/[imx]*)\s*\n/m)) return pattern_match[1] end if (pattern_match = content.match(/#{constant}\s*=\s*(\/.+\/[imx]*)/m)) return pattern_match[1] end end nil end
Source
# File lib/help_parser.rb, line 36 def self.skipped_comment?(line) stripped = line.to_s.sub(/\A\s*#\s?/, "").strip return true if stripped.empty? SKIPPED_COMMENT_PATTERN.match?(stripped) end
Source
# File lib/help_parser.rb, line 167 def self.weak_description?(description, class_name) text = description.to_s.strip return true if text.empty? text == humanize_class_name(class_name) || text.start_with?("Usage:") end