invoca_gems 0.1.0

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: 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: []