1# frozen_string_literal: false
2module RSS
3
4  ##
5  # RSS::Utils is a module that holds various utility functions that are used
6  # across many parts of the rest of the RSS library. Like most modules named
7  # some variant of 'util', its methods are probably not particularly useful
8  # to those who aren't developing the library itself.
9  module Utils
10    module_function
11
12    # Given a +name+ in a name_with_underscores or a name-with-dashes format,
13    # returns the CamelCase version of +name+.
14    #
15    # If the +name+ is already CamelCased, nothing happens.
16    #
17    # Examples:
18    #
19    #   require 'rss/utils'
20    #
21    #   RSS::Utils.to_class_name("sample_name")
22    #   # => "SampleName"
23    #   RSS::Utils.to_class_name("with-dashes")
24    #   # => "WithDashes"
25    #   RSS::Utils.to_class_name("CamelCase")
26    #   # => "CamelCase"
27    def to_class_name(name)
28      name.split(/[_\-]/).collect do |part|
29        "#{part[0, 1].upcase}#{part[1..-1]}"
30      end.join("")
31    end
32
33    # Returns an array of two elements: the filename where the calling method
34    # is located, and the line number where it is defined.
35    #
36    # Takes an optional argument +i+, which specifies how many callers up the
37    # stack to look.
38    #
39    # Examples:
40    #
41    #   require 'rss/utils'
42    #
43    #   def foo
44    #     p RSS::Utils.get_file_and_line_from_caller
45    #     p RSS::Utils.get_file_and_line_from_caller(1)
46    #   end
47    #
48    #   def bar
49    #     foo
50    #   end
51    #
52    #   def baz
53    #     bar
54    #   end
55    #
56    #   baz
57    #   # => ["test.rb", 5]
58    #   # => ["test.rb", 9]
59    #
60    # If +i+ is not given, or is the default value of 0, it attempts to figure
61    # out the correct value. This is useful when in combination with
62    # instance_eval. For example:
63    #
64    #   require 'rss/utils'
65    #
66    #   def foo
67    #     p RSS::Utils.get_file_and_line_from_caller(1)
68    #   end
69    #
70    #   def bar
71    #     foo
72    #   end
73    #
74    #   instance_eval <<-RUBY, *RSS::Utils.get_file_and_line_from_caller
75    #   def baz
76    #     bar
77    #   end
78    #   RUBY
79    #
80    #   baz
81    #
82    #   # => ["test.rb", 8]
83    def get_file_and_line_from_caller(i=0)
84      file, line, = caller[i].split(':')
85      line = line.to_i
86      line += 1 if i.zero?
87      [file, line]
88    end
89
90    # Takes a string +s+ with some HTML in it, and escapes '&', '"', '<' and '>', by
91    # replacing them with the appropriate entities.
92    #
93    # This method is also aliased to h, for convenience.
94    #
95    # Examples:
96    #
97    #   require 'rss/utils'
98    #
99    #   RSS::Utils.html_escape("Dungeons & Dragons")
100    #   # => "Dungeons &amp; Dragons"
101    #   RSS::Utils.h(">_>")
102    #   # => "&gt;_&gt;"
103    def html_escape(s)
104      s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
105    end
106    alias h html_escape
107
108    # If +value+ is an instance of class +klass+, return it, else
109    # create a new instance of +klass+ with value +value+.
110    def new_with_value_if_need(klass, value)
111      if value.is_a?(klass)
112        value
113      else
114        klass.new(value)
115      end
116    end
117
118    # This method is used inside of several different objects to determine
119    # if special behavior is needed in the constructor.
120    #
121    # Special behavior is needed if the array passed in as +args+ has
122    # +true+ or +false+ as its value, and if the second element of +args+
123    # is a hash.
124    def element_initialize_arguments?(args)
125      [true, false].include?(args[0]) and args[1].is_a?(Hash)
126    end
127
128    module ExplicitCleanOther
129      module_function
130      def parse(value)
131        if [true, false, nil].include?(value)
132          value
133        else
134          case value.to_s
135          when /\Aexplicit|yes|true\z/i
136            true
137          when /\Aclean|no|false\z/i
138            false
139          else
140            nil
141          end
142        end
143      end
144    end
145
146    module YesOther
147      module_function
148      def parse(value)
149        if [true, false].include?(value)
150          value
151        else
152          /\Ayes\z/i.match(value.to_s) ? true : false
153        end
154      end
155    end
156
157    module CSV
158      module_function
159      def parse(value, &block)
160        if value.is_a?(String)
161          value = value.strip.split(/\s*,\s*/)
162          value = value.collect(&block) if block_given?
163          value
164        else
165          value
166        end
167      end
168    end
169
170    module InheritedReader
171      def inherited_reader(constant_name)
172        base_class = inherited_base
173        result = base_class.const_get(constant_name)
174        found_base_class = false
175        ancestors.reverse_each do |klass|
176          if found_base_class
177            if klass.const_defined?(constant_name)
178              result = yield(result, klass.const_get(constant_name))
179            end
180          else
181            found_base_class = klass == base_class
182          end
183        end
184        result
185      end
186
187      def inherited_array_reader(constant_name)
188        inherited_reader(constant_name) do |result, current|
189          current + result
190        end
191      end
192
193      def inherited_hash_reader(constant_name)
194        inherited_reader(constant_name) do |result, current|
195          result.merge(current)
196        end
197      end
198    end
199  end
200end
201