awshucks 0.0.1

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