toolbus 0.0.2

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
+ 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: