domesticate_monkeys 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2b075af4113a94ea0fa6dc9bdbf085b0cad3d0d15b37badffaca05ed77e656d1
4
+ data.tar.gz: 2f391f15f8044dec6df881f9e81ff5ebd9a034a5ee1b555898a9e240e300070b
5
+ SHA512:
6
+ metadata.gz: 152653bfd024299e8ce20aadda9ded6d8124c754925ba4a532ba30929ec3c8f586c00d5a61227969de12954e4bb4faa16591ec39420d3d2d97370519ce1d9758
7
+ data.tar.gz: 2cab94b24d90eaa77f9122dbdf7206dbdd5e45bd04e3e4a638a5ce9d651c853927cb2935f3e7631bdb70f661d6a367f7589f35d7ddb26be03bc6d8c45ff4ca1f
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+
2
+ source 'https://rubygems.org'
3
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,25 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ domesticate_monkeys (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.3)
10
+ method_source (1.0.0)
11
+ pry (0.14.1)
12
+ coderay (~> 1.1)
13
+ method_source (~> 1.0)
14
+ rake (13.0.6)
15
+
16
+ PLATFORMS
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ domesticate_monkeys!
21
+ pry
22
+ rake
23
+
24
+ BUNDLED WITH
25
+ 2.1.4
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Jurriaan Schrofer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Domesticate Monkeys
2
+
3
+ Domesticating monkeys, yay!
@@ -0,0 +1,45 @@
1
+ require 'optparse'
2
+ require 'domesticate_monkeys'
3
+ require 'pry'
4
+
5
+ module DomesticateMonkeys
6
+
7
+ options = {}
8
+
9
+ # parse task options
10
+ OptionParser.new do |parser|
11
+
12
+ parser.banner = <<~BANNER
13
+
14
+ domesticate_monkeys shows you all the method redefinitions within your application and its gems.
15
+
16
+ Options:
17
+ BANNER
18
+
19
+ parser.on("-s [SHOW]", "--show [SHOW]", "show view [app, all, overview]") { |view| options[:view] = view }
20
+ parser.on("-t [COUNT]", "--top [COUNT", "show top x redefinitions") { |top| options[:top] = top }
21
+ parser.on("-h", "--help", "show option") { puts parser; exit }
22
+ parser.on("-v", "--version", "show version") { puts VERSION; exit }
23
+ end.parse!
24
+
25
+ # set default arguments
26
+ view = options[:view]&.to_sym || :app
27
+ top = options[:top].to_i || 3
28
+
29
+ # validate task
30
+ abort "Aborted: invalid view argument" unless %i( app all overview ).include?(view&.to_sym)
31
+ abort "Aborted: too many arguments" unless ARGV.empty?
32
+
33
+ # load application
34
+ load MainApp
35
+
36
+ # show results
37
+ dm_view = View.new
38
+ dm_view.send(view)
39
+
40
+ # show reports
41
+ dm_report = Report.new
42
+ dm_report.boot_information
43
+ dm_report.top_redefinitions(top)
44
+
45
+ end
@@ -0,0 +1,20 @@
1
+
2
+ # Set boot flag
3
+ $BOOTING_MAIN_APP = true
4
+
5
+ # Load required domesticate_monkeys framework
6
+ require 'bundler'
7
+
8
+ # Start boot time counter
9
+ boot_start = `date`
10
+
11
+ # Boot the main application
12
+ require './config/environment'
13
+
14
+ # Finalize boot time counter
15
+ boot_start = Time.parse(boot_start)
16
+ boot_end = Time.now
17
+ $BOOT_TIME = (boot_end.to_f - boot_start.to_f).round(3)
18
+
19
+ # Reset boot flag
20
+ $BOOTING_MAIN_APP = false
@@ -0,0 +1,38 @@
1
+
2
+ module DomesticateMonkeys
3
+ class Report
4
+
5
+ attr_reader :snapshot, :all_tracks, :multi_tracks, :method_count, :redefined_method_count
6
+
7
+ def initialize
8
+ @snapshot = Snapshot.new
9
+ @all_tracks = @snapshot.all_tracks
10
+ @multi_tracks = @snapshot.multi_tracks
11
+
12
+ @method_count = @all_tracks.size
13
+ @redefined_method_count = @multi_tracks.size
14
+ end
15
+
16
+ def top_redefinitions(top_amount = 3)
17
+ puts "Showing top #{top_amount} redefinitions:"
18
+ tracks = @multi_tracks.first(top_amount).to_h.values
19
+ tracks.each(&:print)
20
+ nil
21
+ end
22
+
23
+ def boot_information
24
+ plain_text = "It took domesticate_monkeys #{$BOOT_TIME} seconds to analyse your application,"\
25
+ " which defined #{$DOMESTICATE_MONKEYS_COUNT} methods."
26
+ colored_text = "\e[#{35}m#{plain_text}\e[0m"
27
+ puts colored_text
28
+ end
29
+
30
+ def inspect
31
+ # Overwrite default behaviour, which returns – and thus often prints –
32
+ # the values of the object's set instance variables, which is enormous
33
+ # in the case of @all_tracks and @multi_tracks.
34
+ to_s
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module DomesticateMonkeys
3
+ class Snapshot
4
+
5
+ # The Snapshot class serves as a data storage, which contains, and compartmentalizes,
6
+ # all current Track instances.
7
+
8
+ # The Snapshot's data storage serves as a basis for other classes, such as View (which
9
+ # provides insight into the data storage), Report (which provides aggregate information
10
+ # about your application) and TrackExport (which generates a JSON file).
11
+
12
+ attr_reader :all_tracks, :filtered_tracks, :multi_tracks
13
+
14
+ def initialize
15
+ @all_tracks = $DOMESTICATE_MONKEYS_TRACKS
16
+ @filtered_tracks = filter_no_methods
17
+ @multi_tracks = select_multi_tracks
18
+ end
19
+
20
+ def filter_no_methods
21
+ @all_tracks.filter { _1.include?(".") || _1.include?("#") }
22
+ end
23
+
24
+ def select_multi_tracks
25
+ multis = @filtered_tracks.select { |_method, track| track.count > 1 }
26
+ sort_tracks(multis)
27
+ end
28
+
29
+ def sort_tracks(tracks)
30
+ sorted_array = tracks.sort_by { |_method, track| -track.count }
31
+ sorted_hash = sorted_array.map { |_method, track| { _method => track } }.inject(&:merge)
32
+ end
33
+
34
+ def inspect
35
+ # Overwrite default behaviour, which returns – and thus often prints –
36
+ # the values of the object's set instance variables, which is enormous
37
+ # in the case of @all_tracks and @multi_tracks.
38
+ to_s
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,114 @@
1
+ require "pry"
2
+ module DomesticateMonkeys
3
+ class Track
4
+
5
+ # The Track class serves as the main class for tracking all method definitions:
6
+ # each method, for each object, that gets defined during the initialization
7
+ # process, gets it's own track with a uniquely identifying key, such as:
8
+ # 'Nokogiri.scrape', or: 'User#format_name'.
9
+
10
+ # The Track instance keeps track of the amount of definitions, the chronological
11
+ # sequence of the source of the definitions, and potentially other information
12
+ # which we will deem valuable in the future.
13
+
14
+ attr_accessor :method, :count, :sources
15
+
16
+ class << self
17
+
18
+ def add(unbound_method)
19
+
20
+ name = format_method_name(unbound_method)
21
+ source = read_method_source(unbound_method)
22
+ return unless name && source
23
+
24
+ # Find the existing track for the given method, or create a new track
25
+ # if the given method is defined for the first time, through the
26
+ # default value of:
27
+ # $DOMESTICATE_MONKEYS_TRACKS ||= Hash.new(Track.new)
28
+
29
+ # Also, duplicate the found / new record, in order to avoid the same
30
+ # track being updated all the time and being assigned to all different
31
+ # keys.
32
+
33
+ track = $DOMESTICATE_MONKEYS_TRACKS[name].dup
34
+ track.add_source(name, source)
35
+
36
+ rescue
37
+ return
38
+ end
39
+
40
+ def format_method_name(unbound_method)
41
+
42
+ # The formatted method name serves as the uniquely identifying key for
43
+ # all operations, with a distinguishing '#' for instance methods and a
44
+ # '.' for singleton methods.
45
+
46
+ name = unbound_method.to_s
47
+
48
+ return format_instance_method(name) if name.include?('UnboundMethod')
49
+ return format_singleton_method(name) if name.include?('Method')
50
+ end
51
+
52
+ def format_instance_method(name)
53
+ name.slice(/(?<=#<UnboundMethod: )[^(]*/)
54
+ .gsub(/\(.*\)/,'')
55
+ .delete('>')
56
+ end
57
+
58
+ def format_singleton_method(name)
59
+ name.slice(/(?<=#<Method: )[^(]*/)
60
+ .gsub(/\(.*\)/,'')
61
+ .delete('>')
62
+ end
63
+
64
+ def read_method_source(unbound_method)
65
+ unbound_method.source_location&.join(':')
66
+ end
67
+
68
+ end
69
+
70
+ def initialize
71
+ @method = nil
72
+ @count = 0
73
+ @sources = []
74
+ end
75
+
76
+ def add_source(method_name, source)
77
+
78
+ $DOMESTICATE_MONKEYS_COUNT += 1
79
+ printf("\r Methods defined: #{$DOMESTICATE_MONKEYS_COUNT}\r") if $BOOTING_MAIN_APP
80
+
81
+ @method ||= method_name
82
+
83
+ # Duplicate the retrieved self.sources, in order to avoid the instance variable
84
+ # from hanging and being stacked with every new source – shared between multiple
85
+ # instances.
86
+ dupped_sources = @sources.dup
87
+
88
+ # Only add a source to the sources variable if the method is being redefined, i.e.
89
+ # being defined by source code that differs from the previously held definition.
90
+ # If a file is being read for a second time, the method is technically defined again
91
+ # but not redefined, since all behaviour is still the same. Therefore we should not
92
+ # track such cases.
93
+ dupped_sources << source unless dupped_sources.last == source
94
+
95
+ # Update the tracker's state, and add it to our global $DOMESTICATE_MONKEYS_TRACKS dictionary afterwards.
96
+ @sources = dupped_sources
97
+ @count = dupped_sources.size
98
+
99
+ $DOMESTICATE_MONKEYS_TRACKS[self.method] = self
100
+ end
101
+
102
+ def print
103
+ view = <<~EOL
104
+
105
+ #{@count} definitions for:
106
+ #{@method}
107
+ #{@sources.map.with_index { |source, i| "#{i}: #{source}"}.join("\n")}
108
+ EOL
109
+
110
+ puts view
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,73 @@
1
+
2
+ module DomesticateMonkeys
3
+ class View
4
+
5
+ attr_reader :snapshot
6
+
7
+ def initialize(snapshot = nil)
8
+ @snapshot = snapshot || Snapshot.new
9
+ end
10
+
11
+ # parsed CLI option methods
12
+
13
+ def all(path_filter = nil)
14
+ tracks = @snapshot.multi_tracks
15
+ tracks = filter_tracks_by_path(tracks, path_filter) if path_filter
16
+
17
+ tracks.values.each { |track| track.print }
18
+
19
+ # Return nil in order to avoid a lengthy print of the @snapshot.
20
+ return nil
21
+ end
22
+
23
+ def app
24
+ all(app_name)
25
+ end
26
+
27
+ def overview
28
+ app_header = header_text("#{app_monkeys_count} Monkeys within our own application").red
29
+ puts app_header
30
+ app
31
+
32
+ all_header = header_text("#{all_monkeys_count} Monkeys through the whole application, including third party software").yellow
33
+ puts all_header
34
+ all
35
+ end
36
+
37
+ private
38
+
39
+ # Helper methods
40
+
41
+ def filter_tracks_by_path(tracks, path_filter)
42
+ tracks.select do |_method, track|
43
+ track.sources.any? { |source| source.snakecase.include?(path_filter.snakecase) }
44
+ end
45
+ end
46
+
47
+ def app_name
48
+ app_class = Rails.application.class
49
+ # Support both the new Module#module_parent and the deprecated Module#parent methods (Rails 6.1)
50
+ top_class = app_class.respond_to?(:module_parent) ? app_class.module_parent : app_class.parent
51
+ app_name = top_class.name.snakecase
52
+ end
53
+
54
+ def app_monkeys_count
55
+ filter_tracks_by_path(@snapshot.multi_tracks, app_name).count
56
+ end
57
+
58
+ def all_monkeys_count
59
+ @snapshot.multi_tracks.count
60
+ end
61
+
62
+ def header_text(text)
63
+ <<~EOL
64
+
65
+
66
+ #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
67
+ #{text}
68
+ #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
69
+ EOL
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,3 @@
1
+
2
+ $DOMESTICATE_MONKEYS_TRACKS ||= Hash.new(DomesticateMonkeys::Track.new)
3
+ $DOMESTICATE_MONKEYS_COUNT ||= 0
@@ -0,0 +1,28 @@
1
+
2
+ class Module
3
+
4
+ # We can keep track of all methods through the two patches below, which are
5
+ # provided by ruby as useful callbacks.
6
+
7
+ # In order to track as much methods and redefinitions as possible, and thus
8
+ # giving the best representation of your application's method defining
9
+ # process, we should initialize all tracker relevant classes AND the below
10
+ # callbacks as early on in the initialization process as possible.
11
+
12
+ # Although 'the earlier, the better' does definitely apply, this should, in
13
+ # any case, happen BEFORE the initialization of Rails and all other gems.
14
+ # The reason being that we can only track redefinitions of methods defined in
15
+ # gems, such as Rails, if we first are able to built a Track for that method's
16
+ # orignal definition.
17
+
18
+ def method_added(_method)
19
+ unbound_method = instance_method(_method)
20
+ DomesticateMonkeys::Track.add(unbound_method)
21
+ end
22
+
23
+ def self.singleton_method_added(_method)
24
+ unbound_method = singleton_method(_method)
25
+ DomesticateMonkeys::Track.add(unbound_method)
26
+ end
27
+
28
+ end
@@ -0,0 +1,10 @@
1
+
2
+ namespace :dm do
3
+
4
+ task :run do
5
+
6
+ # binding.pry
7
+ puts "hihihi, I'm a monkey!"
8
+ end
9
+
10
+ end
@@ -0,0 +1,6 @@
1
+
2
+ module DomesticateMonkeys
3
+
4
+ VERSION = '0.0.1'
5
+
6
+ end
@@ -0,0 +1,17 @@
1
+
2
+ require "domesticate_monkeys/version"
3
+
4
+ module DomesticateMonkeys
5
+
6
+ Root = File.expand_path('../', __dir__)
7
+ MainApp = Root + "/lib/domesticate_monkeys/boot/main_app.rb"
8
+
9
+ autoload :Track, "domesticate_monkeys/constants/track"
10
+ autoload :View, "domesticate_monkeys/constants/view"
11
+ autoload :Snapshot, "domesticate_monkeys/constants/snapshot"
12
+ autoload :Report, "domesticate_monkeys/constants/report"
13
+
14
+ require "domesticate_monkeys/initializers/global_variables"
15
+ require "domesticate_monkeys/initializers/module"
16
+
17
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: domesticate_monkeys
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jurriaan Schrofer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Monkey patches make your application great yet dangerous, therefore you
42
+ ought to domesticate them!
43
+ email:
44
+ - jschrofer@gmail.com
45
+ executables:
46
+ - domesticate_monkeys
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - MIT-LICENSE
53
+ - README.md
54
+ - bin/domesticate_monkeys
55
+ - lib/domesticate_monkeys.rb
56
+ - lib/domesticate_monkeys/boot/main_app.rb
57
+ - lib/domesticate_monkeys/constants/report.rb
58
+ - lib/domesticate_monkeys/constants/snapshot.rb
59
+ - lib/domesticate_monkeys/constants/track.rb
60
+ - lib/domesticate_monkeys/constants/view.rb
61
+ - lib/domesticate_monkeys/initializers/global_variables.rb
62
+ - lib/domesticate_monkeys/initializers/module.rb
63
+ - lib/domesticate_monkeys/tasks/dm.rake
64
+ - lib/domesticate_monkeys/version.rb
65
+ homepage: https://github.com/jurriaanschrofer/domesticate_monkeys
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ homepage_uri: https://github.com/jurriaanschrofer/domesticate_monkeys
70
+ source_code_uri: https://github.com/jurriaanschrofer/domesticate_monkeys
71
+ changelog_uri: https://github.com/jurriaanschrofer/domesticate_monkeys
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.0.9
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Monkey patches make your application great yet dangerous, therefore you ought
91
+ to domesticate them!
92
+ test_files: []