melon 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +3 -0
- data/History.txt +6 -0
- data/README.textile +29 -0
- data/Rakefile +18 -0
- data/TODO +15 -0
- data/bin/melon +8 -0
- data/features/basic.feature +33 -0
- data/features/edges.feature +13 -0
- data/features/step_definitions/basic_steps.rb +15 -0
- data/features/support/env.rb +7 -0
- data/lib/melon.rb +1 -0
- data/lib/melon/cli.rb +121 -0
- data/lib/melon/commands.rb +122 -0
- data/lib/melon/commands/add.rb +10 -0
- data/lib/melon/commands/basic_command.rb +19 -0
- data/lib/melon/commands/help.rb +51 -0
- data/lib/melon/example.rb +68 -0
- data/lib/melon/hasher.rb +13 -0
- data/lib/melon/version.rb +10 -0
- data/melon.gemspec +32 -0
- data/script/console +12 -0
- data/spec/melon/cli_spec.rb +8 -0
- data/spec/melon/database_spec.rb +32 -0
- data/spec/melon/fingerprint_spec.rb +30 -0
- data/spec/melon/parsed_arguments_spec.rb +77 -0
- data/spec/spec_helper.rb +61 -0
- metadata +144 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/History.txt
ADDED
data/README.textile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
h1. Melon
|
2
|
+
|
3
|
+
A media file cataloging tool.
|
4
|
+
|
5
|
+
h2. Synopsis
|
6
|
+
|
7
|
+
Melon is a tool for tracking files based on content, aiming
|
8
|
+
to scale to arbitrarily large files without slowing down. It uses
|
9
|
+
a sparse hashing algorithm to generate a digest without having to
|
10
|
+
read the whole file, allowing it to go much faster than disk would
|
11
|
+
ordinarily allow.
|
12
|
+
|
13
|
+
h2. Installation
|
14
|
+
|
15
|
+
<pre>
|
16
|
+
gem install melon
|
17
|
+
</pre>
|
18
|
+
|
19
|
+
h2. Usage
|
20
|
+
|
21
|
+
TODO
|
22
|
+
|
23
|
+
h2. Motivation
|
24
|
+
|
25
|
+
"Is this file copied onto my media drive yet?"
|
26
|
+
|
27
|
+
h2. Author
|
28
|
+
|
29
|
+
Copyright 2010 Andrew Roberts
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cucumber'
|
3
|
+
require 'cucumber/rake/task'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
|
6
|
+
desc 'Default: run the cucumber features.'
|
7
|
+
task :default => :features
|
8
|
+
|
9
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
10
|
+
t.cucumber_opts = "features --format pretty"
|
11
|
+
end
|
12
|
+
|
13
|
+
eval("$specification = begin; #{IO.read('melon.gemspec')}; end")
|
14
|
+
Rake::GemPackageTask.new($specification) do |package|
|
15
|
+
package.need_zip = true
|
16
|
+
package.need_tar = true
|
17
|
+
end
|
18
|
+
|
data/TODO
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Command line parsing:
|
2
|
+
* melon [<opts>] <cmd> <cmd-params> [<cmd-opts>]
|
3
|
+
* pivot on <cmd>, melon opts come before cmd, cmd-opts come after
|
4
|
+
* this may make it hard to guess at unrecognized commands
|
5
|
+
* commands don't have a dash
|
6
|
+
|
7
|
+
Testing:
|
8
|
+
* fix rcov rake world
|
9
|
+
|
10
|
+
Commands:
|
11
|
+
* a command helper that
|
12
|
+
* dynamically makes the collection "commands" from files in commands/*
|
13
|
+
* figures out the padding for the banner
|
14
|
+
|
15
|
+
* google "ruby get all classes declared in a directory"
|
data/bin/melon
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Basic usage
|
2
|
+
|
3
|
+
Background:
|
4
|
+
Given a file named "test_file" with:
|
5
|
+
"""
|
6
|
+
This file is a test file
|
7
|
+
"""
|
8
|
+
|
9
|
+
Scenario: Adding files to a melon database
|
10
|
+
When I run "melon -d test.db add test_file"
|
11
|
+
Then the output should contain a hash
|
12
|
+
And the output should contain "test_file"
|
13
|
+
|
14
|
+
Scenario: Adding a file that already exists
|
15
|
+
When I run "melon -d test.db add test_file"
|
16
|
+
And I run "melon -d test.db add test_file"
|
17
|
+
Then the output should contain:
|
18
|
+
"""
|
19
|
+
melon: path already present in database
|
20
|
+
"""
|
21
|
+
And the exit status should not be 0
|
22
|
+
|
23
|
+
Scenario: Checking a file that is not in the database
|
24
|
+
When I run "melon -d test.db check test_file"
|
25
|
+
Then the output should contain "test_file"
|
26
|
+
And the output should start with "/"
|
27
|
+
|
28
|
+
Scenario: Adding a directory
|
29
|
+
Given a directory named "testo"
|
30
|
+
When I run "melon -d test.db add testo"
|
31
|
+
Then the output should contain "directory"
|
32
|
+
And the exit status should not be 0
|
33
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: Edge cases
|
2
|
+
|
3
|
+
Scenario: Adding two files with the identical contents
|
4
|
+
Given a file named "test_file" with:
|
5
|
+
"""
|
6
|
+
This file is a test file
|
7
|
+
"""
|
8
|
+
When I run "cp test_file test_file_2"
|
9
|
+
And I run "melon -d test.db add test_file test_file_2"
|
10
|
+
Then the stderr should contain:
|
11
|
+
"""
|
12
|
+
melon: file exists elsewhere in the database
|
13
|
+
"""
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'aruba/api'
|
2
|
+
|
3
|
+
Then /^the output should be empty$/ do
|
4
|
+
all_output.should match(/^$/)
|
5
|
+
end
|
6
|
+
|
7
|
+
Then /^the output should start with "([^"]*)"$/ do |arg1|
|
8
|
+
all_output.should match(/^#{arg1}/)
|
9
|
+
end
|
10
|
+
|
11
|
+
Then /^the output should contain a hash$/ do
|
12
|
+
all_output.should match(/[a-fA-F0-9]{20}/)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
data/lib/melon.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'melon/cli'
|
data/lib/melon/cli.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
require 'melon/version'
|
5
|
+
require 'melon/commands'
|
6
|
+
|
7
|
+
module Melon
|
8
|
+
class CLI
|
9
|
+
|
10
|
+
def self.execute(arguments=[])
|
11
|
+
new(arguments).run
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :arguments
|
15
|
+
|
16
|
+
def initialize(arguments)
|
17
|
+
self.arguments = arguments
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.default_options
|
21
|
+
options = OpenStruct.new
|
22
|
+
options.database_path = "#{ENV['HOME']}/.melondb"
|
23
|
+
options
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
options = parse_options
|
28
|
+
options.database = PStore.new(File.expand_path(options.database_path))
|
29
|
+
|
30
|
+
# prepare db
|
31
|
+
options.database.transaction do
|
32
|
+
options.database[:by_hash] ||= {}
|
33
|
+
options.database[:by_path] ||= {}
|
34
|
+
end
|
35
|
+
|
36
|
+
unless arguments.empty?
|
37
|
+
run_command(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_command(options)
|
42
|
+
# look for command class in args.shift
|
43
|
+
command_name = arguments.shift
|
44
|
+
begin
|
45
|
+
command = Commands.const_get(command_name.capitalize)
|
46
|
+
command.new(arguments, options).run
|
47
|
+
# rescue NameError
|
48
|
+
# CLI.error "unrecognized command: #{command_name}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_options
|
53
|
+
options = CLI.default_options
|
54
|
+
|
55
|
+
parser = OptionParser.new do |p|
|
56
|
+
p.banner = "Usage: melon [options] COMMAND [command-options] [ARGS]"
|
57
|
+
|
58
|
+
p.separator ""
|
59
|
+
p.separator "Commands:"
|
60
|
+
p.separator ""
|
61
|
+
|
62
|
+
%w(add check).each do |command|
|
63
|
+
cls = Commands.const_get(command.capitalize)
|
64
|
+
p.separator format_command(command, cls.description)
|
65
|
+
end
|
66
|
+
|
67
|
+
p.separator ""
|
68
|
+
p.separator "Options:"
|
69
|
+
p.separator ""
|
70
|
+
|
71
|
+
p.on("-d", "--database PATH",
|
72
|
+
"Path to database file (#{options.database_path})") do |database|
|
73
|
+
options.database_path = database
|
74
|
+
end
|
75
|
+
|
76
|
+
p.on_tail("-v", "--version", "Show version") do
|
77
|
+
puts Melon.version_string
|
78
|
+
exit 0
|
79
|
+
end
|
80
|
+
|
81
|
+
p.on_tail("-h", "--help", "This is it") do
|
82
|
+
puts p
|
83
|
+
exit 0
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
begin
|
89
|
+
parser.order!(arguments)
|
90
|
+
rescue OptionParser::ParseError => e
|
91
|
+
CLI.error e
|
92
|
+
end
|
93
|
+
|
94
|
+
options
|
95
|
+
end
|
96
|
+
|
97
|
+
def format_command(name, desc, margin = 4, width = 22, wrapdesc = 80)
|
98
|
+
pad = "\n" + ' ' * width
|
99
|
+
desc = wrap_text(desc, wrapdesc - width).split("\n").join(pad)
|
100
|
+
|
101
|
+
' ' * margin + "#{name.ljust(width-margin)}#{desc}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def wrap_text(txt, col = 80)
|
105
|
+
txt.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/,
|
106
|
+
"\\1\\3\n")
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def self.error(error_obj_or_str, code = 1)
|
111
|
+
if error_obj_or_str.respond_to?('to_s')
|
112
|
+
error_str = error_obj_or_str.to_s
|
113
|
+
else
|
114
|
+
error_str = error_obj_or_str.inspect
|
115
|
+
end
|
116
|
+
|
117
|
+
$stderr.puts "melon: #{error_str}"
|
118
|
+
exit code
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'pstore'
|
2
|
+
require 'ftools'
|
3
|
+
|
4
|
+
require 'melon/hasher'
|
5
|
+
require 'melon/cli'
|
6
|
+
|
7
|
+
# TODO: in commands, parse arguments with parse! in CLI, parse with order!
|
8
|
+
|
9
|
+
module Melon
|
10
|
+
module Commands
|
11
|
+
# needs a 'verify' command to check integrity of database
|
12
|
+
# needs a 'remove' command, or some way to deal with deletes/renames
|
13
|
+
class Base
|
14
|
+
attr_accessor :args, :options
|
15
|
+
attr_reader :description
|
16
|
+
|
17
|
+
def initialize(args, options)
|
18
|
+
self.args = args
|
19
|
+
self.options = options
|
20
|
+
end
|
21
|
+
|
22
|
+
def parser
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.description
|
27
|
+
raise
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_options!
|
31
|
+
begin
|
32
|
+
parser.parse!(args)
|
33
|
+
rescue OptionParser::ParseError => e
|
34
|
+
CLI.error "#{self.class.to_s.split("::").last.downcase}: #{e}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
class Add < Base
|
41
|
+
|
42
|
+
def self.description
|
43
|
+
"Add files to the melon database"
|
44
|
+
end
|
45
|
+
|
46
|
+
def parser
|
47
|
+
@parser ||= OptionParser.new do |p|
|
48
|
+
p.banner = "Usage: melon add [options] file [file [file ...]]"
|
49
|
+
p.separator ""
|
50
|
+
p.separator Add.description
|
51
|
+
|
52
|
+
p.separator ""
|
53
|
+
p.separator "Options:"
|
54
|
+
p.separator ""
|
55
|
+
|
56
|
+
# p.on("-f", "--force",
|
57
|
+
# "Force the recalculation of the path that",
|
58
|
+
# " already exists in the database") do
|
59
|
+
# options.force = true
|
60
|
+
# end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def run
|
65
|
+
parse_options!
|
66
|
+
|
67
|
+
options.database.transaction do
|
68
|
+
args.each do |arg|
|
69
|
+
filename = File.expand_path(arg)
|
70
|
+
|
71
|
+
if File.directory?(filename)
|
72
|
+
CLI.error "argument is a directory: #{arg}"
|
73
|
+
end
|
74
|
+
|
75
|
+
if options.database[:by_path][filename]# and !options.force
|
76
|
+
CLI.error "path already present in database: #{arg}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# hash strategy should be encapsulated, ergo indirection here
|
80
|
+
hash = Hasher.digest(filename)
|
81
|
+
|
82
|
+
if options.database[:by_hash][hash]
|
83
|
+
CLI.error "file exists elsewhere in the database: #{arg}"
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
options.database[:by_hash][hash] = filename
|
88
|
+
options.database[:by_path][filename] = hash
|
89
|
+
puts "#{hash}:#{filename}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Check < Base
|
96
|
+
def self.description
|
97
|
+
"Determine whether or not a copy of a file resides in the database"
|
98
|
+
end
|
99
|
+
|
100
|
+
def parser
|
101
|
+
@parser ||= OptionParser.new do |p|
|
102
|
+
p.banner = "Usage: melon check file [file [file ...]]"
|
103
|
+
p.separator ""
|
104
|
+
p.separator Check.description
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def run
|
109
|
+
parse_options!
|
110
|
+
|
111
|
+
options.database.transaction do
|
112
|
+
args.each do |filename|
|
113
|
+
hash = Hasher.digest(filename)
|
114
|
+
unless options.database[:by_hash][hash]
|
115
|
+
puts File.expand_path(filename)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Melon
|
2
|
+
module Commands
|
3
|
+
module BasicCommand
|
4
|
+
|
5
|
+
def execute(arguments, options)
|
6
|
+
new(arguments, options).run
|
7
|
+
end
|
8
|
+
|
9
|
+
# returns name {padding} description, for usage"
|
10
|
+
def short_usage(padding=" ")
|
11
|
+
name = Melon::Commands.translate_command(self)
|
12
|
+
extra_spaces = Melon::Commands.commands.collect do |c|
|
13
|
+
c.length
|
14
|
+
end.max - name.length
|
15
|
+
"#{name}#{padding}#{' ' * extra_spaces}#{self.description}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'melon/commands/basic_command'
|
2
|
+
|
3
|
+
module Melon
|
4
|
+
module Commands
|
5
|
+
class Help
|
6
|
+
# Help is a basic command that also ties itself in to cli#usage, so
|
7
|
+
# it's a little bit unconventional. Retrospectively, I should not
|
8
|
+
# have written it first.
|
9
|
+
extend BasicCommand
|
10
|
+
|
11
|
+
def self.description
|
12
|
+
"Get help with a specific command, or with Melon in general"
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :arguments
|
16
|
+
|
17
|
+
def initialize(arguments, options)
|
18
|
+
self.arguments = arguments
|
19
|
+
end
|
20
|
+
|
21
|
+
def parser
|
22
|
+
@parser ||= OptionParser.new do |opts|
|
23
|
+
Melon::Commands.command_hash.each do |name, command|
|
24
|
+
next if command == self.class
|
25
|
+
# TODO help banner: gem help help
|
26
|
+
# TODO flesh out parsing - give short_usage for each command
|
27
|
+
opts.on(name) { command.parser }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
begin
|
34
|
+
parser.parse!(arguments)
|
35
|
+
rescue OptionParser::InvalidOption => e
|
36
|
+
puts "melon: #{e.to_s}"
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
|
40
|
+
if arguments == ['help']
|
41
|
+
puts parser
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
|
45
|
+
# if arguments are empty, we handled it in CLI
|
46
|
+
puts "melon: '#{arguments.join(' ')}' is not a recognized command."
|
47
|
+
exit 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'subcommand'
|
3
|
+
|
4
|
+
require 'melon/version'
|
5
|
+
|
6
|
+
module Melon
|
7
|
+
class CLI
|
8
|
+
def self.execute(arguments=[])
|
9
|
+
new(arguments).run
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :arguments
|
13
|
+
|
14
|
+
def initialize(arguments)
|
15
|
+
self.arguments = arguments
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
# command may be nil, if no command is given
|
20
|
+
command = parser.parse!(arguments)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parser
|
24
|
+
@parser ||= OptionParser.new do |opts|
|
25
|
+
opts.banner = "Example of what option parsing should look like"
|
26
|
+
|
27
|
+
opts.on("-d", "--database=PATH", String,
|
28
|
+
"Path to Melon's sqlite database.",
|
29
|
+
" (default ~/.melon/melon.db)") do |arg|
|
30
|
+
self.options[:database] = arg
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-v", "--version",
|
34
|
+
"Display the program version.") { puts "0.0.0" }
|
35
|
+
opts.on("-h", "--help",
|
36
|
+
"Show this help message.") { puts parser }
|
37
|
+
opts.separator ""
|
38
|
+
|
39
|
+
opts.command :create, "description text" do |subopts|
|
40
|
+
subopts.banner "create: create a directory, somewhere"
|
41
|
+
|
42
|
+
subopts.on("-f" "--force",
|
43
|
+
"Force creation.") { self.options[:force] = true }
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
opts.separator "To see a list of commands, use 'melon help commands'."
|
48
|
+
opts.separator "To get help with a specific command," +
|
49
|
+
" use 'melon help COMMAND'."
|
50
|
+
end
|
51
|
+
|
52
|
+
# OptionParser#command adds lazy option parsers to an array
|
53
|
+
# of subcommands
|
54
|
+
#
|
55
|
+
# melon help COMMAND prints the appropriate option
|
56
|
+
#
|
57
|
+
# PARSING:
|
58
|
+
# the option parser knows what the commands are at parse time
|
59
|
+
# so, scan the parse!() argument for the first command and pivot on
|
60
|
+
# it
|
61
|
+
#
|
62
|
+
# pre-cmd goes to the main option_parser.parse
|
63
|
+
# cmd goes into a string, is returned
|
64
|
+
# post-cmd goes to cmd parser.parse!()
|
65
|
+
#
|
66
|
+
# possibly implement that behavior for order! as well
|
67
|
+
end
|
68
|
+
end
|
data/lib/melon/hasher.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'digestif/hasher'
|
3
|
+
|
4
|
+
module Melon
|
5
|
+
class Hasher
|
6
|
+
def self.digest(filename)
|
7
|
+
hasher = Digestif::Hasher.new(filename)
|
8
|
+
hasher.options.read_size = 40000
|
9
|
+
hasher.options.seek_size = 80000000
|
10
|
+
hasher.digest
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/melon.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
require 'melon/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{melon}
|
7
|
+
s.version = Melon.version
|
8
|
+
s.summary = %q{A media catalog}
|
9
|
+
s.description = %q{A tool for tracking files based on content, aiming to scale to arbitrarily large files without slowing down}
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
s.default_executable = 'melon'
|
15
|
+
|
16
|
+
s.require_path = 'lib'
|
17
|
+
|
18
|
+
s.has_rdoc = false
|
19
|
+
|
20
|
+
s.authors = ["Andrew Roberts"]
|
21
|
+
s.email = %q{adroberts@gmail.com}
|
22
|
+
s.homepage = "http://github.com/aroberts/melon"
|
23
|
+
|
24
|
+
s.add_dependency("digestif", ">=1.0.4")
|
25
|
+
|
26
|
+
s.add_development_dependency('cucumber')
|
27
|
+
s.add_development_dependency('aruba')
|
28
|
+
|
29
|
+
s.platform = Gem::Platform::RUBY
|
30
|
+
s.rubygems_version = %q{1.2.0}
|
31
|
+
end
|
32
|
+
|
data/script/console
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
libs << " -r irb/ext/save-history"
|
7
|
+
|
8
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
9
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
10
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/melon.rb'}"
|
11
|
+
puts "Loading melon gem"
|
12
|
+
exec "#{irb} #{libs} --simple-prompt"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'melon/database'
|
2
|
+
|
3
|
+
describe Melon::Database, "when passing a file that doesn't exist" do
|
4
|
+
before do
|
5
|
+
@database = Melon::Database.new(nonexistant_database_file)
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
FileUtils.rm_rf(@database.path)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be created" do
|
13
|
+
File.exist?(@database.path).should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Melon::Database, "when passing a file that is not a database" do
|
19
|
+
before do
|
20
|
+
@file = non_database_file
|
21
|
+
end
|
22
|
+
|
23
|
+
after do
|
24
|
+
FileUtils.rm_rf(@file)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should throw an exception" do
|
28
|
+
lambda {
|
29
|
+
@database = Melon::Database.new(non_database_file)
|
30
|
+
}.should raise_error(ActiveRecord::StatementInvalid)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'melon/fingerprint'
|
2
|
+
require 'melon/database' # may need to reverse the order of these
|
3
|
+
|
4
|
+
|
5
|
+
describe Fingerprint, "when creating with just a file" do
|
6
|
+
before do
|
7
|
+
File.stub!(:exist?) { true }
|
8
|
+
|
9
|
+
@database = Database.new(nonexistant_database_file)
|
10
|
+
@fingerprint = Fingerprint.new(:file => small_media_file)
|
11
|
+
@fingerprint.save!
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
FileUtils.rm_rf(@database.path)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should resolve the path" do
|
19
|
+
@fingerprint.file.should match(/^\//)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be valid" do
|
23
|
+
@fingerprint.should be_valid
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set the digest field" do
|
27
|
+
@fingerprint.digest.should_not be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'melon/parsed_arguments'
|
2
|
+
|
3
|
+
describe Melon::ParsedArguments, "when splitting valid arguments" do
|
4
|
+
before do
|
5
|
+
@args = Melon::ParsedArguments.new(%w(-t -b help plate /path/to/file -t 5))
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should correctly find the program arguments" do
|
9
|
+
@args.program_arguments.should include("-t", "-b")
|
10
|
+
@args.program_arguments.length.should == 2
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should correctly find the command" do
|
14
|
+
@args.command.should == "help"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should correctly find the command arguments" do
|
18
|
+
@args.command_arguments.should include("plate", "/path/to/file", "-t", "5")
|
19
|
+
@args.command_arguments.length.should == 4
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Melon::ParsedArguments, "when splitting argument-less commands" do
|
24
|
+
before do
|
25
|
+
@args = Melon::ParsedArguments.new(%w(help))
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should correctly find the program arguments" do
|
29
|
+
@args.program_arguments.should be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should correctly find the command" do
|
33
|
+
@args.command.should == "help"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should correctly find the command arguments" do
|
37
|
+
@args.command_arguments.should be_empty
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe Melon::ParsedArguments, "when splitting command-less arguments" do
|
42
|
+
before do
|
43
|
+
@args = Melon::ParsedArguments.new(%w(--help))
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should set the program arguments" do
|
47
|
+
@args.program_arguments.should include("--help")
|
48
|
+
@args.program_arguments.length.should == 1
|
49
|
+
end
|
50
|
+
|
51
|
+
it "shound not set a command" do
|
52
|
+
@args.command.should be_nil
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not set any command arguments" do
|
56
|
+
@args.command_arguments.should be_nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe Melon::ParsedArguments, "when splitting invalid commands" do
|
61
|
+
before do
|
62
|
+
@args = Melon::ParsedArguments.new(%w(fizzle))
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not set a command" do
|
66
|
+
@args.command.should be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should treat the invalid command as a program argument" do
|
70
|
+
@args.program_arguments.should include("fizzle")
|
71
|
+
@args.program_arguments.length.should == 1
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should not set command arguments" do
|
75
|
+
@args.command_arguments.should be_nil
|
76
|
+
end
|
77
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
begin
|
2
|
+
require 'rspec'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
5
|
+
gem 'rspec'
|
6
|
+
require 'rspec'
|
7
|
+
end
|
8
|
+
|
9
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
10
|
+
require 'melon'
|
11
|
+
include Melon
|
12
|
+
|
13
|
+
|
14
|
+
def nonexistant_database_file
|
15
|
+
'tmp/' + (0..8).map{ ('a'..'z').to_a[rand(26)] }.join + '.db'
|
16
|
+
end
|
17
|
+
|
18
|
+
def non_database_file
|
19
|
+
name = nonexistant_database_file
|
20
|
+
File.open(name, 'w') {|f| f.write("bad") }
|
21
|
+
name
|
22
|
+
end
|
23
|
+
|
24
|
+
def small_media_file
|
25
|
+
name = nonexistant_database_file
|
26
|
+
File.open(name, 'w') do |f|
|
27
|
+
f.write(<<EOS
|
28
|
+
<p>
|
29
|
+
It is included in the Ruby standard library.
|
30
|
+
</p>
|
31
|
+
<h2>Description</h2>
|
32
|
+
<p>
|
33
|
+
ftools adds several (class, not instance) methods to the <a
|
34
|
+
href="File.html">File</a> class, for copying, moving, deleting, installing,
|
35
|
+
and comparing files, as well as creating a directory <a
|
36
|
+
href="File.html#M002554">path</a>. See the <a href="File.html">File</a>
|
37
|
+
class for details.
|
38
|
+
</p>
|
39
|
+
<p>
|
40
|
+
<a href="FileUtils.html">FileUtils</a> contains all or nearly all the same
|
41
|
+
functionality and more, and is a recommended option over ftools
|
42
|
+
</p>
|
43
|
+
<p>
|
44
|
+
When you
|
45
|
+
</p>
|
46
|
+
<pre>
|
47
|
+
require 'ftools'
|
48
|
+
</pre>
|
49
|
+
<p>
|
50
|
+
then the <a href="File.html">File</a> class aquires some utility methods
|
51
|
+
for copying, moving, and deleting files, and more.
|
52
|
+
</p>
|
53
|
+
<p>
|
54
|
+
See the method descriptions below, and consider using <a
|
55
|
+
href="FileUtils.html">FileUtils</a> as it is more comprehensive.
|
56
|
+
</p>
|
57
|
+
EOS
|
58
|
+
)
|
59
|
+
end
|
60
|
+
name
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: melon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrew Roberts
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-25 00:00:00 -05:00
|
19
|
+
default_executable: melon
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: digestif
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 31
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 4
|
34
|
+
version: 1.0.4
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: cucumber
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: aruba
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
description: A tool for tracking files based on content, aiming to scale to arbitrarily large files without slowing down
|
66
|
+
email: adroberts@gmail.com
|
67
|
+
executables:
|
68
|
+
- melon
|
69
|
+
extensions: []
|
70
|
+
|
71
|
+
extra_rdoc_files: []
|
72
|
+
|
73
|
+
files:
|
74
|
+
- .gitignore
|
75
|
+
- Gemfile
|
76
|
+
- History.txt
|
77
|
+
- README.textile
|
78
|
+
- Rakefile
|
79
|
+
- TODO
|
80
|
+
- bin/melon
|
81
|
+
- features/basic.feature
|
82
|
+
- features/edges.feature
|
83
|
+
- features/step_definitions/basic_steps.rb
|
84
|
+
- features/support/env.rb
|
85
|
+
- lib/melon.rb
|
86
|
+
- lib/melon/cli.rb
|
87
|
+
- lib/melon/commands.rb
|
88
|
+
- lib/melon/commands/add.rb
|
89
|
+
- lib/melon/commands/basic_command.rb
|
90
|
+
- lib/melon/commands/help.rb
|
91
|
+
- lib/melon/example.rb
|
92
|
+
- lib/melon/hasher.rb
|
93
|
+
- lib/melon/version.rb
|
94
|
+
- melon.gemspec
|
95
|
+
- script/console
|
96
|
+
- spec/melon/cli_spec.rb
|
97
|
+
- spec/melon/database_spec.rb
|
98
|
+
- spec/melon/fingerprint_spec.rb
|
99
|
+
- spec/melon/parsed_arguments_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
has_rdoc: true
|
102
|
+
homepage: http://github.com/aroberts/melon
|
103
|
+
licenses: []
|
104
|
+
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 3
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
version: "0"
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
hash: 3
|
125
|
+
segments:
|
126
|
+
- 0
|
127
|
+
version: "0"
|
128
|
+
requirements: []
|
129
|
+
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 1.3.7
|
132
|
+
signing_key:
|
133
|
+
specification_version: 3
|
134
|
+
summary: A media catalog
|
135
|
+
test_files:
|
136
|
+
- features/basic.feature
|
137
|
+
- features/edges.feature
|
138
|
+
- features/step_definitions/basic_steps.rb
|
139
|
+
- features/support/env.rb
|
140
|
+
- spec/melon/cli_spec.rb
|
141
|
+
- spec/melon/database_spec.rb
|
142
|
+
- spec/melon/fingerprint_spec.rb
|
143
|
+
- spec/melon/parsed_arguments_spec.rb
|
144
|
+
- spec/spec_helper.rb
|