dotrepo 0.0.1

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: 3731baa7ddeb1d596e711460693b68a99861b8f0
4
+ data.tar.gz: 220caf4af84fbbd39993a8374e3c77f8fcb71fd9
5
+ SHA512:
6
+ metadata.gz: 245a836d8fefc3ec02827f67375d5ceff5f44387f2fc2dde11af556405a8b74a376445d7fb61a91417ffccdddfc0fe6def3537664d9a5237b2519052be0b2937
7
+ data.tar.gz: fe26393479992bb2016698801968dcb10791e4a9689aaf08415b7b29871558c37d1649ccad85b8e7725a0b1a76b4020c100feaeaf56bed5b65710770ee6abd92
data/bin/dotrepo ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/dotrepo/cli'
4
+ Dotrepo::CLI.start
@@ -0,0 +1,60 @@
1
+ require 'thor'
2
+ require 'dotrepo'
3
+
4
+ module Dotrepo
5
+ class CLI < Thor
6
+
7
+ desc "version", "display version"
8
+ def version
9
+ say "Dotrepo::VERSION #{Dotrepo::VERSION}"
10
+ end
11
+
12
+ desc "info", "display current configuration"
13
+ def info
14
+ config = Dotrepo::ConfigFile.new
15
+ [:source, :destination].each do |attr|
16
+ shell.say_status "#{attr}:", config.send(attr), :bold
17
+ end
18
+ end
19
+
20
+ desc "setup", "setup your dotbox"
21
+ method_option :repo,
22
+ aliases: "-r",
23
+ desc: "repo to pull dotfiles from"
24
+ def setup
25
+ unless options[:repo]
26
+ shell.say_status "error", "you must specify a repo", :red
27
+ return
28
+ end
29
+
30
+ system "git clone #{options[:repo]} #{config.source}"
31
+ DotFileManager.new( config.source, config.destination ).symlink_dotfiles
32
+ end
33
+
34
+ desc "refresh", "update linked dotfiles"
35
+ def refresh
36
+ # runs the manager again to symlink new files & prune abandoned files
37
+ DotFileManager.new( config.source, config.destination ).symlink_dotfiles
38
+ end
39
+
40
+ dec "uninstall", "revert symlinked files to plain dotfiles"
41
+ def uninstall
42
+ end
43
+
44
+ desc "doctor", "analyze your setup for common issues"
45
+ def doctor
46
+ # do some smart checking based on info
47
+ # - does the source exist as a git repo
48
+ # - is the source up to date & free of modified files
49
+ # - does the source have any dotfiles
50
+ # - does the destination exist
51
+ end
52
+
53
+ private
54
+
55
+ def config
56
+ @_config ||= Dotrepo::ConfigFile.new
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ module Dotrepo
2
+ class ConfigFile
3
+ FILE_PATH = File.join( File.expand_path("~"), ".dotbox/config" )
4
+
5
+ attr_reader :data
6
+
7
+ def initialize
8
+ if File.exists? FILE_PATH
9
+ @data = defaults.merge YAML.load_file FILE_PATH
10
+ else
11
+ @data = defaults
12
+ end
13
+ end
14
+
15
+ def source
16
+ fetch_from_data "source"
17
+ end
18
+
19
+ def destination
20
+ fetch_from_data "destination"
21
+ end
22
+
23
+ private
24
+
25
+ def fetch_from_data key
26
+ data[key]
27
+ end
28
+
29
+ def defaults
30
+ {
31
+ "source" => '~/.dotbox/box',
32
+ "destination" => '~/'
33
+ }
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ module Dotrepo
2
+ class DotFile
3
+ attr_reader :path, :source, :destination
4
+
5
+ def initialize path, manager
6
+ @path = path
7
+ @source = File.join manager.source, path
8
+ @destination = File.join manager.destination, path
9
+ end
10
+
11
+ def source_exists?
12
+ File.exist? source
13
+ end
14
+
15
+ def destination_exists?
16
+ File.exist? destination
17
+ end
18
+
19
+ def backup_destination
20
+ File.rename( destination, "#{destination}.bak" )
21
+ end
22
+
23
+ def linked?
24
+ destination_exists? &&
25
+ File.symlink?(destination) &&
26
+ File.readlink(destination) == source
27
+ end
28
+
29
+ def symlink_to_destination
30
+ return if linked?
31
+
32
+ if destination_exists?
33
+ print "#{destination} exists. backup existing file and link? (y|n) "
34
+ input = $stdin.gets.chomp
35
+ return unless input.downcase == "y"
36
+
37
+ backup_destination
38
+ end
39
+ symlink_to_destination!
40
+ end
41
+
42
+ def symlink_to_destination!
43
+ File.symlink source, destination
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ module Dotrepo
2
+ class DotFileManager
3
+ attr_reader :source, :destination
4
+
5
+ def initialize source, destination
6
+ @source = File.expand_path( source )
7
+ @destination = File.expand_path( destination )
8
+ end
9
+
10
+ def dotfiles
11
+ Dir.glob( File.join( source, '**/.*' ) )
12
+ .map { |f| f.sub( /#{source}\/?/, '' ) }
13
+ .reject { |f| f.match /.git/ }
14
+ .map { |path| DotFile.new( path, self ) }
15
+ end
16
+
17
+ def symlink_dotfiles
18
+ dotfiles.each do |df|
19
+ df.symlink_to_destination
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,3 @@
1
+ module Dotrepo
2
+ VERSION = "0.0.1"
3
+ end
data/lib/dotrepo.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'yaml'
2
+
3
+ require_relative 'dotrepo/dot_file'
4
+ require_relative 'dotrepo/dot_file_manager'
5
+ require_relative 'dotrepo/config_file'
6
+
7
+ unless Dotrepo.const_get(:VERSION)
8
+ require_relative 'dotrepo/version'
9
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dotrepo::ConfigFile do
4
+
5
+ before :each do
6
+ allow( File ).to receive(:exists?)
7
+ .with( Dotrepo::ConfigFile::FILE_PATH )
8
+ .and_return( false )
9
+ end
10
+
11
+ describe "#initialize" do
12
+ it "uses data from config file if present" do
13
+ allow( File ).to receive(:exists?)
14
+ .with( Dotrepo::ConfigFile::FILE_PATH )
15
+ .and_return( true )
16
+
17
+ allow( YAML ).to receive(:load_file)
18
+ .with( Dotrepo::ConfigFile::FILE_PATH )
19
+ .and_return( { "source" => "source",
20
+ "destination" => "destination" })
21
+
22
+ expect( described_class.new.data ).to eq({ "source" => "source",
23
+ "destination" => "destination" })
24
+ end
25
+
26
+ it "uses default data w/ no config file" do
27
+ expect( described_class.new.data ).to eq({ "source" => "~/.dotbox/box",
28
+ "destination" => "~/" })
29
+ end
30
+ end
31
+
32
+ describe "#source" do
33
+ it "returns the 'source' from #data" do
34
+ expect( described_class.new.source ).to eq "~/.dotbox/box"
35
+ end
36
+ end
37
+
38
+ describe "#destination" do
39
+ it "returns the 'destination' from #data" do
40
+ expect( described_class.new.destination ).to eq "~/"
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dotrepo::DotFileManager do
4
+
5
+ after :each do
6
+ Given.cleanup!
7
+ end
8
+
9
+ describe "#initialize" do
10
+ it "sets given expanded source & destination" do
11
+ subject = Dotrepo::DotFileManager.new "source", "destination"
12
+
13
+ expect( subject.source ).to eq File.expand_path("source")
14
+ expect( subject.destination ).to eq File.expand_path("destination")
15
+ end
16
+ end
17
+
18
+ describe "#dotfiles" do
19
+ it "returns DotFiles" do
20
+ Given.file '.bashrc'
21
+ subject = Dotrepo::DotFileManager.new Given::TMP, "destination"
22
+
23
+ expect( subject.dotfiles.first ).to be_a Dotrepo::DotFile
24
+ end
25
+
26
+ it "returns an array of only dotfiles from source" do
27
+ Given.file '.bashrc'
28
+ Given.file 'foo_bar'
29
+ Given.file 'dir/.dot'
30
+ Given.file 'dir/file'
31
+ subject = Dotrepo::DotFileManager.new Given::TMP, "destination"
32
+
33
+ expect( subject.dotfiles.map { |df| df.path } ).to match_array [".bashrc","dir/.dot"]
34
+ end
35
+
36
+ it "ignores git related files" do
37
+ Given.file '.git/config'
38
+ Given.file '.gitignore'
39
+ subject = Dotrepo::DotFileManager.new Given::TMP, "destination"
40
+
41
+ expect( subject.dotfiles.map { |df| df.path } ).to be_empty
42
+ end
43
+ end
44
+
45
+ describe "#symlink_dotfiles" do
46
+ it "calls symlink_to_destination on each dotfile" do
47
+ dotfile_one = instance_double("Dotrepo::DotFile")
48
+ dotfile_two = instance_double("Dotrepo::DotFile")
49
+
50
+ subject = Dotrepo::DotFileManager.new "source", "destination"
51
+ allow( subject ).to receive(:dotfiles)
52
+ .and_return [dotfile_one, dotfile_two]
53
+
54
+ expect( dotfile_one ).to receive(:symlink_to_destination)
55
+ expect( dotfile_two ).to receive(:symlink_to_destination)
56
+
57
+ subject.symlink_dotfiles
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,248 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dotrepo::DotFile do
4
+
5
+ describe "#initialize" do
6
+
7
+ let(:manager_double) {
8
+ instance_double("Dotrepo::DotFileManager",
9
+ source: "/source",
10
+ destination: "/destination" )
11
+ }
12
+ let(:subject) { Dotrepo::DotFile.new("/foo/bar", manager_double) }
13
+
14
+ it "sets the given path" do
15
+ expect( subject.path ).to eq "/foo/bar"
16
+ end
17
+
18
+ it "sets up source & destination" do
19
+ expect( subject.source ).to eq "/source/foo/bar"
20
+ expect( subject.destination ).to eq "/destination/foo/bar"
21
+ end
22
+
23
+ end
24
+
25
+ describe "#source_exists?" do
26
+
27
+ let(:manager_double) {
28
+ instance_double("Dotrepo::DotFileManager",
29
+ source: "/source",
30
+ destination: "/destination" )
31
+ }
32
+ let(:subject) { Dotrepo::DotFile.new("/foo/bar", manager_double) }
33
+
34
+ it "is true if source exists" do
35
+ allow( File ).to receive(:exist?).with("/source/foo/bar")
36
+ .and_return(true)
37
+
38
+ expect( subject.source_exists? ).to be_truthy
39
+ end
40
+ it "is false if source doesn't exist" do
41
+ allow( File ).to receive(:exist?).with("/source/foo/bar")
42
+ .and_return(false)
43
+
44
+ expect( subject.source_exists? ).to be_falsy
45
+ end
46
+ end
47
+
48
+ describe "#destination_exists?" do
49
+
50
+ let(:manager_double) {
51
+ double("Manager",
52
+ source: "/source",
53
+ destination: "/destination" )
54
+ }
55
+ let(:subject) { Dotrepo::DotFile.new("/foo/bar", manager_double) }
56
+
57
+ it "is true if destination exists" do
58
+ allow( File ).to receive(:exist?).with("/destination/foo/bar")
59
+ .and_return(true)
60
+
61
+ expect( subject.destination_exists? ).to be_truthy
62
+ end
63
+ it "is false if destination doesn't exist" do
64
+ allow( File ).to receive(:exist?).with("/destination/foo/bar")
65
+ .and_return(false)
66
+
67
+ expect( subject.destination_exists? ).to be_falsy
68
+ end
69
+ end
70
+
71
+ describe "#backup_destination" do
72
+ let(:manager_double) {
73
+ double("Manager",
74
+ source: "/source",
75
+ destination: "/destination" )
76
+ }
77
+ let(:subject) { Dotrepo::DotFile.new("/foo/bar", manager_double) }
78
+
79
+ it "moves destination file to backup name" do
80
+ expect( File ).to receive(:rename)
81
+ .with("/destination/foo/bar", "/destination/foo/bar.bak")
82
+
83
+ subject.backup_destination
84
+ end
85
+ end
86
+
87
+ describe "linked?" do
88
+ let(:manager_double) {
89
+ double("Manager",
90
+ source: "/source",
91
+ destination: "/destination" )
92
+ }
93
+ let(:subject) { Dotrepo::DotFile.new("/foo/bar", manager_double) }
94
+
95
+
96
+ it "is false if destination doesn't exist" do
97
+ allow( File ).to receive(:exist?)
98
+ .with("/destination/foo/bar")
99
+ .and_return(false)
100
+
101
+ expect( subject.linked? ).to be_falsy
102
+ end
103
+
104
+ it "is true if destination is linked to source" do
105
+ allow( File ).to receive(:exist?)
106
+ .with("/destination/foo/bar")
107
+ .and_return(true)
108
+ allow( File ).to receive(:symlink?)
109
+ .with("/destination/foo/bar")
110
+ .and_return(true)
111
+ allow( File ).to receive(:readlink)
112
+ .with("/destination/foo/bar")
113
+ .and_return("/source/foo/bar")
114
+
115
+ expect( subject.linked? ).to be_truthy
116
+ end
117
+
118
+ it "is false if destination is a file" do
119
+ allow( File ).to receive(:exist?)
120
+ .with("/destination/foo/bar")
121
+ .and_return(true)
122
+ allow( File ).to receive(:symlink?)
123
+ .with("/destination/foo/bar")
124
+ .and_return(false)
125
+
126
+ expect( subject.linked? ).to be_falsy
127
+ end
128
+
129
+ it "is false if destination is linked not to source" do
130
+ allow( File ).to receive(:exist?)
131
+ .with("/destination/foo/bar")
132
+ .and_return(true)
133
+ allow( File ).to receive(:symlink?)
134
+ .with("/destination/foo/bar")
135
+ .and_return(true)
136
+ allow( File ).to receive(:readlink)
137
+ .with("/destination/foo/bar")
138
+ .and_return("/not/source/path")
139
+
140
+ expect( subject.linked? ).to be_falsy
141
+ end
142
+ end
143
+
144
+ describe "#symlink_to_destination!" do
145
+
146
+ let(:manager_double) {
147
+ double("Manager",
148
+ source: "/source",
149
+ destination: "/destination" )
150
+ }
151
+ let(:subject) { Dotrepo::DotFile.new("/foo/bar", manager_double) }
152
+
153
+ it "symlinks source to destination" do
154
+ expect( File ).to receive(:symlink)
155
+ .with("/source/foo/bar","/destination/foo/bar")
156
+
157
+ subject.symlink_to_destination!
158
+ end
159
+ end
160
+
161
+ describe "#symlink_to_destination" do
162
+
163
+ let(:manager_double) {
164
+ double("Manager",
165
+ source: "/source",
166
+ destination: "/destination" )
167
+ }
168
+ let(:subject) {
169
+ described_class.new "foo/bar", manager_double
170
+ }
171
+
172
+ context "when already linked" do
173
+ it "skips linking" do
174
+ subject = described_class.new ".path", manager_double
175
+ allow( subject ).to receive(:linked?)
176
+ .and_return(true)
177
+
178
+ expect( subject ).not_to receive(:symlink_to_destination!)
179
+ subject.symlink_to_destination
180
+ end
181
+ end
182
+
183
+ context "when destination exists as file" do
184
+
185
+ before :each do
186
+ allow( File ).to receive(:exist?).and_call_original
187
+ allow( File ).to receive(:exist?)
188
+ .with("/destination/foo/bar")
189
+ .and_return(true)
190
+ allow( subject ).to receive(:symlink_to_destination!) # just to kill it from trying to actually symlink anything
191
+ end
192
+
193
+ it "asks to backup or skip" do
194
+ out = CatchAndRelease::Catch.stdout do
195
+ CatchAndRelease::Release.stdin 'n' do
196
+ subject.symlink_to_destination
197
+ end
198
+ end
199
+
200
+ expect( out ).to match /^#{"/destination/foo/bar exists. backup existing file and link?"}/
201
+ end
202
+
203
+ context "user says skip" do
204
+ it "skips linking" do
205
+ expect( subject ).not_to receive(:symlink_to_destination!)
206
+
207
+ CatchAndRelease::Release.stdin 'n' do
208
+ subject.symlink_to_destination
209
+ end
210
+ end
211
+ end
212
+
213
+ context "user says backup" do
214
+ it "backs up destination file" do
215
+ expect( subject ).to receive(:backup_destination)
216
+
217
+ CatchAndRelease::Release.stdin 'y' do
218
+ subject.symlink_to_destination
219
+ end
220
+ end
221
+ it "symlinks to destination" do
222
+ allow( subject ).to receive(:backup_destination)
223
+ expect( subject ).to receive(:symlink_to_destination!)
224
+
225
+ CatchAndRelease::Release.stdin 'y' do
226
+ subject.symlink_to_destination
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ context "when destination doesn't exist" do
233
+
234
+ before :each do
235
+ allow( File ).to receive(:exist?)
236
+ .with("/destination/foo/bar")
237
+ .and_return(false)
238
+ end
239
+
240
+ it "symlinks file to destination" do
241
+ expect( subject ).to receive(:symlink_to_destination!)
242
+ subject.symlink_to_destination
243
+ end
244
+
245
+ end
246
+ end
247
+
248
+ end
@@ -0,0 +1,23 @@
1
+ require 'rspec'
2
+ require 'catch_and_release'
3
+ require 'support/given'
4
+
5
+ require 'pry'
6
+
7
+ RSpec.configure do |c|
8
+ c.mock_with :rspec do |mocks|
9
+ mocks.verify_doubled_constant_names = true
10
+ mocks.verify_partial_doubles = true
11
+ end
12
+ end
13
+
14
+ # for eating up stdout & stderr
15
+ unless ENV['VERBOSE']
16
+ stdout = StringIO.open('','w+')
17
+ $stdout = stdout
18
+
19
+ stderr = StringIO.open('','w+')
20
+ $stderr = stderr
21
+ end
22
+
23
+ require 'dotrepo'
@@ -0,0 +1,28 @@
1
+ module Given
2
+ ROOT = Dir.pwd
3
+ TMP = File.join( Dir.pwd, 'tmp' )
4
+
5
+ class << self
6
+
7
+ def fixture name
8
+ cleanup!
9
+
10
+ `rsync -av ./spec/fixtures/#{name}/ #{TMP}/`
11
+ Dir.chdir TMP
12
+ end
13
+
14
+ def file name, content=''
15
+ file_path = File.join( TMP, name )
16
+ FileUtils.mkdir_p( File.dirname(file_path) )
17
+ File.open( file_path, 'w' ) do |file|
18
+ file.write content
19
+ end
20
+ end
21
+
22
+ def cleanup!
23
+ Dir.chdir ROOT
24
+ `rm -rf #{TMP}`
25
+ end
26
+
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dotrepo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Steven Sloan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: ' A simple manager for your dotfiles '
28
+ email:
29
+ - stevenosloan@gmail.com
30
+ executables:
31
+ - dotrepo
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - bin/dotrepo
36
+ - lib/dotrepo.rb
37
+ - lib/dotrepo/cli.rb
38
+ - lib/dotrepo/config_file.rb
39
+ - lib/dotrepo/dot_file.rb
40
+ - lib/dotrepo/dot_file_manager.rb
41
+ - lib/dotrepo/version.rb
42
+ - spec/lib/dotrepo/config_file_spec.rb
43
+ - spec/lib/dotrepo/dot_file_manager_spec.rb
44
+ - spec/lib/dotrepo/dot_file_spec.rb
45
+ - spec/spec_helper.rb
46
+ - spec/support/given.rb
47
+ homepage: http://github.com/stevenosloan/dotrepo
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 2.2.2
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: A simple manager for your dotfiles
71
+ test_files:
72
+ - spec/lib/dotrepo/config_file_spec.rb
73
+ - spec/lib/dotrepo/dot_file_manager_spec.rb
74
+ - spec/lib/dotrepo/dot_file_spec.rb
75
+ - spec/spec_helper.rb
76
+ - spec/support/given.rb