domesticate_monkeys 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []