tmsu_file_db 0.0.1 → 0.0.2

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +142 -0
  3. data/lib/tmsu_file_db.rb +50 -9
  4. data/lib/version.rb +1 -1
  5. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 87c86606a333c58a62d8d6c1d6db1cdee4da35dc
4
- data.tar.gz: fbae0484413b490c0e1c98f5a415b95e7ef46b0f
3
+ metadata.gz: 910c386ec16296462c430627b29d00ce9c718874
4
+ data.tar.gz: 60c51e32d1ddd33291fe0cbc930b09dd18c6f706
5
5
  SHA512:
6
- metadata.gz: 220887318854f6a1a9037bf265edc70f547384f8acc44b4d75b7b3a5f00026fdf0c75c913e453c7d6800446f857cb357ac40d9ed2526817b185a42f7f39f6d11
7
- data.tar.gz: f5fc16da123e6a2c8eb1eec9f476905e7d4a85193125298efac7e42119f41d2b33ea762da7902a8fbffaf1519599b83d22ce87a76c02c374beefe109b418aa5a
6
+ metadata.gz: a9f561e9efc1c6180264896d66d1bab28d8c8a5ac5b5b16c1f8ed1dd08c668e19f1b596e418ed7a0b288ebda99dfac566ff75066b834b7d50acaec78147f6b41
7
+ data.tar.gz: e81b583feb2a1bdadf8c433d38193c9f5965078a080c34e0a4c919c16e51e9e60e0bb12d016f200dc329d4aacd193840c9627e255c8d0518e4feeb83b1fc7683
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ This is an ORM similar to ActiveRecord, but uses the filesystem instead.
2
+
3
+ It uses TMSU which is a "non-hierarchical" filesystem tagging system.
4
+
5
+ Usage:
6
+
7
+ **Install the gem**
8
+
9
+ ```sh
10
+ gem install tmsu_file_db
11
+ ```
12
+
13
+ **Define a model**
14
+
15
+ ```rb
16
+ require 'tmsu_file_db'
17
+
18
+ class User < TmsuModel
19
+
20
+ # this configure block is optional, it defaults to the current directory
21
+ configure root_path: "./db/users"
22
+
23
+ # Validations must return an array
24
+ validate do |record|
25
+ record.name.nil? ? ["name can't be blank"] : []
26
+ end
27
+
28
+ # Specific attributes can be validated as well
29
+ validate(:email) do |email, record|
30
+ email&.include?("@") ? ["email isn't valid"] : []
31
+ end
32
+
33
+ end
34
+ ```
35
+
36
+ **Create instances**
37
+
38
+ # create, update, and delete
39
+ u = User.new name: "max"
40
+ u.valid? # => false
41
+ u.errors # => ["email isn't valid"]
42
+ u.save # => false
43
+ u.email = "maxpleaner@gmail.com"
44
+ u.valid? # => true
45
+ u.save # => true
46
+ u.update(email: "max.pleaner@gmail.com") # => true
47
+ u.update(email: "") # => false
48
+
49
+ # There are getter/setter methods for convenience
50
+ u.name # => "max"
51
+ u["name"] # => "max"
52
+ u[:name] # => "max"
53
+ u.name = "max p."
54
+ u[:name] # => "max p."
55
+
56
+ # All these getter/setters are working on 'attributes' under the hood.
57
+ u.attributes[:name] # => "max p."
58
+
59
+ # each record is assigned a filesystem path
60
+ u.path
61
+
62
+ # creating a new record will create a new file in the root_path
63
+ # but will not add anything to the file unless .write is called
64
+ u.write "hello"
65
+
66
+ # The content of the file is not part of the "attributes" i.e. name and email
67
+ # Those are stored using TMSU tags
68
+ u.tags # => { email: "max.pleaner@gmail.com", name: "max" }
69
+
70
+ # Attributes can be deleted
71
+ u.delete :name
72
+ u.tags # => { email: "max.pleaner@gmail.com" }
73
+ ```
74
+
75
+ **Use class-level query methods**
76
+
77
+ _Note that this does not use Arel or any of that jazz. So chaining queries or using joins will not work._
78
+
79
+ _Note also that there is no `id` on models, only `path`, which is an absolute path._
80
+
81
+ ```rb
82
+ User.where(name: "max p.")[0].name == "max p." # => true
83
+ User.find_by(name: "max p.").name == "max p." # => true
84
+ User.update_all(name: "max") # => true
85
+ User.all[0].name == "max" # => true
86
+
87
+ # You can make arbitrary queries using TMSU syntax
88
+ # e.g. select all users with email set that are not named melvin
89
+ User.query("name != 'melvin' and email")[0].name == "max" # => true
90
+ ```
91
+
92
+ You can also skip `TmsuModel` and use `TmsuRuby.file` instead. This does _not_ handle creation / deletion of files. It should only be used with files that already exist.
93
+
94
+ Note that these methods are technically available on `TmsuModel` instances, callable on the `tmsu_file` attribute. But this shoudln't be done, because it will cause the in-memory attributes to be out of sync.
95
+
96
+ ```rb
97
+ file_path = './my_pic.jpg' # this should already exist
98
+
99
+ tmsu_file = TmsuRuby.file file_path
100
+ tmsu_file.tags # => {}
101
+
102
+ tmsu_file.tag "foo" # .tag can be passed a string
103
+ tmsu_file.tags # => { foo: nil }
104
+
105
+ tmsu_file.untag "foo"
106
+ tmsu_file.tags # => { }
107
+
108
+ tmsu_file.tag ["foo", "bar"] # .tag can also be passed an array
109
+ tmsu_file.tags # => { foo: nil, bar: nil }
110
+
111
+ tmsu_file.tag(a: 1, b: 2) # .tag can also be passed a hash
112
+ tmsu_file.tags # => { foo: nil, bar: nil, a: 1, b: 2 }
113
+ ```
114
+
115
+ It's also possible to use `TmsuRuby` to work on multiple files instead of just one:
116
+
117
+ ```rb
118
+ glob_selector = "./**/*.jpg"
119
+
120
+ tmsu_file = TmsuRuby.file glob_selector
121
+
122
+ # there is a special method used to add tags in this case
123
+ tmsu_file.tag_selector "foo"
124
+ tmsu_file.tag_selector ["a", "b"]
125
+ tmsu_file.tag_selector c: 1, d: 2
126
+
127
+ # Simiarly to untag
128
+ tmsu_file.untag_selector "c"
129
+
130
+ # check that the tags were added to files
131
+ TmsuRuby.file("./my_pic.jpg").tags
132
+ # => { foo: nil, a: nil, b: nil, d: 2 }
133
+ ```
134
+
135
+ Using `TmsuRuby.file` you can search by tag as well:
136
+
137
+ ```rb
138
+ # Returns array of paths (files with the tag, systemwide)
139
+ TmsuRuby.file("name")
140
+ ```
141
+
142
+
data/lib/tmsu_file_db.rb CHANGED
@@ -18,7 +18,7 @@ class TmsuModel
18
18
 
19
19
  def self.configure(root_path:)
20
20
  Config[:root_path] = root_path || "./db".tap do |path|
21
- `mkdir #{path}`
21
+ `mkdir -p #{path}`
22
22
  end
23
23
  end
24
24
 
@@ -51,16 +51,31 @@ class TmsuModel
51
51
  query opts_to_query opts
52
52
  end
53
53
 
54
+ def self.all
55
+
56
+ end
57
+
54
58
  def self.query string
55
- TmsuFile.new(query_glob).paths_query(query)
59
+ TmsuFile.new(query_glob).paths_query(query).map do |path|
60
+ new TmsuFile.new(path).tags
61
+ end
56
62
  end
57
63
 
58
64
  def self.update_all opts={}
59
- Dir.glob(query_glob).each { |path| new(path).update(opts) }
65
+ Dir.glob(query_glob).each do |path|
66
+ errors = new(path).tap { |inst| inst.update(opts) }.errors
67
+ unless errors.empty?
68
+ raise(
69
+ ArgumentError, "couldn't update all. Path #{path} caused errors: #{errors.join(", ")}"
70
+ )
71
+ end
72
+ end
73
+ true
60
74
  end
61
75
 
62
76
  def self.destroy_all opts={}
63
77
  Dir.glob(query_glob).each { |path| `rm #{path}` }
78
+ true
64
79
  end
65
80
 
66
81
  attr_reader :attributes, :errors, :path
@@ -92,7 +107,10 @@ class TmsuModel
92
107
  end
93
108
 
94
109
  def method_missing(sym, *arguments, &blk)
95
- if attributes.keys.include? sym
110
+ attr_name = sym.to_s[0..-1]
111
+ if sym.to_s[-1] == "=" && attributes.keys.include?(attr_name)
112
+ attributes[attr_name] = arguments[0]
113
+ elsif attributes.keys.include? sym
96
114
  attributes[sym]
97
115
  else
98
116
  super
@@ -138,14 +156,22 @@ class TmsuModel
138
156
 
139
157
  def save
140
158
  ensure_persisted
159
+ return false unless valid?
141
160
  tag attributes
142
- self
161
+ true
143
162
  end
144
163
 
145
164
  def update attrs={}
165
+ original_attrs = attributes.clone
146
166
  attrs.each_key { |k| self[k] = attrs[k] }
167
+ unless valid?
168
+ # rollback attribute change
169
+ self.attributes.clear
170
+ original_attrs.each { |k,v| self[k] = v }
171
+ return false
172
+ end
147
173
  save
148
- self
174
+ true
149
175
  end
150
176
 
151
177
  def destroy
@@ -153,6 +179,12 @@ class TmsuModel
153
179
  self
154
180
  end
155
181
 
182
+ def delete(attr)
183
+ untag(attr)
184
+ attributes.delete attr
185
+ attr
186
+ end
187
+
156
188
  end
157
189
 
158
190
  module SystemPatch
@@ -172,7 +204,7 @@ module TmsuRubyInitializer
172
204
  puts "initializing tmsu"
173
205
  puts system "tmsu init"
174
206
  puts "making vfs_path #{vfs_path}"
175
- puts system "mkdir #{vfs_path}"
207
+ puts system "mkdir -p #{vfs_path}"
176
208
  puts "mounting vfs path"
177
209
  puts system "tmsu mount #{vfs_path}"
178
210
  end
@@ -186,7 +218,6 @@ module TmsuFileAPI
186
218
  using SystemPatch
187
219
 
188
220
  def tags
189
- ensure_persisted
190
221
  system("tmsu tags #{path}").split(" ")[1..-1].reduce({}) do |res, tag|
191
222
  key, val = tag.split("=")
192
223
  res.tap { res[key] = val }
@@ -202,7 +233,6 @@ module TmsuFileAPI
202
233
 
203
234
  def untag tag_list
204
235
  `touch #{path}` unless persisted?
205
- attributes.delete
206
236
  system "tmsu untag #{path} #{tag_list}"
207
237
  tags
208
238
  end
@@ -231,6 +261,17 @@ module TmsuFileAPI
231
261
  files tag_obj
232
262
  end
233
263
 
264
+ def untag_selector(tag_obj)
265
+ tag_arg = case tag_obj
266
+ when String
267
+ tag_obj
268
+ when Array
269
+ tag_obj.join(" ")
270
+ end
271
+ system "tmsu tag --tags '#{build_tag_arg tag_obj}' #{path}"
272
+ files tag_obj
273
+ end
274
+
234
275
  def merge_tag(source, dest)
235
276
  source_files = files source
236
277
  dest_files = files dest
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TmsuFileDb
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tmsu_file_db
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - maxpleaner
@@ -31,6 +31,7 @@ executables:
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - README.md
34
35
  - bin/tmsu_file_db
35
36
  - lib/tmsu_file_db.rb
36
37
  - lib/version.rb