clean 1.1.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.
- data/bin/clean +22 -0
- data/lib/clean.rb +9 -0
- data/lib/command_util.rb +46 -0
- data/lib/commands.rb +143 -0
- data/lib/config.rb +31 -0
- data/lib/destinations.rb +21 -0
- data/lib/options.rb +31 -0
- data/res/clean.yml +36 -0
- metadata +95 -0
data/bin/clean
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'clean'
|
5
|
+
|
6
|
+
include Options
|
7
|
+
include Config
|
8
|
+
include Destinations
|
9
|
+
include CommandUtil
|
10
|
+
|
11
|
+
$OPTS=get_options(ARGV.dup)
|
12
|
+
|
13
|
+
##################################################
|
14
|
+
### Make and run commands ########################
|
15
|
+
##################################################
|
16
|
+
|
17
|
+
$OPTS[:dirs].each do |dir|
|
18
|
+
cmds=dir_commands(dir)
|
19
|
+
cmds+=file_commands(dir)
|
20
|
+
|
21
|
+
cmds.sort.map &:run
|
22
|
+
end
|
data/lib/clean.rb
ADDED
data/lib/command_util.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
##################################################
|
2
|
+
### Make commands ################################
|
3
|
+
##################################################
|
4
|
+
|
5
|
+
module CommandUtil
|
6
|
+
def make_file_command filename
|
7
|
+
dest=destination_for filename
|
8
|
+
|
9
|
+
return nil unless dest
|
10
|
+
|
11
|
+
if dest=="_rm"
|
12
|
+
RemoveCommand.new filename
|
13
|
+
else
|
14
|
+
MoveCommand.new filename, dest
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def file_commands dir
|
19
|
+
dir=File.expand_path dir
|
20
|
+
cmds=Dir[File.join(dir, '*')].map do |filename|
|
21
|
+
nil if File.directory? filename
|
22
|
+
make_file_command(File.basename(filename))
|
23
|
+
end
|
24
|
+
|
25
|
+
cmds.compact.map { |c| c.path=dir ; c }
|
26
|
+
end
|
27
|
+
|
28
|
+
def dir_commands dir
|
29
|
+
dir=File.expand_path dir
|
30
|
+
|
31
|
+
cmds=config['directories'].keys.map do |dest|
|
32
|
+
next if dest=='_rm'
|
33
|
+
path=File.join(dir,dest)
|
34
|
+
if File.exists?(path)
|
35
|
+
if !File.directory?(path)
|
36
|
+
Trollop::die "#{path} already exists, and isn't a directory"
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
else
|
41
|
+
CreateCommand.new dest,dir
|
42
|
+
end
|
43
|
+
end
|
44
|
+
cmds.compact
|
45
|
+
end
|
46
|
+
end
|
data/lib/commands.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
class Command
|
2
|
+
attr_accessor :path
|
3
|
+
|
4
|
+
def priority ; nil ; end
|
5
|
+
|
6
|
+
def transform filename
|
7
|
+
if path
|
8
|
+
File.join path, filename
|
9
|
+
else
|
10
|
+
File.join '.', filename
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
unless $OPTS[:silent]
|
16
|
+
dr=dry_run
|
17
|
+
puts(dry_run) if dry_run
|
18
|
+
end
|
19
|
+
|
20
|
+
real_run unless $OPTS[:dry_run]
|
21
|
+
end
|
22
|
+
|
23
|
+
def dry_run ; raise NotImplementedError ; end
|
24
|
+
def real_run ; raise NotImplementedError ; end
|
25
|
+
|
26
|
+
def <=> other
|
27
|
+
priority <=> other.priority
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
##################################################
|
32
|
+
### Move files ###################################
|
33
|
+
##################################################
|
34
|
+
|
35
|
+
class MoveCommand < Command
|
36
|
+
def priority ; 2 ; end
|
37
|
+
|
38
|
+
def initialize *args
|
39
|
+
(@filename, @directory, @path)=args
|
40
|
+
end
|
41
|
+
|
42
|
+
def filename ; transform @filename ; end
|
43
|
+
def directory ; transform @directory ; end
|
44
|
+
|
45
|
+
def dry_run
|
46
|
+
case $OPTS[:on_collision]
|
47
|
+
when 'overwrite'
|
48
|
+
"mv #{filename} #{directory}"
|
49
|
+
when 'rename'
|
50
|
+
"mv #{filename} #{File.join(directory,unique_name)}"
|
51
|
+
else # 'ignore' also
|
52
|
+
# emit nothing
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def real_run
|
57
|
+
case $OPTS[:on_collision]
|
58
|
+
when 'overwrite'
|
59
|
+
mv
|
60
|
+
when 'rename'
|
61
|
+
if collision?
|
62
|
+
FileUtils.mv filename, File.join(directory,unique_name)
|
63
|
+
else
|
64
|
+
mv
|
65
|
+
end
|
66
|
+
else # captures the 'ignore' option too
|
67
|
+
# do nothing
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def mv
|
74
|
+
FileUtils.mv filename, directory
|
75
|
+
end
|
76
|
+
|
77
|
+
def collision? fname=filename
|
78
|
+
File.exists?(File.join(directory,File.basename(fname)))
|
79
|
+
end
|
80
|
+
|
81
|
+
def unique_name
|
82
|
+
fname=filename
|
83
|
+
|
84
|
+
# make sure there's a real collision
|
85
|
+
return fname unless collision? fname
|
86
|
+
|
87
|
+
# ext is like ".jpg"
|
88
|
+
ext=File.extname(fname)
|
89
|
+
|
90
|
+
# Base is the filename - dir and extension, so /foo/bar.txt -> bar
|
91
|
+
base=File.basename(fname,ext)
|
92
|
+
|
93
|
+
# loop until we find an n that'll work. Start with bar-1.txt
|
94
|
+
n=1
|
95
|
+
n+=1 while collision?("#{base}-#{n}#{ext}")
|
96
|
+
|
97
|
+
"#{base}-#{n}#{ext}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
##################################################
|
102
|
+
### Create directories ###########################
|
103
|
+
##################################################
|
104
|
+
|
105
|
+
class CreateCommand < Command
|
106
|
+
def priority ; 1 ; end
|
107
|
+
|
108
|
+
def initialize *args
|
109
|
+
(@dirname, @path)=args
|
110
|
+
end
|
111
|
+
|
112
|
+
def dirname ; transform @dirname ; end
|
113
|
+
|
114
|
+
def dry_run
|
115
|
+
"mkdir #{dirname}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def real_run
|
119
|
+
FileUtils.mkdir_p dirname
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
##################################################
|
124
|
+
### Remove files #################################
|
125
|
+
##################################################
|
126
|
+
|
127
|
+
class RemoveCommand < Command
|
128
|
+
def priority ; 3 ; end
|
129
|
+
|
130
|
+
def initialize *args
|
131
|
+
(@filename, @path)=args
|
132
|
+
end
|
133
|
+
|
134
|
+
def filename ; transform @filename ; end
|
135
|
+
|
136
|
+
def dry_run
|
137
|
+
"rm #{filename}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def real_run
|
141
|
+
FileUtils.rm filename
|
142
|
+
end
|
143
|
+
end
|
data/lib/config.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
##################################################
|
2
|
+
### Read config file #############################
|
3
|
+
##################################################
|
4
|
+
|
5
|
+
module Config
|
6
|
+
def config
|
7
|
+
return @config if @config
|
8
|
+
|
9
|
+
file=File.expand_path($OPTS[:config_file])
|
10
|
+
if File.exists? file
|
11
|
+
@config||=YAML.load(File.open(file))
|
12
|
+
else
|
13
|
+
ex=File.expand_path(File.join(File.dirname(__FILE__),
|
14
|
+
'..','res','clean.yml'))
|
15
|
+
puts "Warning: config file #{file} not found; using default #{ex}"
|
16
|
+
@config||=YAML.load(File.open(ex))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def destination_map
|
21
|
+
unless @destination_map
|
22
|
+
@destination_map={}
|
23
|
+
config['directories'].each do |directory, extensions|
|
24
|
+
extensions.each do |ext|
|
25
|
+
@destination_map[ext]=directory
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@destination_map
|
30
|
+
end
|
31
|
+
end
|
data/lib/destinations.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
##################################################
|
2
|
+
### Find move destinations #######################
|
3
|
+
##################################################
|
4
|
+
|
5
|
+
module Destinations
|
6
|
+
def all_extensions_for filename
|
7
|
+
destination_map.keys.map{ |extension|
|
8
|
+
safe_extension=Regexp.escape(extension)
|
9
|
+
(filename =~ Regexp.new("#{safe_extension}$")) ? extension : nil
|
10
|
+
}.compact
|
11
|
+
end
|
12
|
+
|
13
|
+
def destination_for filename
|
14
|
+
longest_extension=
|
15
|
+
all_extensions_for(filename).sort{|a,b|
|
16
|
+
-(a<=>b)
|
17
|
+
}[0]
|
18
|
+
|
19
|
+
destination_map[longest_extension]
|
20
|
+
end
|
21
|
+
end
|
data/lib/options.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
##################################################
|
2
|
+
### Options ######################################
|
3
|
+
##################################################
|
4
|
+
|
5
|
+
module Options
|
6
|
+
def get_options args=ARGV
|
7
|
+
opts=Trollop::options(args) do
|
8
|
+
version "cleaner 1.1.1"
|
9
|
+
banner <<-EOS
|
10
|
+
Cleaner is a program for automatically sorting files based on their extensions.
|
11
|
+
|
12
|
+
Usage:
|
13
|
+
clean [options] <dirs>+
|
14
|
+
|
15
|
+
where [options] are:
|
16
|
+
EOS
|
17
|
+
opt(:dry_run, "Just print commands, don't move anything",
|
18
|
+
:default=>false)
|
19
|
+
opt(:config_file, "Where to load mappings from",
|
20
|
+
:default=>'~/.clean.yml', :type=>String)
|
21
|
+
opt(:silent, "Don't print commands",
|
22
|
+
:default=>false)
|
23
|
+
opt(:on_collision, "If the destination file already exists, rename, overwrite, or ignore?",
|
24
|
+
:default=>'rename')
|
25
|
+
end
|
26
|
+
|
27
|
+
opts[:dirs]=( args.empty? ? ['.'] : args )
|
28
|
+
|
29
|
+
opts
|
30
|
+
end
|
31
|
+
end
|
data/res/clean.yml
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
directories:
|
2
|
+
images:
|
3
|
+
- .jpg
|
4
|
+
- .JPG
|
5
|
+
- .gif
|
6
|
+
- .GIF
|
7
|
+
- .png
|
8
|
+
- .jpeg
|
9
|
+
|
10
|
+
documents:
|
11
|
+
- .pdf
|
12
|
+
- .doc
|
13
|
+
- .ps
|
14
|
+
|
15
|
+
archives:
|
16
|
+
- .tar.gz
|
17
|
+
- .gz
|
18
|
+
- .zip
|
19
|
+
- .bzip
|
20
|
+
|
21
|
+
programs:
|
22
|
+
- .dmg
|
23
|
+
- .app
|
24
|
+
- .pkg
|
25
|
+
|
26
|
+
windows:
|
27
|
+
- .exe
|
28
|
+
- .EXE
|
29
|
+
|
30
|
+
code:
|
31
|
+
- .java
|
32
|
+
- .rb
|
33
|
+
|
34
|
+
_rm:
|
35
|
+
- .torrent
|
36
|
+
- "~"
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: clean
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 1.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Andrews, Ross
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2009-03-08 00:00:00 -06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activesupport
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
version: 2.1.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: trollop
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 10
|
44
|
+
- 2
|
45
|
+
version: 1.10.2
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
description:
|
49
|
+
email: randrews@geekfu.org
|
50
|
+
executables:
|
51
|
+
- clean
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
files:
|
57
|
+
- res/clean.yml
|
58
|
+
- lib/clean.rb
|
59
|
+
- lib/command_util.rb
|
60
|
+
- lib/commands.rb
|
61
|
+
- lib/config.rb
|
62
|
+
- lib/destinations.rb
|
63
|
+
- lib/options.rb
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://geekfu.org
|
66
|
+
licenses: []
|
67
|
+
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.3.6
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: A utility for sorting messy folders
|
94
|
+
test_files: []
|
95
|
+
|