tmsu_file_db 0.0.1 → 0.0.2

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