taggata 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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