throne 0.0.4 → 0.0.5

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 (4) hide show
  1. data/VERSION +1 -1
  2. data/lib/tasks.rb +328 -0
  3. data/throne.gemspec +2 -1
  4. metadata +2 -1
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
@@ -0,0 +1,328 @@
1
+ require 'yaml'
2
+
3
+ # Defines tasks for managing databases and views.
4
+ #
5
+ # To reference a file in lib/ from your design doc, use a comment like this
6
+ #
7
+ # // !code library.js
8
+ #
9
+ # This will include the contents at this location.
10
+ #
11
+ # These tasks require an environment variable of SERVER_URL to be set.
12
+ #
13
+ # The documents should be stored using the following structure - all items in the
14
+ # root of base path will be included in all databases, items with a database will be
15
+ # included into that database only.
16
+ #
17
+ # base_path/
18
+ # |-- lib
19
+ # | `-- library.js
20
+ # |-- data
21
+ # | `-- .json and .yml folders with seed data
22
+ # |-- design
23
+ # | `-- <design doc name>
24
+ # | |-- lists
25
+ # | | `-- listname.js
26
+ # | |-- shows
27
+ # | | `-- showname.js
28
+ # | |-- validate_doc_update.js
29
+ # | `-- views
30
+ # | `-- statuses
31
+ # | |-- map.js
32
+ # | `-- reduce.js
33
+ # |-- <db name>
34
+ # |-- lib
35
+ # | `-- library.js
36
+ # |-- data
37
+ # | `-- .json and .yml folders with seed data
38
+ # `-- design
39
+ # `-- <design doc name>
40
+ # |-- lists
41
+ # | `-- listname.js
42
+ # |-- shows
43
+ # | `-- showname.js
44
+ # |-- validate_doc_update.js
45
+ # `-- views
46
+ # `-- statuses
47
+ # |-- map.js
48
+ # `-- reduce.js
49
+ #
50
+ class Throne::Tasks
51
+ # This will inject Rake tasks for managing the database data. The base path should
52
+ # be where all the items are stored.
53
+ #
54
+ # @param [String] base_path the path where design docs are stored
55
+ def self.inject_tasks(base_path)
56
+ namespace :throne do
57
+ namespace :documents do
58
+ namespace :load do
59
+ databases_in_path(base_path).each do |dbname, fulldir|
60
+ desc "Loads data for database #{dbname}, optionally into db"
61
+ task dbname.to_sym, :db do |t, args|
62
+ load_data_for_database(url(dbname, args), base_path, dbname)
63
+ end
64
+ end
65
+
66
+ desc "load all dbs"
67
+ task :all => databases_in_path(base_path)
68
+ end
69
+ namespace :dump do
70
+ desc "Dump data for a database into a YAML file"
71
+ task :yml, :db do |t, args|
72
+ dump_docs_from_db(args.db, base_path, :yml)
73
+ end
74
+
75
+ desc "Dump data for a database into a JSON file"
76
+ task :json, :db do |t, args|
77
+ dump_docs_from_db(args.db, base_path, :json)
78
+ end
79
+ end
80
+ end
81
+
82
+ #design tasks
83
+ namespace :design do
84
+ namespace :push do
85
+ databases_in_path(base_path).each do |dbname, fulldir|
86
+ desc "pushes designs for database #{dbname}, optionally into db"
87
+ task dbname.to_sym, :db do |t, args|
88
+ load_design_documents(url(dbname, args), base_path, dbname)
89
+ end
90
+ end
91
+
92
+ desc "push all designs"
93
+ task :all => databases_in_path(base_path)
94
+ end
95
+ end
96
+
97
+ # Db Tasks
98
+ namespace :database do
99
+ desc "Creates a database if it doesn't exist"
100
+ task :createdb, :db do |t, args|
101
+ get_db(args).create_database
102
+ puts "Database at #{url(args.db)}"
103
+ end
104
+
105
+ desc "Deletes a database"
106
+ task :deletedb, :db do |t, args|
107
+ get_db(args).delete_database
108
+ puts "Database at #{url(args.db)}"
109
+ end
110
+
111
+ desc "Deletes, creates and re-loads a database"
112
+ task :rebuilddb, :db do |t, args|
113
+ db = get_db(url(args.db))
114
+ raise "you must specify the database name (task[db])" unless args.db
115
+ db.delete_database
116
+ # re-getting the object will create the DB
117
+ db = get_db(url(args.db))
118
+ # load design docs
119
+ load_design_documents(db.url, base_path, args.db)
120
+ # load data
121
+ load_data_for_database(db.url, base_path, args.db)
122
+ puts "Done."
123
+ end
124
+ end
125
+
126
+
127
+ def self.get_db(url)
128
+ Throne::Database.new(url)
129
+ end
130
+ end
131
+ end
132
+
133
+ # Loads design documents into the database url, extracted from the source path
134
+ # The docs should be layed out in the following format:
135
+ # `-- <design doc name>
136
+ # |-- lists
137
+ # | `-- statuses
138
+ # | `-- list.js
139
+ # `-- views
140
+ # `-- statuses
141
+ # |-- map.js
142
+ # `-- reduce.js
143
+ # @param [String] db_url the url of the database to load the data in to
144
+ # @param [String] source_path the path to search for .yml and .json files
145
+ def self.load_design_documents(db_url, base_path, database)
146
+ # for each folder in base path, create a new design doc key
147
+ # create a lists key
148
+ # for each path in lists, add a key with the folder name
149
+ # inside this, there is a key called list, with the contents of the list function
150
+ # views is the same, except with a map and reduce function
151
+ paths_for_item(base_path, database, 'design/*').each do |doc_path|
152
+ doc_name = File.basename(doc_path)
153
+ doc = {'lists' => {}, 'views' => {}, 'shows' => {}}
154
+
155
+ Dir.glob(File.join(doc_path, 'lists', '*.js')) do |list_path|
156
+ list_name = File.basename(list_path).split('.').first
157
+ doc['lists'][list_name] = {}
158
+ doc['lists'][list_name] =
159
+ inject_code_includes(base_path, database, list_path)
160
+ end
161
+
162
+ Dir.glob(File.join(doc_path, 'shows', '*.js')) do |show_path|
163
+ show_name = File.basename(show_path).split('.').first
164
+ doc['shows'][show_name] = {}
165
+ doc['shows'][show_name] =
166
+ inject_code_includes(base_path, database, show_path)
167
+ end
168
+
169
+ if File.exists?(vfn = File.join(doc_path, 'validate_doc_update.js'))
170
+ doc['validate_doc_update'] =
171
+ inject_code_includes(base_path, database, vfn)
172
+ end
173
+
174
+ Dir.glob(File.join(doc_path, 'views', '*')) do |view_path|
175
+ view_name = File.basename(view_path)
176
+ doc['views'][view_name] = {}
177
+ mapfn = File.join(view_path, 'map.js')
178
+ reducefn = File.join(view_path, 'reduce.js')
179
+ doc['views'][view_name]['map'] =
180
+ inject_code_includes(base_path, database, mapfn) if File.exists?(mapfn)
181
+ doc['views'][view_name]['reduce'] =
182
+ inject_code_includes(base_path, database, reducefn) if
183
+ File.exists?(reducefn)
184
+ end
185
+ # try to get the existing doc
186
+ doc_id = "_design/#{doc_name}"
187
+ db = Throne::Database.new(db_url)
188
+ if svr_doc = db.get(doc_id)
189
+ # merge
190
+ doc = svr_doc.merge(doc)
191
+ else
192
+ doc['_id'] = doc_id
193
+ end
194
+ doc['language'] = 'javascript'
195
+ db.save(doc)
196
+ puts "Design documents from #{doc_path} loaded"
197
+ end
198
+
199
+ # try and get a document with the design name
200
+ # if it's there, replace the lists and views keys with above data
201
+ # otherwise, create a new document, set language to javascript
202
+ # put document.
203
+ # WIN
204
+ end
205
+
206
+ # Loads data into the database url from the source path. Picks up .yml and .json
207
+ # @param [String] db_url the url of the database to load the data in to
208
+ # @param [String] source_path the path to search for .yml and .json files
209
+ def self.load_data_for_database(db_url, base_path, database)
210
+ @db = Throne::Database.new(db_url)
211
+ items = []
212
+ doccount = 0
213
+ # grab and parse all YML files
214
+ paths_for_item(base_path, database, 'data/**/*.yml').each do |yml|
215
+ items << YAML::load(File.open(yml))
216
+ end
217
+ # and json
218
+ paths_for_item(base_path, database, 'data/**/*.json').each do |json|
219
+ items << JSON.parse(File.open(json).read)
220
+ end
221
+ # load em up
222
+ items.each do |item|
223
+ if item.kind_of? Array
224
+ item.each do |doc|
225
+ begin
226
+ @db.save(doc)
227
+ rescue RestClient::RequestFailed => e
228
+ if e.message =~ /409$/
229
+ puts "Duplicate document - this data has probaby already been loaded"
230
+ doccount -= 1
231
+ else
232
+ raise e
233
+ end
234
+ end
235
+ doccount += 1
236
+ end
237
+ elsif item.kind_of? Hash
238
+ begin
239
+ @db.save(item)
240
+ rescue RestClient::RequestFailed => e
241
+ if e.message =~ /409$/
242
+ puts "Duplicate document - this data has probaby already been loaded"
243
+ doccount -= 1
244
+ else
245
+ raise e
246
+ end
247
+ end
248
+ doccount += 1
249
+ else
250
+ puts "There is something funky with the data for #{source_path}"
251
+ end
252
+ end
253
+ puts "#{doccount} document(s) loaded into database at #{@db.url}"
254
+ end
255
+
256
+ def self.dump_docs_from_db(db, base_path, format)
257
+ raise "You must specify a DB name to dump task[dbname]" unless db
258
+ outdir = File.join(base_path, db, "data")
259
+ FileUtils.mkdir_p(outdir) unless File.exists?(outdir)
260
+ @db = Throne::Database.new(url(db))
261
+ docs = []
262
+
263
+ @db.function('_all_docs') do |res|
264
+ docs << @db.get(res['key']) unless res['key'].match(/^_/)
265
+ end
266
+
267
+ outfn = format == :json ? 'dump.json' : 'dump.yml'
268
+ File.open(File.join(outdir, outfn), 'w') do |f|
269
+ if format == :json
270
+ f.puts JSON.pretty_generate(docs)
271
+ elsif format == :yml
272
+ f.puts docs.to_yaml
273
+ else
274
+ raise("Internal Error - invalid dump format specified")
275
+ end
276
+ end
277
+ puts "Data dumped to #{base_path + '/' + db + '/' + outfn}"
278
+ end
279
+
280
+ private
281
+
282
+ def self.inject_code_includes(base_path, database, file)
283
+ res = ''
284
+ File.open(file).each do |line|
285
+ if line =~ /(\/\/|#)\ ?!code (.*)/
286
+ if File.exists?(inc = File.join(base_path, database, 'lib', $2))
287
+ res += File.read(inc)
288
+ elsif File.exists?(inc = File.join(base_path, 'lib', $2))
289
+ res += File.read(inc)
290
+ else
291
+ raise "Include file #{$2} does not exist in lib/ or #{database}/lib"
292
+ end
293
+ else
294
+ res += line
295
+ end
296
+ end
297
+ res
298
+ end
299
+
300
+ # For the gives path, returns a list of database names and their full path.
301
+ # exludes lib and our other dirs.
302
+ def self.databases_in_path(path)
303
+ res = {}
304
+ Dir.glob(path + '/*').each do |fn|
305
+ next if %w(lib data design).any? {|i| File.basename(fn) == i}
306
+ res[File.basename(fn)] = fn if File.directory?(fn)
307
+ end
308
+ res
309
+ end
310
+
311
+ def self.url(dbname, args=nil)
312
+ if svrurl = ENV['SERVER_URL']
313
+ return svrurl + '/' + args.db if args && args.db
314
+ return svrurl + '/' + dbname
315
+ else
316
+ raise("You must provide a SERVER_URL")
317
+ end
318
+ end
319
+
320
+ # Given the base dir, the DB name and the item path we are after, get a list
321
+ # of items. This includes global and DB spec.
322
+ # e.g for item - data , designs, lib.
323
+ def self.paths_for_item(base, database, item_glob)
324
+ items = Dir.glob(File.join(base, item_glob))
325
+ items.concat(Dir.glob(File.join(base, database, item_glob)))
326
+ end
327
+
328
+ end
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{throne}
8
- s.version = "0.0.4"
8
+ s.version = "0.0.5"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Lincoln Stoll"]
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
25
25
  "Rakefile",
26
26
  "TODO",
27
27
  "VERSION",
28
+ "lib/tasks.rb",
28
29
  "lib/throne.rb",
29
30
  "lib/throne/database.rb",
30
31
  "lib/throne/document.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: throne
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lincoln Stoll
@@ -70,6 +70,7 @@ files:
70
70
  - Rakefile
71
71
  - TODO
72
72
  - VERSION
73
+ - lib/tasks.rb
73
74
  - lib/throne.rb
74
75
  - lib/throne/database.rb
75
76
  - lib/throne/document.rb