diff --git a/.rubocop.yml b/.rubocop.yml index 33c33fa..07b68b8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,534 +1,540 @@ --- require: - rubocop-performance - rubocop-rspec AllCops: DisplayCopNames: true TargetRubyVersion: '2.4' Include: - "**/*.rb" Exclude: - bin/* - ".vendor/**/*" - "**/Gemfile" - "**/Rakefile" - pkg/**/* - spec/fixtures/**/* - vendor/**/* - "**/Puppetfile" - "**/Vagrantfile" - "**/Guardfile" Layout/LineLength: Description: People have wide screens, use them. Max: 200 RSpec/BeforeAfterAll: Description: Beware of using after(:all) as it may cause state to leak between tests. A necessary evil in acceptance testing. Exclude: - spec/acceptance/**/*.rb RSpec/HookArgument: Description: Prefer explicit :each argument, matching existing module's style EnforcedStyle: each Style/BlockDelimiters: Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to be consistent then. EnforcedStyle: braces_for_chaining Style/ClassAndModuleChildren: Description: Compact style reduces the required amount of indentation. EnforcedStyle: compact Style/EmptyElse: Description: Enforce against empty else clauses, but allow `nil` for clarity. EnforcedStyle: empty Style/FormatString: Description: Following the main puppet project's style, prefer the % format format. EnforcedStyle: percent Style/FormatStringToken: Description: Following the main puppet project's style, prefer the simpler template tokens over annotated ones. EnforcedStyle: template Style/Lambda: Description: Prefer the keyword for easier discoverability. EnforcedStyle: literal Style/RegexpLiteral: Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168 EnforcedStyle: percent_r Style/TernaryParentheses: Description: Checks for use of parentheses around ternary conditions. Enforce parentheses on complex expressions for better readability, but seriously consider breaking it up. EnforcedStyle: require_parentheses_when_complex Style/TrailingCommaInArguments: Description: Prefer always trailing comma on multiline argument lists. This makes diffs, and re-ordering nicer. EnforcedStyleForMultiline: comma Style/TrailingCommaInArrayLiteral: Description: Prefer always trailing comma on multiline literals. This makes diffs, and re-ordering nicer. EnforcedStyleForMultiline: comma Style/SymbolArray: Description: Using percent style obscures symbolic intent of array's contents. EnforcedStyle: brackets RSpec/MessageSpies: EnforcedStyle: receive Style/Documentation: Exclude: - lib/puppet/parser/functions/**/* - spec/**/* Style/WordArray: EnforcedStyle: brackets Performance/AncestorsInclude: Enabled: true Performance/BigDecimalWithNumericArgument: Enabled: true Performance/BlockGivenWithExplicitBlock: Enabled: true Performance/Caller: Enabled: true Performance/CaseWhenSplat: Enabled: true Performance/Casecmp: Enabled: true Performance/CollectionLiteralInLoop: + Exclude: + - spec/**/* Enabled: true Performance/CompareWithBlock: Enabled: true Performance/ConstantRegexp: Enabled: true Performance/Count: Enabled: true Performance/Detect: Enabled: true Performance/DoubleStartEndWith: Enabled: true Performance/EndWith: Enabled: true Performance/FixedSize: Enabled: true Performance/FlatMap: Enabled: true Performance/MethodObjectAsBlock: Enabled: true Performance/RangeInclude: Enabled: true Performance/RedundantBlockCall: Enabled: true Performance/RedundantMatch: Enabled: true Performance/RedundantMerge: Enabled: true Performance/RedundantSortBlock: Enabled: true Performance/RedundantStringChars: Enabled: true Performance/RegexpMatch: Enabled: true Performance/ReverseEach: Enabled: true Performance/ReverseFirst: Enabled: true Performance/Size: Enabled: true Performance/SortReverse: Enabled: true Performance/Squeeze: Enabled: true Performance/StartWith: Enabled: true Performance/StringInclude: Enabled: true Performance/StringReplacement: Enabled: true Performance/Sum: Enabled: true Performance/TimesMap: Enabled: true Style/CollectionMethods: Enabled: true Style/MethodCalledOnDoEndBlock: Enabled: true Style/StringMethods: Enabled: true Bundler/InsecureProtocolSource: Enabled: false Gemspec/DuplicatedAssignment: Enabled: false Gemspec/OrderedDependencies: Enabled: false Gemspec/RequiredRubyVersion: Enabled: false Gemspec/RubyVersionGlobalsUsage: Enabled: false Layout/ArgumentAlignment: Enabled: false Layout/BeginEndAlignment: Enabled: false Layout/ClosingHeredocIndentation: Enabled: false Layout/EmptyComment: Enabled: false Layout/EmptyLineAfterGuardClause: Enabled: false Layout/EmptyLinesAroundArguments: Enabled: false Layout/EmptyLinesAroundAttributeAccessor: Enabled: false Layout/EndOfLine: Enabled: false Layout/FirstArgumentIndentation: Enabled: false Layout/HashAlignment: Enabled: false Layout/HeredocIndentation: Enabled: false Layout/LeadingEmptyLines: Enabled: false Layout/SpaceAroundMethodCallOperator: Enabled: false Layout/SpaceInsideArrayLiteralBrackets: Enabled: false Layout/SpaceInsideReferenceBrackets: Enabled: false Lint/BigDecimalNew: Enabled: false Lint/BooleanSymbol: Enabled: false Lint/ConstantDefinitionInBlock: Enabled: false Lint/DeprecatedOpenSSLConstant: Enabled: false Lint/DisjunctiveAssignmentInConstructor: Enabled: false Lint/DuplicateBranch: Enabled: false Lint/DuplicateElsifCondition: Enabled: false Lint/DuplicateRegexpCharacterClassElement: Enabled: false Lint/DuplicateRequire: Enabled: false Lint/DuplicateRescueException: Enabled: false Lint/EmptyBlock: Enabled: false Lint/EmptyClass: Enabled: false Lint/EmptyConditionalBody: Enabled: false Lint/EmptyFile: Enabled: false Lint/ErbNewArguments: Enabled: false Lint/FloatComparison: Enabled: false Lint/HashCompareByIdentity: Enabled: false Lint/IdentityComparison: Enabled: false Lint/InterpolationCheck: Enabled: false Lint/MissingCopEnableDirective: Enabled: false Lint/MixedRegexpCaptureTypes: Enabled: false Lint/NestedPercentLiteral: Enabled: false Lint/NoReturnInBeginEndBlocks: Enabled: false Lint/NonDeterministicRequireOrder: Enabled: false Lint/OrderedMagicComments: Enabled: false Lint/OutOfRangeRegexpRef: Enabled: false Lint/RaiseException: Enabled: false Lint/RedundantCopEnableDirective: Enabled: false Lint/RedundantRequireStatement: Enabled: false Lint/RedundantSafeNavigation: Enabled: false Lint/RedundantWithIndex: Enabled: false Lint/RedundantWithObject: Enabled: false Lint/RegexpAsCondition: Enabled: false Lint/ReturnInVoidContext: Enabled: false Lint/SafeNavigationConsistency: Enabled: false Lint/SafeNavigationWithEmpty: Enabled: false Lint/SelfAssignment: Enabled: false Lint/SendWithMixinArgument: Enabled: false Lint/ShadowedArgument: Enabled: false Lint/StructNewOverride: Enabled: false Lint/ToEnumArguments: Enabled: false Lint/ToJSON: Enabled: false Lint/TopLevelReturnWithArgument: Enabled: false Lint/TrailingCommaInAttributeDeclaration: Enabled: false Lint/UnexpectedBlockArity: Enabled: false Lint/UnmodifiedReduceAccumulator: Enabled: false Lint/UnreachableLoop: Enabled: false Lint/UriEscapeUnescape: Enabled: false Lint/UriRegexp: Enabled: false Lint/UselessMethodDefinition: Enabled: false Lint/UselessTimes: Enabled: false Metrics/AbcSize: Enabled: false Metrics/BlockLength: Enabled: false +Metrics/BlockNesting: + Enabled: false Metrics/ClassLength: Enabled: false Metrics/CyclomaticComplexity: Enabled: false Metrics/MethodLength: Enabled: false Metrics/ModuleLength: Enabled: false Metrics/ParameterLists: Enabled: false Metrics/PerceivedComplexity: Enabled: false Migration/DepartmentName: Enabled: false +Naming/AccessorMethodName: + Enabled: false Naming/BlockParameterName: Enabled: false Naming/HeredocDelimiterCase: Enabled: false Naming/HeredocDelimiterNaming: Enabled: false Naming/MemoizedInstanceVariableName: Enabled: false Naming/MethodParameterName: Enabled: false Naming/RescuedExceptionsVariableName: Enabled: false RSpec/Be: Enabled: false RSpec/Capybara/CurrentPathExpectation: Enabled: false RSpec/Capybara/FeatureMethods: Enabled: false RSpec/Capybara/VisibilityMatcher: Enabled: false RSpec/ContextMethod: Enabled: false RSpec/ContextWording: Enabled: false RSpec/DescribeClass: Enabled: false RSpec/EmptyHook: Enabled: false RSpec/EmptyLineAfterExample: Enabled: false RSpec/EmptyLineAfterExampleGroup: Enabled: false RSpec/EmptyLineAfterHook: Enabled: false RSpec/ExampleLength: Enabled: false RSpec/ExampleWithoutDescription: Enabled: false RSpec/ExpectChange: Enabled: false RSpec/ExpectInHook: Enabled: false RSpec/FactoryBot/AttributeDefinedStatically: Enabled: false RSpec/FactoryBot/CreateList: Enabled: false RSpec/FactoryBot/FactoryClassName: Enabled: false RSpec/HooksBeforeExamples: Enabled: false RSpec/ImplicitBlockExpectation: Enabled: false RSpec/ImplicitSubject: Enabled: false RSpec/LeakyConstantDeclaration: Enabled: false RSpec/LetBeforeExamples: Enabled: false RSpec/MissingExampleGroupArgument: Enabled: false RSpec/MultipleExpectations: Enabled: false RSpec/MultipleMemoizedHelpers: Enabled: false RSpec/MultipleSubjects: Enabled: false RSpec/NestedGroups: Enabled: false RSpec/PredicateMatcher: Enabled: false RSpec/ReceiveCounts: Enabled: false RSpec/ReceiveNever: Enabled: false RSpec/RepeatedExampleGroupBody: Enabled: false RSpec/RepeatedExampleGroupDescription: Enabled: false RSpec/RepeatedIncludeExample: Enabled: false RSpec/ReturnFromStub: Enabled: false RSpec/SharedExamples: Enabled: false RSpec/StubbedMock: Enabled: false RSpec/UnspecifiedException: Enabled: false RSpec/VariableDefinition: Enabled: false RSpec/VoidExpect: Enabled: false RSpec/Yield: Enabled: false Security/Open: Enabled: false Style/AccessModifierDeclarations: Enabled: false Style/AccessorGrouping: Enabled: false Style/ArgumentsForwarding: Enabled: false Style/AsciiComments: Enabled: false Style/BisectedAttrAccessor: Enabled: false Style/CaseLikeIf: Enabled: false Style/ClassEqualityComparison: Enabled: false Style/CollectionCompact: Enabled: false Style/ColonMethodDefinition: Enabled: false Style/CombinableLoops: Enabled: false Style/CommentedKeyword: Enabled: false Style/Dir: Enabled: false Style/DocumentDynamicEvalDefinition: Enabled: false Style/DoubleCopDisableDirective: Enabled: false Style/EmptyBlockParameter: Enabled: false Style/EmptyLambdaParameter: Enabled: false Style/Encoding: Enabled: false Style/EvalWithLocation: Enabled: false Style/ExpandPathArguments: Enabled: false Style/ExplicitBlockArgument: Enabled: false Style/ExponentialNotation: Enabled: false Style/FloatDivision: Enabled: false Style/GlobalStdStream: Enabled: false Style/HashAsLastArrayItem: Enabled: false Style/HashLikeCase: Enabled: false Style/HashTransformKeys: Enabled: false Style/HashTransformValues: Enabled: false Style/IfUnlessModifier: Enabled: false Style/KeywordParametersOrder: Enabled: false Style/MinMax: Enabled: false Style/MixinUsage: Enabled: false Style/MultilineWhenThen: Enabled: false Style/NegatedIfElseCondition: Enabled: false Style/NegatedUnless: Enabled: false Style/NilLambda: Enabled: false Style/NumericPredicate: Enabled: false Style/OptionalBooleanParameter: Enabled: false Style/OrAssignment: Enabled: false Style/RandomWithOffset: Enabled: false Style/RedundantArgument: Enabled: false Style/RedundantAssignment: Enabled: false Style/RedundantCondition: Enabled: false Style/RedundantConditional: Enabled: false Style/RedundantFetchBlock: Enabled: false Style/RedundantFileExtensionInRequire: Enabled: false Style/RedundantRegexpCharacterClass: Enabled: false Style/RedundantRegexpEscape: Enabled: false Style/RedundantSelfAssignment: Enabled: false Style/RedundantSort: Enabled: false Style/RescueStandardError: Enabled: false Style/SingleArgumentDig: Enabled: false Style/SlicingWithRange: Enabled: false Style/SoleNestedConditional: Enabled: false Style/StderrPuts: Enabled: false Style/StringConcatenation: Enabled: false Style/Strip: Enabled: false Style/SwapValues: Enabled: false Style/SymbolProc: Enabled: false Style/TrailingBodyOnClass: Enabled: false Style/TrailingBodyOnMethodDefinition: Enabled: false Style/TrailingBodyOnModule: Enabled: false Style/TrailingCommaInHashLiteral: Enabled: false Style/TrailingMethodEndStatement: Enabled: false Style/UnpackFirst: Enabled: false diff --git a/lib/puppet/provider/ini_setting/ruby.rb b/lib/puppet/provider/ini_setting/ruby.rb index 8d2dc44..1d3206b 100644 --- a/lib/puppet/provider/ini_setting/ruby.rb +++ b/lib/puppet/provider/ini_setting/ruby.rb @@ -1,152 +1,152 @@ # frozen_string_literal: true require File.expand_path('../../../util/ini_file', __FILE__) Puppet::Type.type(:ini_setting).provide(:ruby) do def self.instances desc ' Creates new ini_setting file, a specific config file with a provider that uses this as its parent and implements the method self.file_path, and that will provide the value for the path to the ini file.' raise(Puppet::Error, 'Ini_settings only support collecting instances when a file path is hard coded') unless respond_to?(:file_path) # figure out what to do about the seperator ini_file = Puppet::Util::IniFile.new(file_path, '=') resources = [] ini_file.section_names.each do |section_name| ini_file.get_settings(section_name).each do |setting, value| resources.push( new( name: namevar(section_name, setting), value: value, ensure: :present, ), ) end end resources end def self.namevar(section_name, setting) setting.nil? ? section_name : "#{section_name}/#{setting}" end def exists? setting.nil? && ini_file.section_names.include?(section) || !ini_file.get_value(section, setting).nil? if ini_file.section?(section) !ini_file.get_value(section, setting).nil? - elsif resource.parameters.keys.include?(:force_new_section_creation) && !resource[:force_new_section_creation] + elsif resource.parameters.key?(:force_new_section_creation) && !resource[:force_new_section_creation] # for backwards compatibility, if a user is using their own ini_setting # types but does not have this parameter, we need to fall back to the # previous functionality which was to create the section. Anyone # wishing to leverage this setting must define it in their provider # type. See comments on # https://github.com/puppetlabs/puppetlabs-inifile/pull/286 resource[:ensure] = :absent resource[:force_new_section_creation] - elsif resource.parameters.keys.include?(:force_new_section_creation) && resource[:force_new_section_creation] + elsif resource.parameters.key?(:force_new_section_creation) && resource[:force_new_section_creation] !resource[:force_new_section_creation] else false end end def create if setting.nil? && resource[:value].nil? ini_file.set_value(section) else ini_file.set_value(section, setting, separator, resource[:value]) end ini_file.save @ini_file = nil end def destroy ini_file.remove_setting(section, setting) ini_file.save @ini_file = nil end def value ini_file.get_value(section, setting) end def value=(_value) if setting.nil? && resource[:value].nil? ini_file.set_value(section) else ini_file.set_value(section, setting, separator, resource[:value]) end ini_file.save end def section # this method is here so that it can be overridden by a child provider resource[:section] end def setting # this method is here so that it can be overridden by a child provider resource[:setting] end def file_path # this method is here to support purging and sub-classing. # if a user creates a type and subclasses our provider and provides a # 'file_path' method, then they don't have to specify the # path as a parameter for every ini_setting declaration. # This implementation allows us to support that while still # falling back to the parameter value when necessary. if self.class.respond_to?(:file_path) self.class.file_path else resource[:path] end end def separator if resource.class.validattr?(:key_val_separator) resource[:key_val_separator] || '=' else '=' end end def section_prefix if resource.class.validattr?(:section_prefix) resource[:section_prefix] || '[' else '[' end end def section_suffix if resource.class.validattr?(:section_suffix) resource[:section_suffix] || ']' else ']' end end def indent_char if resource.class.validattr?(:indent_char) resource[:indent_char] || ' ' else ' ' end end def indent_width if resource.class.validattr?(:indent_width) resource[:indent_width] || nil else nil end end private def ini_file @ini_file ||= Puppet::Util::IniFile.new(file_path, separator, section_prefix, section_suffix, indent_char, indent_width) end end diff --git a/lib/puppet/util/ini_file.rb b/lib/puppet/util/ini_file.rb index c992fdd..15c2dbe 100644 --- a/lib/puppet/util/ini_file.rb +++ b/lib/puppet/util/ini_file.rb @@ -1,349 +1,349 @@ # frozen_string_literal: true require File.expand_path('../external_iterator', __FILE__) require File.expand_path('../ini_file/section', __FILE__) module Puppet::Util # # ini_file.rb # class IniFile def initialize(path, key_val_separator = ' = ', section_prefix = '[', section_suffix = ']', indent_char = ' ', indent_width = nil) k_v_s = (key_val_separator =~ %r{^\s+$}) ? ' ' : key_val_separator.strip @section_prefix = section_prefix @section_suffix = section_suffix @indent_char = indent_char @indent_width = indent_width ? indent_width.to_i : nil @section_regex = section_regex @setting_regex = %r{^(\s*)([^#;\s]|[^#;\s].*?[^\s#{k_v_s}])(\s*#{k_v_s}[ \t]*)(.*)\s*$} @commented_setting_regex = %r{^(\s*)[#;]+(\s*)(.*?[^\s#{k_v_s}])(\s*#{k_v_s}[ \t]*)(.*)\s*$} @path = path @key_val_separator = key_val_separator @section_names = [] @sections_hash = {} parse_file end def section_regex # Only put in prefix/suffix if they exist # Also, if the prefix is '', the negated # set match should be a match all instead. r_string = '^\s*' r_string += Regexp.escape(@section_prefix) r_string += '(' if @section_prefix != '' r_string += '[^' r_string += Regexp.escape(@section_prefix) r_string += ']' else r_string += '.' end r_string += '*)' r_string += Regexp.escape(@section_suffix) r_string += '\s*$' %r{#{r_string}} end attr_reader :section_names def get_settings(section_name) section = @sections_hash[section_name] section.setting_names.each_with_object({}) do |setting, result| result[setting] = section.get_value(setting) end end def section?(section_name) @sections_hash.key?(section_name) end def get_value(section_name, setting) @sections_hash[section_name].get_value(setting) if @sections_hash.key?(section_name) end - def set_value(*args) # rubocop:disable Naming/AccessorMethodName : Recomended alternative is a common value name + def set_value(*args) case args.size when 1 section_name = args[0] when 3 # Backwards compatible set_value function, See MODULES-5172 (section_name, setting, value) = args when 4 (section_name, setting, separator, value) = args end complete_setting = { setting: setting, separator: separator, value: value, } unless @sections_hash.key?(section_name) add_section(Section.new(section_name, nil, nil, nil, nil)) end section = @sections_hash[section_name] if section.existing_setting?(setting) update_line(section, setting, value) section.update_existing_setting(setting, value) elsif find_commented_setting(section, setting) # So, this stanza is a bit of a hack. What we're trying # to do here is this: for settings that don't already # exist, we want to take a quick peek to see if there # is a commented-out version of them in the section. # If so, we'd prefer to add the setting directly after # the commented line, rather than at the end of the section. # If we get here then we found a commented line, so we # call "insert_inline_setting_line" to update the lines array insert_inline_setting_line(find_commented_setting(section, setting), section, complete_setting) # Then, we need to tell the setting object that we hacked # in an inline setting section.insert_inline_setting(setting, value) # Finally, we need to update all of the start/end line # numbers for all of the sections *after* the one that # was modified. section_index = @section_names.index(section_name) increment_section_line_numbers(section_index + 1) elsif !setting.nil? || !value.nil? section.set_additional_setting(setting, value) end end def remove_setting(section_name, setting) section = @sections_hash[section_name] return unless section.existing_setting?(setting) # If the setting is found, we have some work to do. # First, we remove the line from our array of lines: remove_line(section, setting) # Then, we need to tell the setting object to remove # the setting from its state: section.remove_existing_setting(setting) # Finally, we need to update all of the start/end line # numbers for all of the sections *after* the one that # was modified. section_index = @section_names.index(section_name) decrement_section_line_numbers(section_index + 1) return unless section.empty? # By convention, it's time to remove this newly emptied out section lines.delete_at(section.start_line) decrement_section_line_numbers(section_index + 1) @section_names.delete_at(section_index) @sections_hash.delete(section.name) end def save global_empty = @sections_hash[''].empty? && @sections_hash[''].additional_settings.empty? File.open(@path, 'w') do |fh| @section_names.each_index do |index| name = @section_names[index] section = @sections_hash[name] # We need a buffer to cache lines that are only whitespace whitespace_buffer = [] if section.new_section? && !section.global? if index == 1 && !global_empty || index > 1 fh.puts('') end fh.puts("#{@section_prefix}#{section.name}#{@section_suffix}") end unless section.new_section? # write all of the pre-existing lines (section.start_line..section.end_line).each do |line_num| line = lines[line_num] # We buffer any lines that are only whitespace so that # if they are at the end of a section, we can insert # any new settings *before* the final chunk of whitespace # lines. if line.match?(%r{^\s*$}) whitespace_buffer << line else # If we get here, we've found a non-whitespace line. # We'll flush any cached whitespace lines before we # write it. flush_buffer_to_file(whitespace_buffer, fh) fh.puts(line) end end end # write new settings, if there are any section.additional_settings.each_pair do |key, value| fh.puts("#{@indent_char * (@indent_width || section.indentation || 0)}#{key}#{@key_val_separator}#{value}") end if !whitespace_buffer.empty? flush_buffer_to_file(whitespace_buffer, fh) elsif section.new_section? && !section.additional_settings.empty? && (index < @section_names.length - 1) # We get here if there were no blank lines at the end of the # section. # # If we are adding a new section with a new setting, # and if there are more sections that come after this one, # we'll write one blank line just so that there is a little # whitespace between the sections. # if (section.end_line.nil? && fh.puts('') end end end end private def add_section(section) @sections_hash[section.name] = section @section_names << section.name end def parse_file line_iter = create_line_iter # We always create a "global" section at the beginning of the file, for # anything that appears before the first named section. section = read_section('', 0, line_iter) add_section(section) line, line_num = line_iter.next while line if (match = @section_regex.match(line)) section = read_section(match[1], line_num, line_iter) add_section(section) end line, line_num = line_iter.next end end def read_section(name, start_line, line_iter) settings = {} end_line_num = start_line min_indentation = nil empty = true loop do line, line_num = line_iter.peek if line_num.nil? || @section_regex.match(line) # the global section always exists, even when it's empty; # when it's empty, we must be sure it's thought of as new, # which is signalled with a nil ending line end_line_num = nil if name == '' && empty return Section.new(name, start_line, end_line_num, settings, min_indentation) end if (match = @setting_regex.match(line)) settings[match[2]] = match[4] indentation = match[1].length min_indentation = [indentation, min_indentation || indentation].min end end_line_num = line_num empty = false line_iter.next end end def update_line(section, setting, value) (section.start_line..section.end_line).each do |line_num| next unless (match = @setting_regex.match(lines[line_num])) if match[2] == setting lines[line_num] = "#{match[1]}#{match[2]}#{match[3]}#{value}" end end end def remove_line(section, setting) (section.start_line..section.end_line).each do |line_num| next unless (match = @setting_regex.match(lines[line_num])) if match[2] == setting lines.delete_at(line_num) end end end def create_line_iter ExternalIterator.new(lines) end def lines @lines ||= IniFile.readlines(@path) end # This is mostly here because it makes testing easier--we don't have # to try to stub any methods on File. def self.readlines(path) # rubocop:disable Lint/IneffectiveAccessModifier : Attempting to change breaks tests # If this type is ever used with very large files, we should # write this in a different way, using a temp # file; for now assuming that this type is only used on # small-ish config files that can fit into memory without # too much trouble. File.file?(path) ? File.readlines(path) : [] end # This utility method scans through the lines for a section looking for # commented-out versions of a setting. It returns `nil` if it doesn't # find one. If it does find one, then it returns a hash containing # two keys: # # :line_num - the line number that contains the commented version # of the setting # :match - the ruby regular expression match object, which can # be used to mimic the whitespace from the comment line def find_commented_setting(section, setting) return nil if section.new_section? (section.start_line..section.end_line).each do |line_num| next unless (match = @commented_setting_regex.match(lines[line_num])) if match[3] == setting return { match: match, line_num: line_num } end end nil end # This utility method is for inserting a line into the existing # lines array. The `result` argument is expected to be in the # format of the return value of `find_commented_setting`. def insert_inline_setting_line(result, section, complete_setting) line_num = result[:line_num] s = complete_setting lines.insert(line_num + 1, "#{@indent_char * (@indent_width || section.indentation || 0)}#{s[:setting]}#{s[:separator]}#{s[:value]}") end # Utility method; given a section index (index into the @section_names # array), decrement the start/end line numbers for that section and all # all of the other sections that appear *after* the specified section. def decrement_section_line_numbers(section_index) @section_names[section_index..(@section_names.length - 1)].each do |name| section = @sections_hash[name] section.decrement_line_nums end end # Utility method; given a section index (index into the @section_names # array), increment the start/end line numbers for that section and all # all of the other sections that appear *after* the specified section. def increment_section_line_numbers(section_index) @section_names[section_index..(@section_names.length - 1)].each do |name| section = @sections_hash[name] section.increment_line_nums end end def flush_buffer_to_file(buffer, fh) return if buffer.empty? buffer.each { |l| fh.puts(l) } buffer.clear end end end diff --git a/lib/puppet/util/setting_value.rb b/lib/puppet/util/setting_value.rb index 66dec4f..51e349d 100644 --- a/lib/puppet/util/setting_value.rb +++ b/lib/puppet/util/setting_value.rb @@ -1,184 +1,184 @@ # frozen_string_literal: true module Puppet::Util # This class can work with a list of subsettings inside # an ini file setting string to add, remove, extract and set their values. class SettingValue # The constructor method # @param setting_value [String] The initial setting value # @param subsetting_separator [String] The character is used to separate # subsettings in the setting_value string. # @param default_quote_char [String] Quote the setting string with this character. def initialize(setting_value, subsetting_separator = ' ', default_quote_char = '', key_val_separator = '') @setting_value = setting_value @subsetting_separator = subsetting_separator @quote_char = default_quote_char @key_val_separator = key_val_separator @subsetting_items = [] return unless @setting_value unquoted, quote_char = unquote_setting_value(setting_value) @quote_char = quote_char unless quote_char.empty? # an item can contain escaped separator @subsetting_items = unquoted.scan(Regexp.new("(?:(?:[^\\#{@subsetting_separator}]|\\.)+)")) @subsetting_items.map! { |item| item.strip } end # If the setting value is quoted, the quotes are # removed and the unquoted string and the quoting # character are returned. # @param setting_value [String] The input value # @return [Array] The unquoted string and the quoting character def unquote_setting_value(setting_value) quote_char = '' if setting_value.start_with?('"') && setting_value.end_with?('"') quote_char = '"' elsif setting_value.start_with?("'") && setting_value.end_with?("'") quote_char = "'" end unquoted = if quote_char != '' setting_value[1, setting_value.length - 2] else setting_value end [unquoted, quote_char] end # Get the resulting setting value by joining all the # subsettings, separator and quote characters. # @return [String] - def get_value # rubocop:disable Naming/AccessorMethodName : Unable to alter without breaking code + def get_value value = @subsetting_items.join @subsetting_separator @quote_char + value + @quote_char end # Get the value of the given subsetting item. # If the exact match is used the value will be true # if the item is found. # @param subsetting [String] The name of the subsetting to add. # @param use_exact_match [:true,:false] Should the full name match be used? # @return [nil,true,String] def get_subsetting_value(subsetting, use_exact_match = :false) index = find_subsetting(subsetting, use_exact_match) # the item is not found in the list return nil unless index # the exact match is set and the item is found, the value should be true return true if use_exact_match == :true item = @subsetting_items[index] item[(subsetting.length + @key_val_separator.length)..-1] end # Add a new subsetting item to the list of existing items # if such item is not already there. # @param subsetting [String] The name of the subsetting to add. # @param subsetting_value [String] The value of the subsetting. # It will be appended to the name. # @param use_exact_match [:true,:false] Should the full name match be used? # @param [Symbol] insert_type # @param [String,Integer] insert_value # @return [Array] The resulting subsettings list. def add_subsetting(subsetting, subsetting_value, use_exact_match = :false, insert_type = :end, insert_value = nil) index = find_subsetting(subsetting, use_exact_match) # update the existing values if the subsetting is found in the list return update_subsetting(subsetting, subsetting_value, use_exact_match) if index new_item = item_value(subsetting, subsetting_value) case insert_type when :start @subsetting_items.unshift(new_item) when :end @subsetting_items.push(new_item) when :before before_index = find_subsetting(insert_value, use_exact_match) if before_index @subsetting_items.insert(before_index, new_item) else @subsetting_items.push(new_item) end when :after after_index = find_subsetting(insert_value, use_exact_match) if after_index @subsetting_items.insert(after_index + 1, new_item) else @subsetting_items.push(new_item) end when :index before_index = insert_value.to_i before_index = @subsetting_items.length if before_index > @subsetting_items.length @subsetting_items.insert(before_index, new_item) else @subsetting_items.push(new_item) end @subsetting_items end # Update all matching items in the settings list to the new values. # @param subsetting [String] The name of the subsetting to add. # @param subsetting_value [String] The value of the subsetting. # @param use_exact_match [:true,:false] Should the full name match be used? # @return [Array] The resulting subsettings list. def update_subsetting(subsetting, subsetting_value, use_exact_match = :false) new_item = item_value(subsetting, subsetting_value) @subsetting_items.map! do |item| if match_subsetting?(item, subsetting, use_exact_match) new_item else item end end end # Find the first subsetting item matching the given name, # or, if the exact match is set, equal to the given name # and return its array index value. Returns nil if not found. # @param subsetting [String] The name of the subsetting to search. # @param use_exact_match [:true,:false] Look for the full string match? # @return [Integer, nil] def find_subsetting(subsetting, use_exact_match = :false) @subsetting_items.index do |item| match_subsetting?(item, subsetting, use_exact_match) end end # Check if the subsetting item matches the given name. # If the exact match is set the entire item is matched, # and only the item name and separator string if not. # @param item [String] The item value to check against the subsetting name. # @param subsetting [String] The subsetting name. # @param use_exact_match [:true,:false] Look for the full string match? # @return [true,false] def match_subsetting?(item, subsetting, use_exact_match = :false) if use_exact_match == :true item.eql?(subsetting) else item.start_with?(subsetting + @key_val_separator) end end # Remove all the subsetting items that match # the given subsetting name. # @param subsetting [String] The subsetting name to remove. # @param use_exact_match [:true,:false] Look for the full string match? # @return [Array] The resulting subsettings list. def remove_subsetting(subsetting, use_exact_match = :false) @subsetting_items.delete_if do |item| match_subsetting?(item, subsetting, use_exact_match) end end # The actual value of the subsetting item. # It's built from the subsetting name, its value and the separator # string if present. # @param subsetting [String] The subsetting name # @param subsetting_value [String] The value of the subsetting # @return [String] def item_value(subsetting, subsetting_value) (subsetting || '') + (@key_val_separator || '') + (subsetting_value || '') end end end