taggata 0.0.2 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +15 -15
- data/bin/taggata +2 -45
- data/lib/taggata.rb +4 -4
- data/lib/taggata/cli.rb +102 -0
- data/lib/taggata/db.rb +75 -0
- data/lib/taggata/db_adapters.rb +6 -0
- data/lib/taggata/db_adapters/abstract.rb +45 -0
- data/lib/taggata/db_adapters/sequel.rb +75 -0
- data/lib/taggata/db_adapters/sequel_migrations/001_initial.rb +34 -0
- data/lib/taggata/parser/query.rb +26 -14
- data/lib/taggata/parser/tag.rb +15 -9
- data/lib/taggata/persistent.rb +10 -0
- data/lib/taggata/persistent/abstract.rb +53 -0
- data/lib/taggata/persistent/directory.rb +80 -0
- data/lib/taggata/persistent/file.rb +99 -0
- data/lib/taggata/persistent/file_tag.rb +48 -0
- data/lib/taggata/persistent/tag.rb +40 -0
- data/lib/taggata/persistent/with_parent.rb +11 -0
- data/lib/taggata/scanner.rb +70 -0
- data/lib/taggata/version.rb +1 -1
- data/taggata.gemspec +3 -0
- data/test/parser/query_parser_test.rb +6 -5
- data/test/parser/tag_parser_test.rb +3 -2
- data/test/{filesystem_scanner_test.rb → scanner_test.rb} +10 -5
- data/test/taggata_test_helper.rb +1 -6
- data/test/tagging_test.rb +58 -0
- metadata +81 -9
- data/lib/taggata/directory.rb +0 -49
- data/lib/taggata/file.rb +0 -33
- data/lib/taggata/filesystem_scanner.rb +0 -72
- data/lib/taggata/tag.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 316d77bbeae3f1cb3224e6a8c5e7a0e35398be06
|
4
|
+
data.tar.gz: 847538ab0379d0a7db9d1470c6e04d9257190af5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d648822f874cf532bd2976541cfb790b4ab104a388c8d30424e83d34baa78cf47a13144874968a2fe4da249dc712e570d36116e9363be4b685a19920c5f3c6d5
|
7
|
+
data.tar.gz: f49c703ac45544a2d3e6c5103aa226c98557c1f08f5984baf505cdfb3217d1340565c543b5cf22b422e1419296cf329678ac810c1ec7e4c9a71b7b39e209cb6b
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Taggata [](https://travis-ci.org/adamruzicka/taggata)
|
2
2
|
|
3
3
|
Ruby gem for file tagging, it can:
|
4
4
|
- scan filesystem and store metadata in database
|
@@ -14,28 +14,28 @@ Clone this repository:
|
|
14
14
|
And then execute:
|
15
15
|
|
16
16
|
$ bundle install
|
17
|
-
|
17
|
+
|
18
18
|
## Usage
|
19
19
|
Scan filesystem:
|
20
20
|
|
21
|
-
$ taggata $DATABASE scan $PATH_TO_SCAN
|
22
|
-
|
21
|
+
$ taggata --db-path $DATABASE scan $PATH_TO_SCAN
|
22
|
+
|
23
23
|
Search for entries:
|
24
|
-
|
25
|
-
$ taggata $DATABASE search "$SEARCH_QUERY"
|
26
|
-
|
24
|
+
|
25
|
+
$ taggata --db-path $DATABASE search "$SEARCH_QUERY"
|
26
|
+
|
27
27
|
Get count of matching entries:
|
28
|
-
|
29
|
-
$ taggata $DATABASE
|
30
|
-
|
28
|
+
|
29
|
+
$ taggata --db-path $DATABASE -c search "$SEARCH_QUERY"
|
30
|
+
|
31
31
|
Tag an entry:
|
32
32
|
|
33
|
-
$ taggata $DATABASE tag "$SEARCH_QUERY" "$TAG_QUERY"
|
33
|
+
$ taggata --db-path $DATABASE tag "$SEARCH_QUERY" "$TAG_QUERY"
|
34
|
+
|
35
|
+
Remove files matching a query:
|
34
36
|
|
35
|
-
|
37
|
+
$ taggata --db-path $DATABASE remove "$SEARCH_QUERY"
|
36
38
|
|
37
|
-
$ taggata $DATABASE remove "$SEARCH_QUERY"
|
38
|
-
|
39
39
|
Query formats:
|
40
40
|
Search query has format of "$TYPE:$PARAMETER", where:
|
41
41
|
- $TYPE can be one of:
|
@@ -62,7 +62,7 @@ For example to tag all untagged files containing ```Photos``` in path with tags
|
|
62
62
|
|
63
63
|
## Contributing
|
64
64
|
|
65
|
-
1. Fork it ( https://github.com/
|
65
|
+
1. Fork it ( https://github.com/adamruzicka/taggata/fork )
|
66
66
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
67
67
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
68
68
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/bin/taggata
CHANGED
@@ -1,47 +1,4 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
DB.create_table :file_tags do
|
7
|
-
foreign_key :tag_id, :tags
|
8
|
-
foreign_key :file_id, :files
|
9
|
-
primary_key [:tag_id, :file_id], :name => :file_tags_pk, :unique => true
|
10
|
-
end unless DB.table_exists? :file_tags
|
11
|
-
|
12
|
-
Sequel::Model.plugin(:schema)
|
13
|
-
|
14
|
-
require 'taggata'
|
15
|
-
|
16
|
-
case ARGV[1]
|
17
|
-
when 'scan'
|
18
|
-
root = ::Taggata::Directory.find_or_create(:name => ARGV[2])
|
19
|
-
root.scan
|
20
|
-
when 'search'
|
21
|
-
result = ::Taggata::Parser::Query.parse(ARGV[2])
|
22
|
-
if result.empty?
|
23
|
-
puts "No files matching query '#{ARGV[2]}'"
|
24
|
-
else
|
25
|
-
result.each do |f|
|
26
|
-
puts f.path
|
27
|
-
end
|
28
|
-
end
|
29
|
-
when 'tag'
|
30
|
-
result = ::Taggata::Parser::Query.parse(ARGV[2])
|
31
|
-
tag_hash = ::Taggata::Parser::Tag.parse(ARGV[3])
|
32
|
-
result.each do |file|
|
33
|
-
deletions = tag_hash[:del].map { |tag| [file.id, tag.id] }
|
34
|
-
DB[:file_tags].where([:file_id, :tag_id] => deletions).delete
|
35
|
-
end.flatten 1
|
36
|
-
additions = result.map do |file|
|
37
|
-
adds = tag_hash[:add].map { |tag| [file.id, tag.id] }
|
38
|
-
in_db = DB[:file_tags].where([:file_id, :tag_id] => adds).map([:file_id, :tag_id])
|
39
|
-
(adds - in_db).map { |file_id, tag_id| { :file_id => file_id, :tag_id => tag_id } }
|
40
|
-
end.flatten
|
41
|
-
DB[:file_tags].multi_insert(additions)
|
42
|
-
when 'remove'
|
43
|
-
files = ::Taggata::Parser::Query.parse(ARGV[2])
|
44
|
-
files.each(&:destroy)
|
45
|
-
when 'count'
|
46
|
-
puts ::Taggata::Parser::Query.parse(ARGV[2]).count
|
47
|
-
end
|
3
|
+
require 'taggata/cli'
|
4
|
+
include Taggata::Cli
|
data/lib/taggata.rb
CHANGED
@@ -3,9 +3,9 @@ require 'sequel'
|
|
3
3
|
|
4
4
|
module Taggata
|
5
5
|
require 'taggata/constants'
|
6
|
-
require 'taggata/
|
7
|
-
require 'taggata/
|
8
|
-
require 'taggata/
|
9
|
-
require 'taggata/
|
6
|
+
require 'taggata/persistent'
|
7
|
+
require 'taggata/db'
|
8
|
+
require 'taggata/db_adapters'
|
9
|
+
require 'taggata/scanner'
|
10
10
|
require 'taggata/parser'
|
11
11
|
end
|
data/lib/taggata/cli.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'clamp'
|
2
|
+
require 'taggata'
|
3
|
+
|
4
|
+
module Taggata
|
5
|
+
module Cli
|
6
|
+
Clamp do
|
7
|
+
|
8
|
+
option "--db-path", "DB_PATH", "path to the DB", :attribute_name => :db, :required => true do |db_path|
|
9
|
+
Taggata::Db.new "sqlite://#{db_path}", Taggata::DbAdapters::Sequel
|
10
|
+
end
|
11
|
+
option ['-v', '--verbose'], :flag, 'be verbose', :default => false
|
12
|
+
option ['-q', '--quiet'], :flag, 'be quite', :default => false
|
13
|
+
|
14
|
+
subcommand "scan", "Scan the system" do
|
15
|
+
|
16
|
+
parameter "[ROOT]", "the root of the scanned tree", :attribute_name => :root_path, :default => './'
|
17
|
+
|
18
|
+
def execute
|
19
|
+
scanner = Taggata::Scanner.new db
|
20
|
+
root = Taggata::Persistent::Directory.find_or_create db, :name => root_path
|
21
|
+
scanner.process(root)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
subcommand 'tag', 'tag a file matching query' do
|
27
|
+
|
28
|
+
parameter 'TAG_QUERY', 'the tag query', :attribute_name => :tag_query, :required => true
|
29
|
+
parameter 'SEARCH_QUERY', 'the query to search', :attribute_name => :search_query, :required => true
|
30
|
+
|
31
|
+
def execute
|
32
|
+
tags = ::Taggata::Parser::Tag.new(db).parse(tag_query)
|
33
|
+
files = ::Taggata::Parser::Query.new(db).parse(search_query)
|
34
|
+
db.transaction do
|
35
|
+
files.each do |file|
|
36
|
+
file.add_tags *tags[:add]
|
37
|
+
file.remove_tags *tags[:del]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
subcommand 'search', "search the database" do
|
45
|
+
|
46
|
+
option "-c", :flag, 'show only matched count', :attribute_name => :count, :default => false
|
47
|
+
option "-n", :flag, 'show only names without paths', :attribute_name => :names, :default => false
|
48
|
+
parameter "QUERY", 'the query to perform', :attribute_name => :query, :required => true
|
49
|
+
|
50
|
+
def execute
|
51
|
+
results = ::Taggata::Parser::Query.new(db).parse(query)
|
52
|
+
if count?
|
53
|
+
puts results.count
|
54
|
+
else
|
55
|
+
if results.empty?
|
56
|
+
puts "No results matching the query."
|
57
|
+
else
|
58
|
+
method = names? ? :name : :path
|
59
|
+
results.each { |file| puts file.send(method) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
subcommand 'remove', 'remove files matching query' do
|
67
|
+
|
68
|
+
parameter "QUERY", 'the query to perform', :attribute_name => :query, :required => true
|
69
|
+
|
70
|
+
def execute
|
71
|
+
parser = ::Taggata::Parser::Query.new db
|
72
|
+
results = parser.parse query
|
73
|
+
db.transaction do
|
74
|
+
results.each(&:destroy)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
subcommand 'list', 'list things in database' do
|
81
|
+
|
82
|
+
parameter 'TYPE', 'type of records to list, can be one of file, directory, tag', :attribute_name => :type, :required => true do |type|
|
83
|
+
signal_usage_error 'TYPE must be one of file, directory, tag' unless %(file directory tag).include? type
|
84
|
+
type.to_sym
|
85
|
+
end
|
86
|
+
|
87
|
+
def execute
|
88
|
+
case type
|
89
|
+
when :file
|
90
|
+
db.find(Taggata::Persistent::File, {}).each { |file| puts file.path }
|
91
|
+
when :directory
|
92
|
+
db.find(Taggata::Persistent::Directory, {}).each { |dir| puts dir.path }
|
93
|
+
when :tag
|
94
|
+
db.find(Taggata::Persistent::Tag, {}).each { |tag| puts tag.name }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/taggata/db.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module Taggata
|
2
|
+
class Db
|
3
|
+
|
4
|
+
attr_reader :adapter
|
5
|
+
|
6
|
+
def initialize(db_path, adapter_class)
|
7
|
+
@adapter = adapter_class.new
|
8
|
+
@adapter.initialize_db db_path
|
9
|
+
@adapter.migrate_db
|
10
|
+
end
|
11
|
+
|
12
|
+
def transaction(&block)
|
13
|
+
if block_given?
|
14
|
+
adapter.transaction(block)
|
15
|
+
else
|
16
|
+
raise "Cannot initiate transaction without block."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_untagged_files
|
21
|
+
adapter.find_untagged_files.map { |hash| Persistent::File.new_from_hash self, hash }
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy(klass, options)
|
25
|
+
adapter.destroy(klass, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def bulk_insert(klass, values)
|
29
|
+
adapter.bulk_insert(klass, values)
|
30
|
+
end
|
31
|
+
|
32
|
+
def find(klass, options = {})
|
33
|
+
adapter.find(klass, options).map do |hash|
|
34
|
+
klass.new_from_hash self, hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_one(klass, options = {})
|
39
|
+
record = adapter.find_one(klass, options)
|
40
|
+
klass.new_from_hash(self, record) unless record.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def load(klass, id)
|
44
|
+
klass.new_from_hash(adapter.load(klass, id))
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_or_create(klass, options)
|
48
|
+
search = adapter.find_one(klass, options)
|
49
|
+
if search.nil?
|
50
|
+
o = klass.new_from_hash self, options
|
51
|
+
o.save
|
52
|
+
o
|
53
|
+
else
|
54
|
+
klass.new_from_hash self, search
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def save(object)
|
59
|
+
adapter.save(object.class.table, object.to_hash)
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_child_directories(directory)
|
63
|
+
adapter.find(Persistent::Directory, :parent_id => directory.id).map do |hash|
|
64
|
+
Persistent::Directory.new_from_hash(self, hash)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_child_files(directory)
|
69
|
+
adapter.find(Persistent::File, :parent_id => directory.id) do |hash|
|
70
|
+
Persistent::File.new_from_hash(self, hash)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Taggata
|
2
|
+
module DbAdapters
|
3
|
+
class Abstract
|
4
|
+
|
5
|
+
attr_reader :db
|
6
|
+
|
7
|
+
def bulk_insert(klass, values)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_untagged_files
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def transaction(&block)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy(klass, options)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_one(klass, options)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def find(klass, options)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def save(table, id)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize_db(db_path)
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
def migrate_db
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Taggata
|
2
|
+
module DbAdapters
|
3
|
+
|
4
|
+
Sequel.extension :migration
|
5
|
+
|
6
|
+
class Sequel < Abstract
|
7
|
+
|
8
|
+
def bulk_insert(klass, values)
|
9
|
+
db[klass.table].multi_insert(values)
|
10
|
+
end
|
11
|
+
|
12
|
+
def transaction(block)
|
13
|
+
db.transaction do
|
14
|
+
block.call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_untagged_files
|
19
|
+
file_tags = db[Persistent::FileTag.table].select(:file_id).map { |hash| hash[:file_id] }
|
20
|
+
file_ids = db[Persistent::File.table].select(:id).map { |hash| hash[:id] }
|
21
|
+
untagged_ids = file_ids.reject { |id| file_tags.include? id }
|
22
|
+
find(Taggata::Persistent::File, :id => untagged_ids)
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy(klass, options)
|
26
|
+
db[klass.table].where(options).delete
|
27
|
+
end
|
28
|
+
|
29
|
+
def find(klass, options)
|
30
|
+
db[klass.table].where(options).all
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_one(klass, options)
|
34
|
+
db[klass.table].where(options).limit(1).first
|
35
|
+
end
|
36
|
+
|
37
|
+
def search(klass, options)
|
38
|
+
db[klass.table].where(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def save(table, hash)
|
42
|
+
existing_record = db[table].where(primary_key(table, hash)).limit(1)
|
43
|
+
if existing_record.count == 0
|
44
|
+
db[table].insert(hash)
|
45
|
+
else
|
46
|
+
existing_record.update(hash)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize_db(db_path)
|
51
|
+
@db = ::Sequel.connect db_path
|
52
|
+
end
|
53
|
+
|
54
|
+
def migrate_db
|
55
|
+
::Sequel::Migrator.run(db, self.class.migrations_path, table: 'taggata_schema_info')
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def primary_key(table, hash)
|
61
|
+
case table
|
62
|
+
when :taggata_file_tags
|
63
|
+
{ :file_id => hash[:file_id], :tag_id => hash[:tag_id] }
|
64
|
+
else
|
65
|
+
{ :id => hash[:id] }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.migrations_path
|
70
|
+
File.expand_path('../sequel_migrations', __FILE__)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
up do
|
3
|
+
create_table(:taggata_directories) do
|
4
|
+
primary_key :id
|
5
|
+
column :name, String
|
6
|
+
foreign_key :parent_id, :taggata_directories, type: Integer
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table(:taggata_files) do
|
10
|
+
primary_key :id
|
11
|
+
column :name, String
|
12
|
+
foreign_key :parent_id, :taggata_directories, type: Integer, :null => false
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table(:taggata_tags) do
|
16
|
+
primary_key :id
|
17
|
+
column :name, String, unique: true
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table(:taggata_file_tags) do
|
21
|
+
foreign_key :file_id, :taggata_files, type: Integer, null: false
|
22
|
+
foreign_key :tag_id, :taggata_tags, type: Integer, null: false
|
23
|
+
index [:file_id, :tag_id], unique: true
|
24
|
+
primary_key [:file_id, :tag_id]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
down do
|
29
|
+
drop_table(:taggata_file_tags)
|
30
|
+
drop_table(:taggata_tags)
|
31
|
+
drop_table(:taggata_files)
|
32
|
+
drop_table(:taggata_directories)
|
33
|
+
end
|
34
|
+
end
|