Module: AppMap::Util

Defined in:
lib/appmap/util.rb

Class Method Summary collapse

Class Method Details

.normalize_path(path) ⇒ Object



96
97
98
99
100
101
102
# File 'lib/appmap/util.rb', line 96

def normalize_path(path)
  if path.index(Dir.pwd) == 0
    path[Dir.pwd.length + 1..-1]
  else
    path
  end
end

.sanitize_event(event, &block) ⇒ Object

sanitize_event removes ephemeral values from an event, making events easier to compare across runs.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/appmap/util.rb', line 58

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 message].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.



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/appmap/util.rb', line 42

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.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/appmap/util.rb', line 9

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_headers(env) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/appmap/util.rb', line 83

def select_headers(env)
  # Rack prepends HTTP_ to all client-sent headers.
  matching_headers = env
    .select { |k,v| k.start_with? 'HTTP_'}
    .reject { |k,v| v.blank? }
    .each_with_object({}) do |kv, memo|
      key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
      value = kv[1]
      memo[key] = value
    end
  matching_headers.blank? ? nil : matching_headers
end

.write_appmap(filename, appmap) ⇒ Object

Atomically writes AppMap data to filename.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/appmap/util.rb', line 105

def write_appmap(filename, appmap)
  require 'fileutils'
  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