dotfiles 1.0.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.
- 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
|
+
|