autowow 0.3.0 → 0.4.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.
@@ -1,78 +0,0 @@
1
- require 'open3'
2
-
3
- module Autowow
4
- class Command
5
-
6
- include EasyLogging
7
-
8
- def self.run(*args)
9
- Command.new(*args).check.explain.chronic_execute
10
- end
11
-
12
- def self.run_dry(*args)
13
- Command.new(*args).silent_check.execute
14
- end
15
-
16
- def self.popen3_reader(*args)
17
- args.each do |arg|
18
- reader = <<-EOF
19
- def #{arg}
20
- @#{arg} = @#{arg}.read.rstrip unless @#{arg}.is_a?(String)
21
- return @#{arg}
22
- end
23
- EOF
24
- class_eval(reader)
25
- end
26
- end
27
-
28
- popen3_reader :stdin, :stdout, :stderr
29
- attr_reader :wait_thr
30
-
31
- def initialize(*args)
32
- @cmd = args
33
- end
34
-
35
- def explain
36
- logger.debug(@cmd.join(' ')) unless @cmd.empty?
37
- self
38
- end
39
-
40
- def execute
41
- @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(*@cmd) unless @cmd.empty?
42
- self
43
- end
44
-
45
- def chronic_execute
46
- @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(*@cmd) unless @cmd.empty?
47
- logger.error(stderr) unless stderr.empty?
48
- self
49
- end
50
-
51
- def silent_check
52
- @stdin, @stdout, @stderr, @wait_thr = Open3.popen3('which', @cmd[0])
53
- unless output_matches?(not_empty_matcher)
54
- yield if block_given?
55
- @cmd = []
56
- end
57
- self
58
- end
59
-
60
- def check
61
- silent_check do
62
- logger.info("Skipping '#{@cmd.join(' ')}' because command '#{@cmd[0]}' is not found.")
63
- end
64
- end
65
-
66
- def output_matches?(matcher)
67
- stdout.match(matcher)
68
- end
69
-
70
- def output_does_not_match?(matcher)
71
- !output_matches?(matcher)
72
- end
73
-
74
- def not_empty_matcher
75
- %r{.*\S.*}
76
- end
77
- end
78
- end
data/lib/autowow/fs.rb DELETED
@@ -1,41 +0,0 @@
1
- require_relative 'time_difference'
2
-
3
- module Autowow
4
- class Fs
5
- using RefinedTimeDifference
6
-
7
- def self.ls_dirs
8
- Dir.glob(File.expand_path('./*/')).select {|f| File.directory? f}
9
- end
10
-
11
- def self.latest(files)
12
- files.sort_by{ |f| File.mtime(f) }.reverse!.first
13
- end
14
-
15
- def self.older_than(files, quantity, unit)
16
- files.select do |dir|
17
- TimeDifference.between(File.mtime(dir), Time.now).public_send("in_#{unit}") > quantity
18
- end
19
- end
20
-
21
- def self.for_dirs(dirs)
22
- dirs.each do |working_dir|
23
- # TODO: add handling of directories via extra param to popen3
24
- # https://stackoverflow.com/a/10148084/2771889
25
- Dir.chdir(working_dir) do
26
- yield working_dir
27
- end
28
- end
29
- end
30
-
31
- def self.in_place_or_subdirs(in_place)
32
- if in_place
33
- yield
34
- else
35
- for_dirs(ls_dirs) do
36
- yield
37
- end
38
- end
39
- end
40
- end
41
- end
data/lib/autowow/gem.rb DELETED
@@ -1,32 +0,0 @@
1
- require_relative 'vcs'
2
- require_relative 'command'
3
-
4
- module Autowow
5
- class Gem
6
- include EasyLogging
7
-
8
- def self.gem_release
9
- start_status = Vcs.status
10
- logger.info(start_status)
11
- working_branch = Vcs.current_branch
12
- logger.error("Not on master.") and return unless working_branch.eql?('master')
13
- Vcs.push
14
-
15
- Vcs.on_branch('release') do
16
- Vcs.pull
17
- Vcs.rebase(working_branch)
18
- release
19
- end
20
-
21
- logger.info(Vcs.status)
22
- end
23
-
24
- def self.release
25
- Command.run('rake', 'release')
26
- end
27
-
28
- def self.clean
29
- Command.run('gem', 'clean')
30
- end
31
- end
32
- end
data/lib/autowow/ruby.rb DELETED
@@ -1,38 +0,0 @@
1
- module Autowow
2
- class Ruby
3
- include EasyLogging
4
-
5
- def self.used_versions
6
- rubies = []
7
- Fs.in_place_or_subdirs(Vcs.is_git?(Vcs.status_dry)) do
8
- rubies.push(version)
9
- end
10
- rubies.uniq
11
- end
12
-
13
- def self.version
14
- Command.run_dry('rbenv', 'local').stdout
15
- end
16
-
17
- def self.installed_versions
18
- Command.run_dry('rbenv', 'versions', '--bare', '--skip-aliases').stdout.each_line.map(&:strip)
19
- end
20
-
21
- def self.aliases
22
- aliases = {}
23
- Command.run_dry('rbenv', 'alias').stdout.each_line do |line|
24
- aliases[line.strip.split(' => ')[0]] = line.strip.split(' => ')[1]
25
- end
26
- aliases
27
- end
28
-
29
- def self.obsolete_versions
30
- alias_map = aliases
31
- used_versions_and_aliases = used_versions
32
- used_versions.each do |v|
33
- used_versions_and_aliases.push(alias_map[v]) if alias_map.has_key?(v)
34
- end
35
- installed_versions - used_versions_and_aliases
36
- end
37
- end
38
- end
data/lib/autowow/vcs.rb DELETED
@@ -1,304 +0,0 @@
1
- require 'uri'
2
- require 'net/https'
3
- require 'net/http'
4
- require 'json'
5
- require 'launchy'
6
-
7
- require_relative 'command'
8
- require_relative 'decorators/string_decorator'
9
- require_relative 'fs'
10
- require_relative 'time_difference'
11
- require_relative 'gem'
12
- require_relative 'ruby'
13
-
14
- module Autowow
15
- class Vcs
16
- include EasyLogging
17
- include StringDecorator
18
-
19
- using RefinedTimeDifference
20
-
21
- def self.branch_merged
22
- start_status = status
23
- logger.info(start_status)
24
- working_branch = current_branch
25
- logger.error("Nothing to do.") and return if working_branch.eql?('master')
26
-
27
- keep_changes do
28
- checkout('master')
29
- pull
30
- end
31
- branch_force_delete(working_branch)
32
-
33
- logger.info(status)
34
- end
35
-
36
- def self.update_projects
37
- Fs.in_place_or_subdirs(is_git?(status_dry)) do
38
- update_project
39
- end
40
- end
41
-
42
- def self.update_project
43
- start_status = status_dry
44
- return unless is_git?(start_status)
45
- logger.info("Updating #{File.expand_path('.')} ...")
46
- logger.warn("Skipped: uncommitted changes on master.") and return if uncommitted_changes?(start_status) and current_branch.eql?('master')
47
-
48
- on_branch('master') do
49
- has_upstream?(remotes.stdout) ? pull_upstream : pull
50
- end
51
- end
52
-
53
- def self.clear_branches
54
- logger.info(branch.stdout)
55
- working_branch = current_branch
56
- master_branch = 'master'
57
-
58
- (branches - [master_branch, working_branch]).each do |branch|
59
- branch_force_delete(branch) if branch_pushed(branch)
60
- end
61
-
62
- logger.info(branch.stdout)
63
- end
64
-
65
- def self.add_upstream
66
- start_status = status_dry
67
- logger.error("Not a git repository.") and return unless is_git?(start_status)
68
- remote_list = remotes.stdout
69
- logger.warn("Already has upstream.") and return if has_upstream?(remote_list)
70
- logger.info(remote_list)
71
-
72
- url = URI.parse(origin_push_url(remote_list))
73
- host = "api.#{url.host}"
74
- path = "/repos#{url.path}"
75
- request = Net::HTTP.new(host, url.port)
76
- request.verify_mode = OpenSSL::SSL::VERIFY_NONE
77
- request.use_ssl = url.scheme == 'https'
78
- response = request.get(path)
79
-
80
- if response.kind_of?(Net::HTTPRedirection)
81
- logger.error('Repository moved / renamed. Update remote or implement redirect handling. :)')
82
- elsif response.kind_of?(Net::HTTPNotFound)
83
- logger.error('Repository not found. Maybe it is private.')
84
- elsif response.kind_of?(Net::HTTPSuccess)
85
- parsed_response = JSON.parse(response.body)
86
- logger.warn('Not a fork.') and return unless parsed_response['fork']
87
- parent_url = parsed_response.dig('parent', 'html_url')
88
- add_remote('upstream', parent_url) unless parent_url.to_s.empty?
89
- logger.info(remotes.stdout)
90
- else
91
- logger.error("Github API (#{url.scheme}://#{host}#{path}) could not be reached: #{response.body}")
92
- end
93
- end
94
-
95
- def self.greet(latest_project_info = nil)
96
- logger.info("\nGood morning!\n\n")
97
- if is_git?(status_dry)
98
- logger.error('Inside repo, cannot show report about all repos.')
99
- else
100
- latest_project_info ||= get_latest_project_info
101
- logger.info(latest_project_info)
102
- check_projects_older_than(1, :months)
103
- end
104
- logger.info("\nThe following Ruby versions are not used by any projects, maybe consider removing them?\n #{Ruby.obsolete_versions.join("\n ")}")
105
- end
106
-
107
- def self.hi
108
- logger.error("In a git repository. Try 1 level higher.") && return if is_git?(status_dry)
109
- latest_project_info = get_latest_project_info
110
- logger.info("\nHang on, updating your local projects and remote forks...\n\n")
111
- git_projects.each do |project|
112
- Dir.chdir(project) do
113
- logger.info("\nGetting #{project} in shape...")
114
- yield if block_given?
115
- update_project
116
- end
117
- end
118
- greet(latest_project_info)
119
- end
120
-
121
- def self.hi!
122
- logger.error("In a git repository. Try 1 level higher.") && return if is_git?(status_dry)
123
- hi do
124
- logger.info('Removing unused branches...')
125
- clear_branches
126
- logger.info('Adding upstream...')
127
- add_upstream
128
- logger.info('Removing unused gems...')
129
- logger.info(Gem.clean.stdout)
130
- end
131
- end
132
-
133
- def self.open
134
- Launchy.open(origin_push_url(remotes.stdout))
135
- end
136
-
137
- def self.get_latest_project_info
138
- latest = latest_repo
139
- time_diff = TimeDifference.between(File.mtime(latest), Time.now).humanize_higher_than(:days).downcase
140
- time_diff_text = time_diff.empty? ? 'recently' : "#{time_diff} ago"
141
- "It looks like you were working on #{File.basename(latest)} #{time_diff_text}.\n\n"
142
- end
143
-
144
- def self.latest_repo
145
- Fs.latest(git_projects)
146
- end
147
-
148
- def self.check_projects_older_than(quantity, unit)
149
- old_projects = Fs.older_than(git_projects, quantity, unit)
150
- deprecated_projects = old_projects.reject do |project|
151
- Dir.chdir(project) { branches.reject{ |branch| branch_pushed(branch) }.any? }
152
- end
153
-
154
- logger.info("The following projects have not been touched for more than #{quantity} #{unit} and all changes have been pushed, maybe consider removing them?") unless deprecated_projects.empty?
155
- deprecated_projects.each do |project|
156
- time_diff = TimeDifference.between(File.mtime(project), Time.now).humanize_higher_than(:weeks).downcase
157
- logger.info(" #{File.basename(project)} (#{time_diff})")
158
- end
159
- end
160
-
161
- def self.stash
162
- Command.run('git', 'stash').output_does_not_match?(%r{No local changes to save})
163
- end
164
-
165
- def self.current_branch
166
- Command.run_dry('git', 'symbolic-ref', '--short', 'HEAD').stdout
167
- end
168
-
169
- def self.status
170
- status = Command.run('git', 'status')
171
- status.stdout + status.stderr
172
- end
173
-
174
- def self.status_dry
175
- status = Command.run_dry('git', 'status')
176
- status.stdout + status.stderr
177
- end
178
-
179
- def self.checkout(existing_branch)
180
- Command.run('git', 'checkout', existing_branch)
181
- end
182
-
183
- def self.create(branch)
184
- Command.run('git', 'checkout', '-b', branch)
185
- Command.run('git', 'push', '--set-upstream', 'origin', branch)
186
- end
187
-
188
- def self.pull
189
- Command.run('git', 'pull')
190
- end
191
-
192
- def self.pull_upstream
193
- Command.run('git', 'fetch', 'upstream')
194
- Command.run('git', 'merge', 'upstream/master')
195
- Command.run('git', 'push', 'origin', 'master')
196
- end
197
-
198
- def self.branch
199
- Command.run('git', 'branch')
200
- end
201
-
202
- def self.stash_pop
203
- Command.run('git', 'stash', 'pop')
204
- end
205
-
206
- def self.branch_force_delete(branch)
207
- Command.run('git', 'branch', '-D', branch)
208
- end
209
-
210
- def self.remotes
211
- Command.run_dry('git', 'remote', '-v')
212
- end
213
-
214
- def self.has_upstream?(remotes)
215
- remotes.include?('upstream')
216
- end
217
-
218
- def self.uncommitted_changes?(start_status)
219
- !(start_status.include?('nothing to commit, working tree clean') or start_status.include?('nothing added to commit but untracked files present'))
220
- end
221
-
222
- def self.is_git?(start_status)
223
- !start_status.include?('Not a git repository')
224
- end
225
-
226
- def self.origin_push_url(remotes)
227
- # Order is important: first try to match "url" in "#{url}.git" as non-dot_git matchers would include ".git" in the match
228
- origin_push_url_ssl_dot_git(remotes) or
229
- origin_push_url_ssl(remotes)or
230
- origin_push_url_https_dot_git(remotes) or
231
- origin_push_url_https(remotes)
232
- end
233
-
234
- def self.origin_push_url_https(remotes)
235
- remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\s)\(push\))}]
236
- end
237
-
238
- def self.origin_push_url_https_dot_git(remotes)
239
- remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\.)git(\s)\(push\))}]
240
- end
241
-
242
- def self.origin_push_url_ssl_dot_git(remotes)
243
- url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\.)git(\s)\(push\))}]
244
- "https://#{url.gsub(':', '/')}" if url
245
- end
246
-
247
- def self.origin_push_url_ssl(remotes)
248
- url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\s)\(push\))}]
249
- "https://#{url.gsub(':', '/')}" if url
250
- end
251
-
252
- def self.add_remote(name, url)
253
- Command.run('git', 'remote', 'add', name, url)
254
- end
255
-
256
- def self.on_branch(branch)
257
- keep_changes do
258
- working_branch = current_branch
259
- switch_needed = !working_branch.eql?(branch)
260
- if switch_needed
261
- result = checkout(branch)
262
- create(branch) if result.stderr.eql?("error: pathspec '#{branch}' did not match any file(s) known to git.")
263
- end
264
-
265
- yield
266
-
267
- checkout(working_branch) if switch_needed
268
- end
269
- end
270
-
271
- def self.keep_changes
272
- status = status_dry
273
- pop_stash = uncommitted_changes?(status)
274
- stash if pop_stash
275
- yield
276
- stash_pop if pop_stash
277
- end
278
-
279
- def self.git_projects
280
- Fs.ls_dirs.select do |dir|
281
- Dir.chdir(dir) do
282
- is_git?(status_dry)
283
- end
284
- end
285
- end
286
-
287
- def self.branch_pushed(branch)
288
- Command.run_dry('git', 'log', branch, '--not', '--remotes').stdout.empty?
289
- end
290
-
291
- def self.branches
292
- branches = Command.run_dry('git', 'for-each-ref', "--format='%(refname)'", 'refs/heads/').stdout
293
- branches.each_line.map { |line| line.strip[%r{(?<='refs/heads/)(.*)(?=')}] }
294
- end
295
-
296
- def self.push
297
- Command.run('git', 'push')
298
- end
299
-
300
- def self.rebase(branch)
301
- Command.run('git', 'rebase', branch)
302
- end
303
- end
304
- end