invoca_gems 0.1.0

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: 6a2b87073e43fb10edbab421db7aa4c230ce7dda
4
+ data.tar.gz: bdc0d31c950d2b5eabe2cc4c591dc740160ff688
5
+ SHA512:
6
+ metadata.gz: dbf0d9e1a6301fa42bb871355e582226fecc8b6c9321028390ab714ae5e5f986acd2a1edf486075b166c1efc95c5d47bf2665f1f50eac67f15cda8c4bc104d75
7
+ data.tar.gz: 940827af82abd909c66ebf1949e788567c946be8b24e2fb8c6366aa366d3c81df6b70bab3f21596c1a2479cff7e463d09acda431ab863876fbde1dfe9bdfb603
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .idea
data/.ruby_version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in invoca_gems.gemspec
4
+ gemspec
5
+
6
+ # These are used for testing.
7
+ gem 'invoca-utils', '0.0.2', git: 'git@github.com:Invoca/invoca-utils', ref: 'c508647bc8adcba14e9cb86e03a6721af83d0195'
8
+
9
+ gem 'test_source_gem', path: 'test/fixtures/test_source_gem'
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ GIT
2
+ remote: git@github.com:Invoca/invoca-utils
3
+ revision: c508647bc8adcba14e9cb86e03a6721af83d0195
4
+ ref: c508647bc8adcba14e9cb86e03a6721af83d0195
5
+ specs:
6
+ invoca-utils (0.0.2)
7
+
8
+ PATH
9
+ remote: .
10
+ specs:
11
+ invoca_gems (0.1.0)
12
+
13
+ PATH
14
+ remote: test/fixtures/test_source_gem
15
+ specs:
16
+ test_source_gem (0.1.0)
17
+
18
+ GEM
19
+ remote: https://rubygems.org/
20
+ specs:
21
+ coderay (1.1.1)
22
+ method_source (0.8.2)
23
+ minitest (5.8.4)
24
+ pry (0.10.3)
25
+ coderay (~> 1.1.0)
26
+ method_source (~> 0.8.1)
27
+ slop (~> 3.4)
28
+ rake (10.5.0)
29
+ slop (3.6.0)
30
+ thor (0.19.1)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ bundler (~> 1.8)
37
+ invoca-utils (= 0.0.2)!
38
+ invoca_gems!
39
+ minitest
40
+ pry
41
+ rake (~> 10.0)
42
+ test_source_gem!
43
+ thor
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # InvocaGems
2
+
3
+ Makes working on the Invoca gems used by applications easier.
4
+
5
+ ## Lighting Talk Intro
6
+ Watch it here and count how many times I say "uuum"! (Answer: lots)
7
+
8
+ [Invoca Gem Lightning Talk](https://drive.google.com/a/invoca.com/file/d/0BzmD8uReM33rRi0ydTBiQlRmMzA/view)
9
+
10
+ ## Workflow
11
+ The normal workflow would go something like this....
12
+
13
+ ### 1) Create a branch on the app-repo
14
+ Start in the repo for the application that needs the changes. (For example web or pnapi). Create a branch for your story changes.
15
+
16
+ ### 2) Open the gem for editing
17
+ I am using invoca-metrics in this example.
18
+
19
+ ```
20
+ bundle exec invoca_gem edit invoca-metrics
21
+ ```
22
+ This will do the following:
23
+ * check out the invoca-metrics repo into invoca_gems/invoca_metrics.
24
+ * create a new branch on that repo starting at the sha from the app-repo Gemfile. The branch will use the same name as the app-repo.
25
+ * update the app-repo Gemfile to refer to the invoca metrics gem using a path.
26
+
27
+ ### 3) Make your gem changes
28
+ At this point you can edit the invoca-metrics gem from the invoca_gems/invoca_metrics directory. You can confirm your changes work on the app-repo by running tests there.
29
+
30
+ ### 4) Save your gem changes
31
+ When ready, save your changes to the invoca_metrics gem.
32
+
33
+ ```
34
+ bundle exec invoca_gem save invoca-metrics --commit-message 'made some changes'
35
+ ```
36
+ This can take a little while, so brace for it. This will do the following:
37
+ * Commit your changes to the invoca_metrics gem and push them to github.
38
+ * Update the app-repo Gemfile for the new version and sha of the invoca_metrics gem.
39
+ * Update any other invoca gems that depend invoca_metrics. For each of these gems it will:
40
+ * Pull down the gem and create a branch at the sha from the app-repo.
41
+ * Update the gem version of invoca_metrics in the gemspec.
42
+ * Update the sha in the Gemfile for the dependent gem.
43
+ * Bundle install to pick up the changes. (Takes a while.)
44
+ * Commit the changes and push to github.
45
+ * Update the app-repo gem to refer to the new sha.
46
+ * Bundle install the app-repo to pick up all the changes.
47
+
48
+ Whew.
49
+
50
+ ### 5) Create pull requests
51
+ When ready you can view the changes and issue pull requests from github using the following command.
52
+
53
+ ```
54
+ bundle exec invoca_gem show_changes
55
+ ```
56
+
57
+ This will open a browser window showing all of the changed gems. From there you can open pull requests and confirm build results.
58
+
59
+
60
+ ## Other commands
61
+
62
+ Run the following to find available commands.
63
+ ```
64
+ bundle exec invoca_gem
65
+ ```
66
+
67
+ ## Installation
68
+
69
+ Add this line to your application's Gemfile as a development dependency:
70
+
71
+ ```ruby
72
+ group :development do
73
+ gem 'invoca_gems'
74
+ end
75
+ ```
76
+
77
+ And then execute:
78
+
79
+ $ bundle
80
+
81
+
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ namespace :test do
6
+ Rake::TestTask.new do |t|
7
+ t.name = :unit
8
+ t.libs.push "lib"
9
+ t.test_files = FileList['test/unit/*_test.rb','test/unit/*/*_test.rb']
10
+ t.verbose = true
11
+ end
12
+
13
+ end
14
+
15
+ task :default => 'test:unit'
16
+
17
+
data/exe/invoca_gem ADDED
@@ -0,0 +1,3 @@
1
+ require "invoca_gems"
2
+ InvocaGems::Cli.start(ARGV)
3
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'invoca_gems/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "invoca_gems"
8
+ spec.version = InvocaGems::VERSION
9
+ spec.authors = ["Bob Smith"]
10
+ spec.email = ["bob@ringrevenue.com"]
11
+
12
+ spec.summary = "help manage invoca_gems"
13
+ spec.description = "manage invoca gems as if they were in-app gems"
14
+ spec.homepage = "http://www.invoca.com"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.8"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "pry"
25
+ spec.add_development_dependency "thor"
26
+ end
@@ -0,0 +1,18 @@
1
+ require "pry"
2
+ require "invoca_gems/version"
3
+ require "invoca_gems/presenter"
4
+ require "invoca_gems/shell_command"
5
+ require "invoca_gems/bundler"
6
+ require "invoca_gems/gem"
7
+ require "invoca_gems/git_repo"
8
+ require "invoca_gems/gemfile_line"
9
+ require "invoca_gems/gemfile"
10
+ require "invoca_gems/gemspec_line"
11
+ require "invoca_gems/gemspec"
12
+
13
+ require "invoca_gems/cli"
14
+
15
+ module InvocaGems
16
+ end
17
+
18
+
@@ -0,0 +1,88 @@
1
+ class InvocaGems::Bundler
2
+
3
+ GitSource = Struct.new(:name, :uri, :ref, :version)
4
+ def find_git_source(gem_name)
5
+ if source = definition.send(:sources).git_sources.find { |gs| source_name(gs) == gem_name }
6
+ GitSource.new(source_name(source), source.uri, source.ref, source.version)
7
+ end
8
+ end
9
+
10
+ PathSource = Struct.new(:name, :path, :version)
11
+ def find_path_source(gem_name)
12
+ if (spec = find_spec(gem_name)) && spec.source.respond_to?(:path)
13
+ PathSource.new(spec.name, spec.source.path, spec.version.to_s)
14
+ end
15
+ end
16
+
17
+ def dependants(gem_name)
18
+ dependant_hash[gem_name]
19
+ end
20
+
21
+ def dependencies_yuml_url
22
+ "http://yuml.me/diagram/class/#{URI::encode(invoca_dependencies.join(','))}"
23
+ end
24
+
25
+ def invoca_source_names
26
+ @invoca_source_names ||= definition.send(:sources).all_sources.map do |source|
27
+ if is_invoca_github_source?(source) || is_edited_source?(source)
28
+ source_name(source)
29
+ end
30
+ end.compact
31
+ end
32
+
33
+
34
+ private
35
+
36
+ def source_name(source)
37
+ # bundler replaces the name on the source with the github name, but the
38
+ # @name variable is still set with no other way to get to it.
39
+ source.instance_eval("@name") || source.name
40
+ end
41
+
42
+
43
+ def is_invoca_github_source?(source)
44
+ source.respond_to?(:uri) && source.uri =~ /Invoca/
45
+ end
46
+
47
+ def is_edited_source?(source)
48
+ source.respond_to?(:path) && source.path.to_s =~ /\Ainvoca_gems\//
49
+ end
50
+
51
+ def find_spec(gem_name)
52
+ definition.specs.select{ |s| s.name == gem_name }.first
53
+ end
54
+
55
+ def definition
56
+ @definition ||= Bundler.definition
57
+ end
58
+
59
+ def dependant_hash
60
+ unless @dependants
61
+ @dependants = {}
62
+ definition.specs.each do |spec|
63
+ spec.dependencies.each do |dependancy|
64
+ @dependants[dependancy.name] ||= []
65
+ @dependants[dependancy.name] << spec.name
66
+ end
67
+ end
68
+ @dependants
69
+ end
70
+ end
71
+
72
+
73
+ def invoca_dependencies
74
+ edges = []
75
+ definition.specs.each do |spec|
76
+ if invoca_source_names.include?(spec.name)
77
+ edges << "[#{spec.name}|#{spec.version}]"
78
+ spec.dependencies.each do |dep|
79
+ if invoca_source_names.include?(dep.name)
80
+ edges << "[#{spec.name}]->[#{dep.name}]"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ edges.uniq
86
+ end
87
+ end
88
+
@@ -0,0 +1,102 @@
1
+ require "thor"
2
+ require "open-uri"
3
+ require 'bundler'
4
+
5
+ class InvocaGems::Cli < Thor
6
+
7
+ desc "graph", "show graph of gem dependencies"
8
+ def graph
9
+ InvocaGems::Presenter.with_presenter do |presenter|
10
+ presenter.message "Generating Dependency Graph"
11
+ bw = InvocaGems::Bundler.new
12
+ `open #{bw.dependencies_yuml_url}`
13
+ end
14
+ end
15
+
16
+ desc "edit GEM", "configure a gem for local editing"
17
+ def edit(gem_name)
18
+ InvocaGems::Presenter.with_presenter do |presenter|
19
+ presenter.set_task "Configuring #{gem_name}"
20
+ gem = InvocaGems::Gem.new(gem_name, presenter)
21
+ gem.edit
22
+ presenter.set_subtask("done")
23
+ presenter.message("#{gem_name} is now available at #{gem.repo_directory}")
24
+ end
25
+ end
26
+
27
+ desc "save GEM", "save gem changes"
28
+ option :commit_message
29
+ def save(gem_name)
30
+ InvocaGems::Presenter.with_presenter do |presenter|
31
+ presenter.set_task "Saving changes to #{gem_name}"
32
+ gem = InvocaGems::Gem.new(gem_name, presenter)
33
+ gem.save(options[:commit_message])
34
+ presenter.message "Done"
35
+ end
36
+ end
37
+
38
+ desc "update GEM VERSION SHA", "updates a gem"
39
+ def update(gem_name, version, sha)
40
+ InvocaGems::Presenter.with_presenter do |presenter|
41
+ gem = InvocaGems::Gem.new(gem_name, presenter)
42
+ gem.update_version(version, sha)
43
+ end
44
+ end
45
+
46
+ desc "has_unsaved_changes GEM", "reports if the gem has changes"
47
+ def has_unsaved_changes(gem_name)
48
+ InvocaGems::Presenter.with_presenter do |presenter|
49
+ gem = InvocaGems::Gem.new(gem_name, presenter)
50
+ if gem.git_repo.unsaved_changes?
51
+ presenter.message "found unsaved changes"
52
+ else
53
+ presenter.message "no unsaved changes"
54
+ end
55
+ end
56
+ end
57
+
58
+ desc "show_changes", "show committed and pushed changes for the gem"
59
+ def show_changes
60
+ InvocaGems::Presenter.with_presenter do |presenter|
61
+ bw = InvocaGems::Bundler.new
62
+ bw.invoca_source_names.each do |gem_name|
63
+ presenter.set_task("#{gem_name}")
64
+ gem = InvocaGems::Gem.new(gem_name, presenter)
65
+ if gem.repo_exists? && gem.git_repo.branch_exists?(gem.local_branch_name)
66
+ # TODO - check for differences with master
67
+ url = gem.uri.gsub("git@github.com:", "https://github.com/").gsub(".git","") + "/compare/#{gem.local_branch_name}"
68
+ `open #{url}`
69
+ presenter.set_subtask(url)
70
+ else
71
+ presenter.clear_task(keep: false)
72
+ end
73
+ end
74
+ presenter.message("done")
75
+ end
76
+ end
77
+
78
+
79
+ desc "list", "display the available invoca gems"
80
+ def list
81
+ InvocaGems::Presenter.with_presenter do |presenter|
82
+ presenter.silence do
83
+ format = " %-30s %10s %-60s"
84
+ bw = InvocaGems::Bundler.new
85
+ puts
86
+ puts format % ["Name", "Version", "Status"]
87
+ puts format % ["="*30, "="*10, "="*60]
88
+ bw.invoca_source_names.each do |gem_name|
89
+ gem = InvocaGems::Gem.new(gem_name, presenter)
90
+ status =
91
+ if gem.path
92
+ presenter.using_mode( :green, "open for editing at #{gem.path}" )
93
+ else
94
+ "#{gem.sha[0..6]} for #{gem.uri}"
95
+ end
96
+ puts format % [gem.name, gem.version, status]
97
+ end
98
+ puts
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,225 @@
1
+ class InvocaGems::Gem
2
+ WORKING_DIRECTORY = "invoca_gems"
3
+
4
+ include InvocaGems::ShellCommand
5
+
6
+ attr_reader :name, :uri, :sha, :version, :used_by, :dependants, :path, :branch, :presenter
7
+
8
+ def initialize(gem_name, presenter, defer_bundle: false)
9
+ @presenter = presenter
10
+ bundle_wrapper = InvocaGems::Bundler.new
11
+ try_load_from_git_source(bundle_wrapper, gem_name)
12
+ try_load_from_path_source(bundle_wrapper, gem_name)
13
+ @name or presenter.stop_with_message("Could not find gem #{gem_name}")
14
+ @dependants = bundle_wrapper.dependants(gem_name)
15
+ @defer_bundle = defer_bundle
16
+ end
17
+
18
+ def edit
19
+ configure_branch
20
+ update_gemfile_for_source
21
+ bundle_install_application
22
+ end
23
+
24
+ def save(commit_message=nil)
25
+ if git_repo.unsaved_changes?
26
+ if commit_message
27
+ commit_changes(commit_message)
28
+ else
29
+ presenter.stop_with_message "unsaved changes found in #{repo_directory}"
30
+ end
31
+ else
32
+ git_repo.push
33
+ end
34
+
35
+ update_gemfile_for_github(gemfile_path: "Gemfile",
36
+ gem_name: name,
37
+ gem_uri: uri,
38
+ new_version: version,
39
+ new_sha: git_repo.sha)
40
+
41
+ presenter.set_subtask("now at #{version} #{git_repo.sha[0..6]}")
42
+ update_dependencies(name, version, git_repo.sha)
43
+ bundle_install_application
44
+ end
45
+
46
+ def update_version(new_version, new_sha)
47
+ presenter.set_subtask "updating #{name} to #{new_version} #{new_sha}"
48
+ update_gemfile_for_github(
49
+ gemfile_path: "Gemfile",
50
+ gem_name: name,
51
+ gem_uri: uri,
52
+ new_version: new_version,
53
+ new_sha: new_sha)
54
+ update_dependencies(name, new_version, new_sha)
55
+ bundle_install_application
56
+ end
57
+
58
+ def update_dependencies(gem_to_update, version, sha)
59
+ if dependants
60
+ dependants.uniq.each do |dependant|
61
+ presenter.set_task "Updating #{dependant} for #{gem_to_update}"
62
+ gem = self.class.new(dependant, presenter, defer_bundle: true)
63
+ gem.edit
64
+
65
+ # Dependant gem update
66
+ gem.update_gemfile_for_github(
67
+ gemfile_path: gem.repo_directory + '/Gemfile',
68
+ gem_name: name,
69
+ gem_uri: uri,
70
+ new_version: version,
71
+ new_sha: sha)
72
+
73
+ # Project gem update
74
+ gem.update_gemfile_for_github(
75
+ gemfile_path: gem.repo_directory + '/Gemfile',
76
+ gem_name: name,
77
+ gem_uri: uri,
78
+ new_version: version,
79
+ new_sha: sha)
80
+
81
+ gem.update_gem_gemspec(gem.repo_directory + "/#{gem.name}.gemspec", gem_to_update, version)
82
+ gem.bundle_install_gem
83
+ gem.commit_changes("Update #{gem_to_update} to #{version} (#{sha[0..6]})")
84
+ gem.save # Will nest dependency upudate
85
+ end
86
+ end
87
+ end
88
+
89
+ def update_gem_gemspec(gemspec_path, gem_name, version)
90
+ presenter.set_subtask "updating #{gemspec_path} for #{gem_name}"
91
+ gemspec = InvocaGems::Gemspec.new(gemspec_path)
92
+ gemspec_gem = gemspec.find(gem_name)
93
+ gemspec_gem.update_version(version)
94
+ gemspec_gem.save!
95
+ end
96
+
97
+ def commit_changes(message)
98
+ git_repo.commit("#{local_branch_name} #{message}")
99
+ git_repo.push
100
+ end
101
+
102
+ def update_gemfile_for_github(gemfile_path:, gem_name:, gem_uri:, new_version:, new_sha:)
103
+ presenter.set_subtask "updating #{gemfile_path} for #{gem_name}"
104
+ gemfile = InvocaGems::Gemfile.new(gemfile_path)
105
+ gemfile_gem = gemfile.find(gem_name, allow_missing: true) || gemfile.append(gem_name)
106
+ gemfile_gem.update_as_github(new_version, gem_uri, new_sha)
107
+ gemfile_gem.save!
108
+ end
109
+
110
+
111
+ def repo_directory
112
+ WORKING_DIRECTORY + '/' + name
113
+ end
114
+
115
+ def repo_exists?
116
+ File.directory?(repo_directory)
117
+ end
118
+
119
+ def git_repo
120
+ create_working_directory
121
+ unless @git_repo
122
+ unless repo_exists?
123
+ presenter.set_subtask "creating #{repo_directory} from #{uri}"
124
+ InvocaGems::GitRepo.clone(WORKING_DIRECTORY, uri, name, presenter: presenter)
125
+ end
126
+ @git_repo = repo_for_path(repo_directory)
127
+ @git_repo.fetch
128
+ end
129
+ @git_repo
130
+ end
131
+
132
+ def repo_for_path(path)
133
+ InvocaGems::GitRepo.new(presenter, path)
134
+ end
135
+
136
+ def configure_branch
137
+ git_repo.unsaved_changes? and presenter.stop_with_message "Unsaved changes found in #{repo_directory}"
138
+ if !git_repo.branch_exists?(local_branch_name)
139
+ presenter.set_subtask "Creating branch #{local_branch_name}"
140
+ git_repo.create_branch_at(local_branch_name, sha)
141
+ end
142
+ presenter.set_subtask "Using branch #{local_branch_name}"
143
+ git_repo.checkout_branch(local_branch_name)
144
+
145
+ if git_repo.sha != sha
146
+ presenter.stop_with_message "Commited work not found in gem, use -- mumble mumble to fix."
147
+ end
148
+ end
149
+
150
+ def update_gemfile_for_source
151
+ gemfile = InvocaGems::Gemfile.new('Gemfile')
152
+ gemfile_gem = gemfile.find(name)
153
+ gemfile_gem.update_as_path(repo_directory)
154
+ gemfile_gem.save!
155
+ end
156
+
157
+ def local_branch_name
158
+ @local_branch_name ||= repo_for_path('.').branch
159
+ end
160
+
161
+ def try_load_from_git_source(bundle_wrapper, gem_name)
162
+ if result = bundle_wrapper.find_git_source(gem_name)
163
+ @name = result.name
164
+ @uri = result.uri
165
+ @version = result.version
166
+ @sha = result.ref
167
+ end
168
+ end
169
+
170
+ def try_load_from_path_source(bundle_wrapper, gem_name)
171
+ if !@name && result = bundle_wrapper.find_path_source(gem_name)
172
+ @name = result.name
173
+ @path = result.path
174
+ @version = result.version
175
+ get_git_statistics_from_path(path)
176
+ end
177
+ end
178
+
179
+ def get_git_statistics_from_path(path)
180
+ repo = repo_for_path(path)
181
+ @uri = repo.uri
182
+ @sha = repo.sha
183
+ @branch = repo.branch
184
+ end
185
+
186
+ def configure_appilication
187
+ unless @application_configured
188
+ create_working_directory
189
+ @application_configured = true
190
+ end
191
+ end
192
+
193
+ def create_working_directory
194
+ if File.read(".gitignore") !~ /#{WORKING_DIRECTORY}/m
195
+ presenter.set_subtask "updating .gitignore"
196
+ File.open(".gitignore","a") { |f| f.write("# Invoca Gem support\n#{WORKING_DIRECTORY}/**\n") }
197
+ end
198
+
199
+ if !File.directory?(WORKING_DIRECTORY)
200
+ presenter.set_subtask "creating #{WORKING_DIRECTORY}"
201
+ Dir.mkdir(WORKING_DIRECTORY)
202
+ end
203
+ end
204
+
205
+ def bundle_install_application
206
+ unless @defer_bundle
207
+ without_ruby_options do
208
+ shell_command(command: "bundle install --quiet", presenter: presenter)
209
+ end
210
+ end
211
+ end
212
+
213
+ def bundle_install_gem
214
+ without_ruby_options do
215
+ shell_command(command: "bundle install --quiet", path: repo_directory, presenter: presenter)
216
+ end
217
+ end
218
+
219
+ def without_ruby_options
220
+ old_opt = ENV.delete("RUBYOPT")
221
+ yield
222
+ ensure
223
+ ENV["RUBYOPT"] = old_opt
224
+ end
225
+ end
@@ -0,0 +1,34 @@
1
+ class InvocaGems::Gemfile
2
+ def initialize(path)
3
+ @path = path
4
+ @lines = File.readlines(path).map(&:chomp)
5
+ end
6
+
7
+ def find(gem_name, allow_missing: false)
8
+ if index = @lines.find_index { |l| l =~ /\A\s*gem\s+.*[\'\"]#{gem_name}[\'\"]/ }
9
+ InvocaGems::GemfileLine.new(self, @lines[index], index)
10
+ else
11
+ allow_missing or InvocaGems::Presenter.stop_with_message "could not find gem #{gem_name}"
12
+ nil
13
+ end
14
+ end
15
+
16
+ def append(gem_name)
17
+ InvocaGems::GemfileLine.new(self, "gem '#{gem_name}'", -1)
18
+ end
19
+
20
+ def save!(gemspec)
21
+ update_gemspec(gemspec)
22
+ File.open(@path, "w") { |f| f.write(@lines.join("\n") + "\n") }
23
+ end
24
+
25
+ private
26
+
27
+ def update_gemspec(gemspec)
28
+ if gemspec.index == -1
29
+ @lines << gemspec.line
30
+ else
31
+ @lines = @lines[0, gemspec.index] + [gemspec.line] + @lines[gemspec.index+1..-1]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ class InvocaGems::GemfileLine
2
+ attr_reader :gemfile, :line, :index
3
+ attr_reader :gem_name, :gem_version, :gem_ref, :gem_git, :gem_github, :gem_options, :gem_options
4
+
5
+ def initialize(gemfile, line, index)
6
+ @gemfile = gemfile
7
+ @line = line
8
+ @index = index
9
+ @prefix = @line.match(/\A(\s*)gem/)[1]
10
+
11
+ instance_eval(line)
12
+ end
13
+
14
+ def save!
15
+ gemfile.save!(self)
16
+ end
17
+
18
+ def gem(name, *args)
19
+ @gem_name = name
20
+ @gem_options = args.last.is_a?(Hash) ? args.pop.dup : {}
21
+ @gem_version = args.first || ">= 0"
22
+ @gem_branch = @gem_options[:branch] || 'master'
23
+ @gem_ref = @gem_options.delete(:ref)
24
+ @gem_git = @gem_options.delete(:git)
25
+ @gem_github = @gem_options.delete(:github)
26
+ end
27
+
28
+ def update_as_path(path)
29
+ @line = [
30
+ "#{@prefix}gem ",
31
+ "'#{gem_name}',".ljust(24),
32
+ " path: '#{path}'"
33
+ ].join('')
34
+ end
35
+
36
+ # TODO - Perfer branch before ref.
37
+ # TODO - handle local branch...
38
+ def update_as_github(gem_version, uri, sha)
39
+ @line = [
40
+ "#{@prefix}gem ",
41
+ "'#{gem_name}',".ljust(24),
42
+ " '#{gem_version}', ".ljust(11),
43
+ "git: '#{uri}',".ljust(56),
44
+ "ref: '#{sha}'"
45
+ ].join('')
46
+ end
47
+ end
@@ -0,0 +1,22 @@
1
+ class InvocaGems::Gemspec
2
+ def initialize(path)
3
+ @path = path
4
+ @lines = File.readlines(path).map(&:chomp)
5
+ end
6
+
7
+ def find(gem_name)
8
+ index = @lines.find_index { |l| l =~ /\A\s*\S+\s+[\'\"]#{gem_name}[\'\"]/ } or InvocaGems::Presenter.stop_with_message "could not find gem #{gem_name} in #{@path}"
9
+ InvocaGems::GemspecLine.new(self, gem_name, @lines[index], index)
10
+ end
11
+
12
+ def save!(gemspec)
13
+ update_gemspec(gemspec)
14
+ File.open(@path, "w") { |f| f.write(@lines.join("\n") + "\n") }
15
+ end
16
+
17
+ private
18
+
19
+ def update_gemspec(gemspec)
20
+ @lines = @lines[0, gemspec.index] + [gemspec.line] + @lines[gemspec.index+1..-1]
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ class InvocaGems::GemspecLine
2
+ attr_reader :gemspec, :gem_name, :line, :index, :prefix
3
+
4
+ def initialize(gemspec, gem_name, line, index)
5
+ @gemspec = gemspec
6
+ @gem_name = gem_name
7
+ @line = line
8
+ @index = index
9
+ @prefix = @line.match(/\A(\s*\S+\s+)[\'\"]/)[1]
10
+ end
11
+
12
+ def save!
13
+ gemspec.save!(self)
14
+ end
15
+
16
+ def update_version(new_version)
17
+ @line = "#{@prefix}'#{gem_name}', '#{new_version}'"
18
+ end
19
+ end
@@ -0,0 +1,80 @@
1
+ require 'shellwords'
2
+ require 'pathname'
3
+
4
+ class InvocaGems::GitRepo
5
+ # Encapsulates running git commands in a given directory
6
+
7
+ include InvocaGems::ShellCommand
8
+ attr_accessor :presenter
9
+
10
+ def initialize(presenter, repo_root = nil)
11
+ @presenter = presenter
12
+ @repo_root = Pathname.new(repo_root || Dir.pwd)
13
+ end
14
+
15
+ def branch
16
+ git_cmd('rev-parse --abbrev-ref HEAD')
17
+ end
18
+
19
+ def sha
20
+ git_cmd("rev-parse --verify HEAD")
21
+ end
22
+
23
+ def uri
24
+ git_cmd("config --get remote.origin.url")
25
+ end
26
+
27
+ def fetch
28
+ git_cmd("fetch")
29
+ end
30
+
31
+ def commit(message)
32
+ git_cmd("add -A")
33
+ git_cmd("commit -m #{Shellwords.escape(message)}")
34
+ end
35
+
36
+ def is_ancestor_commit?(earlier_sha, current_sha)
37
+ git_cmd("merge-base --is-ancestor #{earlier_sha} #{current_sha}", raise_errors: false)
38
+ end
39
+
40
+
41
+ def branch_exists?(branch_name)
42
+ branch_name_escaped = Regexp.escape(branch_name)
43
+ if result = git_cmd("branch -a")
44
+ result.split("\n").any? do |branch_line|
45
+ branch_line =~ / #{branch_name_escaped}\z/ || branch_line =~ / remotes\/origin\/#{branch_name_escaped}\z/
46
+ end
47
+ end
48
+ end
49
+
50
+ def checkout_branch(branch_name)
51
+ git_cmd("checkout #{branch_name}")
52
+ end
53
+
54
+
55
+ def self.clone(root_directory, uri, directory_name, presenter:)
56
+ git_cmd(root_directory, "clone #{uri} #{directory_name}", presenter: presenter)
57
+ end
58
+
59
+ def unsaved_changes?
60
+ !git_cmd("diff --quiet", raise_errors: false) || !git_cmd("diff --quiet --cached", raise_errors: false)
61
+ end
62
+
63
+ def create_branch_at(branch_name, sha)
64
+ git_cmd("branch #{branch_name} #{sha}")
65
+ end
66
+
67
+ def push
68
+ git_cmd("push -u origin #{branch}")
69
+ end
70
+
71
+
72
+ def self.git_cmd(path, str, raise_errors: true, presenter: )
73
+ shell_command(command: "/usr/bin/git #{str}", path: path, raise_errors: raise_errors, presenter: presenter)
74
+ end
75
+
76
+ def git_cmd(str, raise_errors: true)
77
+ self.class.git_cmd(@repo_root, str, raise_errors: raise_errors, presenter: presenter)
78
+ end
79
+
80
+ end
@@ -0,0 +1,118 @@
1
+ class InvocaGems::Presenter
2
+ def set_task(message)
3
+ clear_task
4
+ @task = using_mode(:green, message)
5
+ set_subtask('')
6
+ end
7
+
8
+ def clear_task(keep: true)
9
+ if @task
10
+ if keep
11
+ @new_line_needed = false
12
+ puts
13
+ else
14
+ print reset_line
15
+ @new_line_needed = false
16
+ end
17
+ @task = nil
18
+ end
19
+ end
20
+
21
+ def message(message)
22
+ puts using_mode(:green, message)
23
+ end
24
+
25
+ def warning(message)
26
+ puts using_mode(:yellow, "warning: #{message}")
27
+ end
28
+
29
+ def error(message)
30
+ puts using_mode(:red, "error: #{message}")
31
+ end
32
+
33
+ def set_subtask(message)
34
+ print "#{reset_line}#{@task}: #{message}"
35
+ end
36
+
37
+ def reset_line
38
+ "\r\e[K"
39
+ end
40
+
41
+ def puts(*args)
42
+ unless @silent
43
+ if @new_line_needed
44
+ super()
45
+ @new_line_needed = false
46
+ end
47
+ super
48
+ end
49
+ end
50
+
51
+ def print(*args)
52
+ unless @silent
53
+ @new_line_needed = true
54
+ super
55
+ end
56
+ end
57
+
58
+ def silence
59
+ @silent = true
60
+ yield
61
+ ensure
62
+ @silent = false
63
+ end
64
+
65
+ def set_mode(codes)
66
+ "\e[#{[codes].flatten.map{ |c| COLOR_CODES[c].to_s }.join(";")}m"
67
+ end
68
+
69
+ def using_mode(codes, message)
70
+ set_mode(codes) + message + set_mode(:clear)
71
+ end
72
+
73
+ class StopWithMessage < StandardError; end
74
+ def stop_with_message(message)
75
+ raise StopWithMessage, message
76
+ end
77
+
78
+ def self.stop_with_message(message)
79
+ raise StopWithMessage, message
80
+ end
81
+
82
+
83
+ def self.with_presenter
84
+ presenter = new
85
+ begin
86
+ yield presenter
87
+ rescue StopWithMessage => ex
88
+ presenter.error(ex.message)
89
+ end
90
+ end
91
+
92
+ COLOR_CODES = {
93
+ clear: 0,
94
+ bright: 1,
95
+ underline: 4,
96
+ blink: 5,
97
+ swap: 7,
98
+ hide: 8,
99
+ black: 30,
100
+ red: 31,
101
+ green: 32,
102
+ yellow: 33,
103
+ blue: 34,
104
+ magenta: 35,
105
+ white: 37,
106
+ default: 39,
107
+ black_background: 40,
108
+ red_background: 41,
109
+ blue_background: 40,
110
+ green_background: 42,
111
+ blue_background: 44,
112
+ magenta_background: 45,
113
+ cyan_background: 46,
114
+ white_background: 47,
115
+ default_background: 49
116
+ }
117
+
118
+ end
@@ -0,0 +1,22 @@
1
+ require 'open3'
2
+
3
+ module InvocaGems::ShellCommand
4
+ def self.included(klass)
5
+ klass.send(:extend, InvocaGems::ShellCommand)
6
+ end
7
+
8
+ # Returns the command output, stripped
9
+ # Or nil if command failed
10
+ Response = Struct.new(:stdout, :stderr, :code)
11
+ def shell_command(command:, path: '.', presenter:, raise_errors: true)
12
+ presenter.set_subtask "cd #{path}; #{command}"
13
+ r = Response.new(*Open3.capture3("cd #{path}; #{command}"))
14
+ if r.code && r.code.success?
15
+ r.stdout.strip
16
+ else
17
+ if raise_errors
18
+ presenter.stop_with_message "cd #{path}; #{command}: returned error #{r.code.exitstatus}: #{r.stdout}\n#{r.stderr}"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module InvocaGems
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: invoca_gems
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bob Smith
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
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'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: manage invoca gems as if they were in-app gems
84
+ email:
85
+ - bob@ringrevenue.com
86
+ executables:
87
+ - invoca_gem
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".ruby_version"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - README.md
97
+ - Rakefile
98
+ - exe/invoca_gem
99
+ - invoca_gems.gemspec
100
+ - lib/invoca_gems.rb
101
+ - lib/invoca_gems/bundler.rb
102
+ - lib/invoca_gems/cli.rb
103
+ - lib/invoca_gems/gem.rb
104
+ - lib/invoca_gems/gemfile.rb
105
+ - lib/invoca_gems/gemfile_line.rb
106
+ - lib/invoca_gems/gemspec.rb
107
+ - lib/invoca_gems/gemspec_line.rb
108
+ - lib/invoca_gems/git_repo.rb
109
+ - lib/invoca_gems/presenter.rb
110
+ - lib/invoca_gems/shell_command.rb
111
+ - lib/invoca_gems/version.rb
112
+ homepage: http://www.invoca.com
113
+ licenses: []
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.2.2
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: help manage invoca_gems
135
+ test_files: []