ro 1.1.1 → 1.2.0

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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YmViOTZiYmVkMjE5NjliNmRhZDlhYzBlN2VkYWQ3N2QxNDg2MjU1Yg==
4
+ NjU3NjMzOGVkNmQyZDFmNzk0MjBlOTc1MjRiYTFjYzhhOTFjM2E3NA==
5
5
  data.tar.gz: !binary |-
6
- ZGY4OGU3Yzc2Nzg5NTllZDIxMzllNjkzN2ExNDQyMDFhM2Q1ZWI0Yw==
6
+ ZWNmMmIyZjcwNTJkYmU0ZDJkZTYwZGYwYjE4MWEyZDdiMmM5Y2ZkMQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- Y2U4MjU2MDBjMGI4MGI3ODBiOTUwZTYyZTUyOGJiODBkNWMxNTYwZTMxOTBh
10
- YjAzNTVhNjc5ODE3MzNiYWY1MzMwNWU2YjU0MGI5MmM2MGI5Y2IwMjU3MDM5
11
- MjMwYjFlZmJjNzA5MmEwZGEwYjI4NzM3MWI3MjRmMTA4ZmJiNDE=
9
+ NjUxM2JkYjg5OTMxODM3ZGVmYjBhYjgwOTc3ZTFlYjY4N2FiNTQyMDc3ZDUz
10
+ M2FmNWQxMTMzYjk4NjlhYzIxYTcwYTQ3MTM2ODA0NDEwNzY2YjI2YWJiM2Ey
11
+ MzQzMTI5ZTE2MjZhMTE0Mjg0ZTNiZWY3OTdiNWYzYzk4ODA4MDA=
12
12
  data.tar.gz: !binary |-
13
- MDg3M2FiOTRiMTlmZGMzNmJkNzI3MWFjNTE1YjkyMzJmN2U3NTVjMGQxMjc3
14
- MjYxYjM1OGM3Yzk2YmVmZTc4NDlkMjdiNjZkMGVkNjRmYTUwYmJmOTk5YzE2
15
- NTJmY2NjYzNiZTcyMDQ4NTc3NTY5ZTcxYzZlYjU3ZDdkNTQ5Njk=
13
+ NWRkODVlNDYzY2I4NzA4N2Q1ZDM5MTNhYzc1OTU1YWE5MjQ5NWMxZGUwOTZk
14
+ YzA0MmJhNjdmNjg1ZjBhNTIxN2QwNzA1M2Q4ZWZkMmQ3ZDJlYjFmYmYwZjA4
15
+ M2VmZDlkZjY2ZWMyMTBhYjdlYzI0NTU2NWE5MDg5MTE4OTIwYmY=
data/README.md CHANGED
@@ -5,7 +5,7 @@ ro
5
5
 
6
6
 
7
7
  TL;DR
8
- --------
8
+ -----
9
9
 
10
10
  <pre>
11
11
 
@@ -139,6 +139,31 @@ INSTALL
139
139
  gem install ro
140
140
 
141
141
 
142
+ CONFIG
143
+ ------
144
+
145
+ if you are using the url methods you'll need to make sure your application can
146
+ route to the assets. by default ro assumes that the urls it generates are
147
+ routeable under '/ro' so it is up to you to make sure this works.
148
+
149
+ for a rails app this might mean writing a 'RoController' or, more simply, just
150
+ putting your ro data in ./public/ro.
151
+
152
+ for a middleman app this might mean putting your ro data in ./source/ro.
153
+
154
+ if you choose a non-standard approach you'll need to
155
+
156
+ ```
157
+
158
+ Ro.route = '/my-custom-route'
159
+
160
+
161
+ ```
162
+
163
+ in all cases ro urls will be prefixed by the route, so be sure that this prefix
164
+ is either automatically, or manually, exposed.
165
+
166
+
142
167
  DOCS
143
168
  ----
144
169
 
data/Rakefile CHANGED
@@ -84,7 +84,7 @@ task :gemspec do
84
84
  lib = This.lib
85
85
  object = This.object
86
86
  version = This.version
87
- files = shiteless[Dir::glob("**/**")]
87
+ files = shiteless[Dir::glob("**/**")].select{|path| test(?f, path)}
88
88
  executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
89
89
  #has_rdoc = true #File.exist?('doc')
90
90
  test_files = test(?e, "test/#{ lib }.rb") ? "test/#{ lib }.rb" : nil
data/TODO.md ADDED
@@ -0,0 +1,50 @@
1
+ todo:
2
+
3
+ Model[id]
4
+ Model.select
5
+ Model.where
6
+
7
+ - reconsider using active_model...
8
+
9
+ - with ro, do we/should we need to expand assets?
10
+ - posts
11
+ - body.md -> html -> expand_asset_urls?
12
+
13
+ - https://github.com/rails/rails/blob/master/activemodel/lib/active_model/model.rb
14
+
15
+ - track where shit comes from...
16
+
17
+ - custom ui better?
18
+ - chiclets
19
+ - posts
20
+
21
+ done:
22
+
23
+ - expand relative asset links in html
24
+
25
+ - patch based transactions
26
+
27
+ - http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git/
28
+
29
+ - git checkout -b test-branch
30
+ - add, commit, etc
31
+ - add assets last
32
+
33
+ - git format-patch master --stdout > p.patch
34
+
35
+ - actor.date.uuid
36
+
37
+ -
38
+ a:~/git/ahoward/ro $ git apply --stat p.patch
39
+ tmp/coat.zip | Bin
40
+ tmp/a.txt | 1 +
41
+ 2 files changed, 1 insertion(+)
42
+
43
+ - git apply --check p.patch
44
+
45
+ - git am --signoff < fix_empty_poster.patch
46
+
47
+ - iff fail: sorry, your edits conflict with another user!
48
+ - because they need to review new content ;-)
49
+
50
+ - git branch -D test-branch
data/bin/ro CHANGED
@@ -2,14 +2,24 @@
2
2
 
3
3
  Main {
4
4
 
5
- option('--root', '-r'){
6
- argument :required
5
+ argument('root'){
6
+ optional
7
7
  }
8
8
 
9
+ def run
10
+ help!
11
+ end
12
+
9
13
  mode(:console){
10
14
  def run
11
- if params['root'].given?
15
+ if params['root'].given?
12
16
  Ro.root = params['root'].value
17
+ else
18
+ if ENV['RO_ROOT']
19
+ Ro.root = ENV['RO_ROOT']
20
+ else
21
+ Ro.root = './ro'
22
+ end
13
23
  end
14
24
 
15
25
  basename = File.basename(Ro.root)
data/lib/ro.rb CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  #
12
12
  module Ro
13
- Version = '1.1.1' unless defined?(Version)
13
+ Version = '1.2.0' unless defined?(Version)
14
14
 
15
15
  def version
16
16
  Ro::Version
@@ -20,17 +20,13 @@
20
20
  {
21
21
  'map' => [ 'map' , ' >= 6.5.1' ] ,
22
22
  'fattr' => [ 'fattr' , ' >= 2.2.1' ] ,
23
- 'tilt' => [ 'tilt' , ' >= 1.4.1' ] ,
23
+ 'tilt' => [ 'tilt' , ' >= 1.3.1' ] ,
24
24
  'pygments' => [ 'pygments.rb' , ' >= 0.5.0' ] ,
25
25
  'coerce' => [ 'coerce' , ' >= 0.0.4' ] ,
26
26
  'stringex' => [ 'stringex' , ' >= 2.1.0' ] ,
27
- # 'rails' => [ 'rails' , ' >= 3.1' ] ,
28
- # 'tagz' => [ 'tagz' , ' >= 9.9.2' ] ,
29
- # 'multi_json' => [ 'multi_json' , ' >= 1.0.3' ] ,
30
- # 'uuidtools' => [ 'uuidtools' , ' >= 2.1.2' ] ,
31
- # 'wrap' => [ 'wrap' , ' >= 1.5.0' ] ,
32
- # 'rails_current' => [ 'rails_current' , ' >= 1.8.0' ] ,
33
- # 'rails_errors2html' => [ 'rails_errors2html' , ' >= 1.3.0' ] ,
27
+ 'systemu' => [ 'systemu' , ' >= 2.5.2' ] ,
28
+ 'nokogiri' => [ 'nokogiri' , ' >= 1.6.1' ] ,
29
+ 'main' => [ 'main' , ' >= 5.2.0' ] ,
34
30
  }
35
31
  end
36
32
 
@@ -80,22 +76,26 @@
80
76
  #
81
77
 
82
78
  module Ro
83
- Fattr(:root){
84
- Root.new(
85
- case
86
- when defined?(Rails.root)
87
- root = Rails.root.to_s
88
- File.join(root, 'public', 'ro')
79
+ def Ro.default_root
80
+ [ ENV['RO_ROOT'], "./public/ro", "./source/ro" ].compact.detect{|d| test(?d, d)} || "./ro"
81
+ end
89
82
 
90
- when defined?(Middleman::Application)
91
- root = Middleman::Application.server.root.to_s
92
- File.join(root, 'source', 'ro')
83
+ def Ro.root=(root)
84
+ @root = Root.new(root.to_s)
85
+ end
93
86
 
94
- else
95
- ENV['RO_ROOT'] || "./ro"
96
- end
97
- )
98
- }
87
+ def Ro.root(*args)
88
+ Ro.root = args.first unless args.empty?
89
+ @root ||= Root.new(Ro.default_root)
90
+ end
91
+
92
+ def Ro.git
93
+ root.git
94
+ end
95
+
96
+ def Ro.patch(*args, &block)
97
+ git.patch(*args, &block)
98
+ end
99
99
 
100
100
  Fattr(:cache){
101
101
  Cache.new
@@ -105,7 +105,7 @@
105
105
  nil
106
106
  }
107
107
 
108
- Fattr(:mount){
108
+ Fattr(:route){
109
109
  '/ro'
110
110
  }
111
111
 
@@ -155,14 +155,18 @@
155
155
 
156
156
  def Ro.slug_for(*args, &block)
157
157
  options = Map.options_for!(args)
158
- options[:join] = '-'
158
+ unless options.has_key?(:join)
159
+ options[:join] = '-'
160
+ end
159
161
  args.push(options)
160
162
  Slug.for(*args, &block)
161
163
  end
162
164
 
163
165
  def Ro.name_for(*args, &block)
164
166
  options = Map.options_for!(args)
165
- options[:join] = '_'
167
+ unless options.has_key?(:join)
168
+ options[:join] = '_'
169
+ end
166
170
  args.push(options)
167
171
  Slug.for(*args, &block)
168
172
  end
@@ -191,6 +195,49 @@
191
195
  Template.render_source(*args, &block)
192
196
  end
193
197
 
198
+ def Ro.expand_asset_urls(html, node)
199
+ begin
200
+ accurate_expand_asset_urls(html, node)
201
+ rescue Object
202
+ sloppy_expand_asset_urls(html, node)
203
+ end
204
+ end
205
+
206
+ def Ro.accurate_expand_asset_urls(html, node)
207
+ doc = Nokogiri::HTML(html)
208
+
209
+ doc.traverse do |element|
210
+ if element.respond_to?(:attributes)
211
+ element.attributes.each do |attr, attribute|
212
+ value = attribute.value
213
+ if value =~ %r{(?:./)?assets/(.+)$}
214
+ begin
215
+ base, ext = $1.split('.', 2)
216
+ url = node.url_for(base)
217
+ attribute.value = url
218
+ rescue Object
219
+ next
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ doc.xpath('//body').inner_html.strip
227
+ end
228
+
229
+ def Ro.sloppy_expand_asset_urls(html, node)
230
+ html.to_s.gsub(%r{['"]assets/([^'"]+)['"]}) do |match|
231
+ base, ext = $1.split('.', 2)
232
+
233
+ begin
234
+ node.url_for(base).inspect
235
+ rescue Object
236
+ match
237
+ end
238
+ end
239
+ end
240
+
194
241
  def Ro.paths_for(*args)
195
242
  path = args.flatten.compact.join('/')
196
243
  path.gsub!(%r|[.]+/|, '/')
@@ -262,6 +309,8 @@
262
309
  blankslate.rb
263
310
 
264
311
  root.rb
312
+ lock.rb
313
+ git.rb
265
314
 
266
315
  cache.rb
267
316
 
data/lib/ro/cache.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  module Ro
2
- class Cache < ::Hash
2
+ class Cache < ::Map
3
3
  def write(key, value)
4
- self[key] = value
4
+ invalidate(key)
5
+ set(key => value)
5
6
  end
6
7
 
7
8
  def read(key, &block)
8
- if has_key?(key)
9
- self[key]
9
+ if has?(key)
10
+ get(key)
10
11
  else
11
12
  if block
12
13
  value = block.call
@@ -16,5 +17,10 @@ module Ro
16
17
  end
17
18
  end
18
19
  end
20
+
21
+ def invalidate(key)
22
+ prefix = Array(key).dup.tap{|array| array.pop}
23
+ set(prefix, {})
24
+ end
19
25
  end
20
26
  end
data/lib/ro/git.rb ADDED
@@ -0,0 +1,359 @@
1
+ module Ro
2
+ class Git
3
+ attr_accessor :root
4
+ attr_accessor :branch
5
+ attr_accessor :patching
6
+
7
+ def initialize(root, options = {})
8
+ options = Map.for(options)
9
+
10
+ @root = root
11
+ @branch = options[:branch] || 'master'
12
+ end
13
+
14
+ # patch takes a block, allows abitrary edits (additions, modifications,
15
+ # deletions) to be performed by it, and then computes a single, atomic patch
16
+ # that is applied to the repo and pushed. the patch is returned. if the
17
+ # patch was not applied then patch.applied==false and it's up to client code
18
+ # to decide how to proceed, perhaps retrying or saving the patchfile for
19
+ # later manual application
20
+ #
21
+ def patch(*args, &block)
22
+ options = Map.options_for!(args)
23
+
24
+ user = options[:user] || ENV['USER'] || 'ro'
25
+ msg = options[:message] || "#{ user } edits on #{ File.basename(@root).inspect }"
26
+
27
+ patch = nil
28
+
29
+ Thread.exclusive do
30
+ @root.lock do
31
+ Dir.chdir(@root) do
32
+ # ensure .git-ness
33
+ #
34
+ status, stdout, stderr = spawn("git rev-parse --git-dir", :raise => true, :capture => true)
35
+
36
+ git_root = stdout.to_s.strip
37
+
38
+ dot_git = File.expand_path(git_root)
39
+
40
+ unless test(?d, dot_git)
41
+ raise Error.new("missing .git directory #{ dot_git }")
42
+ end
43
+
44
+ # calculate a tmp branch name
45
+ #
46
+ time = Coerce.time(options[:time] || Time.now).utc.iso8601(2).gsub(/[^\w]/, '')
47
+ branch = "#{ user }-#{ time }-#{ rand.to_s.gsub(/^0./, '') }"
48
+
49
+ # allow block to edit, compute the patch, attempt to apply it
50
+ #
51
+ begin
52
+ # get pristine
53
+ #
54
+ spawn("git checkout -f master", :raise => true)
55
+ spawn("git fetch --all", :raise => true)
56
+ spawn("git reset --hard origin/master", :raise => true)
57
+
58
+ # pull recent changes
59
+ #
60
+ trying('to pull'){ spawn("git pull origin master") }
61
+
62
+ # create a new temporary branch
63
+ #
64
+ spawn("git checkout -b #{ branch.inspect }", :raise => true)
65
+
66
+ # the block can perform arbitrary edits
67
+ #
68
+ block.call
69
+
70
+ # add all changes - additions, deletions, or modifications
71
+ #
72
+ spawn("git add . --all", :raise => true)
73
+
74
+ # commit if anything changed
75
+ #
76
+ changes_to_apply =
77
+ spawn("git commit -am #{ msg.inspect }")
78
+
79
+ if changes_to_apply
80
+ # create the patch
81
+ #
82
+ status, stdout, stderr =
83
+ spawn("git format-patch master --stdout", :raise => true, :capture => true)
84
+
85
+ patch = Patch.new(:data => stdout, :name => branch)
86
+
87
+ unless stdout.to_s.strip.empty?
88
+ # apply the patch
89
+ #
90
+ spawn("git checkout master", :raise => true)
91
+
92
+ status, stdout, stderr =
93
+ spawn("git am --signoff --3way", :capture => true, :stdin => patch.data)
94
+
95
+ patch.applied = !!(status == 0)
96
+
97
+ # commit the patch back to the repo
98
+ #
99
+ patch.committed =
100
+ begin
101
+ trying('to pull'){ spawn("git pull origin master") }
102
+ trying('to push'){ spawn("git push origin master") }
103
+ true
104
+ rescue Object
105
+ false
106
+ end
107
+ end
108
+ end
109
+ ensure
110
+ # get pristine
111
+ #
112
+ spawn("git checkout -f master", :raise => true)
113
+ spawn("git fetch --all", :raise => true)
114
+ spawn("git reset --hard origin/master", :raise => true)
115
+
116
+ # get changes
117
+ #
118
+ trying('to pull'){ spawn("git pull") }
119
+
120
+ # nuke the tmp branch
121
+ #
122
+ spawn("git branch -D #{ branch.inspect }")
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ patch
129
+ end
130
+
131
+ #
132
+ class Patch
133
+ fattr :data
134
+ fattr :name
135
+ fattr :applied
136
+ fattr :committed
137
+ fattr :status
138
+ fattr :stdout
139
+ fattr :stderr
140
+
141
+ def initialize(*args)
142
+ options = Map.options_for!(args)
143
+
144
+ self.class.fattrs.each do |key|
145
+ send(key, options.get(key)) if options.has?(key)
146
+ end
147
+
148
+ unless args.empty?
149
+ self.data = args.join
150
+ end
151
+ end
152
+
153
+ def save(path)
154
+ return false unless data
155
+ path = path.to_s
156
+ FileUtils.mkdir_p(File.dirname(path))
157
+ IO.binwrite(path, data)
158
+ end
159
+
160
+ %w( to_s to_str ).each do |method|
161
+ class_eval <<-__, __FILE__, __LINE__
162
+ def #{ method }
163
+ data
164
+ end
165
+ __
166
+ end
167
+
168
+ %w( filename pathname basename ).each do |method|
169
+ class_eval <<-__, __FILE__, __LINE__
170
+ def #{ method }
171
+ name
172
+ end
173
+ __
174
+ end
175
+
176
+ %w( success success? applied applied? ).each do |method|
177
+ class_eval <<-__, __FILE__, __LINE__
178
+ def #{ method }
179
+ status && status == 0
180
+ end
181
+ __
182
+ end
183
+ end
184
+
185
+
186
+
187
+ def save(directory, options = {})
188
+ if directory.is_a?(Node)
189
+ directory = directory.path
190
+ end
191
+
192
+ options = Map.for(options)
193
+
194
+ dir = File.expand_path(directory.to_s)
195
+
196
+ relative_path = Ro.relative_path(dir, :from => @root)
197
+
198
+ exists = test(?d, dir)
199
+
200
+ action = exists ? 'edited' : 'created'
201
+
202
+ msg = options[:message] || "#{ ENV['USER'] } #{ action } #{ relative_path }"
203
+
204
+ @root.lock do
205
+ FileUtils.mkdir_p(dir) unless exists
206
+
207
+ Dir.chdir(dir) do
208
+ # .git
209
+ #
210
+ git_root = `git rev-parse --git-dir`.strip
211
+
212
+ if git_root.empty?
213
+ git_root = '.'
214
+ end
215
+
216
+ dot_git = File.expand_path(File.join(git_root, '.git'))
217
+
218
+ unless test(?d, dot_git)
219
+ raise Error.new("missing .git directory #{ dot_git }")
220
+ end
221
+
222
+ # correct branch
223
+ #
224
+ spawn("git checkout #{ @branch.inspect }", :raise => true)
225
+
226
+ # return if nothing to do...
227
+ #
228
+ if `git status --porcelain`.strip.empty?
229
+ return true
230
+ end
231
+
232
+ # commit the work
233
+ #
234
+ trying "to commit" do
235
+
236
+ committed =
237
+ spawn("git add --all . && git commit -m #{ msg.inspect } -- .")
238
+
239
+ =begin
240
+ unless committed
241
+ spawn "git reset --hard"
242
+ end
243
+ =end
244
+
245
+ #require 'pry'
246
+ #binding.pry
247
+ =begin
248
+ retried = false
249
+ begin
250
+ spawn "git add --all . && git commit -m #{ msg.inspect } -- ."
251
+ committed = true
252
+ rescue
253
+ raise if retried
254
+ spawn "git reset --hard", :raise => false
255
+ retry
256
+ end
257
+ =end
258
+ end
259
+
260
+
261
+ trying "to push" do
262
+ pushed = nil
263
+
264
+ unless spawn("git push origin master")
265
+ # merge
266
+ #
267
+ unless spawn("git pull")
268
+ spawn("git checkout --ours -- .")
269
+ spawn("git add --all .")
270
+ spawn("git commit -F #{ dot_git }/MERGE_MSG")
271
+ else
272
+ raise 'wtf!?'
273
+ end
274
+
275
+ pushed = spawn("git push origin master")
276
+ else
277
+ pushed = true
278
+ end
279
+
280
+ pushed
281
+ end
282
+
283
+ =begin
284
+ git push
285
+
286
+ git pull
287
+
288
+ # publish
289
+ git checkout --ours -- .
290
+ git add --all .
291
+ git commit -F .git/MERGE_MSG
292
+ git push
293
+ =end
294
+
295
+
296
+
297
+ end
298
+ end
299
+ end
300
+
301
+ class Error < ::StandardError;end
302
+
303
+ def trying(*args, &block)
304
+ options = Map.options_for!(args)
305
+ label = ['trying', *args].join(' - ')
306
+
307
+ n = Integer(options[:n] || 3)
308
+ timeout = options[:timeout]
309
+ e = nil
310
+ done = nil
311
+ not_done = Object.new.freeze
312
+
313
+ result =
314
+ catch(:trying) do
315
+ n.times do |i|
316
+ done = block.call
317
+ if done
318
+ throw(:trying, done)
319
+ else
320
+ unless timeout == false
321
+ sleep( (i + 1) * (timeout || (1 + rand)) )
322
+ end
323
+ end
324
+ end
325
+
326
+ not_done
327
+ end
328
+
329
+ if result == not_done
330
+ raise(Error.new("#{ label } failed #{ n } times"))
331
+ else
332
+ done
333
+ end
334
+ end
335
+
336
+ def spawn(command, options = {})
337
+ options = Map.for(options)
338
+
339
+ status, stdout, stderr = systemu(command, :stdin => options[:stdin])
340
+
341
+ Ro.log(:debug, "command: #{ command }")
342
+ Ro.log(:debug, "status: #{ status }")
343
+ Ro.log(:debug, "stdout:\n#{ stdout }")
344
+ Ro.log(:debug, "stderr:\n#{ stderr }")
345
+
346
+ if options[:raise] == true
347
+ unless status == 0
348
+ raise "command (#{ command }) failed with #{ status }"
349
+ end
350
+ end
351
+
352
+ if options[:capture]
353
+ [status, stdout, stderr]
354
+ else
355
+ status == 0
356
+ end
357
+ end
358
+ end
359
+ end