ro 1.1.1 → 1.2.0

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