file_indexer 1.0.0 → 1.0.1

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
  SHA256:
3
- metadata.gz: b0df350bb38bc48552894171e4ed305c67f152cf4bcac83f46af44911ff659f0
4
- data.tar.gz: 59d9c6aa03fc9fe4e6bc4311c6a5295384bff30410cfe17e4b847c7d13618f02
3
+ metadata.gz: 7fc83ca7cc27629d1d6c8acbbc0e581158c4e960c5f0ab98c038eeff45118527
4
+ data.tar.gz: a1f07b862588d490953a6b9244fdc28e74caf821e8775abe028ccad20e991ff7
5
5
  SHA512:
6
- metadata.gz: a088e4f5918659b8f250ea09137a2b781b2b0978701b39783835c5afce08ea70586ab81f45af4e787e554c4b217e2ba26e5930d4a7db2b9e8aea62b0b186ac81
7
- data.tar.gz: e806cec507fb7a9b8b8687e44671a03ae67c51e09c2398e9ce79de2b93ccc88096307a7d3f40556fe2eef3712c466c434f719c2e6c65501b4c1531010078031d
6
+ metadata.gz: 753d11d8e8f3c96e0c496e21c7d5eab05b36e7cc55040e6480ccb16bde50119b5d34c99881cc8cc192c0e6c54ece347510758b87fe1f443b38909f9848aca564
7
+ data.tar.gz: ef2afddf154e51691352eff1943af6fa82b9876dd34a6359d12c66da677c853336a037c715cfea50e12c124cd0bfb471912c1268605afa8ba8c02246852765a8
data/lib/file_indexer.rb CHANGED
@@ -2,7 +2,7 @@ require_relative 'file_indexer/indexer'
2
2
 
3
3
  # Namespace for a simple file indexer
4
4
  module FileIndexer
5
- VERSION = [1, 0, 0]
5
+ VERSION = [1, 0, 1]
6
6
 
7
7
  # Equivalent to FileIndexer::Indexer.new(dirs, exts, db_file, &action)
8
8
  def self.new(dirs, exts, db_file, &action)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'listen'
3
3
  require 'observer'
4
+ require 'pathname'
4
5
 
5
6
  module FileIndexer
6
7
 
@@ -22,7 +23,7 @@ module FileIndexer
22
23
  attr_reader :db_file
23
24
 
24
25
  def initialize(dirs, exts, db_file, &action)
25
- @dirs = dirs
26
+ @dirs = dirs.map { |d| sanitize_dirpath(d) }
26
27
  @exts = exts
27
28
  @db_file = db_file
28
29
  @action = action
@@ -31,6 +32,7 @@ module FileIndexer
31
32
  @listener = nil
32
33
 
33
34
  @files.merge!(read_db)
35
+ prune!
34
36
  index_all!
35
37
  end
36
38
 
@@ -41,25 +43,31 @@ module FileIndexer
41
43
 
42
44
  # @see #index_all
43
45
  def index_all!(dirs = @dirs, exts = @exts)
44
- @files = index_all(dirs, exts)
46
+ files.merge!(index_all(dirs, exts))
45
47
  changed!
46
48
  files
47
49
  end
48
50
 
49
51
  # Index a list of files
50
- def index(files)
51
- files.map do |file|
52
+ def index(fs)
53
+ fs.map do |file|
54
+ next if files.key?(file)
52
55
  [file, action.(file)]
53
- end.to_h
56
+ end.compact.to_h
54
57
  end
55
58
 
56
59
  # @see #index
57
- def index!(nf)
58
- files.merge!(index(nf))
60
+ def index!(fs)
61
+ files.merge!(index(fs))
59
62
  changed!
60
63
  files
61
64
  end
62
65
 
66
+ # Prune nonexistant files from the index
67
+ def prune!
68
+ files.select! { |f,_| File.exist?(f) }.to_h
69
+ end
70
+
63
71
  # Watch directories for changes using the listen gem, when called make sure
64
72
  # to call #stop when done watching.
65
73
  def watch(dirs = @dirs, exts = @exts)
@@ -89,6 +97,11 @@ module FileIndexer
89
97
 
90
98
  private
91
99
 
100
+ # Makes sure a directory exists, and sanitizes it
101
+ def sanitize_dirpath(dir)
102
+ Pathname.new(File.expand_path(dir)).realpath.to_s
103
+ end
104
+
92
105
  # Glob all files with certain extensions in a directory
93
106
  def glob(dir, exts = @exts)
94
107
  Dir.glob("#{dir}**/*.{#{exts.join(',')}}")
@@ -0,0 +1,11 @@
1
+ require_relative 'file_indexer/indexer'
2
+
3
+ # Namespace for a simple file indexer
4
+ module FileIndexer
5
+ VERSION = [1, 0, 1]
6
+
7
+ # Equivalent to FileIndexer::Indexer.new(dirs, exts, db_file, &action)
8
+ def self.new(dirs, exts, db_file, &action)
9
+ Indexer.new(dirs, exts, db_file, &action)
10
+ end
11
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+ require 'listen'
3
+ require 'observer'
4
+ require 'pathname'
5
+
6
+ module FileIndexer
7
+
8
+ # A simple file indexer, keeps a database of files and associated metadata
9
+ class Indexer
10
+ include Observable
11
+
12
+ # The directories that will be indexed
13
+ attr_accessor :dirs
14
+ # Only files with these extensions will be considered. Just use '*' for all
15
+ # files.
16
+ attr_accessor :exts
17
+ # This is a Proc that will be called for every file and its result will be
18
+ # stored in the database
19
+ attr_accessor :action
20
+ # This is the actual database of files, it is a Hash
21
+ attr_accessor :files
22
+ # This is the filename which will be used as the persistent database
23
+ attr_reader :db_file
24
+
25
+ def initialize(dirs, exts, db_file, &action)
26
+ @dirs = dirs.map { |d| sanitize_dirpath(d) }
27
+ @exts = exts
28
+ @db_file = db_file
29
+ @action = action
30
+
31
+ @files = {}
32
+ @listener = nil
33
+
34
+ @files.merge!(read_db)
35
+ prune!
36
+ index_all!
37
+ end
38
+
39
+ # Index multiple directories
40
+ def index_all(dirs = @dirs, exts = @exts)
41
+ dirs.map { |dir| index(glob(dir, exts)) }.reduce(&:merge)
42
+ end
43
+
44
+ # @see #index_all
45
+ def index_all!(dirs = @dirs, exts = @exts)
46
+ files.merge!(index_all(dirs, exts))
47
+ changed!
48
+ files
49
+ end
50
+
51
+ # Index a list of files
52
+ def index(fs)
53
+ fs.map do |file|
54
+ next if files.key?(file)
55
+ [file, action.(file)]
56
+ end.compact.to_h
57
+ end
58
+
59
+ # @see #index
60
+ def index!(fs)
61
+ files.merge!(index(fs))
62
+ changed!
63
+ files
64
+ end
65
+
66
+ # Prune nonexistant files from the index
67
+ def prune!
68
+ files.select! { |f,_| File.exist?(f) }.to_h
69
+ end
70
+
71
+ # Watch directories for changes using the listen gem, when called make sure
72
+ # to call #stop when done watching.
73
+ def watch(dirs = @dirs, exts = @exts)
74
+ @listener =
75
+ Listen.to(*dirs, only: /\.(#{exts.join('|')})$/) do |mod, add, del|
76
+ del.each { |f| files.delete(f) }
77
+ index!(mod + add)
78
+ end
79
+ @listener.start
80
+ end
81
+
82
+ # Stop watching directories for changes
83
+ def stop
84
+ @listener&.stop
85
+ end
86
+
87
+ # Write the databse to disk
88
+ def write_db
89
+ File.open(db_file, 'w') { |f| f.puts(Marshal.dump(files)) }
90
+ end
91
+
92
+ # Read the database from disk
93
+ def read_db
94
+ return {} unless File.exist?(db_file)
95
+ File.open(db_file, 'r') { |f| Marshal.load(f.read) }
96
+ end
97
+
98
+ private
99
+
100
+ # Makes sure a directory exists, and sanitizes it
101
+ def sanitize_dirpath(dir)
102
+ Pathname.new(File.expand_path(dir)).realpath.to_s
103
+ end
104
+
105
+ # Glob all files with certain extensions in a directory
106
+ def glob(dir, exts = @exts)
107
+ Dir.glob("#{dir}**/*.{#{exts.join(',')}}")
108
+ end
109
+
110
+ # Notify observers of changes and rewrite the database file
111
+ def changed!(*args)
112
+ changed
113
+ write_db
114
+ notify_observers(*args)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,63 @@
1
+ require 'test/unit'
2
+ require_relative '../lib/file_indexer'
3
+
4
+ DIRS = [File.expand_path(File.join(__dir__, 'foo/')) + '/',
5
+ File.expand_path(File.join(__dir__, 'bar/')) + '/']
6
+ FILES = [File.join(DIRS[0], 'a.foo'),
7
+ File.join(DIRS[0], 'b.bar'),
8
+ File.join(DIRS[0], 'c.kek'),
9
+ File.join(DIRS[1], 'a.foo'),
10
+ File.join(DIRS[1], 'b.foo')]
11
+ EXTS = %w[foo bar]
12
+ DB_FILE = File.join(__dir__, 'db_file')
13
+ TMP_FILE = File.join(DIRS[0], 'd.foo')
14
+
15
+ class TestIndexer < Test::Unit::TestCase
16
+ def setup
17
+ DIRS.each { |d| FileUtils.mkdir_p(d) }
18
+ FILES.each { |f| FileUtils.touch(f) }
19
+ @indexer = FileIndexer.new(DIRS, EXTS, DB_FILE) do |f|
20
+ File.basename(f).upcase
21
+ end
22
+ end
23
+
24
+ def teardown
25
+ @indexer.stop
26
+ FileUtils.rm(DB_FILE) if File.exist?(DB_FILE)
27
+ FileUtils.rm(TMP_FILE) if File.exist?(TMP_FILE)
28
+ DIRS.each { |d| FileUtils.rm_rf(d) }
29
+ end
30
+
31
+ def test_glob
32
+ count = @indexer.dirs.map { |d| @indexer.send(:glob, d).length }.reduce(:+)
33
+ assert(4 == count, "Glob counts #{count} files, not 4")
34
+ end
35
+
36
+ def test_index_all
37
+ values = @indexer.index_all.values
38
+ assert(
39
+ ['A.FOO', 'B.BAR', 'B.FOO', 'A.FOO'] == values,
40
+ "Values are #{values}"
41
+ )
42
+ end
43
+
44
+ def test_index
45
+ assert(['A.FOO'] == @indexer.index([File.join(DIRS[0], 'a.foo')]).values)
46
+ end
47
+
48
+ def test_watch
49
+ @indexer.watch
50
+ sleep 0.1
51
+ FileUtils.touch(TMP_FILE)
52
+ sleep 1
53
+ assert(5 == @indexer.files.values.length)
54
+ end
55
+
56
+ def test_write_db
57
+ assert(true == File.exist?(DB_FILE))
58
+ end
59
+
60
+ def test_read_db
61
+ assert(4 == @indexer.read_db.values.length)
62
+ end
63
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file_indexer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stone Tickle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-02 00:00:00.000000000 Z
11
+ date: 2018-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: listen
@@ -39,6 +39,9 @@ files:
39
39
  - "./README.md"
40
40
  - "./lib/file_indexer.rb"
41
41
  - "./lib/file_indexer/indexer.rb"
42
+ - "./pkg/file_indexer-1.0.0/lib/file_indexer.rb"
43
+ - "./pkg/file_indexer-1.0.0/lib/file_indexer/indexer.rb"
44
+ - "./pkg/file_indexer-1.0.0/test/test_indexer.rb"
42
45
  - "./test/test_indexer.rb"
43
46
  homepage:
44
47
  licenses: