dotfiles 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/dotfiles +7 -0
- data/lib/dot_files.rb +32 -0
- data/lib/dot_files/command.rb +125 -0
- data/lib/dot_files/commands/files.rb +89 -0
- data/lib/dot_files/commands/help.rb +5 -0
- data/lib/dot_files/commands/setup.rb +32 -0
- data/lib/dot_files/config.rb +40 -0
- data/lib/dot_files/dispatch.rb +33 -0
- data/lib/dot_files/dsl.rb +35 -0
- data/lib/dot_files/errors.rb +9 -0
- metadata +98 -0
data/bin/dotfiles
ADDED
data/lib/dot_files.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
require 'digest'
|
6
|
+
|
7
|
+
require 'dot_files/errors'
|
8
|
+
require 'dot_files/config'
|
9
|
+
require 'dot_files/dsl'
|
10
|
+
require 'dot_files/dispatch'
|
11
|
+
require 'dot_files/command'
|
12
|
+
|
13
|
+
module DotFiles
|
14
|
+
VERSION = '1.0'
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
## Return a configuration object for configuring access to the gem
|
19
|
+
def config
|
20
|
+
@config ||= Config.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def site
|
24
|
+
"http://localhost:3000"
|
25
|
+
end
|
26
|
+
|
27
|
+
def configuration_path
|
28
|
+
File.join(ENV['HOME'], '.dotfiles')
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module DotFiles
|
2
|
+
class Command
|
3
|
+
|
4
|
+
def initialize(block)
|
5
|
+
(class << self;self end).send :define_method, :command, &block
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(*args)
|
9
|
+
arity = method(:command).arity
|
10
|
+
args << nil while args.size < arity
|
11
|
+
send :command, *args
|
12
|
+
end
|
13
|
+
|
14
|
+
def error(message, exit_code = 1)
|
15
|
+
puts "\e[31m#{message}\e[0m"
|
16
|
+
Process.exit(exit_code)
|
17
|
+
end
|
18
|
+
|
19
|
+
def request(path, options = {})
|
20
|
+
uri = URI.parse(DotFiles.site + "/" + path)
|
21
|
+
|
22
|
+
if options[:data]
|
23
|
+
req = Net::HTTP::Post.new(uri.path)
|
24
|
+
else
|
25
|
+
req = Net::HTTP::Get.new(uri.path)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
req.basic_auth(options[:username] || DotFiles.config.username, options[:api_key] || DotFiles.config.api_key)
|
30
|
+
|
31
|
+
res = Net::HTTP.new(uri.host, uri.port)
|
32
|
+
|
33
|
+
req.add_field("Accept", "application/json")
|
34
|
+
req.add_field("Content-type", "application/json")
|
35
|
+
|
36
|
+
if options[:data]
|
37
|
+
options[:data] = options[:data].to_json
|
38
|
+
end
|
39
|
+
|
40
|
+
#res.use_ssl = true
|
41
|
+
#res.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
42
|
+
begin
|
43
|
+
Timeout.timeout(10) do
|
44
|
+
res = res.request(req, options[:data])
|
45
|
+
case res
|
46
|
+
when Net::HTTPSuccess
|
47
|
+
return res.body.strip
|
48
|
+
else
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
rescue Timeout::Error
|
53
|
+
puts "Sorry, the request timed out. Please try again later."
|
54
|
+
Process.exit(1)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def require_setup
|
59
|
+
unless DotFiles.config.username && DotFiles.config.api_key
|
60
|
+
error "You haven't configured this computer yet. Run 'dotfiles setup' to authorise this computer."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
## Return an array of all files which need to be synced
|
66
|
+
def remote_files
|
67
|
+
if files = get_remote_files
|
68
|
+
array = []
|
69
|
+
for filename, remote_sha in files
|
70
|
+
hash = {:filename => filename, :action => nil, :local_sha => nil, :local_cached_sha => nil, :remote_sha => remote_sha, :local_path => File.join(ENV['HOME'], filename)}
|
71
|
+
|
72
|
+
if File.exist?(hash[:local_path])
|
73
|
+
hash[:local_sha] = Digest::SHA1.hexdigest(File.read(hash[:local_path]))
|
74
|
+
hash[:local_cached_sha] = DotFiles.config.shas[hash[:filename]]
|
75
|
+
|
76
|
+
local_file_changed = (hash[:local_sha] != hash[:local_cached_sha])
|
77
|
+
remote_file_changed = (hash[:local_cached_sha] != hash[:remote_sha])
|
78
|
+
|
79
|
+
if local_file_changed && remote_file_changed
|
80
|
+
hash[:action] = :conflict
|
81
|
+
elsif !local_file_changed && remote_file_changed
|
82
|
+
hash[:action] = :update_local
|
83
|
+
elsif local_file_changed && !remote_file_changed
|
84
|
+
hash[:action] = :update_remote
|
85
|
+
else
|
86
|
+
hash[:action] = :none
|
87
|
+
end
|
88
|
+
else
|
89
|
+
hash[:action] = :create_local
|
90
|
+
end
|
91
|
+
|
92
|
+
array << hash
|
93
|
+
end
|
94
|
+
array
|
95
|
+
else
|
96
|
+
error "We couldn't get a list of files from the remote service. Please try later."
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def puts(text = '')
|
101
|
+
text = text.to_s
|
102
|
+
text.gsub!(/\{\{(.*)\}\}/) { "\e[33m#{$1}\e[0m"}
|
103
|
+
super
|
104
|
+
end
|
105
|
+
|
106
|
+
def remote_file_contents(path)
|
107
|
+
req = request("#{DotFiles.config.username}/#{path}")
|
108
|
+
req ? JSON.parse(req)['file'] : nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def save_remote_file(path, contents)
|
112
|
+
req = request("save", :data => {:dot_file => {:path => path, :file => contents}})
|
113
|
+
req ? true : false
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def get_remote_files
|
119
|
+
req = request("files_list")
|
120
|
+
req ? JSON.parse(req) : nil
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
desc 'Display a list of all files which are registered for syncing'
|
2
|
+
usage 'files'
|
3
|
+
command :status do
|
4
|
+
require_setup
|
5
|
+
|
6
|
+
files = remote_files.select{|f| f[:action] != :none }
|
7
|
+
puts
|
8
|
+
if files.empty?
|
9
|
+
puts " No changes are required at this time. You can make changes to your remote or"
|
10
|
+
puts " local files and and rerun {{dotfiles status}} to see how changes will be applied"
|
11
|
+
puts " on your computer."
|
12
|
+
else
|
13
|
+
puts " The following changes need to be made to your local files to bring them inline"
|
14
|
+
puts " with the remote system. Run {{dotfiles sync}} to carry out these changes."
|
15
|
+
puts
|
16
|
+
puts " You can login at http://mydotfiles.com to make changes to your files or use"
|
17
|
+
puts " the {{dotfiles add path/to/file}} command to upload a new file to your your"
|
18
|
+
puts " remote MyDotFiles account."
|
19
|
+
puts
|
20
|
+
for info in files
|
21
|
+
puts " #{info[:action].to_s.gsub('_', ' ').rjust(15)}: #{info[:filename]}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
puts
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Sync changes between local and remote machines'
|
28
|
+
usage 'sync'
|
29
|
+
command :sync do
|
30
|
+
require_setup
|
31
|
+
|
32
|
+
DotFiles.config.shas = Hash.new unless DotFiles.config.shas.is_a?(Hash)
|
33
|
+
|
34
|
+
files = remote_files
|
35
|
+
if files.all?{|f| f[:action] == :none}
|
36
|
+
error "No changes required at this time."
|
37
|
+
else
|
38
|
+
for file in files.select{|f| f[:action] != :none}
|
39
|
+
case file[:action]
|
40
|
+
when :create_local, :update_local
|
41
|
+
puts "{{downloading}} #{file[:filename]}"
|
42
|
+
contents = remote_file_contents(file[:filename])
|
43
|
+
puts "{{saving}} #{file[:filename]}"
|
44
|
+
local_path = File.join(ENV['HOME'], file[:filename])
|
45
|
+
File.open(local_path, 'w') { |f| f.write(contents) }
|
46
|
+
puts "{{saved}} #{contents.size.to_s} bytes to #{local_path}"
|
47
|
+
DotFiles.config.shas[file[:filename]] = Digest::SHA1.hexdigest(contents)
|
48
|
+
when :update_remote
|
49
|
+
puts "{{uploading}} #{file[:filename]}"
|
50
|
+
local_path = File.join(ENV['HOME'], file[:filename])
|
51
|
+
contents = File.read(local_path)
|
52
|
+
save_remote_file(file[:filename], contents)
|
53
|
+
puts "{{uploaded}} #{contents.size.to_s} bytes to #{file[:filename]}"
|
54
|
+
DotFiles.config.shas[file[:filename]] = Digest::SHA1.hexdigest(contents)
|
55
|
+
else
|
56
|
+
puts "nothing to do with #{file[:filename]}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
DotFiles.config.last_sync_at = Time.now.utc
|
61
|
+
DotFiles.config.save
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
desc 'Add a new (or overwrite an existing) file'
|
67
|
+
usage 'add path/to/dotfile'
|
68
|
+
command :add do |path|
|
69
|
+
filename = path.gsub(/\A#{ENV['HOME']}\//, '')
|
70
|
+
local_path = File.join(ENV['HOME'], filename)
|
71
|
+
contents = File.read(local_path)
|
72
|
+
save_remote_file(filename, contents)
|
73
|
+
DotFiles.config.shas[filename] = Digest::SHA1.hexdigest(contents)
|
74
|
+
DotFiles.config.save
|
75
|
+
puts "#{filename} added to remote successfully"
|
76
|
+
end
|
77
|
+
|
78
|
+
desc 'Pull a remote file to your local file system'
|
79
|
+
usage 'pull path/to/dotfile'
|
80
|
+
command :pull do |path|
|
81
|
+
filename = path.gsub(/\A#{ENV['HOME']}\//, '')
|
82
|
+
local_path = File.join(ENV['HOME'], filename)
|
83
|
+
if contents = remote_file_contents(filename)
|
84
|
+
File.open(local_path, 'w') { |f| f.write(contents) }
|
85
|
+
puts "Downloaded #{contents.size} bytes to #{local_path}."
|
86
|
+
else
|
87
|
+
error "Couldn't download remote file from '#{filename}'. Does it exist?"
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
desc 'Authorise/setup this computer with access to your DotFiles account'
|
2
|
+
usage 'setup'
|
3
|
+
command :setup do
|
4
|
+
|
5
|
+
require 'highline/import'
|
6
|
+
HighLine.track_eof = false
|
7
|
+
|
8
|
+
|
9
|
+
puts "Welcome to MyDotFiles."
|
10
|
+
puts
|
11
|
+
puts "To begin, we need you to enter your login details so we can authorise"
|
12
|
+
puts "this computer to sync dot files from your mydotfiles.com account:"
|
13
|
+
puts
|
14
|
+
|
15
|
+
username = ask("Username: ")
|
16
|
+
password = ask("Password: ") { |q| q.echo = ''}
|
17
|
+
|
18
|
+
puts
|
19
|
+
puts "Attempting to authenticate you as #{username}..."
|
20
|
+
|
21
|
+
url = "https://#{DotFiles.site}/apikey"
|
22
|
+
api_key = request("apikey", :username => username, :api_key => password)
|
23
|
+
|
24
|
+
if api_key
|
25
|
+
DotFiles.config.username = username
|
26
|
+
DotFiles.config.api_key = api_key
|
27
|
+
DotFiles.config.save
|
28
|
+
puts "\e[32mThis computer has now been authorised. Run 'dotfiles sync' to get the latest files.\e[0m"
|
29
|
+
else
|
30
|
+
error "Your account could not be authorised. Please check your email address & password."
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module DotFiles
|
2
|
+
class Config
|
3
|
+
|
4
|
+
attr_reader :path, :configuration
|
5
|
+
|
6
|
+
def initialize(path = DotFiles.configuration_path)
|
7
|
+
@path = path
|
8
|
+
if File.exist?(path)
|
9
|
+
file_data = File.read(path)
|
10
|
+
@configuration = (file_data.nil? || file_data.length == 0 ? {} : YAML::load(file_data))
|
11
|
+
else
|
12
|
+
@configuration = Hash.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(key)
|
17
|
+
configuration[key.to_sym]
|
18
|
+
end
|
19
|
+
|
20
|
+
def set(key, value)
|
21
|
+
configuration[key.to_sym] = value
|
22
|
+
save
|
23
|
+
value
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(method_name, value = nil)
|
27
|
+
method_name = method_name.to_s
|
28
|
+
if method_name[-1,1] == '='
|
29
|
+
set(method_name.gsub(/\=\z/, ''), value)
|
30
|
+
else
|
31
|
+
get(method_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def save
|
36
|
+
File.open(path, 'w') { |f| f.write(YAML::dump(@configuration)) }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DotFiles
|
2
|
+
module Dispatch
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def run(command, args = [])
|
6
|
+
command = 'help' if command.nil?
|
7
|
+
command = command.to_sym
|
8
|
+
if DotFiles::DSL.commands[command]
|
9
|
+
if args.size < DotFiles::DSL.commands[command][:required_args]
|
10
|
+
puts "usage: #{DotFiles::DSL.commands[command][:usage]}"
|
11
|
+
else
|
12
|
+
DotFiles::DSL.commands[command][:block].call(*args)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
puts "Command not found. Check 'deploy help' for full information."
|
16
|
+
end
|
17
|
+
rescue DotFiles::Errors::AccessDenied
|
18
|
+
puts "Access Denied. The username & API key stored for your account was invalid. Have you run 'dotfiles setup' on this computer?"
|
19
|
+
Process.exit(1)
|
20
|
+
rescue DotFiles::Error
|
21
|
+
puts "An error occured with your request."
|
22
|
+
Process.exit(1)
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_commands
|
26
|
+
Dir[File.join(File.dirname(__FILE__), 'commands', '*.rb')].each do |path|
|
27
|
+
DotFiles::DSL.module_eval File.read(path), path
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module DotFiles
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def command(command, options = {}, &block)
|
7
|
+
@commands = Hash.new if @commands.nil?
|
8
|
+
@commands[command] = Hash.new
|
9
|
+
@commands[command][:description] = @next_description
|
10
|
+
@commands[command][:usage] = @next_usage
|
11
|
+
@commands[command][:flags] = @next_flags
|
12
|
+
@commands[command][:required_args] = (options[:required_args] || 0)
|
13
|
+
@commands[command][:block] = Command.new(block)
|
14
|
+
@next_usage, @next_description, @next_flags = nil, nil, nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def commands
|
18
|
+
@commands || Hash.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def desc(value)
|
22
|
+
@next_description = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def usage(value)
|
26
|
+
@next_usage = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def flag(key, value)
|
30
|
+
@next_flags = Hash.new if @next_flags.nil?
|
31
|
+
@next_flags[key] = value
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dotfiles
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Adam Cooke
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-09-04 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: highline
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 5
|
30
|
+
- 0
|
31
|
+
version: 1.5.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: json
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 1
|
44
|
+
- 5
|
45
|
+
version: 1.1.5
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
description:
|
49
|
+
email: adam@atechmedia.com
|
50
|
+
executables:
|
51
|
+
- dotfiles
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
files:
|
57
|
+
- lib/dot_files/command.rb
|
58
|
+
- lib/dot_files/commands/files.rb
|
59
|
+
- lib/dot_files/commands/help.rb
|
60
|
+
- lib/dot_files/commands/setup.rb
|
61
|
+
- lib/dot_files/config.rb
|
62
|
+
- lib/dot_files/dispatch.rb
|
63
|
+
- lib/dot_files/dsl.rb
|
64
|
+
- lib/dot_files/errors.rb
|
65
|
+
- lib/dot_files.rb
|
66
|
+
- bin/dotfiles
|
67
|
+
has_rdoc: true
|
68
|
+
homepage: http://atechmedia.com
|
69
|
+
licenses: []
|
70
|
+
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.3.6
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: CLI client for the mydotfiles.com
|
97
|
+
test_files: []
|
98
|
+
|