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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af1fd56a3c40bf5cae4dd502103e294280763987
4
- data.tar.gz: f8f7b604307f9ce1402a5b73989475be2778ab45
3
+ metadata.gz: 316d77bbeae3f1cb3224e6a8c5e7a0e35398be06
4
+ data.tar.gz: 847538ab0379d0a7db9d1470c6e04d9257190af5
5
5
  SHA512:
6
- metadata.gz: fce98df89bbb25cbe946517853ef9b675b12687071538c246bad6c6ae165697d65ecac7458e997699d2d6668d6a67c9b2d959748fd7e9b56012347c49dec23bd
7
- data.tar.gz: cc2c1d8091c499072ebc92c0c7616626f96fd2eb83700ac92467126591a10587f31e2ae6efdcbf182be980ff786a350bc7b3c2148d5334577aeed24fb6b440fa
6
+ metadata.gz: d648822f874cf532bd2976541cfb790b4ab104a388c8d30424e83d34baa78cf47a13144874968a2fe4da249dc712e570d36116e9363be4b685a19920c5f3c6d5
7
+ data.tar.gz: f49c703ac45544a2d3e6c5103aa226c98557c1f08f5984baf505cdfb3217d1340565c543b5cf22b422e1419296cf329678ac810c1ec7e4c9a71b7b39e209cb6b
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Taggata [![Build Status](https://travis-ci.org/adamruzicka/regren.svg?branch=master)](https://travis-ci.org/adamruzicka/regren)
1
+ # Taggata [![Build Status](https://travis-ci.org/adamruzicka/taggata.svg?branch=master)](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 count "$SEARCH_QUERY"
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
- Remove files matchine query:
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/[my-github-username]/taggata/fork )
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`)
@@ -1,47 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'sequel'
4
- DB = Sequel.connect("sqlite://#{ARGV[0]}")
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
@@ -3,9 +3,9 @@ require 'sequel'
3
3
 
4
4
  module Taggata
5
5
  require 'taggata/constants'
6
- require 'taggata/file'
7
- require 'taggata/directory'
8
- require 'taggata/tag'
9
- require 'taggata/filesystem_scanner'
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
@@ -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
@@ -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,6 @@
1
+ module Taggata
2
+ module DbAdapters
3
+ require 'taggata/db_adapters/abstract'
4
+ require 'taggata/db_adapters/sequel'
5
+ end
6
+ 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