FileSet 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/doc/README +62 -0
  2. data/doc/Specifications +28 -0
  3. data/lib/fileset.rb +301 -0
  4. metadata +62 -0
@@ -0,0 +1,62 @@
1
+ == Fileset
2
+ === Simple file management
3
+
4
+ Applications tend to need preferences and configuration files, which can be a bit of a pain to maintain. Fileset allows you to describe a directory structure with raw files or YAML documents, and then populate the local directories of the install target.
5
+
6
+ === Usage
7
+
8
+ Essentially, FileSet lets you do this:
9
+
10
+ ==== Search paths
11
+
12
+ set = FileSet.new([""] + %w{etc myapp}, %w{~ .myapp}, %{.myapp})
13
+
14
+ When you create a fileset, the directories you give it define where in the
15
+ filesystem it will work.
16
+
17
+ Throughout, FileSet uses arrays of strings to reference files, which is a little cheaper when manipulating file paths, and slightly more platform agnostic than strings delimited by '/'. Development so far has been entirely in Linux, so I'd imagine this will work pretty well on OS X, and mostly all right on Windows - although I wouldn't expect Windows-style %ESCAPES% to work.
18
+
19
+ ==== Definition
20
+ set.define do
21
+ dir 'www' do
22
+ file 'index.html', align(<<-EOF)
23
+ <<<
24
+ <html><body>
25
+ <h1 class="ironic">Sparse Docco</h1>
26
+ </body></html>
27
+ EOF
28
+ end
29
+
30
+ dir 'conf' do
31
+ yaml_file 'uses.yaml', {'count' => 1001}
32
+ end
33
+
34
+ dir 'empty'
35
+ end
36
+
37
+ A few features:
38
+ * a simple DSL for definition of files
39
+ * Basic text or yaml file definition
40
+ * Left flush alignment of file text (aligned an optional '<<<')
41
+ * Definition blocks can be repeated, as can dir blocks, so the FileSet can be handed around to different program modules to collect their file requirements
42
+
43
+ ==== Filesystem population
44
+ set.populate
45
+
46
+ This step creates directories and writes files. The first directory listed in the search path used to initialize the FileSet that can be written to is the destination for the files. The intention is that an administrator will be able to deploy system-wide configuration, while users will still be able to install default configs in their home directories.
47
+
48
+ FileSet::populate won't overwrite existing files, so re-populating won't wipe out the configuration changes your users have made.
49
+
50
+ FileSet::populate is intentionally left as a separate step, so that it can be initiated as appropriate to the application. Some apps may want to populate automatically every time they're run, others might want to wait for a commandline switch.
51
+
52
+ If populate is never run, the default values in the define block will be returned if the files are read.
53
+
54
+ ==== File access
55
+ set.load(%w{www index.html}) #=> "<html><body>...."
56
+ conf = set.get_file(%w{conf uses.yaml})
57
+ conf.contents['count'] += 1
58
+ conf.store
59
+
60
+ FileSet#load returns the contents of the file: either a string or the data stored in the YAML document.
61
+
62
+ FileSet#get_file returns a wrapper that allows the contents of the file to be accessed, changed, and then rewritten with FileSet#store.
@@ -0,0 +1,28 @@
1
+
2
+ FileSet retrieving files
3
+ - should get default text files
4
+ - should get text files from library files
5
+ - should get default nested files
6
+ - should get text files from filesystem
7
+ - should get yaml files named with strings
8
+ - should get yaml files from the filesystem
9
+ - should get yaml files named with symbols
10
+
11
+ FileSet defined with the DSL
12
+ - should create files on populate
13
+ - should not clobber existing files in populate
14
+ - should allow empty directory definition
15
+ - should get files once populated
16
+
17
+ FileSet defined programmatically
18
+ - should create files on populate
19
+ - should not clobber existing files in populate
20
+ - should allow empty directory definition
21
+ - should get files once populated
22
+
23
+ FileSet - the unpath method:
24
+ - state => ["state"]
25
+
26
+ Finished in 0.156611 seconds
27
+
28
+ 16 examples, 0 failures
@@ -0,0 +1,301 @@
1
+ require 'fileutils'
2
+
3
+ module FileSetWorks
4
+ class LiveFile
5
+ def initialize(path, filerep)
6
+ @path = path
7
+ @rep = filerep
8
+ end
9
+
10
+ def load
11
+ ::File::open(@path, "r") do |file|
12
+ @rep.load(file)
13
+ end
14
+ end
15
+
16
+ def store
17
+ ::File::open(@path, "w") do |file|
18
+ @rep.store(file)
19
+ end
20
+ end
21
+
22
+ def contents
23
+ return @rep.contents
24
+ end
25
+
26
+ def contents=(new_contents)
27
+ return @rep.contents=(new_contents)
28
+ end
29
+ end
30
+
31
+ module Population
32
+ class EveryPath
33
+ def initialize(file_rep, search_paths)
34
+ @rep = file_rep
35
+ @search_paths = search_paths
36
+ end
37
+
38
+
39
+ def populate
40
+ catch :done do
41
+ paths do |sp|
42
+ begin
43
+ per_path(sp)
44
+ rescue SystemCallError
45
+ next
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def paths
52
+ @search_paths.each do |sp|
53
+ yield sp
54
+ end
55
+ end
56
+
57
+ def per_path(search_path)
58
+ @rep.create_in(search_path)
59
+ end
60
+ end
61
+
62
+ class LowestPath < EveryPath
63
+ def per_path(search_path)
64
+ @rep.create_in(search_path)
65
+ throw :done
66
+ end
67
+
68
+ def paths
69
+ @search_paths.reverse.each do |sp|
70
+ yield sp
71
+ end
72
+ end
73
+ end
74
+
75
+ class HighestPath < EveryPath
76
+ def per_path(search_path)
77
+ @rep.create_in(search_path)
78
+ throw :done
79
+ end
80
+ end
81
+ end
82
+
83
+ class FilesetItem
84
+ def initialize(path, populator = default_populator())
85
+ @path = path
86
+ @populator = populator
87
+ end
88
+
89
+ def default_populator
90
+ Population::LowestPath
91
+ end
92
+
93
+ def populate(search_paths)
94
+ @populator.new(self, search_paths).populate
95
+ end
96
+
97
+ def create_in(search_path)
98
+ path = ::File::join(search_path, @path)
99
+ return if ::File::exists?(path)
100
+ init_self(path)
101
+ end
102
+ end
103
+
104
+ class File < FilesetItem
105
+ def initialize(path, data, populator = default_populator())
106
+ super(path, populator)
107
+ @contents = data
108
+ end
109
+
110
+ attr_reader :path
111
+ attr_accessor :contents
112
+
113
+ def load(file)
114
+ @contents = file.read
115
+ return @contents
116
+ end
117
+
118
+ def store(file)
119
+ file.write(@contents)
120
+ end
121
+
122
+ def init_self(path)
123
+ ::File::open(path, "w") do |file|
124
+ store(file)
125
+ end
126
+ end
127
+ end
128
+
129
+ class YAMLFile < File
130
+ #TODO: YAML::Stream
131
+ def load(file)
132
+ @contents = YAML::load(file)
133
+ return @contents
134
+ end
135
+
136
+ def store(file)
137
+ YAML::dump(@contents, file)
138
+ end
139
+ end
140
+
141
+ class Directory < FilesetItem
142
+ def default_populator
143
+ Population::EveryPath
144
+ end
145
+
146
+ def init_self(path)
147
+ FileUtils::mkdir(path)
148
+ end
149
+ end
150
+ end
151
+
152
+ class FileSet
153
+ def initialize(search_paths)
154
+ @prefix = []
155
+ @files = {}
156
+ @search_paths = search_paths.map{|p| unpath([*p])}
157
+ end
158
+
159
+ def find(path)
160
+ path = unpath(path)
161
+ @search_paths.reverse.map do |sp|
162
+ ::File::join(sp + path)
163
+ end.find do |path|
164
+ ::File::exists?(path)
165
+ end
166
+ end
167
+
168
+ def load(*path)
169
+ get_file(path).contents
170
+ end
171
+
172
+ def get_file(path)
173
+ path = unpath(path)
174
+ file_rep = @files[path]
175
+ unless (file_path = find(path)).nil?
176
+ file = FileSetWorks::LiveFile.new(file_path, file_rep)
177
+ file.load
178
+ return file
179
+ end
180
+ return file_rep
181
+ end
182
+
183
+ def populate()
184
+ @files.keys.sort do |left, right|
185
+ left.length <=> right.length
186
+ end.each do |key|
187
+ @files[key].populate(@search_paths)
188
+ end
189
+ end
190
+
191
+ def unpath(parts)
192
+ if Array === parts and parts.length == 1
193
+ parts = parts[0]
194
+ end
195
+
196
+ case parts
197
+ when Array
198
+ if (parts.find{|part| not (String === part or Symbol === part)}.nil?)
199
+ parts = parts.map{|part| part.to_s}
200
+ else
201
+ raise "path must be composed of strings or symbols"
202
+ end
203
+ when String
204
+ parts = path_split(parts)
205
+ when Symbol
206
+ parts = [parts.to_s]
207
+ when ::File
208
+ parts = parts.path
209
+ parts = path_split(parts)
210
+ else
211
+ raise "path must be String, Array of Strings or File"
212
+ end
213
+
214
+ parts = parts.map do |part|
215
+ if /^~/ =~ part
216
+ ::File::expand_path(part).split(::File::Separator)
217
+ else
218
+ part
219
+ end
220
+ end.flatten
221
+
222
+ return parts
223
+ end
224
+
225
+ def define(&block)
226
+ self.instance_eval &block
227
+ end
228
+
229
+ def add_item(path, item)
230
+ if path.length > 1
231
+ case @files[path[0..-2]]
232
+ when FileSetWorks::Directory
233
+ when nil
234
+ add_dir(path[0..-2])
235
+ else
236
+ raise "Tried to add items below #{path[0..-2]} which is not a directory"
237
+ end
238
+ end
239
+ @files[path] = item
240
+ end
241
+
242
+ def add_dir(path)
243
+ add_item(path, FileSetWorks::Directory.new(path))
244
+ end
245
+
246
+ def add_file(path, data = nil)
247
+ add_item(path, FileSetWorks::File.new(path, data))
248
+ end
249
+
250
+ def dir(name)
251
+ path = @prefix + [name]
252
+ add_dir(path)
253
+ @prefix.push(name)
254
+ yield if block_given?
255
+ @prefix.pop
256
+ end
257
+
258
+ def file(name, data=nil)
259
+ path = @prefix + [name.to_s]
260
+ add_file(path, data)
261
+ end
262
+
263
+ def align(string)
264
+ lines = string.split(/\n/)
265
+ first = lines.shift
266
+ match = /^(\s*)<<</.match(first)
267
+ unless(match.nil?)
268
+ catch(:haircut) do
269
+ return lines.map do |line|
270
+ raise line if /^#{match[1]}|^\s*$/ !~ line
271
+ throw :haircut if /^#{match[1]}|^\s*$/ !~ line
272
+ line.sub(/^#{match[1]}/, "")
273
+ end.join("\n")
274
+ end
275
+ end
276
+ return string
277
+ end
278
+
279
+ def yaml_file(name)
280
+ path = @prefix + [name.to_s]
281
+ @files[path] = FileSetWorks::YAMLFile::new(path, yield )
282
+ end
283
+
284
+ private
285
+
286
+ def path_split(path)
287
+ path.split(::File::Separator)
288
+ end
289
+
290
+ def contents_from(*path)
291
+ path = unpath(path)
292
+ m = /(.*):\d+/.match(caller[0])
293
+ dir = ::File::dirname(::File::expand_path(m[1]))
294
+
295
+ file_path = ::File::join(unpath(dir), path)
296
+ ::File::open(file_path, "r") do |file|
297
+ file.read
298
+ end
299
+ end
300
+
301
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: FileSet
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Judson Lester
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-29 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: " FileSet provides an API for accessing configuration and data files for your\n application, including the population of default values, and managing search\n paths. Written to encourage a cross-platform approach to maintaining configs\n for an application.\n"
17
+ email: nyarly@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - doc/README
24
+ - doc/Specifications
25
+ files:
26
+ - lib/fileset.rb
27
+ - doc/README
28
+ - doc/Specifications
29
+ has_rdoc: true
30
+ homepage: http://fileset.rubyforge.org/
31
+ licenses: []
32
+
33
+ post_install_message: Another tidy package brought to you by Judson
34
+ rdoc_options:
35
+ - --inline-source
36
+ - --main
37
+ - doc/README
38
+ - --title
39
+ - FileSet-0.1 RDoc
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project: fileset
57
+ rubygems_version: 1.3.5
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Manage configuration and data files simply
61
+ test_files: []
62
+