taggata 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ module Taggata
2
+ module Persistent
3
+ module WithParent
4
+
5
+ def parent
6
+ Directory.find_one db, :id => parent_id
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ module Taggata
2
+ class Scanner
3
+
4
+ attr_reader :db, :jobs, :progress
5
+
6
+ def initialize(db)
7
+ @db = db
8
+ @jobs = []
9
+ @progress = { :files => 0, :directories => 0 }
10
+ end
11
+
12
+ def report_header
13
+ puts "Files\tDirectories\tQueued"
14
+ end
15
+
16
+ def report
17
+ puts "#{progress[:files]}\t#{progress[:directories]}\t\t#{jobs.length}"
18
+ end
19
+
20
+ def process(dir)
21
+ report_header
22
+ db.transaction do
23
+ jobs << [dir.id, dir.name]
24
+ until jobs.empty?
25
+ do_job *jobs.shift
26
+ report
27
+ end
28
+ end
29
+ end
30
+
31
+ def do_job(parent_id, path)
32
+ files, directories = Dir.glob(File.join(path, '*'))
33
+ .sort
34
+ .partition { |entry| ::File.file? entry }
35
+ save_missing files.map { |f| ::File.basename f },
36
+ parent_id,
37
+ Persistent::File unless files.empty?
38
+ save_missing directories.map { |f| ::File.basename f },
39
+ parent_id,
40
+ Persistent::Directory unless directories.empty?
41
+ progress[:files] += files.length
42
+ add_directory_jobs directories,
43
+ parent_id unless directories.empty?
44
+ progress[:directories] += 1
45
+ end
46
+
47
+ def save_missing(files, parent_id, klass)
48
+ in_db = find_in_db(klass, parent_id, files, :name)
49
+ to_save = (files - in_db).map do |basename|
50
+ { :name => basename, :parent_id => parent_id }
51
+ end
52
+ db.adapter.db[klass.table].multi_insert(to_save)
53
+ end
54
+
55
+ def find_in_db(klass, parent_id, names, param)
56
+ db.adapter.db[klass.table]
57
+ .where(:parent_id => parent_id)
58
+ .where(:name => names)
59
+ .map(param)
60
+ end
61
+
62
+ def add_directory_jobs(dirs, parent_id)
63
+ ids = find_in_db Persistent::Directory,
64
+ parent_id,
65
+ dirs.map { |d| ::File.basename d },
66
+ :id
67
+ ids.zip(dirs).each { |job| jobs << [job.first, job.last] }
68
+ end
69
+ end
70
+ end
@@ -1,3 +1,3 @@
1
1
  module Taggata
2
- VERSION = '0.0.2'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -21,10 +21,13 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_dependency 'sequel', '~> 4.22.0', '>= 4.22.0'
23
23
  spec.add_dependency 'sqlite3', '~> 1.3.10', '>= 1.3.10'
24
+ spec.add_dependency 'clamp', '~>1.0.0', '>= 1.0.0'
24
25
 
26
+ spec.add_development_dependency 'thor', '~> 0.0', '>= 0.0.0'
25
27
  spec.add_development_dependency 'bundler', '~> 1.7'
26
28
  spec.add_development_dependency 'rake', '~> 10.0'
27
29
  spec.add_development_dependency 'minitest', '~> 5.6.1', '>= 5.6.1'
28
30
  spec.add_development_dependency 'minitest-reporters', '~> 1.0.16', '>= 1.0.16'
31
+ spec.add_development_dependency 'pry', '~> 0.0', '>= 0.0.0'
29
32
  spec.add_development_dependency 'mocha', '~> 1.1', '>= 1.1.0'
30
33
  end
@@ -3,7 +3,8 @@ require 'taggata_test_helper'
3
3
  module Taggata
4
4
  module Parser
5
5
  describe Query do
6
- let(:parser) { ::Taggata::Parser::Query }
6
+ let(:db) { Db.new 'sqlite:/', DbAdapters::Sequel }
7
+ let(:parser) { ::Taggata::Parser::Query.new db }
7
8
 
8
9
  it 'translates' do
9
10
  parser.send(:translate, '&').must_equal :and
@@ -16,9 +17,9 @@ module Taggata
16
17
  end
17
18
 
18
19
  it 'applies' do
19
- parser.apply(:and, [1, 2], [2, 3]).must_equal [2]
20
- parser.apply(:or, [1, 2], [2, 3]).must_equal [1, 2, 3]
21
- proc { parser.apply('q', [], []) }.must_raise RuntimeError
20
+ parser.send(:apply, :and, [1, 2], [2, 3]).must_equal [2]
21
+ parser.send(:apply, :or, [1, 2], [2, 3]).must_equal [1, 2, 3]
22
+ proc { parser.send(:apply, 'q', [], []) }.must_raise RuntimeError
22
23
  end
23
24
 
24
25
  it 'converts to postfix' do
@@ -43,7 +44,7 @@ module Taggata
43
44
 
44
45
  it 'tries to resolve the token' do
45
46
  tag = mock.tap { |t| t.expects(:files).returns([1]) }
46
- ::Taggata::Tag.expects(:find).with(:name => '2014').returns(tag)
47
+ ::Taggata::Persistent::Tag.expects(:find_one).with(db, :name => '2014').returns(tag)
47
48
  parser.send(:resolve, 'is:2014').must_equal([1])
48
49
  end
49
50
  end
@@ -3,10 +3,11 @@ require 'taggata_test_helper'
3
3
  module Taggata
4
4
  module Parser
5
5
  describe Tag do
6
- let(:parser) { ::Taggata::Parser::Tag }
6
+ let(:db) { Db.new 'sqlite:/', DbAdapters::Sequel }
7
+ let(:parser) { ::Taggata::Parser::Tag.new db }
7
8
 
8
9
  after do
9
- ::Taggata::Tag.each(&:destroy)
10
+ ::Taggata::Persistent::Tag.destroy(db, {})
10
11
  end
11
12
 
12
13
  it 'parses' do
@@ -1,10 +1,11 @@
1
1
  require 'taggata_test_helper'
2
2
 
3
3
  module Taggata
4
- describe FilesystemScanner do
4
+ describe Scanner do
5
5
  let(:workdir) { ::Dir.mktmpdir }
6
- let(:scanner) { FilesystemScanner.new }
7
- let(:root) { Directory.find_or_create(:name => workdir) }
6
+ let(:db) { Db.new 'sqlite:/', DbAdapters::Sequel }
7
+ let(:scanner) { Scanner.new db }
8
+ let(:root) { Persistent::Directory.find_or_create(db, :name => workdir) }
8
9
  let(:file) { mock }
9
10
 
10
11
  before do
@@ -17,31 +18,35 @@ module Taggata
17
18
 
18
19
  it 'process creates initial job' do
19
20
  root
21
+ scanner.expects(:report_header)
20
22
  scanner.expects(:do_job).with(root.id, root.name)
21
23
  scanner.process(root)
22
24
  end
23
25
 
24
26
  it 'scans empty directory' do
27
+ scanner.expects(:report_header)
25
28
  scanner.expects(:save_missing).never
26
29
  scanner.expects(:add_directory_jobs).never
27
30
  scanner.process(root)
28
31
  end
29
32
 
30
33
  it 'scans directory with files' do
34
+ scanner.expects(:report_header)
31
35
  basenames = (1..5).map { |i| "file-#{i}" }
32
36
  basenames.each do |name|
33
37
  FileUtils.touch(::File.join(workdir, name))
34
38
  end
35
- scanner.expects(:save_missing).with(basenames, root.id, ::Taggata::File)
39
+ scanner.expects(:save_missing).with(basenames, root.id, Persistent::File)
36
40
  scanner.expects(:add_directory_jobs).never
37
41
  scanner.process(root)
38
42
  end
39
43
 
40
44
  it 'scans subdirectories' do
45
+ scanner.expects(:report_header)
41
46
  basenames = (1..5).map { |i| "file-#{i}" }
42
47
  subdir_path = ::File.join(workdir, 'subdir')
43
48
  FileUtils.mkdir_p(subdir_path)
44
- Directory.find_or_create(:name => 'subdir',
49
+ Persistent::Directory.find_or_create(db, :name => 'subdir',
45
50
  :parent_id => root.id)
46
51
  basenames.each do |name|
47
52
  FileUtils.touch(::File.join(subdir_path, name))
@@ -1,10 +1,5 @@
1
1
  require 'sequel'
2
- DB = Sequel.sqlite
3
- DB.create_table :file_tags do
4
- foreign_key :tag_id, :tags
5
- foreign_key :file_id, :files
6
- end unless DB.table_exists? :file_tags
7
- Sequel::Model.plugin(:schema)
2
+
8
3
  require 'taggata'
9
4
  require 'minitest/autorun'
10
5
  require 'minitest/reporters'
@@ -0,0 +1,58 @@
1
+ require 'taggata_test_helper'
2
+
3
+ module Taggata
4
+ module Persistent
5
+ describe Directory do
6
+ let(:db) { Db.new 'sqlite:/', DbAdapters::Sequel }
7
+
8
+ it 'can be created as root' do
9
+ root = Directory.find_or_create db, :name => 'root'
10
+ root.id.must_equal 1
11
+ root.parent.must_be_nil
12
+ end
13
+
14
+ it 'can have children' do
15
+ root = Directory.find_or_create db, :name => 'root'
16
+ subdir = Directory.find_or_create db, :name => 'subdir', :parent_id => root.id
17
+ subdir.parent_id.must_equal root.id
18
+ file = File.find_or_create db, :name => 'file', :parent_id => subdir.id
19
+ file.path.must_equal "root/subdir/file"
20
+ subdir.files.length.must_equal 1
21
+ subdir.directories.length.must_equal 0
22
+ root.entries.length.must_equal 1
23
+ end
24
+
25
+ end
26
+
27
+ describe File do
28
+ let(:db) { Db.new 'sqlite:/', DbAdapters::Sequel }
29
+ let(:root) { Directory.find_or_create db, :name => 'root' }
30
+
31
+ it 'cannot be created without parent' do
32
+ Proc.new { File.find_or_create(db, :name => 'myfile') }.must_raise Sequel::NotNullConstraintViolation
33
+ File.find_or_create db, :name => 'myfile', :parent_id => root.id
34
+ end
35
+
36
+ it 'can be tagged' do
37
+ file = File.find_or_create db, :name => 'myfile', :parent_id => root.id
38
+ tag = Tag.find_or_create db, :name => 'mytag'
39
+ file.add_tags(tag)
40
+ file.tags.map(&:name).must_equal ['mytag']
41
+ file.add_tags_by_name('tag1', 'tag2', 'tag3')
42
+ file.tags.map(&:name).must_equal ['mytag', 'tag1', 'tag2', 'tag3']
43
+ end
44
+
45
+ it 'can have tags removed' do
46
+ file = File.find_or_create db, :name => 'myfile', :parent_id => root.id
47
+ tag = Tag.find_or_create db, :name => 'mytag'
48
+ file.add_tags tag
49
+ file.add_tags_by_name 'mytag2'
50
+ file.tags.length.must_equal 2
51
+ file.remove_tags tag
52
+ file.remove_tags_by_name 'mytag2'
53
+ file.tags.length.must_equal 0
54
+ end
55
+
56
+ end
57
+ end
58
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taggata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Ruzicka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-22 00:00:00.000000000 Z
11
+ date: 2015-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -50,6 +50,46 @@ dependencies:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
52
  version: 1.3.10
53
+ - !ruby/object:Gem::Dependency
54
+ name: clamp
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 1.0.0
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 1.0.0
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 1.0.0
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 1.0.0
73
+ - !ruby/object:Gem::Dependency
74
+ name: thor
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '0.0'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.0
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.0'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 0.0.0
53
93
  - !ruby/object:Gem::Dependency
54
94
  name: bundler
55
95
  requirement: !ruby/object:Gem::Requirement
@@ -118,6 +158,26 @@ dependencies:
118
158
  - - ">="
119
159
  - !ruby/object:Gem::Version
120
160
  version: 1.0.16
161
+ - !ruby/object:Gem::Dependency
162
+ name: pry
163
+ requirement: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: '0.0'
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: 0.0.0
171
+ type: :development
172
+ prerelease: false
173
+ version_requirements: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: '0.0'
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 0.0.0
121
181
  - !ruby/object:Gem::Dependency
122
182
  name: mocha
123
183
  requirement: !ruby/object:Gem::Requirement
@@ -155,20 +215,31 @@ files:
155
215
  - Rakefile
156
216
  - bin/taggata
157
217
  - lib/taggata.rb
218
+ - lib/taggata/cli.rb
158
219
  - lib/taggata/constants.rb
159
- - lib/taggata/directory.rb
160
- - lib/taggata/file.rb
161
- - lib/taggata/filesystem_scanner.rb
220
+ - lib/taggata/db.rb
221
+ - lib/taggata/db_adapters.rb
222
+ - lib/taggata/db_adapters/abstract.rb
223
+ - lib/taggata/db_adapters/sequel.rb
224
+ - lib/taggata/db_adapters/sequel_migrations/001_initial.rb
162
225
  - lib/taggata/parser.rb
163
226
  - lib/taggata/parser/query.rb
164
227
  - lib/taggata/parser/tag.rb
165
- - lib/taggata/tag.rb
228
+ - lib/taggata/persistent.rb
229
+ - lib/taggata/persistent/abstract.rb
230
+ - lib/taggata/persistent/directory.rb
231
+ - lib/taggata/persistent/file.rb
232
+ - lib/taggata/persistent/file_tag.rb
233
+ - lib/taggata/persistent/tag.rb
234
+ - lib/taggata/persistent/with_parent.rb
235
+ - lib/taggata/scanner.rb
166
236
  - lib/taggata/version.rb
167
237
  - taggata.gemspec
168
- - test/filesystem_scanner_test.rb
169
238
  - test/parser/query_parser_test.rb
170
239
  - test/parser/tag_parser_test.rb
240
+ - test/scanner_test.rb
171
241
  - test/taggata_test_helper.rb
242
+ - test/tagging_test.rb
172
243
  homepage: https://github.com/adamruzicka/taggata
173
244
  licenses:
174
245
  - BSD-2-Clause
@@ -189,12 +260,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
260
  version: '0'
190
261
  requirements: []
191
262
  rubyforge_project:
192
- rubygems_version: 2.4.5
263
+ rubygems_version: 2.4.7
193
264
  signing_key:
194
265
  specification_version: 4
195
266
  summary: Gem for scanning the filesystem and storing it in sqlite database with tagging
196
267
  test_files:
197
- - test/filesystem_scanner_test.rb
198
268
  - test/parser/query_parser_test.rb
199
269
  - test/parser/tag_parser_test.rb
270
+ - test/scanner_test.rb
200
271
  - test/taggata_test_helper.rb
272
+ - test/tagging_test.rb
@@ -1,49 +0,0 @@
1
- module Taggata
2
- class Directory < Sequel::Model(:directories)
3
- set_schema do
4
- primary_key :id
5
- String :name
6
- foreign_key :parent_id, :directories
7
- end
8
-
9
- create_table unless table_exists?
10
-
11
- one_to_many :directories,
12
- :key => :parent_id,
13
- :class => self
14
-
15
- one_to_many :files,
16
- :key => :parent_id,
17
- :class => ::Taggata::File
18
-
19
- many_to_one :parent,
20
- :key => :parent_id,
21
- :class => self
22
-
23
- def entries
24
- directories + files
25
- end
26
-
27
- # Scan children of this directory
28
- def scan
29
- scanner = ::Taggata::FilesystemScanner.new
30
- scanner.process(self)
31
- validate
32
- end
33
-
34
- def validate
35
- missing = ::Taggata::Tag.find_or_create(:name => MISSING_TAG_NAME)
36
- files.reject { |f| ::File.exist? f.path }.each { |f| f.add_tag missing }
37
- directories.each(&:validate)
38
- end
39
-
40
- # Get full path of this directory
41
- #
42
- # @result full path of this directory
43
- def path
44
- parents = [self]
45
- parents << parents.last.parent while parents.last.parent
46
- ::File.join(parents.reverse.map(&:name))
47
- end
48
- end
49
- end