Module: AppMap::Util
- Defined in:
- lib/appmap/util.rb
Constant Summary collapse
- CLEAR =
https://wynnnetherland.com/journal/a-stylesheet-author-s-guide-to-terminal-colors/ Embed in a String to clear all previous ANSI sequences.
"\e[0m"- BOLD =
"\e[1m"- BLACK =
Colors
"\e[30m"- RED =
"\e[31m"- GREEN =
"\e[32m"- YELLOW =
"\e[33m"- BLUE =
"\e[34m"- MAGENTA =
"\e[35m"- CYAN =
"\e[36m"- WHITE =
"\e[37m"
Class Method Summary collapse
- .blank?(obj) ⇒ Boolean
- .class_from_string(fq_class, must: true) ⇒ Object
- .classify(word) ⇒ Object
- .color(text, color, bold: false) ⇒ Object
- .deep_dup(hash) ⇒ Object
- .normalize_path(path) ⇒ Object
- .parse_function_name(name) ⇒ Object
- .ruby_minor_version ⇒ Object
-
.sanitize_event(event, &block) ⇒ Object
sanitize_event removes ephemeral values from an event, making events easier to compare across runs.
-
.sanitize_paths(h) ⇒ Object
sanitize_paths removes ephemeral values from objects with embedded paths (e.g. an event or a classmap), making events easier to compare across runs.
-
.scenario_filename(name, max_length: 255, separator: '_', extension: '.appmap.json') ⇒ Object
scenario_filename builds a suitable file name from a scenario name.
- .select_rack_headers(env) ⇒ Object
- .startup_message(msg) ⇒ Object
-
.swaggerize_path(path) ⇒ Object
Convert a Rails-style path from /org/:org_id(.:format) to Swagger-style paths like /org/org_id.
- .try(obj, *methods) ⇒ Object
- .underscore(camel_cased_word) ⇒ Object
-
.write_appmap(filename, appmap) ⇒ Object
Atomically writes AppMap data to
filename.
Class Method Details
.blank?(obj) ⇒ Boolean
202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/appmap/util.rb', line 202 def blank?(obj) return true if obj.nil? return true if obj.is_a?(String) && obj == '' return true if obj.respond_to?(:length) && obj.length == 0 return true if obj.respond_to?(:size) && obj.size == 0 false end |
.class_from_string(fq_class, must: true) ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/appmap/util.rb', line 24 def class_from_string(fq_class, must: true) fq_class.split('::').inject(Object) do |mod, class_name| mod.const_get(class_name) end rescue NameError raise if must end |
.classify(word) ⇒ Object
181 182 183 |
# File 'lib/appmap/util.rb', line 181 def classify(word) word.split(/[\-_]/).map(&:capitalize).join end |
.color(text, color, bold: false) ⇒ Object
175 176 177 178 179 |
# File 'lib/appmap/util.rb', line 175 def color(text, color, bold: false) color = Util.const_get(color.to_s.upcase) if color.is_a?(Symbol) bold = bold ? BOLD : "" "#{bold}#{color}#{text}#{CLEAR}" end |
.deep_dup(hash) ⇒ Object
197 198 199 200 |
# File 'lib/appmap/util.rb', line 197 def deep_dup(hash) # This is a simple way to avoid the need for deep_dup from activesupport. Marshal.load(Marshal.dump(hash)) end |
.normalize_path(path) ⇒ Object
142 143 144 145 146 147 148 |
# File 'lib/appmap/util.rb', line 142 def normalize_path(path) if path.index(Dir.pwd) == 0 && !path.index(Bundler.bundle_path.to_s) path[Dir.pwd.length + 1..-1] else path end end |
.parse_function_name(name) ⇒ Object
32 33 34 35 36 37 38 39 40 |
# File 'lib/appmap/util.rb', line 32 def parse_function_name(name) package_tokens = name.split('/') class_and_name = package_tokens.pop class_name, function_name, static = class_and_name.include?('.') ? class_and_name.split('.', 2) + [ true ] : class_and_name.split('#', 2) + [ false ] raise "Malformed fully-qualified function name #{name}" unless function_name [ package_tokens.empty? ? 'ruby' : package_tokens.join('/'), class_name, static, function_name ] end |
.ruby_minor_version ⇒ Object
231 232 233 |
# File 'lib/appmap/util.rb', line 231 def ruby_minor_version @ruby_minor_version ||= RUBY_VERSION.split('.')[0..1].join('.').to_f end |
.sanitize_event(event, &block) ⇒ Object
sanitize_event removes ephemeral values from an event, making events easier to compare across runs.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/appmap/util.rb', line 94 def sanitize_event(event, &block) event.delete(:thread_id) event.delete(:elapsed) delete_object_id = ->(obj) { (obj || {}).delete(:object_id) } delete_object_id.call(event[:receiver]) delete_object_id.call(event[:return_value]) i[parameters exceptions ].each do |field| (event[field] || []).each(&delete_object_id) end i[http_client_request http_client_response http_server_request http_server_response].each do |field| headers = event.dig(field, :headers) next unless headers headers['Date'] = '<instanceof date>' if headers['Date'] headers['Server'] = headers['Server'].match(/^(\w+)/)[0] if headers['Server'] end case event[:event] when :call sanitize_paths(event) end event end |
.sanitize_paths(h) ⇒ Object
sanitize_paths removes ephemeral values from objects with embedded paths (e.g. an event or a classmap), making events easier to compare across runs.
78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/appmap/util.rb', line 78 def sanitize_paths(h) require 'hashie' h.extend(Hashie::Extensions::DeepLocate) keys = i(path location) h.deep_locate ->(k,v,o) { next unless keys.include?(k) fix = ->(v) {v.gsub(%r{#{Gem.dir}/gems/.*(?=lib)}, '')} keys.each {|k| o[k] = fix.(o[k]) if o[k] } } h end |
.scenario_filename(name, max_length: 255, separator: '_', extension: '.appmap.json') ⇒ Object
scenario_filename builds a suitable file name from a scenario name. Special characters are removed, and the file name is truncated to fit within shell limitations.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/appmap/util.rb', line 45 def scenario_filename(name, max_length: 255, separator: '_', extension: '.appmap.json') # Cribbed from v5 version of ActiveSupport:Inflector#parameterize: # https://github.com/rails/rails/blob/v5.2.4/activesupport/lib/active_support/inflector/transliterate.rb#L92 # Replace accented chars with their ASCII equivalents. fname = name.encode('utf-8', invalid: :replace, undef: :replace, replace: '_') # Turn unwanted chars into the separator. fname.gsub!(/[^a-z0-9\-_]+/i, separator) re_sep = Regexp.escape(separator) re_duplicate_separator = /#{re_sep}{2,}/ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i # No more than one of the separator in a row. fname.gsub!(re_duplicate_separator, separator) # Finally, Remove leading/trailing separator. fname.gsub!(re_leading_trailing_separator, '') if (fname.length + extension.length) > max_length require 'base64' require 'digest' fname_digest = Base64.urlsafe_encode64 Digest::MD5.digest(fname), padding: false fname[max_length - fname_digest.length - extension.length - 1..-1] = [ '-', fname_digest ].join end [ fname, extension ].join end |
.select_rack_headers(env) ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/appmap/util.rb', line 119 def select_rack_headers(env) finalize_headers = lambda do |headers| blank?(headers) ? nil : headers end # Rack prepends HTTP_ to all client-sent headers. if !env['rack.version'] warn "Request headers does not contain rack.version. HTTP_ prefix is not expected." return finalize_headers.call(env.dup) end matching_headers = env .select { |k,v| k.start_with? 'HTTP_'} .reject { |k,v| blank?(v) } .each_with_object({}) do |kv, memo| key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-') value = kv[1] memo[key] = value end finalize_headers.call(matching_headers) end |
.startup_message(msg) ⇒ Object
223 224 225 226 227 228 229 |
# File 'lib/appmap/util.rb', line 223 def (msg) if defined?(::Rails) && defined?(::Rails.logger) && ::Rails.logger ::Rails.logger.info msg elsif ENV['DEBUG'] == 'true' warn msg end end |
.swaggerize_path(path) ⇒ Object
Convert a Rails-style path from /org/:org_id(.:format) to Swagger-style paths like /org/org_id
152 153 154 155 156 157 158 |
# File 'lib/appmap/util.rb', line 152 def swaggerize_path(path) path = path.split('(.')[0] tokens = path.split('/') tokens.map do |token| token.gsub(/^:(.*)/, '{\1}') end.join('/') end |
.try(obj, *methods) ⇒ Object
215 216 217 218 219 220 221 |
# File 'lib/appmap/util.rb', line 215 def try(obj, *methods) return nil if methods.empty? return nil unless obj.respond_to?(methods.first) obj.public_send(*methods) end |
.underscore(camel_cased_word) ⇒ Object
186 187 188 189 190 191 192 193 194 195 |
# File 'lib/appmap/util.rb', line 186 def underscore(camel_cased_word) return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word) word = camel_cased_word.to_s.gsub("::", "/") # word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') word.tr!("-", "_") word.downcase! word end |
.write_appmap(filename, appmap) ⇒ Object
Atomically writes AppMap data to filename.
161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/appmap/util.rb', line 161 def write_appmap(filename, appmap) require 'tmpdir' # This is what Ruby Tempfile does; but we don't want the file to be unlinked. mode = File::RDWR | File::CREAT | File::EXCL ::Dir::Tmpname.create([ 'appmap_', '.json' ]) do |tmpname| tempfile = File.open(tmpname, mode) tempfile.write(appmap) tempfile.close # Atomically move the tempfile into place. FileUtils.mv tempfile.path, filename end end |