dotfiles 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path('../../lib', __FILE__)
4
+ require 'dot_files'
5
+
6
+ DotFiles::Dispatch.load_commands
7
+ DotFiles::Dispatch.run(ARGV.shift, ARGV)
@@ -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,5 @@
1
+ desc 'Get Help'
2
+ usage 'help'
3
+ command :help do
4
+ puts "This is help!"
5
+ 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
@@ -0,0 +1,9 @@
1
+ module DotFiles
2
+
3
+ class Error < StandardError; end
4
+
5
+ module Errors
6
+ class AccessDenied < Error; end
7
+ end
8
+
9
+ 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
+