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.
@@ -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
+