# frozen_string_literal: true module GFSM module Commands class Changelog < BaseCommand NEXT_ENTRY_MARKER = "" def self.help cli_info = GFSM::Tools::VersionBumperSettings.cli_info <<~HELP Usage: gfsm changelog [help|generate|extract] [--output-file ] [--input-file ] [--no-incremental] [--only-new-entries] [--extract-version ] #{cli_info[:usage]} Commands: help # Prints this help generate # Generate the changelog for the current version extract # Extract the changelog for the latest or specified version Options: --output-file # Path to the output changelog file. Defaults to 'CHANGELOG.md'. If not specified, the generate changelog content will be written to stdout --no-incremental # When provided, the generated changelog won't look for an existing changelog file. When outputting to stdout the changelog will never be incremental --only-new-entries # When provided, the generated changelog won't look for an existing changelog file and will contain only the new entries for the current version, without the Changelog or version headings --input-file # Path to the input changelog file. Defaults to 'CHANGELOG.md' --extract-version # Version from which to extract the changelog. Defaults to the latest one on the changelog file #{cli_info[:options]} Environment variables: OUTPUT_FILE # Equivalent to --output-file NO_INCREMENTAL # Equivalent to --no-incremental ONLY_NEW_ENTRIES # Equivalent to --only-new-entries INPUT_FILE # Equivalent to --input-file EXTRACT_VERSION # Equivalent to --extract-version #{cli_info[:environment_variables]} HELP end def run(args = []) case args.shift when 'help' GFSM::Output.puts(GFSM::Commands::Changelog.help) when 'generate' no_incremental = ENV.has_key?("NO_INCREMENTAL") || args.include?("--no-incremental") only_new_entries = ENV.has_key?("ONLY_NEW_ENTRIES") || args.include?("--only-new-entries") output_file_path = get_output_file_path(args) changelog_section = compute_this_version_section(args, only_new_entries) if only_new_entries GFSM::Output.puts changelog_section elsif !output_file_path GFSM::Output.puts <<~CHANGELOG # Changelog #{changelog_section} CHANGELOG else if File.file?(output_file_path) && !no_incremental existing_content = File.read(output_file_path) file_content = existing_content.gsub(/#{Regexp.quote(NEXT_ENTRY_MARKER)}/i, changelog_section) File.open(output_file_path, 'w') { |file| file.write file_content } else File.open(output_file_path, 'w') do |file| file.write <<~CHANGELOG # Changelog #{changelog_section} CHANGELOG end end end when 'extract' input_file_path = get_input_file_path(args) version_to_extract = get_version_to_extract(args) unless File.file?(input_file_path) GFSM::Output.error "No changelog file found at #{input_file_path}" else section = extract_version_section(input_file_path, version_to_extract) if section.nil? GFSM::Output.error "No changelog section found for version #{version_to_extract}" else GFSM::Output.puts section end end else GFSM::Output.warn GFSM::Commands::Version.help end true end private def compute_this_version_section(args, only_new_entries) settings = GFSM::Tools::VersionBumperSettings.new(args) version_bumper = GFSM::Tools::VersionBumper.new(settings) version = version_bumper.execute subdivisions = version_bumper.subdivisions.to_a subdivisions.sort_by! { |subdivision_data| subdivision_data[0].priority }.reverse! changelog_entries = "" subdivisions.each do |subdivision_data| change_type = subdivision_data[0] commits = subdivision_data[1] changelog_entries += "#{change_type.to_changelog_entry}\n\n" commits.each do |commit| changelog_entries += "- #{commit.to_changelog_entry}\n" end changelog_entries += "\n" end changelog_entries = changelog_entries[0...-1] unless changelog_entries.empty? return changelog_entries if only_new_entries section = "#{NEXT_ENTRY_MARKER}\n## #{version}" return section if !subdivisions || subdivisions.empty? "#{section}\n\n#{changelog_entries}" end def extract_version_section(input_file_path, version_to_extract) file_content = File.read(input_file_path) # Parse the file contect to subdivide the changelog sections based on the version version_sections = file_content.split(/^## /) # Remove the initial empty section version_sections.shift version_to_extract_index = if version_to_extract.nil? || version_to_extract == "latest" 0 else version_sections.find_index { |section| section.start_with?("#{version_to_extract}\n") } end return nil if version_to_extract_index.nil? || version_to_extract_index > version_sections.length version_section = version_sections[version_to_extract_index] # Remove the first line that contains version heading version_section = version_section[version_section.index("\n")..-1] # Remove the empty lines at the start and at the end of version_section version_section = version_section.strip return version_section end def extract_switch_value(args, switch, default_value, env_name = nil) return ENV.fetch(env_name) if ENV.has_key?(env_name) switch_index = args.find_index(switch) return default_value unless switch_index && (switch_index + 1) < args.length args[switch_index + 1] end def extract_switch_value_if_present(args, switch, default_value, env_name) return ENV.fetch(env_name) if ENV.has_key?(env_name) switch_index = args.find_index(switch) return nil unless switch_index return default_value unless (switch_index + 1) < args.length args[switch_index + 1] end def get_output_file_path(args) extract_switch_value_if_present(args, "--output-file", "./CHANGELOG.md", "OUTPUT_FILE") end def get_input_file_path(args) extract_switch_value(args, "--input-file", "./CHANGELOG.md", "INPUT_FILE") end def get_version_to_extract(args) extract_switch_value(args, "--extract-version", nil, "EXTRACT_VERSION") end end end end