toolbus 0.0.2

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
+ SHA1:
3
+ metadata.gz: e446516a16a126e50c3638221d68fcb12478b05a
4
+ data.tar.gz: 6984f53761683637177632968e9c380e2a3ab4f6
5
+ SHA512:
6
+ metadata.gz: 6179c6fb159e91bd7df282445e8b23b2d036323af0fc3820e5c99918d101a2599636426c2926ec77cade5336e96a8b76eeda175bc320620e24b91752b3e69633
7
+ data.tar.gz: 9b1f79ea4cba5ba67d7bbf96f508f0cae3f86e5df75d6834aa73ebbeb13c5c1102c88367d9dd017086c5ad01910ff3e95915716606898ae7f7649ee63605a19b
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ Blueprint details
2
+
3
+ # One reserved word:
4
+ # ANYTHING.
5
+
6
+ # Maybe others are necessary?
7
+ # ANYTHING_MATCHING("?"). # will cast symbols and strings
8
+ # CONDITION. a predicate method. will be tested against all children, considered true if it passes.
9
+
10
+ Todo:
11
+
12
+ * Implement SyntaxTree.include?
13
+ * Fix StatusBoxView bug - the terminal line isn't clearing properly.
14
+ * Fill out README.
15
+ * Test various SyntaxTree inclusions.
data/bin/toolbus ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'toolbus'
4
+
5
+ Toolbus.new.scan
data/lib/toolbus.rb ADDED
@@ -0,0 +1,93 @@
1
+ require 'json/pure'
2
+ require 'open-uri'
3
+ require_relative './views.rb'
4
+ require_relative './utils.rb'
5
+
6
+ TOOLBUS_ROOT = File.join(`gem which toolbus`.chomp.chomp("/lib/toolbus.rb"))
7
+
8
+ class Toolbus
9
+ include GitUtils
10
+
11
+ def initialize
12
+ validate_repo
13
+ @features = fetch_features
14
+ end
15
+
16
+ def validate_repo
17
+ ConsoleManager.error "Unpushed commits! Push or stash before running." unless latest_commit_online?
18
+ ConsoleManager.error "Uncommitted changes! Stash or commit and push before running." unless git_status_clean?
19
+ end
20
+
21
+ def fetch_features
22
+ # TODO: GET all features for our tools and versions, once that API exists
23
+ JSON.parse(File.read(File.open(File.join(TOOLBUS_ROOT, 'spec/fixture/sample.json'))))['data']
24
+ end
25
+
26
+ SCAN_TIME_SECONDS = 4.0
27
+ def scan
28
+ statuses = []
29
+ progress = 0.0
30
+ @features.map { |feature| feature.default = 0 } # helps measure progress
31
+ num_steps = scanning_plan.inject(0) { |total, (file, blueprints)| total + blueprints.length }
32
+
33
+ scanning_plan.each_with_index do |(file, search_for), file_index|
34
+ statuses << "Scanning #{file}"
35
+ search_for.each do |search_for|
36
+ id = search_for.keys.first
37
+ blueprint = search_for.values.first
38
+
39
+ progress += 1
40
+ begin
41
+ match = SyntaxTree.new(file).include?(SyntaxTree.new(blueprint))
42
+ rescue Parser::SyntaxError
43
+ statuses << "Syntax Error: #{file}"
44
+ next
45
+ end
46
+
47
+ if match
48
+ feature = @features.find { |feature| feature['id'] == id }
49
+ feature['count'] += 1
50
+ statuses << "POST /completions: repo_url: #{repo_url}, feature_id: #{id}, commit: ???, filename: #{file}, first_line: #{match[:first_line]}, last_line: #{match[:last_line]}"
51
+ end
52
+
53
+ percent_complete = (progress / num_steps) * 100
54
+ ConsoleManager.repaint([
55
+ ProgressBarView.new(percent_complete),
56
+ TableView.new(features_found),
57
+ StatusBoxView.new(statuses),
58
+ "Found #{num_completions} total completions across #{num_features_completed}/#{@features.count} features across #{file_index}/#{scanning_plan.count} files!"
59
+ ])
60
+ sleep SCAN_TIME_SECONDS / num_steps
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def scanning_plan
68
+ hash_with_array_values = Hash.new { |h, k| h[k] = [] }
69
+
70
+ @features.inject(hash_with_array_values) do |plan, feature|
71
+ Dir.glob(feature['search_in']).each do |file|
72
+ plan[file] << { feature['id'] => feature['search_for'] }
73
+ end
74
+ plan
75
+ end
76
+ end
77
+
78
+ def feature_module_and_name(feature)
79
+ [feature['module'], ': ', feature['name']].join
80
+ end
81
+
82
+ def features_found
83
+ @features.map { |feature| [feature_module_and_name(feature), feature['count']] }.to_h
84
+ end
85
+
86
+ def num_completions
87
+ @features.inject(0) { |total, feature| total + feature['count'] }
88
+ end
89
+
90
+ def num_features_completed
91
+ @features.select { |feature| feature['count'] > 0 }.count
92
+ end
93
+ end
data/lib/utils.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'parser/current'
2
+
3
+ class SyntaxTree
4
+ def initialize(ruby)
5
+ # todo: turn on
6
+ # @ast = Parser::CurrentRuby.parse(ruby)
7
+ end
8
+
9
+ def include?(smaller_ast)
10
+ # TODO: find if smaller_ast is a subset of self.
11
+ rand < 0.4 ? { first_line: 10, last_line: 12 } : nil
12
+ end
13
+ end
14
+
15
+ module GitUtils
16
+ attr_reader :repo_url, :head_sha
17
+
18
+ def git_status_clean?
19
+ `git status -s`.length == 0
20
+ end
21
+
22
+ def latest_commit_online?
23
+ `git log --oneline origin/master..HEAD`.length == 0
24
+ end
25
+
26
+ def repo_url
27
+ @repo_url ||= `git config --get remote.origin.url`.gsub('git@github.com:', '').chomp
28
+ end
29
+
30
+ def head_sha
31
+ @head_sha ||= `git rev-parse HEAD`
32
+ end
33
+ end
34
+
35
+ # Pulled from ActionSupport.
36
+ class String
37
+ def truncate(truncate_at, options = {})
38
+ return dup unless length > truncate_at
39
+
40
+ omission = options[:omission] || '...'
41
+ length_with_room_for_omission = truncate_at - omission.length
42
+ stop = \
43
+ if options[:separator]
44
+ rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
45
+ else
46
+ length_with_room_for_omission
47
+ end
48
+
49
+ "#{self[0, stop]}#{omission}"
50
+ end
51
+ end
data/lib/views.rb ADDED
@@ -0,0 +1,74 @@
1
+ module TerminalUtils
2
+ SCREEN_WIDTH = [`tput cols`.to_i, 100].min
3
+ SAVE_CURSOR = `tput sc`
4
+ RESTORE_CURSOR = `tput rc`
5
+ ERASE_DISPLAY = `tput clear`
6
+ RED = `tput setaf 1`
7
+ GREEN = `tput setaf 2`
8
+ RESET = `tput sgr0`
9
+ end
10
+
11
+ print TerminalUtils::SAVE_CURSOR
12
+
13
+ class ConsoleManager
14
+ include TerminalUtils
15
+
16
+ def self.repaint(rows)
17
+ print RESTORE_CURSOR
18
+ num_rows = rows.flat_map(&:to_s).length
19
+ rows.each { |row| puts row.to_s; puts }
20
+ end
21
+
22
+ def self.error(message)
23
+ puts RED + message + RESET
24
+ exit
25
+ end
26
+ end
27
+
28
+ class ProgressBarView
29
+ include TerminalUtils
30
+ EXTRA_CHARS_OFFSET = 8 # brackets, % completion
31
+ USABLE_WIDTH = SCREEN_WIDTH - EXTRA_CHARS_OFFSET
32
+
33
+ def initialize(percent)
34
+ @percent = Float([percent, 100.0].min)
35
+ end
36
+
37
+ def to_s
38
+ complete = '#' * ((@percent / 100.0) * USABLE_WIDTH).to_i
39
+ incomplete = ' ' * (USABLE_WIDTH - complete.length)
40
+ [GREEN, ' [', complete, incomplete, ']', (@percent.to_i.to_s + '%').rjust(5), RESET].join
41
+ end
42
+ end
43
+
44
+ class TableView
45
+ include TerminalUtils
46
+
47
+ def initialize(map)
48
+ @map = map
49
+ end
50
+
51
+ def to_s
52
+ @map.map do |(key, count)|
53
+ color = count > 0 ? GREEN : RED
54
+ done = count > 0 ? "✓" : " "
55
+ description = key.truncate(SCREEN_WIDTH - 4).ljust(SCREEN_WIDTH - 3)
56
+ [color, done, ' ', description, count.to_s, RESET].join
57
+ end
58
+ end
59
+ end
60
+
61
+ class StatusBoxView
62
+ include TerminalUtils
63
+
64
+ def initialize(statuses)
65
+ @statuses = statuses
66
+ end
67
+
68
+ def to_s
69
+ puts '-' * SCREEN_WIDTH
70
+ puts
71
+ num_lines = @statuses.length
72
+ @statuses.last(8).fill(num_lines, 8 - num_lines) { '' }
73
+ end
74
+ end
@@ -0,0 +1,87 @@
1
+ {
2
+ "data": [{
3
+ "type": "feature",
4
+ "id": 1,
5
+ "name": "Optimistic locking",
6
+ "module": "ActiveRecord::Locking",
7
+ "search_in": "app/models/**/*rb",
8
+ "search_for": "locking_column = :ANYTHING"
9
+ }, {
10
+ "type": "feature",
11
+ "id": 2,
12
+ "name": "Pessimistic locking",
13
+ "module": "ActiveRecord::Locking",
14
+ "search_in": "app/**/*.rb",
15
+ "search_for": "lock! OR with_lock"
16
+ }, {
17
+ "type": "feature",
18
+ "id": 3,
19
+ "name": "Transactions",
20
+ "module": "ActiveRecord::Transactions",
21
+ "search_in": "app/**/*.rb",
22
+ "search_for": "class ANYTHING < ActiveRecord::Base\nend"
23
+ }, {
24
+ "type": "feature",
25
+ "id": 4,
26
+ "name": "Single table inheritance",
27
+ "module": "ActiveRecord::Transactions",
28
+ "search_in": "app/models/**/*rb",
29
+ "search_for": "ANYTHING < ActiveRecord::Base\nend"
30
+ }, {
31
+ "type": "feature",
32
+ "id": 5,
33
+ "name": "Alternative ActiveRecord initialization patterns",
34
+ "module": "ActiveRecord::Base",
35
+ "search_in": "app/**/*.rb",
36
+ "search_for": "ANYTHING.new {}"
37
+ }, {
38
+ "type": "feature",
39
+ "id": 6,
40
+ "name": "Conditions",
41
+ "module": "ActiveRecord::Base",
42
+ "search_in": "in AP,P",
43
+ "search_for": "where(ANYTHING_MATCHING('?'))"
44
+ }, {
45
+ "type": "feature",
46
+ "id": 7,
47
+ "name": "Saving arrays, hashes, and other non-mappable objects in text columns",
48
+ "module": "ActiveRecord::AttributeMethods::Serialization",
49
+ "search_in": "app/models/**/*rb",
50
+ "search_for": "serialize :ANYTHING"
51
+ }, {
52
+ "type": "feature",
53
+ "id": 8,
54
+ "name": "Connection to multiple databases in different models",
55
+ "module": "ActiveRecord::Base",
56
+ "search_in": "app/**/*.rb",
57
+ "search_for": "MyModel.connection OR ActiveRecord::Base.connection"
58
+ }, {
59
+ "type": "feature",
60
+ "id": 9,
61
+ "name": "Connection to MongoDB",
62
+ "module": "ActiveJob::Enqueuing",
63
+ "search_in": "app/models/**/*rb",
64
+ "search_for": "include Mongoid::Document"
65
+ }, {
66
+ "type": "feature",
67
+ "id": 10,
68
+ "name": "ActiveJob::Enqueueing#enqueue",
69
+ "module": "ActiveJob::Enqueuing",
70
+ "search_in": "app/**/*.rb",
71
+ "search_for": "enqueue"
72
+ }, {
73
+ "type": "feature",
74
+ "id": 11,
75
+ "name": "ActiveJob::Enqueueing#retry_job",
76
+ "module": "ActiveSupport::CoreExt",
77
+ "search_in": "app/jobs/*.rb",
78
+ "search_for": "retry_job"
79
+ }, {
80
+ "type": "feature",
81
+ "id": 12,
82
+ "name": "Benchmarking",
83
+ "module": "ActiveRecord::Inheritance",
84
+ "search_in": "app/**/*.rb",
85
+ "search_for": "Benchmark.ms"
86
+ }]
87
+ }
@@ -0,0 +1,62 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'toolbus'
5
+
6
+ RSpec.configure do |config|
7
+
8
+ # These options will default to `true` in RSpec 4
9
+ config.expect_with :rspec do |expectations|
10
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
11
+ end
12
+
13
+ config.mock_with :rspec do |mocks|
14
+ mocks.verify_partial_doubles = true
15
+ end
16
+
17
+ # These two settings work together to allow you to limit a spec run
18
+ # to individual examples or groups you care about by tagging them with
19
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
20
+ # get run.
21
+ # config.filter_run :focus
22
+ # config.run_all_when_everything_filtered = true
23
+
24
+ # Limits the available syntax to the non-monkey patched syntax that is
25
+ # recommended. For more details, see:
26
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
27
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
28
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
29
+ config.disable_monkey_patching!
30
+
31
+ # This setting enables warnings. It's recommended, but in some cases may
32
+ # be too noisy due to issues in dependencies.
33
+ config.warnings = true
34
+
35
+ # Many RSpec users commonly either run the entire suite or an individual
36
+ # file, and it's useful to allow more verbose output when running an
37
+ # individual spec file.
38
+ if config.files_to_run.one?
39
+ # Use the documentation formatter for detailed output,
40
+ # unless a formatter has already been configured
41
+ # (e.g. via a command-line flag).
42
+ config.default_formatter = 'doc'
43
+ end
44
+
45
+ # Print the 10 slowest examples and example groups at the
46
+ # end of the spec run, to help surface which specs are running
47
+ # particularly slow.
48
+ # config.profile_examples = 10
49
+
50
+ # Run specs in random order to surface order dependencies. If you find an
51
+ # order dependency and want to debug it, you can fix the order by providing
52
+ # the seed, which is printed after each run.
53
+ # --seed 1234
54
+ # config.order = :random
55
+
56
+ # Seed global randomization in this process using the `--seed` CLI option.
57
+ # Setting this allows you to use `--seed` to deterministically reproduce
58
+ # test failures related to randomization by passing the same `--seed` value
59
+ # as the one that triggered the failure.
60
+ # Kernel.srand config.seed
61
+
62
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Toolbus do
4
+ pending "write this"
5
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toolbus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jason Benn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json_pure
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.10'
69
+ description: Scans and parses clean git repos, figures out how much of an API you've
70
+ used
71
+ email: jasoncbenn@gmail.com
72
+ executables:
73
+ - toolbus
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - README.md
78
+ - bin/toolbus
79
+ - lib/toolbus.rb
80
+ - lib/utils.rb
81
+ - lib/views.rb
82
+ - spec/fixture/sample.json
83
+ - spec/spec_helper.rb
84
+ - spec/toolbus_spec.rb
85
+ homepage: http://www.jbenn.net
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.4.5
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: 'To learn a tool: build projects, measure your progress with Toolbus'
109
+ test_files:
110
+ - spec/fixture/sample.json
111
+ - spec/spec_helper.rb
112
+ - spec/toolbus_spec.rb
113
+ has_rdoc: