melon 0.2.0 → 0.3.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.
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ === 0.3.0 2011-01-28
2
+ * Major improvements:
3
+ * added 'help' command'
4
+
1
5
  === 0.2.0 2011-01-26
2
6
  * Major improvements:
3
7
  * added --recursive flag to add subcommand
data/features/add.feature CHANGED
@@ -51,7 +51,12 @@ Feature: Adding files to the database
51
51
  """
52
52
  Third test file
53
53
  """
54
- When I run "melon -d test.db add -r dir"
54
+ And a file named "test_file" with:
55
+ """
56
+ Test file that's not in the subfolder
57
+ """
58
+ When I run "melon -d test.db add -r dir test_file"
55
59
  Then the output should contain "dir/test1"
56
60
  And the output should contain "dir/test2"
57
61
  And the output should contain "dir/test/test3"
62
+ And the output should contain "test_file"
data/lib/melon/cli.rb CHANGED
@@ -57,6 +57,8 @@ module Melon
57
57
  def parse_options
58
58
  options = self.class.default_options
59
59
 
60
+ # TODO: empty args should be -h
61
+
60
62
  parser = OptionParser.new do |p|
61
63
  p.banner = "Usage: melon [options] COMMAND [command-options] [ARGS]"
62
64
 
@@ -64,11 +66,18 @@ module Melon
64
66
  p.separator "Commands:"
65
67
  p.separator ""
66
68
 
67
- %w(add check).each do |command|
68
- cls = Commands.const_get(command.capitalize)
69
- p.separator format_command(command, cls.description)
70
- end
69
+ Commands.each do |command|
70
+ # help goes last
71
+ if command.command_name == 'help'
72
+ next
73
+ end
71
74
 
75
+ p.separator format_command(command.command_name,
76
+ command.description)
77
+ end
78
+ # TODO: add help command back into parser helptext
79
+ # p.separator format_command(Commands::Help.command_name,
80
+ # Commands::Help.description)
72
81
  p.separator ""
73
82
  p.separator "Options:"
74
83
  p.separator ""
@@ -0,0 +1,56 @@
1
+ require 'melon/hasher'
2
+ require 'melon/commands/base'
3
+
4
+ module Melon
5
+ module Commands
6
+ class Add < Base
7
+ def self.description
8
+ "Add files to the melon database"
9
+ end
10
+
11
+ def parser_options(parser)
12
+ parser.on("-q", "--quiet", "Suppress printing of hash and path") do
13
+ options.quiet = true
14
+ end
15
+
16
+ parser.on("-r", "--recursive", "Recursively add directory contents") do
17
+ options.recursive = true
18
+ end
19
+ end
20
+
21
+ def run
22
+ parse_options!
23
+
24
+ if options.recursive
25
+ self.args = recursively_expand(args)
26
+ end
27
+
28
+ options.database.transaction do
29
+ args.each do |arg|
30
+ filename = File.expand_path(arg)
31
+
32
+ if File.directory?(filename)
33
+ error "argument is a directory: #{arg}"
34
+ end
35
+
36
+ if options.database[:by_path][filename]
37
+ error "path already present in database: #{arg}"
38
+ end
39
+
40
+ # hash strategy should be encapsulated, ergo indirection here
41
+ hash = Hasher.digest(filename)
42
+
43
+ if options.database[:by_hash][hash]
44
+ error "file exists elsewhere in the database: #{arg}"
45
+ end
46
+
47
+
48
+ options.database[:by_hash][hash] = filename
49
+ options.database[:by_path][filename] = hash
50
+ puts "#{hash}:#{filename}" unless options.quiet
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,73 @@
1
+ require 'optparse'
2
+ require 'melon/helpers'
3
+
4
+ module Melon
5
+ module Commands
6
+ class Base
7
+ include Helpers
8
+ attr_accessor :args, :options
9
+ attr_reader :description
10
+
11
+ def initialize(args, options)
12
+ self.args = args
13
+ self.options = options
14
+ end
15
+
16
+ def self.command_name
17
+ self.to_s.split("::")[-1].downcase
18
+ end
19
+
20
+ def parser
21
+ @parser ||= OptionParser.new do |p|
22
+ p.banner = usagebanner
23
+ p.separator ""
24
+ p.separator blockquote(self.class.description + ".", :margin => 0)
25
+ p.separator ""
26
+
27
+ if helptext
28
+ p.separator blockquote(" " + helptext)
29
+ p.separator ""
30
+ end
31
+
32
+ if self.respond_to? :parser_options
33
+ p.separator "Options:"
34
+ p.separator ""
35
+ parser_options(p)
36
+ end
37
+ end
38
+ end
39
+
40
+
41
+ def usagebanner
42
+ usage = "Usage: melon #{self.class.command_name}"
43
+ usage << " [options]" if self.respond_to?(:parser_options)
44
+ usage << ' ' << usageargs if usageargs
45
+ usage
46
+ end
47
+
48
+ def usageargs; end
49
+ def helptext; end
50
+
51
+ def self.description
52
+ raise
53
+ end
54
+
55
+ def parse_options!
56
+ begin
57
+ parser.parse!(args)
58
+ rescue OptionParser::ParseError => e
59
+ error "#{self.class.command_name}: #{e}"
60
+ end
61
+
62
+ # verify remaining args are files - overrideable
63
+ verify_args
64
+ end
65
+
66
+ def verify_args
67
+ args.each do |arg|
68
+ error "no such file: #{arg}" unless File.exists?(arg)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,35 @@
1
+ require 'melon/commands/base'
2
+
3
+ module Melon
4
+ module Commands
5
+ class Check < Base
6
+ def self.description
7
+ "Determine whether or not a copy of a file resides in the database"
8
+ end
9
+
10
+ def helptext
11
+ <<EOS
12
+ If the file's hash matches a hash in the database, nothing is
13
+ printed. Otherwise, the full path to the file is printed.
14
+ EOS
15
+ end
16
+
17
+ def usageargs
18
+ "file [file [file ...]"
19
+ end
20
+
21
+ def run
22
+ parse_options!
23
+
24
+ options.database.transaction do
25
+ args.each do |filename|
26
+ hash = Hasher.digest(filename)
27
+ unless options.database[:by_hash][hash]
28
+ puts File.expand_path(filename)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ require 'melon/commands/base'
2
+ require 'melon/commands'
3
+
4
+ module Melon
5
+ module Commands
6
+ class Help < Base
7
+ def self.description
8
+ "Get help with a specific command"
9
+ end
10
+
11
+ def run
12
+ help = args.shift
13
+ begin
14
+ puts Commands[help.capitalize].new(args, options).parser
15
+ rescue NameError => e
16
+ # don't swallow NoMethodErrors
17
+ raise e unless e.instance_of?(NameError)
18
+ error "unrecognized command: #{help}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ require 'melon/commands/base'
2
+
3
+ module Melon
4
+ module Commands
5
+ class List < Base
6
+ def self.description
7
+ "List the files tracked by the database"
8
+ end
9
+
10
+ def parser_options(parser)
11
+ parser.on("-p", "--paths", "print only paths") do
12
+ options.only_paths = true
13
+ end
14
+ # TODO: re-enable this as -h after help command goes in
15
+ # parser.on("--hashes", "print only hashes") { options.only_hashes = true }
16
+ end
17
+
18
+ def verify_args
19
+ error "invalid argument: #{args.shift}" unless args.empty?
20
+ end
21
+
22
+ def run
23
+ parse_options!
24
+
25
+ options.database.transaction do
26
+ options.database[:by_hash].each_pair do |hash, path|
27
+ puts "#{path}:#{hash}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ require 'melon/commands/base'
2
+
3
+ module Melon
4
+ module Commands
5
+ class Show < Base
6
+ def self.description
7
+ "Show where the database thinks a file is located"
8
+ end
9
+
10
+ def usageargs
11
+ "file [file [file ...]]"
12
+ end
13
+
14
+ def helptext
15
+ <<EOS
16
+ If the file's hash matches a hash in the database, then
17
+ the associated path in the database is printed. Otherwise,
18
+ nothing is printed.
19
+ EOS
20
+ end
21
+
22
+ def run
23
+ parse_options!
24
+
25
+ options.database.transaction do
26
+ args.each do |filename|
27
+ hash = Hasher.digest(filename)
28
+ if path = options.database[:by_hash][hash]
29
+ puts path
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,14 +1,13 @@
1
- require 'melon/hasher'
2
- require 'melon/helpers'
1
+ Dir[File.join(File.dirname(__FILE__), "commands/*.rb")].each {|f| require f}
3
2
 
4
3
  module Melon
5
4
  module Commands
6
5
  def self.each
7
6
  consts = []
8
- base = self.const_get('Base')
9
- self.constants.each do |c|
7
+ self.constants.sort.each do |c|
10
8
  const = self.const_get(c)
11
- if const.superclass == base
9
+
10
+ if const.superclass == Base
12
11
  consts << const
13
12
  yield const
14
13
  end
@@ -20,215 +19,19 @@ module Melon
20
19
  alias :[] :const_get
21
20
  end
22
21
 
23
- # needs a 'verify' command to check integrity of database
22
+
23
+ # 1.0 list
24
+ # TODO: needs a 'verify' command to check integrity of database
24
25
  # both internal 2-hash consistency (consistency) and db<->filesystem
25
26
  # matching up (integrity) [file exists, hashes match]
26
- # needs a 'remove' command, or some way to deal with deletes/renames
27
- # needs a 'list' command
28
- class Base
29
- include Helpers
30
- attr_accessor :args, :options
31
- attr_reader :description
32
-
33
- def initialize(args, options)
34
- self.args = args
35
- self.options = options
36
- end
37
-
38
- def parser
39
- raise
40
- end
41
-
42
- def self.description
43
- raise
44
- end
45
-
46
- def parse_options!
47
- begin
48
- parser.parse!(args)
49
- rescue OptionParser::ParseError => e
50
- error "#{self.class.to_s.split("::").last.downcase}: #{e}"
51
- end
52
-
53
- # verify remaining args are files - overrideable
54
- verify_args
55
- end
56
-
57
- def verify_args
58
- args.each do |arg|
59
- error "no such file: #{arg}" unless File.exists?(arg)
60
- end
61
- end
62
- end
63
-
64
- class Add < Base
65
-
66
- def self.description
67
- "Add files to the melon database"
68
- end
69
-
70
- def parser
71
- @parser ||= OptionParser.new do |p|
72
- p.banner = "Usage: melon add [options] file [file [file ...]]"
73
- p.separator ""
74
- p.separator blockquote(Add.description + ".")
75
-
76
- p.separator ""
77
- p.separator "Options:"
78
- p.separator ""
79
-
80
- p.on("-q", "--quiet", "Suppress printing of hash and path") do
81
- options.quiet = true
82
- end
83
-
84
- p.on("-r", "--recursive", "Recursively add directory contents") do
85
- options.recursive = true
86
- end
87
-
88
- # p.on("-f", "--force",
89
- # "Force the recalculation of the path that",
90
- # " already exists in the database") do
91
- # options.force = true
92
- # end
93
- end
94
- end
95
-
96
- def run
97
- parse_options!
98
-
99
- if options.recursive
100
- self.args = args.collect do |arg|
101
- if File.directory?(arg)
102
- Dir["#{arg}/**/*"]
103
- else
104
- arg
105
- end
106
- end.flatten.reject { |arg| File.directory?(arg) }
107
- end
108
-
109
- options.database.transaction do
110
- args.each do |arg|
111
- filename = File.expand_path(arg)
112
-
113
- if File.directory?(filename)
114
- error "argument is a directory: #{arg}"
115
- end
116
-
117
- if options.database[:by_path][filename]# and !options.force
118
- error "path already present in database: #{arg}"
119
- end
120
-
121
- # hash strategy should be encapsulated, ergo indirection here
122
- hash = Hasher.digest(filename)
123
-
124
- if options.database[:by_hash][hash]
125
- error "file exists elsewhere in the database: #{arg}"
126
- end
127
-
128
-
129
- options.database[:by_hash][hash] = filename
130
- options.database[:by_path][filename] = hash
131
- puts "#{hash}:#{filename}" unless options.quiet
132
- end
133
- end
134
- end
135
- end
136
-
137
- class Show < Base
138
- def self.description
139
- "Show where the database thinks a file is located"
140
- end
141
-
142
- def parser
143
- @parser ||= OptionParser.new do |p|
144
- p.banner = "Usage: melon show file [file [file ...]]"
145
- p.separator ""
146
- p.separator blockquote(self.class.description + <<EOS
147
- . If the file's hash matches a hash in the database, then
148
- the associated path in the database is printed. Otherwise,
149
- nothing is printed.
150
- EOS
151
- )
152
- p.separator ""
153
-
154
- end
155
- end
156
-
157
- def run
158
- parse_options!
159
-
160
- options.database.transaction do
161
- args.each do |filename|
162
- hash = Hasher.digest(filename)
163
- if path = options.database[:by_hash][hash]
164
- puts path
165
- end
166
- end
167
- end
168
- end
169
- end
170
-
171
- class Check < Base
172
- def self.description
173
- "Determine whether or not a copy of a file resides in the database"
174
- end
175
-
176
- def parser
177
- @parser ||= OptionParser.new do |p|
178
- p.banner = "Usage: melon check file [file [file ...]]"
179
- p.separator ""
180
- p.separator blockquote(self.class.description + <<EOS
181
- . If the file's hash matches a hash in the database, nothing is
182
- printed. Otherwise, the full path to the file is printed.
183
- EOS
184
- )
185
- p.separator ""
186
-
187
- end
188
- end
189
-
190
- def run
191
- parse_options!
192
-
193
- options.database.transaction do
194
- args.each do |filename|
195
- hash = Hasher.digest(filename)
196
- unless options.database[:by_hash][hash]
197
- puts File.expand_path(filename)
198
- end
199
- end
200
- end
201
- end
202
- end
203
-
204
- class List < Base
205
- def self.description
206
- "List the files tracked by the database"
207
- end
208
-
209
- def parser
210
- @parser ||= OptionParser.new do |p|
211
- p.banner = "Usage: melon list"
212
- p.separator ""
213
- p.separator blockquote(self.class.description)
214
- p.separator ""
215
-
216
- end
217
- end
218
-
219
- def verify_args
220
- error "invalid argument: #{args.shift}" unless args.empty?
221
- end
222
-
223
- def run
224
- parse_options!
225
-
226
- options.database.transaction do
227
- options.database[:by_hash].each_pair do |hash, path|
228
- puts "#{path}:#{hash}"
229
- end
230
- end
231
- end
232
- end
27
+ # TODO: needs a 'remove' command, or some way to deal with deletes/renames
28
+ # remove: given a tracked file, removes it
29
+ # given an untracked file, it hashes it
30
+ # and removes it by hash
31
+ # TODO: list needs --paths(only) and --hashes(only)
32
+ # --count
33
+ # TODO: check needs -r
34
+ # TODO: update- a function of add, ignore files that are already present in the db
35
+ # TODO: handle moving a file somehow -- hopefully a function of update
233
36
  end
234
37
  end
data/lib/melon/hasher.rb CHANGED
@@ -3,6 +3,7 @@ require 'digestif/hasher'
3
3
 
4
4
  module Melon
5
5
  class Hasher
6
+ # TODO: scale seek size up with file size
6
7
  def self.digest(filename)
7
8
  hasher = Digestif::Hasher.new(filename)
8
9
  hasher.options.read_size = 40000
data/lib/melon/helpers.rb CHANGED
@@ -6,7 +6,7 @@ module Melon
6
6
  :width => 22,
7
7
  :wrap => 80
8
8
  }.update(options)
9
-
9
+
10
10
  pad = "\n" + ' ' * options[:width]
11
11
  desc = self.wrap_text(desc, options[:wrap] - options[:width])
12
12
  desc = desc.split("\n").join(pad)
@@ -25,8 +25,12 @@ module Melon
25
25
  :margin => 4,
26
26
  :wrap => 70
27
27
  }.update(options)
28
+
28
29
  options[:width] = options.delete(:margin)
29
- format_command('', string.gsub(/\s+/,' ').gsub('\. ', '. '), options)
30
+ options[:margin] = 0
31
+ format_command('', string.gsub(/\s+/,' ').
32
+ gsub(/\. /, '. ').
33
+ gsub(/^ /, ' '), options)
30
34
  end
31
35
 
32
36
  def error(error_obj_or_str, code = 1)
@@ -39,5 +43,15 @@ module Melon
39
43
  $stderr.puts "melon: #{error_str}"
40
44
  exit code
41
45
  end
46
+
47
+ def recursively_expand(filelist)
48
+ filelist.collect do |arg|
49
+ if File.directory?(arg)
50
+ Dir["#{arg}/**/*"]
51
+ else
52
+ arg
53
+ end
54
+ end.flatten.reject { |arg| File.directory?(arg) }
55
+ end
42
56
  end
43
57
  end
data/lib/melon/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Melon
2
2
  def self.version
3
- "0.2.0"
3
+ "0.3.0"
4
4
  end
5
5
 
6
6
  def self.version_string
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: melon
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andrew Roberts
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-26 00:00:00 -05:00
18
+ date: 2011-01-28 00:00:00 -05:00
19
19
  default_executable: melon
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -88,6 +88,12 @@ files:
88
88
  - lib/melon.rb
89
89
  - lib/melon/cli.rb
90
90
  - lib/melon/commands.rb
91
+ - lib/melon/commands/add.rb
92
+ - lib/melon/commands/base.rb
93
+ - lib/melon/commands/check.rb
94
+ - lib/melon/commands/help.rb
95
+ - lib/melon/commands/list.rb
96
+ - lib/melon/commands/show.rb
91
97
  - lib/melon/example.rb
92
98
  - lib/melon/hasher.rb
93
99
  - lib/melon/helpers.rb