module Smash module CloudPowers module LangHelp # Allows you to modify all keys, including nested, with a block that you pass. # If no block is passed, a copy is returned. # # Parameters # * params +Hash+|+Array+ - hash to be modified # * +block+ (optional) - a block to be used to modify each key should # modify the key and return that value so it can be used in the copy # # Returns # +Hash+|+Array+ - a copy of the given Array or Hash, with all Hash keys modified # # Example # hash = { 'foo' => 'v1', 'bar' => { fleep: { 'florp' => 'yo' } } } # modify_keys_with(hash) { |key| key.to_sym } # # => { foo: 'v1', bar: { fleep: { florp: 'yo' } } } # # Notes # * see `#modify_keys_with()` for handling first-level keys # * see `#pass_the_buck()` for the way nested structures are handled # * case for different types taken from _MultiXML_ (multi_xml.rb) # * TODO: look at optimization def deep_modify_keys_with(params) case params when Hash params.inject({}) do |carry, (k, v)| carry.tap do |h| if block_given? key = yield k value = if v.kind_of?(Hash) deep_modify_keys_with(v) { |new_key| Proc.new.call(new_key) } else v end h[key] = value else h[k] = v end end end when Array params.map{ |value| symbolize_keys(value) } else params end end # Take an Enumerable apart based on a block. This method works like # Array#reject! except it creates 2 new Enumerables of the same type you # started with; One has the rejected elements and the other has the kept. # # Parameters # * +Enumerable+ - The Enumerable object to start with # # Returns # * +Enumerable+ - The extracted elements in the same type of Enumerable # passed as a parameter # # Notes: # * This modifies the Object passed as a parameter and returns another def extract!(enumerable) return enumerable if enumerable.nil? if block_given? copy = enumerable.dup enumerable.reject! { |k,v| yield k,v } copy.respond_to?(:-) ? (copy - enumerable) : copy.keep_if { |k,v| !enumerable.has_key? k } else enumerable end end # Search through a +Hash+ without knowing if the key is a +String+ or # +Symbol+. A +String+ modification that _normalizes_ each value to compare # is used that is case insensitive. It only leaves word characters, not # including underscore. After the value is found, if it exists and can # be found, the +Hash+, minus that value, is returns in an +Array+ with # the element you were searching for # # Parameters # * key +String+|+Symbol+ - the key you are searching for # * hash +Hash+ - the +Hash+ to search through and return a modified copy # from def find_and_remove(key, hash) candidate_keys = hash.select do |k,v| to_pascal(key).casecmp(to_pascal(k)) == 0 end.keys interesting_value = hash.delete(candidate_keys.first) [interesting_value, hash] end # Join the message and backtrace into a String with line breaks # # Parameters # * error +Exception+ # # Returns # +String+ def format_error_message(error) begin [error.message, error.backtrace.join("\n")].join("\n") rescue Exception # if the formatting won't work, return the original exception error end end # Change valid JSON into a hash # # Parameter # * var +String+ # # Returns # +Hash+ or +nil+ - +nil+ is returned if the JSON is invalid def from_json(var) begin JSON.parse(var) rescue JSON::ParserError, TypeError nil end end # Allows you to modify all first-level keys with a block that you pass. # If no block is passed, a copy is returned. # # Parameters # * params +Hash+|+Array+ # * block (optional) - should modify the key and return that value so it can be used in the copy # # Returns # +Hash+|+Array+ - a copy of the given Array or Hash, with all Hash keys modified # # Example # hash = { 'foo' => 'v1', 'bar' => { fleep: { 'florp' => 'yo' } } } # modify_keys_with(hash) { |k| k.to_sym } # # => { :foo => 'v1', :bar => { fleep: { 'florp' => 'yo' } } } # # Notes # * see +#deep_modify_keys_with()+ for handling nested keys # * case for different types taken from _MultiXML_ (multi_xml.rb) def modify_keys_with(params) params.inject({}) do |carry, (k, v)| carry.tap do |h| key = block_given? ? (yield k) : k h[key] = v end end end # Change strings into camelCase # # Parameters # * var +String+ # # Returns # +String+ def to_camel(var) var = var.to_s unless var.kind_of? String step_one = to_snake(var) step_two = to_pascal(step_one) step_two[0, 1].downcase + step_two[1..-1] end # Change strings into a hyphen delimited phrase # # Parameters # * var +String+ # # Returns # +String+ def to_hyph(var) var = var.to_s unless var.kind_of? String var.gsub(/:{2}|\//, '-'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). gsub(/\s+/, '-'). tr("_", "-"). gsub(/^\W/, ''). downcase end # Assure your arguments are a +Hash+ # # Parameters # * start_point +Object+ - Best to start with a +Hash+, then a 2-D Array, # then something +Enumerable+ that is at least Ordered # # Returns # +Hash+ # # Notes # * If a +Hash+ is given, a copy is returned # * If an +Array+ is given, # * And if the +Array+ is a properly formatted, 2-D +Array+, to_h # is called # * Else Hash[, ] def to_basic_hash(start_point, default_key: 'key') case start_point when Hash start_point when Enumerable two_dimensional_elements = start_point.select do |value| value.respond_to? :each end if two_dimensional_elements.count - start_point.count start_point.to_h else Hash[default_key, start_point] end else Hash[default_key, start_point] end end # Change strings into an i-var format # # Parameters # * key +String+ # # Returns # +String+ def to_i_var(key) "@#{to_snake(key.to_s)}" end # Change strings into PascalCase # # Parameters # * var +String+ # # Returns # +String+ def to_pascal(var) var = var.to_s unless var.kind_of? String var.gsub(/^(.{1})|\W.{1}|\_.{1}/) { |s| s.gsub(/[^a-z0-9]+/i, '').capitalize } end # Change strings into a ruby_file_name with extension # # Parameters # * var +String+ # # Returns # +String+ # # Notes # * given_string.rb # * includes ruby file extension # * see #to_snake() def to_ruby_file_name(name) return name if /\w+\.rb$/ =~ name.to_s "#{to_snake(name)}.rb" end # Change strings into PascalCase # # Parameters # * var +String+ # # Returns # +String+ # # Notes # * given_string # * will not have file extensions # * see #to_ruby_file_name() def to_snake(var) var.to_s.gsub(/^\W*([A-Z|a-z]*)/,'\1'). gsub(/:{2}|\//, '_'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). gsub(/\s+/, '_'). tr("-", "_"). downcase end # Predicate method to check if a String is parsable, as JSON # # Parameters # * json +String+ # # Returns # +Boolean+ # # Notes # * See from_json def valid_json?(json) !!from_json(json) end # Predicate method to check if a String is a valid URL # # Parameters # * url +String+ # # Returns # +Boolean+ def valid_url?(url) url =~ /\A#{URI::regexp}\z/ end end end end