tagmv 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +19 -7
- data/lib/tagmv.rb +2 -0
- data/lib/tagmv/command_line.rb +31 -7
- data/lib/tagmv/filesystem.rb +43 -11
- data/lib/tagmv/options.rb +24 -0
- data/lib/tagmv/prune_path.rb +1 -1
- data/lib/tagmv/runner.rb +20 -15
- data/lib/tagmv/tree.rb +18 -19
- data/lib/tagmv/version.rb +3 -1
- data/tagmv.gemspec +2 -2
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7bc6f70a29aae56b307b09be9a3108070ed3330934020412932d62bac97a1202
|
4
|
+
data.tar.gz: b082ddb01729079be19620e3be09e80a3f8f1b51fc311c9f7b7f7fd072232666
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19567f27d8539032061a6bcd615f4289466c19f991f1c417a56916c4ef27dd1a87ced04c28a575c85ee920c6f57dbc57db56ad51cea15d4566ba827eb8235e5a
|
7
|
+
data.tar.gz: fd6e0bc16ac871938088ceba64b251b8a088ec3781fb74c4a4b73d33844c3b52c49ae3c02321e918c664b497b238c4f30cb4bfeda7b24b0160891c230907a6fb
|
data/README.md
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
The ultimate keep-your-files-organized solution!
|
4
4
|
|
5
|
-
Moves your files into directories that represent tags, these tags are kept organized as a hierarchy according to
|
5
|
+
Moves your files into directories that represent tags, these tags are kept organized as a hierarchy according to usage.
|
6
6
|
|
7
7
|
Tag all your files with multiple tags, and watch them end up organized!
|
8
8
|
|
9
|
-
|
9
|
+
Relies on convention-only, no FUSE, no symbolic links, no hard links. No special tools required.
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -18,17 +18,29 @@ Works using the most basic parts of the filesystem - no FUSE, no symbolic or har
|
|
18
18
|
|
19
19
|
This will move your files and directories into:
|
20
20
|
|
21
|
-
~/t/tag1
|
21
|
+
~/t/tag1-/tag2-/tag3-/
|
22
22
|
|
23
|
-
As you tag more files and folders, it will order the tag directories
|
23
|
+
As you tag more files and folders, it will re-order the tag directories by the most commonly used tags, keeping your files automatically organized for you.
|
24
24
|
|
25
25
|
## Current features
|
26
26
|
|
27
|
-
Default directory is
|
27
|
+
Default root directory for all your tagged files is `~/t/` (not configurable yet)
|
28
28
|
|
29
|
-
|
29
|
+
## Configuration
|
30
30
|
|
31
|
-
|
31
|
+
Configuration file: `~/.tagmv.yml`
|
32
|
+
|
33
|
+
Supports top level tags - so that you can "pin" certain tags to always show as the top level directories within `~/t/`
|
34
|
+
|
35
|
+
## Commands
|
36
|
+
|
37
|
+
`-d, --dry-run` Check to see what gets moved where
|
38
|
+
|
39
|
+
`-r, --reorder` **[default]** Move everything in `~/t/` into order of tag usage (example: `tagmv -r`)
|
40
|
+
|
41
|
+
`-s, --skip-reorder` Skip reorder (e.g. you are editing tagged files and don't want them moved around yet)
|
42
|
+
|
43
|
+
`-t, --tags *tags` Tags for your files or directories, as many as you want
|
32
44
|
|
33
45
|
|
34
46
|
## Upcoming features
|
data/lib/tagmv.rb
CHANGED
data/lib/tagmv/command_line.rb
CHANGED
@@ -5,26 +5,50 @@ module Tagmv
|
|
5
5
|
extend self
|
6
6
|
|
7
7
|
Choice.options do
|
8
|
-
header "
|
8
|
+
header "A Directory-based Tagging Organizer"
|
9
|
+
header ""
|
10
|
+
header "Usage:"
|
11
|
+
header " $ tagmv file1 file2 directory1 directory2 -t tag1 tag2 tag3 -d"
|
9
12
|
header ""
|
10
13
|
header "Options:"
|
11
14
|
|
12
15
|
footer ""
|
13
|
-
footer "tagmv by
|
16
|
+
footer "tagmv by #{Tagmv::AUTHORS.join(', ')} (#{Tagmv::HOMEPAGE})"
|
17
|
+
|
18
|
+
option :dry_run do
|
19
|
+
short '-d'
|
20
|
+
long '--dry-run'
|
21
|
+
desc 'Check to see what gets moved where'
|
22
|
+
end
|
23
|
+
|
24
|
+
option :reorder do
|
25
|
+
short '-r'
|
26
|
+
long '--reorder'
|
27
|
+
desc 'Move all tagged files & directories into order of tag usage [default]'
|
28
|
+
end
|
29
|
+
|
30
|
+
option :skip_reorder do
|
31
|
+
short '-s'
|
32
|
+
long '--skip-reorder'
|
33
|
+
desc "Skip reorder (for example: you are editing tagged files and don't want them moved around yet)"
|
34
|
+
end
|
14
35
|
|
15
|
-
option :tags
|
36
|
+
option :tags do
|
16
37
|
short '-t'
|
17
38
|
long '--tags *tags'
|
18
|
-
desc '
|
39
|
+
desc 'Tags for your files or directories, as many as you want'
|
19
40
|
end
|
20
41
|
end
|
21
42
|
|
22
43
|
def parse
|
23
|
-
return Choice.help if Choice.rest.empty?
|
44
|
+
return Choice.help if !Choice.choices[:reorder] && (Choice.choices.empty? || Choice.rest.empty?)
|
24
45
|
|
25
46
|
opts = Hash.new
|
26
|
-
opts[:files]
|
27
|
-
opts[:
|
47
|
+
opts[:files] = Choice.rest
|
48
|
+
opts[:dry_run] = Choice.choices[:dry_run]
|
49
|
+
opts[:tags] = Choice.choices[:tags]
|
50
|
+
opts[:reorder] = Choice.choices[:reorder]
|
51
|
+
opts[:skip_reorder]= Choice.choices[:skip_reorder]
|
28
52
|
opts
|
29
53
|
end
|
30
54
|
end
|
data/lib/tagmv/filesystem.rb
CHANGED
@@ -7,25 +7,42 @@ module Tagmv
|
|
7
7
|
attr_accessor :root
|
8
8
|
end
|
9
9
|
|
10
|
-
attr_reader :tags, :files, :tag_order, :top_level_tags
|
10
|
+
attr_reader :tags, :files, :reorder, :tag_order, :top_level_tags
|
11
11
|
def initialize(opts={})
|
12
|
-
@tags = opts[:tags]
|
13
|
-
@files = opts[:files]
|
12
|
+
@tags = scrub_tags(opts[:tags])
|
13
|
+
@files = opts[:files]
|
14
|
+
@dry_run = opts[:dry_run]
|
15
|
+
@reorder = opts[:reorder]
|
14
16
|
@tag_order = opts[:tag_order]
|
15
17
|
@top_level_tags = opts[:top_level_tags]
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
# only keep legit file characters & remove trailing periods
|
20
|
-
|
20
|
+
def scrub_tags(tags)
|
21
|
+
# only keep legit file characters & remove trailing periods, remove duplicates after
|
22
|
+
bad_chars = /^[\-]|[^0-9A-Za-z\.\-\_]|[\.]+$/
|
23
|
+
tags.map {|t| t.gsub(bad_chars, '') }.uniq
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
26
|
+
def scrub_files
|
27
|
+
files.select do |file|
|
28
|
+
path = File.expand_path(file)
|
29
|
+
if File.exist?(path)
|
30
|
+
path
|
31
|
+
else
|
32
|
+
puts "tmv: rename #{file} to #{target_dir}/#{File.basename(file)}: #{Errno::ENOENT.exception}"
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def tags_in_order
|
39
|
+
return tags unless reorder
|
40
|
+
|
41
|
+
(top_level_tags | tag_order) & tags
|
25
42
|
end
|
26
43
|
|
27
44
|
def tag_dirs
|
28
|
-
|
45
|
+
tags_in_order.map {|x| x.gsub(/$/, '-') }
|
29
46
|
end
|
30
47
|
|
31
48
|
def target_dir
|
@@ -33,16 +50,31 @@ module Tagmv
|
|
33
50
|
end
|
34
51
|
|
35
52
|
def prepare_dir
|
36
|
-
|
53
|
+
@@prepare_dir ||= Hash.new do |h, key|
|
54
|
+
h[key] = FileUtils.mkdir_p(key, options)
|
55
|
+
end
|
56
|
+
@@prepare_dir[target_dir]
|
37
57
|
end
|
38
58
|
|
39
59
|
def move_files
|
40
|
-
|
60
|
+
# skip duplicate moves
|
61
|
+
return if reorder && scrub_files.size == 1 && (scrub_files.first.sub(target_dir + '/','') !=~ /\//)
|
62
|
+
|
63
|
+
FileUtils.mv(scrub_files, target_dir, options)
|
41
64
|
rescue ArgumentError
|
42
65
|
end
|
43
66
|
|
44
67
|
def transfer
|
45
68
|
prepare_dir && move_files
|
46
69
|
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def options
|
73
|
+
if @dry_run
|
74
|
+
{noop: true, verbose: true}
|
75
|
+
else
|
76
|
+
{}
|
77
|
+
end
|
78
|
+
end
|
47
79
|
end
|
48
80
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Tagmv
|
2
|
+
class Options
|
3
|
+
attr_accessor :additions
|
4
|
+
def initialize(opts = {})
|
5
|
+
@additions = opts
|
6
|
+
end
|
7
|
+
|
8
|
+
def input
|
9
|
+
Tagmv::CommandLine.parse || {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_config
|
13
|
+
Tagmv::Config.new.load
|
14
|
+
end
|
15
|
+
|
16
|
+
def options
|
17
|
+
return if @options
|
18
|
+
|
19
|
+
@options = load_config
|
20
|
+
@options.merge(additions)
|
21
|
+
@options.merge(input)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/tagmv/prune_path.rb
CHANGED
data/lib/tagmv/runner.rb
CHANGED
@@ -2,27 +2,32 @@ module Tagmv
|
|
2
2
|
module Runner
|
3
3
|
extend self
|
4
4
|
|
5
|
-
def
|
6
|
-
@tree ||= Tagmv::Tree.scan_tree_entries
|
7
|
-
end
|
8
|
-
|
9
|
-
def update_tree
|
10
|
-
opts = Tagmv::CommandLine.parse
|
11
|
-
tree.with(opts)
|
12
|
-
end
|
13
|
-
|
14
|
-
def move_files
|
5
|
+
def reorder_files
|
15
6
|
tree.entries.each do |entry|
|
16
|
-
|
17
|
-
|
7
|
+
fs_options = options.merge(tags: entry.tags, files: entry.files, tag_order: tree.tag_order, reorder: true)
|
8
|
+
Tagmv::Filesystem.new(fs_options).transfer
|
18
9
|
end
|
19
10
|
end
|
20
11
|
|
12
|
+
def move_new_files
|
13
|
+
tree.entries << Entry.new(options)
|
14
|
+
fs_options = options.merge(tag_order: tree.tag_order, reorder: false)
|
15
|
+
Tagmv::Filesystem.new(fs_options).transfer
|
16
|
+
end
|
17
|
+
|
21
18
|
def run
|
22
|
-
|
23
|
-
|
24
|
-
move_files
|
19
|
+
reorder_files unless options[:skip_reorder]
|
20
|
+
move_new_files unless options[:files].empty?
|
25
21
|
Tagmv::PrunePath.prune_tag_dirs
|
26
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def tree
|
26
|
+
@tree ||= Tagmv::Tree.scan_tree_entries
|
27
|
+
end
|
28
|
+
|
29
|
+
def options
|
30
|
+
@options ||= Tagmv::Options.new.options
|
31
|
+
end
|
27
32
|
end
|
28
33
|
end
|
data/lib/tagmv/tree.rb
CHANGED
@@ -22,14 +22,20 @@ module Tagmv
|
|
22
22
|
|
23
23
|
def self.false_tag_regex
|
24
24
|
# detect when there's a false tag i.e. tag2. in path/to/tag1./not_a_tag/tag2./
|
25
|
-
|
25
|
+
/\/.+-\/[^-]+\/.+-/
|
26
|
+
#/\/(.*[^-]\/|[^\/]{0,1}?-)/
|
26
27
|
end
|
28
|
+
|
27
29
|
def self.tags_in_path_regex
|
28
|
-
|
30
|
+
/[^(\.|\-)\/]\K.+?(?=\-\/)/
|
29
31
|
end
|
30
32
|
|
31
33
|
def self.path_has_file_regex
|
32
|
-
/#{tags_in_path_regex}.*[
|
34
|
+
/#{tags_in_path_regex}.*[^\-]$/
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.file_in_tag_dir
|
38
|
+
/.+(\-\/[^\/]+)$/
|
33
39
|
end
|
34
40
|
|
35
41
|
def self.tags(file)
|
@@ -37,22 +43,12 @@ module Tagmv
|
|
37
43
|
file[Filesystem.root.length..-1].scan(tags_in_path_regex).reject {|x| x =~ /\//}
|
38
44
|
end
|
39
45
|
|
40
|
-
def self.empty_dirs
|
41
|
-
files = Find.find(Filesystem.root).select {|x| x =~ path_has_file_regex }
|
42
|
-
tree = Tree.new
|
43
|
-
files.map do |file|
|
44
|
-
next if file =~ false_tag_regex
|
45
|
-
tree.with(files: [file], tags: tags(file))
|
46
|
-
end
|
47
|
-
tree
|
48
|
-
end
|
49
|
-
|
50
46
|
def self.scan_tree_entries
|
51
|
-
files = Find.find(Filesystem.root).select {|x| x =~ path_has_file_regex }
|
47
|
+
files = Find.find(Filesystem.root).select {|x| x =~ path_has_file_regex }.select {|x| x =~ file_in_tag_dir }
|
52
48
|
tree = Tree.new
|
53
|
-
files.
|
54
|
-
next if file =~
|
55
|
-
tree.
|
49
|
+
files.each do |file|
|
50
|
+
next if file =~ false_tag_regex # break when /dev./oh/blah./foo
|
51
|
+
tree.entries << Entry.new(files: [file], tags: tags(file))
|
56
52
|
end
|
57
53
|
tree
|
58
54
|
end
|
@@ -62,12 +58,15 @@ module Tagmv
|
|
62
58
|
# {"dev."=>{"book."=>{"javascript."=>{"Secrets_of_the_Javascript_Ninja.pdf"=>{}}, "ruby."=>{"rails_antipatterns.pdf"=>{}}}, "ruby."=>{"oh"=>{}, "tagmv"=>{}}}}
|
63
59
|
def self.scan_tree_hash
|
64
60
|
Dir.chdir(Filesystem.root)
|
65
|
-
|
61
|
+
|
62
|
+
# reject /dev-/-, /dev-/j-/ and /dev-/notag/tag-
|
63
|
+
paths = Dir["**/*"].delete_if {|x| /\/(.*[^-]\/|[^\/]{0,1}?-)/ =~ x}
|
64
|
+
paths.inject({}) do |hash,path|
|
66
65
|
tree = hash
|
67
66
|
path.split("/").each do |n|
|
68
67
|
tree[n] ||= {}
|
69
68
|
tree = tree[n]
|
70
|
-
break if n[-1] != "
|
69
|
+
break if n[-1] != "-"
|
71
70
|
end
|
72
71
|
hash
|
73
72
|
end
|
data/lib/tagmv/version.rb
CHANGED
data/tagmv.gemspec
CHANGED
@@ -6,11 +6,11 @@ require 'tagmv/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "tagmv"
|
8
8
|
spec.version = Tagmv::VERSION
|
9
|
-
spec.authors =
|
9
|
+
spec.authors = Tagmv::AUTHORS
|
10
10
|
spec.email = ["james.robey+tagmv@gmail.com"]
|
11
11
|
spec.summary = %q{Tag your files by moving them into a tree-like tag structure}
|
12
12
|
spec.description = %q{Moves your files into directories that represent tags, these tags are kept organized as a hierarchy according to tag counts}
|
13
|
-
spec.homepage =
|
13
|
+
spec.homepage = Tagmv::HOMEPAGE
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tagmv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Robey
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-06-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -114,12 +114,13 @@ files:
|
|
114
114
|
- lib/tagmv/config.rb
|
115
115
|
- lib/tagmv/entry.rb
|
116
116
|
- lib/tagmv/filesystem.rb
|
117
|
+
- lib/tagmv/options.rb
|
117
118
|
- lib/tagmv/prune_path.rb
|
118
119
|
- lib/tagmv/runner.rb
|
119
120
|
- lib/tagmv/tree.rb
|
120
121
|
- lib/tagmv/version.rb
|
121
122
|
- tagmv.gemspec
|
122
|
-
homepage: https://github.com/foucist/tagmv
|
123
|
+
homepage: https://github.com/foucist/tagmv
|
123
124
|
licenses:
|
124
125
|
- MIT
|
125
126
|
metadata: {}
|
@@ -139,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
140
|
version: '0'
|
140
141
|
requirements: []
|
141
142
|
rubyforge_project:
|
142
|
-
rubygems_version: 2.
|
143
|
+
rubygems_version: 2.7.7
|
143
144
|
signing_key:
|
144
145
|
specification_version: 4
|
145
146
|
summary: Tag your files by moving them into a tree-like tag structure
|