awshucks 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGES +4 -0
  2. data/LICENSE +19 -0
  3. data/README +32 -0
  4. data/Rakefile +14 -0
  5. data/TODO +143 -0
  6. data/bin/awshucks +10 -0
  7. data/lib/awshucks.rb +29 -0
  8. data/lib/awshucks/cli.rb +76 -0
  9. data/lib/awshucks/command.rb +98 -0
  10. data/lib/awshucks/commands.rb +3 -0
  11. data/lib/awshucks/commands/backup.rb +59 -0
  12. data/lib/awshucks/commands/backups.rb +25 -0
  13. data/lib/awshucks/commands/help.rb +22 -0
  14. data/lib/awshucks/commands/list.rb +39 -0
  15. data/lib/awshucks/commands/new_config.rb +36 -0
  16. data/lib/awshucks/commands/reset_metadata_cache.rb +38 -0
  17. data/lib/awshucks/commands/restore.rb +54 -0
  18. data/lib/awshucks/config.rb +83 -0
  19. data/lib/awshucks/ext.rb +23 -0
  20. data/lib/awshucks/file_info.rb +23 -0
  21. data/lib/awshucks/file_store.rb +111 -0
  22. data/lib/awshucks/gemspec.rb +48 -0
  23. data/lib/awshucks/scanner.rb +58 -0
  24. data/lib/awshucks/specification.rb +128 -0
  25. data/lib/awshucks/version.rb +18 -0
  26. data/resources/awshucks.yml +21 -0
  27. data/spec/awshucks_spec.rb +7 -0
  28. data/spec/cli_spec.rb +130 -0
  29. data/spec/command_spec.rb +111 -0
  30. data/spec/commands/backup_spec.rb +164 -0
  31. data/spec/commands/backups_spec.rb +41 -0
  32. data/spec/commands/help_spec.rb +42 -0
  33. data/spec/commands/list_spec.rb +77 -0
  34. data/spec/commands/new_config_spec.rb +102 -0
  35. data/spec/commands/reset_metadata_cache_spec.rb +93 -0
  36. data/spec/commands/restore_spec.rb +219 -0
  37. data/spec/config_spec.rb +152 -0
  38. data/spec/ext_spec.rb +28 -0
  39. data/spec/file_info_spec.rb +45 -0
  40. data/spec/file_store_spec.rb +352 -0
  41. data/spec/scanner_spec.rb +106 -0
  42. data/spec/spec_helper.rb +36 -0
  43. data/spec/specification_spec.rb +41 -0
  44. metadata +121 -0
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'awshucks/specification'
3
+ require 'awshucks/version'
4
+ require 'rake'
5
+
6
+ # The Gem Specification plus some extras for awshucks.
7
+ module Awshucks
8
+ SPEC = Awshucks::Specification.new do |spec|
9
+ spec.name = "awshucks"
10
+ spec.version = Awshucks::VERSION
11
+ spec.rubyforge_project = "awshucks"
12
+ spec.author = "Nathan Witmer"
13
+ spec.email = "nathan-ruby@otherward.net"
14
+ spec.homepage = "http://awshucks.rubyforge.org/"
15
+
16
+ spec.summary = "A Summary of awshucks."
17
+ spec.description = <<-DESC
18
+ TODO: A longer more detailed description of awshucks.
19
+ DESC
20
+
21
+ spec.extra_rdoc_files = FileList["[A-Z]*"]
22
+ spec.has_rdoc = true
23
+ spec.rdoc_main = "README"
24
+ spec.rdoc_options = [ "--line-numbers" , "--inline-source" ]
25
+
26
+ spec.test_files = FileList["spec/**/*.rb", "test/**/*.rb"]
27
+ spec.executable = spec.name
28
+ spec.files = spec.test_files + spec.extra_rdoc_files +
29
+ FileList["lib/**/*.rb", "resources/**/*"]
30
+
31
+ # add dependencies
32
+ # spec.add_dependency("somegem", ">= 0.4.2")
33
+ spec.add_dependency("aws-s3", ">= 0.4.0")
34
+ # spec.add_dependency("main", ">= 0.2.0")
35
+
36
+ spec.platform = Gem::Platform::RUBY
37
+
38
+ spec.local_rdoc_dir = "doc/rdoc"
39
+ spec.remote_rdoc_dir = "#{spec.name}/rdoc"
40
+ spec.local_coverage_dir = "doc/coverage"
41
+ spec.remote_coverage_dir= "#{spec.name}/coverage"
42
+
43
+ spec.remote_site_dir = "#{spec.name}/"
44
+
45
+ end
46
+ end
47
+
48
+
@@ -0,0 +1,58 @@
1
+ module Awshucks
2
+
3
+ class Scanner
4
+
5
+ def self.each_file(dir, ignore = [])
6
+ new(dir, ignore).each_file {|entry| yield entry }
7
+ end
8
+
9
+ def initialize(dir, ignore = [])
10
+ @base_dir, @ignored = dir, ignore
11
+ end
12
+
13
+ # yields FileInfo object for each non-ignored file in the specified directory
14
+ def each_file
15
+ directory_stack << ''
16
+ while dir = directory_stack.pop
17
+ scan_directory(dir) { |entry| yield entry }
18
+ end
19
+ end
20
+
21
+ #######
22
+ private
23
+ #######
24
+
25
+ attr_reader :base_dir
26
+ attr_reader :ignored
27
+
28
+ def scan_directory(dir_name)
29
+ Dir.open(File.join(base_dir, dir_name)) do |dir|
30
+ while entry = dir.read
31
+ next if entry == '.' || entry == '..' || ignored?(entry)
32
+
33
+ stat = File.lstat(File.join(base_dir, dir_name, entry))
34
+ next if stat.symlink?
35
+
36
+ relative_path = File.join(dir_name, entry)
37
+
38
+ if stat.directory?
39
+ directory_stack << relative_path
40
+ else
41
+ # strip off the leading / in the relative path
42
+ yield FileInfo.new(relative_path[1..-1], File.join(base_dir, relative_path), stat)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def directory_stack
49
+ @directory_stack ||= []
50
+ end
51
+
52
+ def ignored?(filename)
53
+ ignored.any? {|ignore| File.fnmatch(ignore, filename) }
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,128 @@
1
+ require 'rubygems'
2
+ require 'rubygems/specification'
3
+ require 'rake'
4
+
5
+ module Awshucks
6
+ # Add some additional items to Gem::Specification
7
+ # A Awshucks::Specification adds additional pieces of information the
8
+ # typical gem specification
9
+ class Specification
10
+
11
+ RUBYFORGE_ROOT = "/var/www/gforge-projects/"
12
+
13
+ # user that accesses remote site
14
+ attr_accessor :remote_user
15
+
16
+ # remote host, default 'rubyforge.org'
17
+ attr_accessor :remote_host
18
+
19
+ # name the rdoc main
20
+ attr_accessor :rdoc_main
21
+
22
+ # local directory in development holding the generated rdoc
23
+ # default 'doc'
24
+ attr_accessor :local_rdoc_dir
25
+
26
+ # remote directory for storing rdoc, default 'doc'
27
+ attr_accessor :remote_rdoc_dir
28
+
29
+ # local directory for coverage report
30
+ attr_accessor :local_coverage_dir
31
+
32
+ # remote directory for storing coverage reports
33
+ # This defaults to 'coverage'
34
+ attr_accessor :remote_coverage_dir
35
+
36
+ # local directory for generated website, default +site/public+
37
+ attr_accessor :local_site_dir
38
+
39
+ # remote directory relative to +remote_root+ for the website.
40
+ # website.
41
+ attr_accessor :remote_site_dir
42
+
43
+ # is a .tgz to be created?, default 'true'
44
+ attr_accessor :need_tar
45
+
46
+ # is a .zip to be created, default 'true'
47
+ attr_accessor :need_zip
48
+
49
+
50
+ def initialize
51
+ @remote_user = nil
52
+ @remote_host = "rubyforge.org"
53
+
54
+ @rdoc_main = "README"
55
+ @local_rdoc_dir = "doc"
56
+ @remote_rdoc_dir = "doc"
57
+ @local_coverage_dir = "coverage"
58
+ @remote_coverage_dir = "coverage"
59
+ @local_site_dir = "site/public"
60
+ @remote_site_dir = "."
61
+
62
+ @need_tar = true
63
+ @need_zip = true
64
+
65
+ @spec = Gem::Specification.new
66
+
67
+ yield self if block_given?
68
+
69
+ # update rdoc options to take care of the rdoc_main if it is
70
+ # there, and add a default title if one is not given
71
+ if not @spec.rdoc_options.include?("--main") then
72
+ @spec.rdoc_options.concat(["--main", rdoc_main])
73
+ end
74
+
75
+ if not @spec.rdoc_options.include?("--title") then
76
+ @spec.rdoc_options.concat(["--title","'#{name} -- #{summary}'"])
77
+ end
78
+ end
79
+
80
+ # if this gets set then it overwrites what would be the
81
+ # rubyforge default. If rubyforge project is not set then use
82
+ # name. If rubyforge project and name are set, but they are
83
+ # different then assume that name is a subproject of the
84
+ # rubyforge project
85
+ def remote_root
86
+ if rubyforge_project.nil? or
87
+ rubyforge_project == name then
88
+ return RUBYFORGE_ROOT + "#{name}/"
89
+ else
90
+ return RUBYFORGE_ROOT + "#{rubyforge_project}/#{name}/"
91
+ end
92
+ end
93
+
94
+ # rdoc files is the same as what would be generated during gem
95
+ # installation. That is, everything in the require paths plus
96
+ # the rdoc_extra_files
97
+ #
98
+ def rdoc_files
99
+ flist = extra_rdoc_files.dup
100
+ @spec.require_paths.each do |rp|
101
+ flist << FileList["#{rp}/**/*.rb"]
102
+ end
103
+ flist.flatten.uniq
104
+ end
105
+
106
+ # calculate the remote directories
107
+ def remote_root_location
108
+ "#{remote_user}@#{remote_host}:#{remote_root}"
109
+ end
110
+
111
+ def remote_rdoc_location
112
+ remote_root_location + @remote_rdoc_dir
113
+ end
114
+
115
+ def remote_coverage_location
116
+ remote_root_location + @remote_coverage_dir
117
+ end
118
+
119
+ def remote_site_location
120
+ remote_root_location + @remote_site_dir
121
+ end
122
+
123
+ # we delegate any other calls to spec
124
+ def method_missing(method_id,*params,&block)
125
+ @spec.send method_id, *params, &block
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,18 @@
1
+ module Awshucks
2
+ class Version
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ BUILD = 1
6
+
7
+ class << self
8
+ def to_a
9
+ [MAJOR, MINOR, BUILD]
10
+ end
11
+
12
+ def to_s
13
+ to_a.join(".")
14
+ end
15
+ end
16
+ end
17
+ VERSION = Version.to_s
18
+ end
@@ -0,0 +1,21 @@
1
+ ---
2
+ # awshucks-generated config file
3
+ # The default filename is awshucks.yml, but can be overridden.
4
+
5
+ # Amazon S3 configuration
6
+ access_key_id: insert your access key here
7
+ secret_access_key: insert your secret access key here
8
+
9
+ # This is where awshucks will store all of your backed-up files.
10
+ bucket: bucket name
11
+
12
+ # Backup location configurations. Add as many of them as you like.
13
+ # Currently the only required key is location.
14
+
15
+ # For example (replace these with your own):
16
+
17
+ photos:
18
+ location: "~/pictures"
19
+
20
+ email:
21
+ location: "~/mail"
@@ -0,0 +1,7 @@
1
+ require File.join(File.dirname(__FILE__),"spec_helper.rb")
2
+
3
+ describe Awshucks do
4
+ it "should require the aws/s3 gem" do
5
+ require('aws/s3').should be_false
6
+ end
7
+ end
@@ -0,0 +1,130 @@
1
+ require File.join(File.dirname(__FILE__),"spec_helper.rb")
2
+
3
+ describe Awshucks::CLI, '#execute' do
4
+
5
+ before(:each) do
6
+ @cli = Awshucks::CLI.new
7
+ @cmd = mock('pretend command')
8
+ @cmd.stub!(:command).and_return('pretend')
9
+ @old_stdout, $stdout = $stdout, StringIO.new
10
+ @old_stderr, $stderr = $stderr, StringIO.new
11
+ end
12
+
13
+ after(:each) do
14
+ $stdout = @old_stdout
15
+ $stderr = @old_stderr
16
+ end
17
+
18
+ it "fails with a help message with empty arguments" do
19
+ @cli.execute([])
20
+ $stdout.string.should include("--help")
21
+ end
22
+
23
+ it "loads the configuration from awshucks.yml in the current working directory by default" do
24
+ config = mock('config')
25
+ Awshucks::Config.should_receive(:new).with('awshucks.yml').and_return(config)
26
+ config.stub!(:help_message=)
27
+ Dir.chdir(dummy_fs_path) do
28
+ @cli.execute([])
29
+ @cli.send(:config) # lazily loaded, so force it
30
+ end
31
+ end
32
+
33
+ it "assigns the main help method to the config object as a way to piggyback it for commands" do
34
+ config = mock('config')
35
+ Awshucks::Config.should_receive(:new).with('awshucks.yml').and_return(config)
36
+ config.should_receive(:help_message=).with(/-h, --help/)
37
+ Dir.chdir(dummy_fs_path) do
38
+ @cli.execute([])
39
+ @cli.send(:config) # lazily loaded, so force it
40
+ end
41
+ end
42
+
43
+ it "runs the specified subcommand if a command is provided" do
44
+ @cmd.should_receive(:execute)
45
+ Awshucks::Command.should_receive(:commands).at_least(1).times.and_return({'pretend' => @cmd})
46
+ @cli.execute(['pretend'])
47
+ end
48
+
49
+ it "prints an error and exit if a specified subcommand is not found" do
50
+ Awshucks::Command.should_receive(:parse_and_execute).and_raise(Awshucks::UnknownCommandError.new('pretend'))
51
+ lambda { @cli.execute(['pretend']) }.should raise_error(SystemExit)
52
+ $stderr.string.should =~ /could not find.*pretend/i
53
+ end
54
+
55
+ it "prints an error and exit if a subcommand raises an error" do
56
+ Awshucks::Command.should_receive(:parse_and_execute).and_raise(Awshucks::CommandError.new('pretend error'))
57
+ lambda { @cli.execute(['pretend']) }.should raise_error(SystemExit)
58
+ $stderr.string.should =~ /pretend error/
59
+ end
60
+
61
+ end
62
+
63
+ describe Awshucks::CLI, '#execute with no command-line arguments provided' do
64
+
65
+ before(:each) do
66
+ @old_stdout, $stdout = $stdout, StringIO.new
67
+ @cli = Awshucks::CLI.new
68
+ @cli.execute([])
69
+ end
70
+
71
+ after(:each) do
72
+ $stdout = @old_stdout
73
+ end
74
+
75
+ it "prints out a help message to stdout" do
76
+ $stdout.string.should =~ /-h, --help/
77
+ end
78
+
79
+ it "prints out a list of available commands" do
80
+ $stdout.string.should =~ / help/
81
+ end
82
+
83
+ end
84
+
85
+ describe Awshucks::CLI, '#execute with the -h argument' do
86
+
87
+ before(:each) do
88
+ @old_stdout, $stdout = $stdout, StringIO.new
89
+ @cli = Awshucks::CLI.new
90
+ @cli.execute(%w(-h))
91
+ end
92
+
93
+ after(:each) do
94
+ $stdout = @old_stdout
95
+ end
96
+
97
+ it "prints out a help message to stdout" do
98
+ $stdout.string.should =~ /-h, --help/
99
+ end
100
+
101
+ it "includes the version string" do
102
+ $stdout.string.should include("version #{Awshucks::VERSION}")
103
+ end
104
+
105
+ end
106
+
107
+ describe Awshucks::CLI, '#execute with the -c argument' do
108
+
109
+ before(:each) do
110
+ @cli = Awshucks::CLI.new
111
+ end
112
+
113
+ it "executes the given command with the specified config" do
114
+ config = mock('config')
115
+ Awshucks::Config.should_receive(:new).with('asdf.yml').and_return(config)
116
+ config.stub!(:help_message=)
117
+ Awshucks::Command.should_receive(:parse_and_execute)
118
+ @cli.execute(['-c', 'asdf.yml', 'command'])
119
+ end
120
+
121
+ end
122
+
123
+ describe Awshucks::CLI, '.execute' do
124
+ it "instantiates the CLI and calls execute with ARGV" do
125
+ @cli = mock('cli')
126
+ Awshucks::CLI.should_receive(:new).and_return(@cli)
127
+ @cli.should_receive(:execute).with(ARGV)
128
+ Awshucks::CLI.execute
129
+ end
130
+ end
@@ -0,0 +1,111 @@
1
+ require File.join(File.dirname(__FILE__),"spec_helper.rb")
2
+
3
+ describe Awshucks::Command, '.commands' do
4
+
5
+ it "should return a list of commands stored at the class level" do
6
+ Awshucks::Command.commands.should be_kind_of(Hash)
7
+ end
8
+
9
+ it "should have the standard commands loaded via the inherited callback" do
10
+ Awshucks::Command.commands['help'].should == Awshucks::HelpCommand
11
+ Awshucks::Command.commands['backup'].should == Awshucks::BackupCommand
12
+ Awshucks::Command.commands['backups'].should == Awshucks::BackupsCommand
13
+ Awshucks::Command.commands['list'].should == Awshucks::ListCommand
14
+ Awshucks::Command.commands['new_config'].should == Awshucks::NewConfigCommand
15
+ Awshucks::Command.commands['reset_metadata_cache'].should == Awshucks::ResetMetadataCacheCommand
16
+ Awshucks::Command.commands['restore'].should == Awshucks::RestoreCommand
17
+ end
18
+
19
+ end
20
+
21
+ describe Awshucks::Command, '.parse_and_execute' do
22
+
23
+ before(:each) do
24
+ @cmd = mock('command')
25
+ @config = mock('config')
26
+ Awshucks::Command.commands['mock'] = @cmd
27
+ end
28
+
29
+ it "should call execute on the command matching the first argument" do
30
+ @cmd.should_receive(:execute)
31
+ Awshucks::Command.parse_and_execute(['mock'], @config)
32
+ end
33
+
34
+ it "should strip the first argument from the arguments passed in when calling execute" do
35
+ @cmd.should_receive(:execute).with(['arg', 'arg2'], @config)
36
+ Awshucks::Command.parse_and_execute(['mock', 'arg', 'arg2'], @config)
37
+ end
38
+
39
+ it "should raise an unknown command error if the command isn't one that's registered" do
40
+ lambda { Awshucks::Command.parse_and_execute(['blahblah'], nil) }.should raise_error(Awshucks::UnknownCommandError)
41
+ end
42
+ end
43
+
44
+ describe Awshucks::Command, '.execute' do
45
+ it "should instantiate and call execute on the command class" do
46
+ class MockCommand < Awshucks::Command
47
+ end
48
+
49
+ @mock = mock('dummy command instance')
50
+ MockCommand.should_receive(:new).and_return(@mock)
51
+ @mock.should_receive(:execute)
52
+
53
+ MockCommand.execute(nil, nil)
54
+ end
55
+ end
56
+
57
+ describe Awshucks::Command, '.option_string, when no custom options are defined' do
58
+
59
+ before(:each) do
60
+ class DummyCommand < Awshucks::Command
61
+ self.command = 'command name'
62
+ self.usage = 'usage string'
63
+ self.description = 'description'
64
+ end
65
+ end
66
+
67
+ it "should include usage in the option parser string" do
68
+ DummyCommand.option_string.should include('usage string')
69
+ end
70
+
71
+ it "should include description in the option parser string" do
72
+ DummyCommand.option_string.should include('description')
73
+ end
74
+
75
+ it "should state 'no options' in option parser string" do
76
+ DummyCommand.option_string.should include('No options')
77
+ end
78
+
79
+ end
80
+
81
+ describe Awshucks::Command, '.option_string, with custom options defined' do
82
+
83
+ before(:each) do
84
+ class DummyCommand < Awshucks::Command
85
+ self.command = 'command name'
86
+ self.usage = 'usage string'
87
+ self.description = 'description'
88
+ def custom_options(opts)
89
+ opts.on('-v', "be verbose")
90
+ end
91
+ end
92
+ end
93
+
94
+ it "should include custom options in option parser string if a block was used" do
95
+ DummyCommand.option_string.should include('Options:')
96
+ DummyCommand.option_string.should include('be verbose')
97
+ end
98
+
99
+ end
100
+
101
+ describe Awshucks::Command, '#parsed_options' do
102
+ it "should return an OpenStruct by default for use in option definitions" do
103
+ Awshucks::Command.new.send(:parsed_options).should be_instance_of(OpenStruct)
104
+ end
105
+ end
106
+
107
+ describe Awshucks::Command, '#execute' do
108
+ it "raises an exception since it's meant to be an abstract method" do
109
+ lambda {Awshucks::Command.new.execute(nil, nil) }.should raise_error(Awshucks::CommandError)
110
+ end
111
+ end