ronin-repos 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/workflows/ruby.yml +27 -0
  4. data/.gitignore +13 -0
  5. data/.rspec +1 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +1 -0
  8. data/COPYING.txt +165 -0
  9. data/ChangeLog.md +7 -0
  10. data/Gemfile +32 -0
  11. data/README.md +159 -0
  12. data/Rakefile +34 -0
  13. data/bin/ronin-repos +35 -0
  14. data/data/templates/repo/README.md.erb +11 -0
  15. data/gemspec.yml +36 -0
  16. data/lib/ronin/repos/cache_dir.rb +267 -0
  17. data/lib/ronin/repos/class_dir.rb +150 -0
  18. data/lib/ronin/repos/cli/command.rb +63 -0
  19. data/lib/ronin/repos/cli/commands/install.rb +71 -0
  20. data/lib/ronin/repos/cli/commands/list.rb +80 -0
  21. data/lib/ronin/repos/cli/commands/new.rb +83 -0
  22. data/lib/ronin/repos/cli/commands/purge.rb +39 -0
  23. data/lib/ronin/repos/cli/commands/remove.rb +71 -0
  24. data/lib/ronin/repos/cli/commands/update.rb +91 -0
  25. data/lib/ronin/repos/cli.rb +45 -0
  26. data/lib/ronin/repos/exceptions.rb +33 -0
  27. data/lib/ronin/repos/repository.rb +343 -0
  28. data/lib/ronin/repos/root.rb +26 -0
  29. data/lib/ronin/repos/version.rb +24 -0
  30. data/lib/ronin/repos.rb +81 -0
  31. data/man/ronin-repos-install.1 +64 -0
  32. data/man/ronin-repos-install.1.md +49 -0
  33. data/man/ronin-repos-list.1 +54 -0
  34. data/man/ronin-repos-list.1.md +41 -0
  35. data/man/ronin-repos-new.1 +37 -0
  36. data/man/ronin-repos-purge.1 +54 -0
  37. data/man/ronin-repos-purge.1.md +41 -0
  38. data/man/ronin-repos-remove.1 +60 -0
  39. data/man/ronin-repos-remove.1.md +46 -0
  40. data/man/ronin-repos-update.1 +64 -0
  41. data/man/ronin-repos-update.1.md +49 -0
  42. data/man/ronin-repos.1 +49 -0
  43. data/man/ronin-repos.1.md +37 -0
  44. data/ronin-repos.gemspec +62 -0
  45. data/spec/cache_dir_spec.rb +272 -0
  46. data/spec/class_dir_spec.rb +97 -0
  47. data/spec/fixtures/cache/repo1/dir/file1.txt +0 -0
  48. data/spec/fixtures/cache/repo1/file1.txt +0 -0
  49. data/spec/fixtures/cache/repo1/file2.txt +0 -0
  50. data/spec/fixtures/cache/repo2/dir/file1.txt +0 -0
  51. data/spec/fixtures/cache/repo2/dir/file2.txt +0 -0
  52. data/spec/fixtures/cache/repo2/file1.txt +0 -0
  53. data/spec/fixtures/cache/repo2/file2.txt +0 -0
  54. data/spec/fixtures/cache/repo2/only-exists-in-repo2.txt +0 -0
  55. data/spec/fixtures/class_dir/file1.rb +0 -0
  56. data/spec/fixtures/class_dir/file2.rb +0 -0
  57. data/spec/fixtures/class_dir/only_in_class_dir.rb +0 -0
  58. data/spec/repos_spec.rb +67 -0
  59. data/spec/repository_spec.rb +415 -0
  60. data/spec/spec_helper.rb +6 -0
  61. metadata +143 -0
@@ -0,0 +1,37 @@
1
+ # ronin-repos 1 "2022-01-01" Ronin Repos "User Manuals"
2
+
3
+ ## SYNOPSIS
4
+
5
+ `ronin-repos` [*options*] [*COMMAND*]
6
+
7
+ ## DESCRIPTION
8
+
9
+ Allows downloading and managing git repositories. `ronin-repos` can install
10
+ and use any git repository containing Ruby code or other data.
11
+
12
+ ## OPTIONS
13
+
14
+ `-h`, `--help`
15
+ Prints help information.
16
+
17
+ ## FILES
18
+
19
+ *~/.cache/ronin-repos*
20
+ Installation directory for repositories.
21
+
22
+ ## ENVIRONMENT
23
+
24
+ HOME
25
+ Specifies the home directory of the user. Ronin will search for the
26
+ *~/.cache/ronin-repos* cache directory within the home directory.
27
+
28
+ XDG_CACHE_HOME
29
+ Specifies the cache directory to use. Defaults to *$HOME/.cache*.
30
+
31
+ ## AUTHOR
32
+
33
+ Postmodern <postmodern.mod3@gmail.com>
34
+
35
+ ## SEE ALSO
36
+
37
+ ronin-repos-install(1) ronin-repos-list(1) ronin-repos-remove(1) ronin-repos-update(1) ronin-repos-purge(1)
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'ronin/repos/version'
14
+ Ronin::Repos::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+ gem.metadata = gemspec['metadata'] if gemspec['metadata']
24
+
25
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
26
+
27
+ gem.files = `git ls-files`.split($/)
28
+ gem.files = glob[gemspec['files']] if gemspec['files']
29
+ gem.files += Array(gemspec['generated_files'])
30
+
31
+ gem.executables = gemspec.fetch('executables') do
32
+ glob['bin/*'].map { |path| File.basename(path) }
33
+ end
34
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
35
+
36
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
37
+ gem.test_files = glob[gemspec['test_files'] || 'spec/{**/}*_spec.rb']
38
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
39
+
40
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
41
+ %w[ext lib].select { |dir| File.directory?(dir) }
42
+ })
43
+
44
+ gem.requirements = gemspec['requirements']
45
+ gem.required_ruby_version = gemspec['required_ruby_version']
46
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
47
+ gem.post_install_message = gemspec['post_install_message']
48
+
49
+ split = lambda { |string| string.split(/,\s*/) }
50
+
51
+ if gemspec['dependencies']
52
+ gemspec['dependencies'].each do |name,versions|
53
+ gem.add_dependency(name,split[versions])
54
+ end
55
+ end
56
+
57
+ if gemspec['development_dependencies']
58
+ gemspec['development_dependencies'].each do |name,versions|
59
+ gem.add_development_dependency(name,split[versions])
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,272 @@
1
+ require 'spec_helper'
2
+ require 'ronin/repos/cache_dir'
3
+
4
+ describe Ronin::Repos::CacheDir do
5
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'fixtures')) }
6
+ let(:cache_dir) { File.join(fixtures_dir,'cache') }
7
+
8
+ describe "PATH" do
9
+ subject { described_class::PATH }
10
+
11
+ it "must default to ~/.cache/ronin-repos" do
12
+ expect(subject).to eq(File.join(ENV['HOME'],'.cache','ronin-repos'))
13
+ end
14
+ end
15
+
16
+ subject { described_class.new(cache_dir) }
17
+
18
+ describe "#initialize" do
19
+ context "when no arguments are given" do
20
+ subject { described_class.new }
21
+
22
+ it "must set #path to PATH" do
23
+ expect(subject.path).to eq(described_class::PATH)
24
+ end
25
+ end
26
+
27
+ context "when given a path argument" do
28
+ subject { described_class.new(cache_dir) }
29
+
30
+ it "must set #path" do
31
+ expect(subject.path).to eq(cache_dir)
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "#[]" do
37
+ context "when a repository with the given name exists in the cache dir" do
38
+ let(:name) { 'repo2' }
39
+
40
+ it "must return a Repository object with the matching name" do
41
+ repo = subject[name]
42
+
43
+ expect(repo).to be_kind_of(Repository)
44
+ expect(repo.name).to eq(name)
45
+ end
46
+ end
47
+
48
+ context "when given an unknown repository name" do
49
+ let(:name) { 'does_not_exist' }
50
+
51
+ it do
52
+ expect {
53
+ subject[name]
54
+ }.to raise_error(RepositoryNotFound,"repository not found: #{name.inspect}")
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "#each" do
60
+ context "when given a block" do
61
+ it "must yield each Repository object" do
62
+ yielded_repos = []
63
+
64
+ subject.each do |repo|
65
+ yielded_repos << repo
66
+ end
67
+
68
+ expect(yielded_repos.length).to eq(2)
69
+ expect(yielded_repos[0]).to be_kind_of(Repository)
70
+ expect(yielded_repos[0].name).to eq("repo1")
71
+
72
+ expect(yielded_repos[1]).to be_kind_of(Repository)
73
+ expect(yielded_repos[1].name).to eq("repo2")
74
+ end
75
+
76
+ context "when #path does not exist" do
77
+ subject { described_class.new('/does/not/exist') }
78
+
79
+ it "must not yield anything" do
80
+ expect { |b|
81
+ subject.each(&b)
82
+ }.to_not yield_control
83
+ end
84
+ end
85
+ end
86
+
87
+ context "when no block is given" do
88
+ it "must return an Enumerator object" do
89
+ yielded_repos = subject.each.to_a
90
+
91
+ expect(yielded_repos.length).to eq(2)
92
+ expect(yielded_repos[0]).to be_kind_of(Repository)
93
+ expect(yielded_repos[0].name).to eq("repo1")
94
+
95
+ expect(yielded_repos[1]).to be_kind_of(Repository)
96
+ expect(yielded_repos[1].name).to eq("repo2")
97
+ end
98
+
99
+ context "when #path does not exist" do
100
+ subject { described_class.new('/does/not/exist') }
101
+
102
+ it "the Enumerator must not return anything" do
103
+ expect(subject.each.to_a).to be_empty
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "#install" do
110
+ let(:name) { 'new_repo' }
111
+ let(:uri) { "https://github.com/example/#{name}.git" }
112
+ let(:path) { File.join(cache_dir,name) }
113
+
114
+ let(:new_repo) { double('Repository') }
115
+
116
+ it "must `git clone` the given URI into the cache directory" do
117
+ expect(Repository).to receive(:system).with(
118
+ 'git', 'clone', uri, path
119
+ ).and_return(true)
120
+
121
+ expect(Repository).to receive(:new).and_return(new_repo)
122
+
123
+ expect(subject.install(uri)).to be(new_repo)
124
+ end
125
+
126
+ context "when a custom name is given" do
127
+ let(:custom_name) { 'custom-repo' }
128
+ let(:path) { File.join(cache_dir,custom_name) }
129
+
130
+ it "must use the custom name instead of deriving the repository's name" do
131
+ expect(Repository).to receive(:system).with(
132
+ 'git', 'clone', uri, path
133
+ ).and_return(true)
134
+
135
+ expect(Repository).to receive(:new).and_return(new_repo)
136
+
137
+ expect(subject.install(uri,custom_name)).to be(new_repo)
138
+ end
139
+ end
140
+ end
141
+
142
+ describe "#update" do
143
+ it "must call #update on each repository in the cache directory"
144
+
145
+ context "but system() returns nil" do
146
+ it do
147
+ expect_any_instance_of(Repository).to receive(:system).and_return(nil)
148
+
149
+ expect {
150
+ subject.update
151
+ }.to raise_error(CommandNotInstalled,"git is not installed")
152
+ end
153
+ end
154
+ end
155
+
156
+ describe "#remove" do
157
+ let(:name) { 'repo2' }
158
+
159
+ it "must call #delete on the Repository" do
160
+ expect_any_instance_of(Repository).to receive(:delete)
161
+
162
+ subject.remove(name)
163
+ end
164
+
165
+ context "when given an unknown repository name" do
166
+ let(:name) { 'does-not-exist' }
167
+
168
+ it do
169
+ expect {
170
+ subject.remove(name)
171
+ }.to raise_error(RepositoryNotFound,"repository not found: #{name.inspect}")
172
+ end
173
+ end
174
+ end
175
+
176
+ describe "#purge" do
177
+ it "must call #delete on every repository in the cache directory" do
178
+ expect(FileUtils).to receive(:rm_rf).with(File.join(cache_dir,'repo1'))
179
+ expect(FileUtils).to receive(:rm_rf).with(File.join(cache_dir,'repo2'))
180
+
181
+ subject.purge
182
+ end
183
+ end
184
+
185
+ describe "#find_file" do
186
+ let(:relative_path) { 'only-exists-in-repo2.txt' }
187
+
188
+ it "must return the first file that matches the given relative path" do
189
+ expect(subject.find_file(relative_path)).to eq(
190
+ File.join(cache_dir,'repo2',relative_path)
191
+ )
192
+ end
193
+
194
+ context "when the relative path does not exist within any repository" do
195
+ let(:relative_path) { 'does/not/exist.txt' }
196
+
197
+ it "must return nil" do
198
+ expect(subject.find_file(relative_path)).to be(nil)
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "#glob" do
204
+ context "when the pattern matches files within all repositories" do
205
+ let(:pattern) { 'dir/*.txt' }
206
+
207
+ let(:expected_paths) do
208
+ Dir[File.join(cache_dir,'*',pattern)]
209
+ end
210
+
211
+ it "must return the absolute paths that matches the pattern, in order" do
212
+ expect(subject.glob(pattern)).to eq(expected_paths.sort)
213
+ end
214
+
215
+ context "when a block is given" do
216
+ it "must yield the matching absolute paths" do
217
+ expect { |b|
218
+ subject.glob(pattern,&b)
219
+ }.to yield_successive_args(*expected_paths.sort)
220
+ end
221
+ end
222
+ end
223
+
224
+ context "when the relative path does not exist within any repository" do
225
+ let(:pattern) { 'does/not/exist/*.txt' }
226
+
227
+ it "must return []" do
228
+ expect(subject.glob(pattern)).to eq([])
229
+ end
230
+
231
+ context "when a block is given" do
232
+ it "must not yield" do
233
+ expect { |b|
234
+ subject.glob(pattern,&b)
235
+ }.to_not yield_control
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ describe "#list_files" do
242
+ context "when given no arguments" do
243
+ it "must list every unique file within each repository" do
244
+ expect(subject.list_files).to eq(
245
+ Set.new(
246
+ %w[
247
+ dir/file1.txt
248
+ dir/file2.txt
249
+ file1.txt
250
+ file2.txt
251
+ only-exists-in-repo2.txt
252
+ ]
253
+ )
254
+ )
255
+ end
256
+ end
257
+
258
+ context "when given a glob pattern" do
259
+ it "must list only the files that match the glob pattern" do
260
+ expect(subject.list_files('dir/*.txt')).to eq(
261
+ Set.new(%w[dir/file1.txt dir/file2.txt])
262
+ )
263
+ end
264
+ end
265
+ end
266
+
267
+ describe "#to_s" do
268
+ it "must return the cache directory path" do
269
+ expect(subject.to_s).to eq(cache_dir)
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+ require 'ronin/repos/class_dir'
3
+
4
+ describe Ronin::Repos::ClassDir do
5
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'fixtures')) }
6
+
7
+ describe ".repo_class_dir" do
8
+ context "when a repo_class_dir has been defined" do
9
+ module TestClassDir
10
+ module WithReposClassDirSet
11
+ include Ronin::Repos::ClassDir
12
+
13
+ repo_class_dir "dir"
14
+ end
15
+ end
16
+
17
+ subject { TestClassDir::WithReposClassDirSet }
18
+
19
+ it "must return the previously set .repo_class_dir" do
20
+ expect(subject.repo_class_dir).to eq("dir")
21
+ end
22
+ end
23
+
24
+ context "but when no repo_class_dir has been defined" do
25
+ module TestReposDir
26
+ module WithoutReposDirSet
27
+ include Ronin::Repos::ClassDir
28
+ end
29
+ end
30
+
31
+ subject { TestReposDir::WithoutReposDirSet }
32
+
33
+ it do
34
+ expect {
35
+ subject.repo_class_dir
36
+ }.to raise_error(NotImplementedError,"#{subject} did not define a repo_class_dir")
37
+ end
38
+ end
39
+ end
40
+
41
+ module TestReposDir
42
+ module ExampleNamespace
43
+ include Ronin::Core::ClassRegistry
44
+ include Ronin::Repos::ClassDir
45
+
46
+ class_dir "#{__dir__}/fixtures/class_dir"
47
+ repo_class_dir 'dir'
48
+ end
49
+ end
50
+
51
+ subject { TestReposDir::ExampleNamespace }
52
+
53
+ describe ".list_files" do
54
+ before do
55
+ expect(Ronin::Repos).to receive(:list_files).and_return(
56
+ Set.new(
57
+ %w[
58
+ file1.rb
59
+ file2.rb
60
+ ]
61
+ )
62
+ )
63
+ end
64
+
65
+ it "must list the modules in the .class_dir and in all .repo_class_dir" do
66
+ expect(subject.list_files).to eq(
67
+ %w[
68
+ file1
69
+ file2
70
+ only_in_class_dir
71
+ ]
72
+ )
73
+ end
74
+ end
75
+
76
+ describe ".path_for" do
77
+ context "when the module name exists in the .class_dir" do
78
+ it "must return the path to the module file in .class_dir" do
79
+ expect(subject.path_for('only_in_class_dir')).to eq(
80
+ File.join(subject.class_dir,'only_in_class_dir.rb')
81
+ )
82
+ end
83
+ end
84
+
85
+ context "when the module name exists in one of the installed repos" do
86
+ it "must call Repos.find_file with the .repo_class_dir and module file name" do
87
+ expect(Ronin::Repos).to receive(:find_file).with(
88
+ File.join(subject.repo_class_dir,'file.rb')
89
+ ).and_return("/path/to/#{subject.repo_class_dir}/file.rb")
90
+
91
+ expect(subject.path_for('file')).to eq(
92
+ "/path/to/#{subject.repo_class_dir}/file.rb"
93
+ )
94
+ end
95
+ end
96
+ end
97
+ end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'ronin/repos'
3
+
4
+ describe Ronin::Repos do
5
+ it "should have a version" do
6
+ expect(subject.const_defined?('VERSION')).to be(true)
7
+ end
8
+
9
+ let(:cache_dir) { described_class.class_variable_get('@@cache_dir') }
10
+
11
+ describe "@@cache_dir" do
12
+ subject { cache_dir }
13
+
14
+ it "must be a CacheDir pointing to ~/.cache/ronin-repos" do
15
+ expect(subject).to be_kind_of(CacheDir)
16
+ expect(subject.path).to eq(File.expand_path("~/.cache/ronin-repos"))
17
+ end
18
+ end
19
+
20
+ describe ".find_file" do
21
+ let(:relative_path) { 'file.txt' }
22
+ let(:matches) do
23
+ [
24
+ "/path/to/repo1/file.txt",
25
+ "/path/to/repo2/file.txt"
26
+ ]
27
+ end
28
+
29
+ it "must call @@cache_dir.find_file" do
30
+ expect(cache_dir).to receive(:find_file).with(relative_path).and_return(matches)
31
+
32
+ expect(subject.find_file(relative_path)).to be(matches)
33
+ end
34
+ end
35
+
36
+ describe ".glob" do
37
+ let(:pattern) { 'dir/*.txt' }
38
+ let(:matches) do
39
+ [
40
+ "/path/to/repo1/dir/file.txt",
41
+ "/path/to/repo2/dir/file.txt"
42
+ ]
43
+ end
44
+
45
+ it "must call @@cache_dir.glob" do
46
+ expect(cache_dir).to receive(:glob).with(pattern).and_return(matches)
47
+
48
+ expect(subject.glob(pattern)).to be(matches)
49
+ end
50
+ end
51
+
52
+ describe ".list_files" do
53
+ let(:pattern) { 'dir/*.txt' }
54
+ let(:files) do
55
+ Set[
56
+ "dir/file.txt",
57
+ "dir/file.txt"
58
+ ]
59
+ end
60
+
61
+ it "must call @@cache_dir.list_files" do
62
+ expect(cache_dir).to receive(:list_files).with(pattern).and_return(files)
63
+
64
+ expect(subject.list_files(pattern)).to be(files)
65
+ end
66
+ end
67
+ end