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
data/lib/taggata/parser/query.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
module Taggata
|
2
2
|
module Parser
|
3
3
|
class Query
|
4
|
-
|
4
|
+
|
5
|
+
attr_reader :db
|
6
|
+
|
7
|
+
def initialize(db)
|
8
|
+
@db = db
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(query)
|
5
12
|
process(postfix(query))
|
6
13
|
end
|
7
14
|
|
@@ -11,21 +18,26 @@ module Taggata
|
|
11
18
|
#
|
12
19
|
# @param token String the terminal token to resolve
|
13
20
|
# @result [::Taggata::File] list of files with this tag
|
14
|
-
def
|
21
|
+
def resolve(token)
|
15
22
|
type, name = token.split(':', 2)
|
16
23
|
case type.downcase
|
17
24
|
when 'is', 'tag'
|
18
|
-
::Taggata::Tag.
|
25
|
+
tag = ::Taggata::Persistent::Tag.find_one(db, :name => name)
|
26
|
+
tag.nil? ? [] : tag.files
|
27
|
+
when 'like'
|
28
|
+
files = ::Taggata::Persistent::File.find(db, Sequel.like(:name, name))
|
19
29
|
when 'file', 'name'
|
20
|
-
|
30
|
+
# File.all.select { |f| f.name[/#{name}/] }
|
31
|
+
files = ::Taggata::Persistent::File.find(db, {})
|
32
|
+
files.select { |f| f.name[/#{name}/] }
|
21
33
|
when 'path'
|
22
|
-
File.
|
34
|
+
files = ::Taggata::Persistent::File.find(db, {})
|
35
|
+
files.select { |f| f.path[/#{name}/] }
|
23
36
|
when 'missing'
|
24
|
-
::Taggata::Tag.
|
37
|
+
tag = ::Taggata::Persistent::Tag.find_or_create(db, :name => MISSING_TAG_NAME)
|
38
|
+
tag.files
|
25
39
|
when 'untagged'
|
26
|
-
|
27
|
-
.select { |id| DB[:file_tags].where(:file_id => id).empty? }
|
28
|
-
File.where(:id => ids).all
|
40
|
+
db.find_untagged_files
|
29
41
|
else
|
30
42
|
fail "Unknown token type '#{type}'"
|
31
43
|
end
|
@@ -35,7 +47,7 @@ module Taggata
|
|
35
47
|
#
|
36
48
|
# @param postfix [String] the input in postfix notation as array
|
37
49
|
# @return [::Taggata::File]
|
38
|
-
def
|
50
|
+
def process(postfix)
|
39
51
|
stack = []
|
40
52
|
postfix.each do |token|
|
41
53
|
if operator? token
|
@@ -55,7 +67,7 @@ module Taggata
|
|
55
67
|
# @param op_A [::Taggata::File] first operand
|
56
68
|
# @param op_B [::Taggata::File] second operand
|
57
69
|
# @result [::Taggata::File] result of applying operator to operands
|
58
|
-
def
|
70
|
+
def apply(operator, op_A, op_B)
|
59
71
|
case operator
|
60
72
|
when :and
|
61
73
|
op_A & op_B
|
@@ -70,7 +82,7 @@ module Taggata
|
|
70
82
|
#
|
71
83
|
# @param query String the query string
|
72
84
|
# @result [String] query in postfix notation as an array
|
73
|
-
def
|
85
|
+
def postfix(query)
|
74
86
|
postfix = []
|
75
87
|
operators = []
|
76
88
|
query.split.each do |token|
|
@@ -93,7 +105,7 @@ module Taggata
|
|
93
105
|
#
|
94
106
|
# @param token String
|
95
107
|
# @result the token
|
96
|
-
def
|
108
|
+
def translate(token)
|
97
109
|
return :and if ['and', '&'].include? token.downcase
|
98
110
|
return :or if ['or', '|'].include? token.downcase
|
99
111
|
token
|
@@ -103,7 +115,7 @@ module Taggata
|
|
103
115
|
#
|
104
116
|
# @param token String
|
105
117
|
# @result true/false
|
106
|
-
def
|
118
|
+
def operator?(token)
|
107
119
|
['&', '|', 'or', 'and', '(', :and, :or].include? token.downcase
|
108
120
|
end
|
109
121
|
end
|
data/lib/taggata/parser/tag.rb
CHANGED
@@ -1,31 +1,37 @@
|
|
1
1
|
module Taggata
|
2
2
|
module Parser
|
3
3
|
class Tag
|
4
|
+
|
5
|
+
attr_reader :db
|
6
|
+
|
7
|
+
def initialize(db)
|
8
|
+
@db = db
|
9
|
+
end
|
10
|
+
|
4
11
|
# Parses give tagging string
|
5
12
|
#
|
6
13
|
# @param query String tagging string
|
7
14
|
# @return [Hash]
|
8
|
-
def
|
15
|
+
def parse(query)
|
9
16
|
result = { :add => [], :del => [] }
|
10
17
|
hash = query.split.reduce(result) do |acc, tag|
|
11
18
|
handle_tag(tag, acc)
|
12
19
|
end
|
13
|
-
dels = hash[:del].empty? ? [] : ::Taggata::Tag.
|
20
|
+
dels = hash[:del].empty? ? [] : ::Taggata::Persistent::Tag.find(db, :name => hash[:del])
|
14
21
|
adds = hash[:add].empty? ? [] : find_tags(hash[:add])
|
15
22
|
{ :add => adds, :del => dels }
|
16
23
|
end
|
17
24
|
|
18
25
|
private
|
19
26
|
|
20
|
-
def
|
21
|
-
in_db = ::Taggata::Tag.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
::Taggata::Tag.where(:name => names).all
|
27
|
+
def find_tags(names)
|
28
|
+
in_db = ::Taggata::Persistent::Tag.find(db, :name => names).map(&:name)
|
29
|
+
values = (names - in_db).map { |name| { :name => name } }
|
30
|
+
::Taggata::Persistent::Tag.bulk_insert(db, values)
|
31
|
+
::Taggata::Persistent::Tag.find(db, :name => names)
|
26
32
|
end
|
27
33
|
|
28
|
-
def
|
34
|
+
def handle_tag(tag, result)
|
29
35
|
if tag.start_with?('-')
|
30
36
|
result[:del] << tag[1..-1]
|
31
37
|
elsif tag.start_with?('+')
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Taggata
|
2
|
+
module Persistent
|
3
|
+
require 'taggata/persistent/abstract'
|
4
|
+
require 'taggata/persistent/with_parent'
|
5
|
+
require 'taggata/persistent/directory'
|
6
|
+
require 'taggata/persistent/file'
|
7
|
+
require 'taggata/persistent/tag'
|
8
|
+
require 'taggata/persistent/file_tag'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Taggata
|
2
|
+
module Persistent
|
3
|
+
class Abstract
|
4
|
+
|
5
|
+
def save
|
6
|
+
@id ||= db.save(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.destroy(db, options)
|
10
|
+
db.destroy(self, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def destroy(options = {})
|
14
|
+
db.destroy(self.class, self.to_hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.find_one(db, options)
|
18
|
+
db.find_one(self, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.bulk_insert(db, values)
|
22
|
+
db.bulk_insert(self, values)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.find(db, options)
|
26
|
+
db.find(self, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.find_or_create(db, options)
|
30
|
+
db.find_or_create(self, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def invalidate_cache
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_hash
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.new_from_hash(db, hash)
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.table
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def show(indent = 0)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Taggata
|
2
|
+
module Persistent
|
3
|
+
class Directory < Abstract
|
4
|
+
|
5
|
+
include ::Taggata::Persistent::WithParent
|
6
|
+
|
7
|
+
attr_reader :db, :name, :parent_id, :id
|
8
|
+
|
9
|
+
def initialize(db, name, parent_id = nil, id = nil)
|
10
|
+
@db = db
|
11
|
+
@name = name
|
12
|
+
@parent_id = parent_id
|
13
|
+
@id = id
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.table
|
17
|
+
:taggata_directories
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash
|
21
|
+
{
|
22
|
+
:name => name,
|
23
|
+
:parent_id => parent_id,
|
24
|
+
:id => id
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.new_from_hash(db, hash)
|
29
|
+
self.new(db,
|
30
|
+
hash[:name],
|
31
|
+
hash[:parent_id],
|
32
|
+
hash[:id])
|
33
|
+
end
|
34
|
+
|
35
|
+
def show(indent = 0)
|
36
|
+
indent.times { print " " }
|
37
|
+
puts "+ #{self.name}"
|
38
|
+
entries.each { |e| e.show(indent + 1) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def directories
|
42
|
+
@child_directories ||= db.find_child_directories(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def files
|
46
|
+
@child_files ||= db.find_child_files(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def invalidate_cache
|
50
|
+
@child_directories = @child_files = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def entries
|
54
|
+
directories + files
|
55
|
+
end
|
56
|
+
|
57
|
+
# Scan children of this directory
|
58
|
+
def scan
|
59
|
+
scanner = ::Taggata::FilesystemScanner.new db
|
60
|
+
scanner.process(self)
|
61
|
+
validate
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate
|
65
|
+
missing = ::Taggata::Persistent::Tag.find_or_create(:name => MISSING_TAG_NAME)
|
66
|
+
files.reject { |f| ::File.exist? f.path }.each { |f| f.add_tag missing }
|
67
|
+
directories.each(&:validate)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get full path of this directory
|
71
|
+
#
|
72
|
+
# @result full path of this directory
|
73
|
+
def path
|
74
|
+
parents = [self]
|
75
|
+
parents << parents.last.parent while parents.last.parent
|
76
|
+
::File.join(parents.reverse.map(&:name))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Taggata
|
2
|
+
module Persistent
|
3
|
+
class File < Abstract
|
4
|
+
|
5
|
+
include WithParent
|
6
|
+
|
7
|
+
attr_reader :db, :name, :parent_id, :id
|
8
|
+
|
9
|
+
def initialize(db, name, parent_id, id = nil)
|
10
|
+
@name = name
|
11
|
+
@db = db
|
12
|
+
@parent_id = parent_id
|
13
|
+
@id = id
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_tags_by_name(*tag_names)
|
17
|
+
tag_records = nil
|
18
|
+
db.transaction do
|
19
|
+
tag_records = tag_names.map { |tag_name| ::Taggata::Persistent::Tag.find_or_create(db, :name => tag_name) }
|
20
|
+
end
|
21
|
+
add_tags(*tag_records)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_tags(*to_add)
|
25
|
+
current_tag_ids = tags.map(&:id)
|
26
|
+
missing_tag_ids = to_add.map(&:id) - current_tag_ids
|
27
|
+
return if missing_tag_ids.empty?
|
28
|
+
options = missing_tag_ids.map { |tag_id| { :file_id => id, :tag_id => tag_id } }
|
29
|
+
db.bulk_insert(FileTag, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove_tags(*to_remove)
|
33
|
+
return if to_remove.empty?
|
34
|
+
current_tag_ids = tags.map(&:id)
|
35
|
+
to_remove_ids = current_tag_ids - to_remove.map(&:id)
|
36
|
+
options = to_remove_ids.map { |tag_id| { :file_id => id, :tag_id => tag_id } }
|
37
|
+
db.destroy(FileTag, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def remove_tags_by_name(*tag_names)
|
41
|
+
tags = tag_names.map { |tag_name| Tag.find_or_create db, :name => tag_name }
|
42
|
+
remove_tags(*tags)
|
43
|
+
end
|
44
|
+
|
45
|
+
def destroy
|
46
|
+
file_tags.each(&:destroy)
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def file_tags
|
51
|
+
::Taggata::Persistent::FileTag.find(db, :file_id => id)
|
52
|
+
end
|
53
|
+
|
54
|
+
def tags
|
55
|
+
file_tags.map(&:tag)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_hash
|
59
|
+
{
|
60
|
+
:name => name,
|
61
|
+
:id => id,
|
62
|
+
:parent_id => parent_id
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.new_from_hash(db, hash)
|
67
|
+
self.new(db,
|
68
|
+
hash[:name],
|
69
|
+
hash[:parent_id],
|
70
|
+
hash[:id])
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.table
|
74
|
+
:taggata_files
|
75
|
+
end
|
76
|
+
|
77
|
+
def show(indent = 0)
|
78
|
+
indent.times { print " " }
|
79
|
+
puts "- #{self.name}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def path
|
83
|
+
::File.join(parent.path, name)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# def before_destroy
|
91
|
+
# remove_all_tags
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# # Gets full path of the file
|
95
|
+
# #
|
96
|
+
# # @return String full path of the file
|
97
|
+
|
98
|
+
# end
|
99
|
+
# end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Taggata
|
2
|
+
module Persistent
|
3
|
+
class FileTag < Abstract
|
4
|
+
|
5
|
+
attr_reader :db, :file_id, :tag_id
|
6
|
+
|
7
|
+
def initialize(db, file_id, tag_id)
|
8
|
+
@file_id = file_id
|
9
|
+
@tag_id = tag_id
|
10
|
+
@db = db
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.table
|
14
|
+
:taggata_file_tags
|
15
|
+
end
|
16
|
+
|
17
|
+
def file
|
18
|
+
::Taggata::Persistent::File.find_one(db, :id => file_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def tag
|
22
|
+
::Taggata::Persistent::Tag.find_one(db, :id => tag_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
{
|
27
|
+
:file_id => file_id,
|
28
|
+
:tag_id => tag_id
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.new_from_hash(db, hash)
|
33
|
+
self.new(db,
|
34
|
+
hash[:file_id],
|
35
|
+
hash[:tag_id])
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.file_tags_hashes(tags, files)
|
41
|
+
file_tags = tags.map do |tag|
|
42
|
+
files.map { |file| { :file_id => file.id, :tag_id => tag.id } }
|
43
|
+
end.flatten
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Taggata
|
2
|
+
module Persistent
|
3
|
+
class Tag < Abstract
|
4
|
+
|
5
|
+
attr_reader :db, :name, :id
|
6
|
+
|
7
|
+
def initialize(db, name, id = nil)
|
8
|
+
@db = db
|
9
|
+
@name = name
|
10
|
+
@id = id
|
11
|
+
end
|
12
|
+
|
13
|
+
def files
|
14
|
+
file_tags.map { |file_tag| file_tag.file }
|
15
|
+
end
|
16
|
+
|
17
|
+
def file_tags
|
18
|
+
FileTag.find(db, :tag_id => id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
{
|
23
|
+
:name => name,
|
24
|
+
:id => id
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.new_from_hash(db, hash)
|
29
|
+
self.new(db,
|
30
|
+
hash[:name],
|
31
|
+
hash[:id])
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.table
|
35
|
+
:taggata_tags
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|