rack-scriptstacker 0.0.1 → 0.1.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,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ee8de66daca3fb8a4b83d4733f8ad8857910dd71
4
- data.tar.gz: d38e64e90eff4b45bb3717275bfce9e996a393ef
3
+ metadata.gz: bd27878e3558110c50299ac46ab58ad6dd4eb93f
4
+ data.tar.gz: 3bd7cd806c466de5c48a5022bd5d2bbc11aece15
5
5
  SHA512:
6
- metadata.gz: 949a8db63c8925981f1202a706e92c9a72394499402a2b8fefe444f3047739085a3120d8dd51ecf0d048e4e1ec6ae45889206d2cfd2e8a57760e02beb4cfee93
7
- data.tar.gz: c2a77c84f5a5a0087a0bb30bdcb822ef9331f0c3770f8c9371d43f6568a7cbcda1e4e99f1ccc879fde3167a4ee24e89a1988bacbaf290b324a298b1c0cd4b1c2
6
+ metadata.gz: 8e0248a50ebbfd9be7072736c7ef90bf91a54133a1e714e9aae3d8df38e1d734aaf714d2209a8a063a7117b2ee96539a5f63a3517410e9dd62183c6f1e275b02
7
+ data.tar.gz: 9e8702eb86e8f2a510c43ec20bda3081d12df1f3a8da17021ee7e214108588a715f85af3307986790fd6c7dad8182853cbde7117951d20887ef9d7c2523bfe51
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ group :development do
6
+ gem 'pry'
7
+ end
data/README.md CHANGED
@@ -4,6 +4,8 @@ Painless static file handling for Rack apps.
4
4
 
5
5
  - Automatically configures `Rack::Static` by default.
6
6
  - Glob and inject JavaScript/CSS files into served HTML.
7
+ - Inserts CSS before `</head>` and JS before `</body>` by default.
8
+ - Change the inject mode to use placeholder slots instead.
7
9
 
8
10
  # Usage
9
11
 
@@ -13,10 +15,8 @@ Painless static file handling for Rack apps.
13
15
  <!doctype html>
14
16
  <html lang="en">
15
17
  <head>
16
- <!-- ScriptStacker: CSS //-->
17
18
  </head>
18
19
  <body>
19
- <!-- ScriptStacker: JAVASCRIPT //-->
20
20
  </body>
21
21
  </html>
22
22
  ```
@@ -32,7 +32,7 @@ class App
32
32
  def call env
33
33
  [
34
34
  200,
35
- { 'Content-Type' => 'text/html' },
35
+ {'Content-Type' => 'text/html'},
36
36
  [File.read('index.html')]
37
37
  ]
38
38
  end
@@ -101,3 +101,24 @@ use Rack::ScriptStacker,
101
101
  javascript 'static/javascript'
102
102
  end
103
103
  ```
104
+
105
+ ## Use placeholder slots
106
+
107
+ ```html
108
+ <!-- index.html //-->
109
+
110
+ <!doctype html>
111
+ <html lang="en">
112
+ <head>
113
+ <!-- ScriptStacker: CSS //-->
114
+ </head>
115
+ <body>
116
+ <!-- ScriptStacker: JAVASCRIPT //-->
117
+ </body>
118
+ </html>
119
+
120
+ use Rack::ScriptStacker, inject_mode: :slot do
121
+ css 'static/css'
122
+ javascript 'static/javascript'
123
+ end
124
+ ```
@@ -12,26 +12,39 @@ end
12
12
 
13
13
  module Rack
14
14
  class ScriptStacker
15
+ module InjectMode
16
+ TAG = :tag
17
+ SLOT = :slot
18
+ end
19
+
15
20
  DEFAULT_CONFIG = {
16
21
  configure_static: true,
22
+ inject_mode: InjectMode::TAG,
17
23
  stackers: {
18
24
  css: {
19
25
  template: '<link rel="stylesheet" type="text/css" href="%s" />',
20
26
  glob: '*.css',
21
- slot: 'CSS'
27
+ slot: 'CSS',
28
+ inject_before_tag: '</head>',
22
29
  },
23
30
  javascript: {
24
31
  template: '<script type="text/javascript" src="%s"></script>',
25
32
  glob: '*.js',
26
- slot: 'JAVASCRIPT'
33
+ slot: 'JAVASCRIPT',
34
+ inject_before_tag: '</body>',
27
35
  }
28
36
  }
29
37
  }
30
38
 
31
39
  def initialize app, config={}, &stack_spec
32
40
  @config = DEFAULT_CONFIG.recursive_merge config
33
- @path_specs = ScriptStackerUtils::SpecSolidifier.new.call stack_spec
34
- @runner = ScriptStackerUtils::Runner.new @config[:stackers]
41
+ @path_specs = ScriptStackerUtils::SpecSolidifier.new(
42
+ @config[:stackers].keys
43
+ ).call stack_spec
44
+ @runner = ScriptStackerUtils::Runner.new(
45
+ @config[:stackers],
46
+ @config[:inject_mode]
47
+ )
35
48
  @app = @config[:configure_static] ? configure_static(app) : app
36
49
  end
37
50
 
@@ -64,7 +77,8 @@ module Rack
64
77
 
65
78
  module ScriptStackerUtils
66
79
  class SpecSolidifier < BasicObject
67
- def initialize
80
+ def initialize stacker_names
81
+ @stacker_names = stacker_names
68
82
  @specs = ::Hash.new { |hash, key| hash[key] = [] }
69
83
  end
70
84
 
@@ -74,8 +88,13 @@ module Rack
74
88
  end
75
89
 
76
90
  def method_missing name, *args
91
+ if !@stacker_names.include? name
92
+ ::Kernel.raise ::ArgumentError.new(
93
+ "Expected one of #{@stacker_names}, but got #{name.inspect}."
94
+ )
95
+ end
77
96
  if args.size != 1
78
- raise ::ArgumentError.new(
97
+ ::Kernel.raise ::ArgumentError.new(
79
98
  "Expected a path spec like 'static/css' => 'stylesheets', " +
80
99
  "but got #{args.inspect} instead."
81
100
  )
@@ -124,10 +143,11 @@ module Rack
124
143
  end
125
144
 
126
145
  class Runner
127
- def initialize stacker_configs
146
+ def initialize stacker_configs, inject_mode
128
147
  @stackers = stacker_configs.map do |name, config|
129
148
  [name, Stacker.new(config)]
130
149
  end.to_h
150
+ @inject_mode = inject_mode
131
151
  end
132
152
 
133
153
  def replace_in_body body, path_specs
@@ -139,10 +159,23 @@ module Rack
139
159
 
140
160
  body.map do |chunk|
141
161
  @stackers.values.reduce chunk do |memo, stacker|
142
- stacker.replace_slot memo
162
+ inject_into memo, stacker
143
163
  end
144
164
  end
145
165
  end
166
+
167
+ private
168
+
169
+ def inject_into chunk, stacker
170
+ case @inject_mode
171
+ when Rack::ScriptStacker::InjectMode::SLOT
172
+ stacker.replace_slot chunk
173
+ when Rack::ScriptStacker::InjectMode::TAG
174
+ stacker.tag_inject chunk
175
+ else
176
+ raise ArgumentError.new "Unexpected InjectMode #{@inject_mode.inspect}."
177
+ end
178
+ end
146
179
  end
147
180
 
148
181
  class Stacker
@@ -150,6 +183,8 @@ module Rack
150
183
  @template = config[:template]
151
184
  @glob = config[:glob]
152
185
  @slot = config[:slot]
186
+ @inject_before_tag = config[:inject_before_tag]
187
+
153
188
  @files = []
154
189
  end
155
190
 
@@ -159,17 +194,32 @@ module Rack
159
194
  end
160
195
  end
161
196
 
197
+ def tag_inject chunk
198
+ lines = chunk.split "\n", -1 # this preserves any trailing newlines
199
+ index = lines.find_index { |line| line.match @inject_before_tag }
200
+ return if index.nil?
201
+ indent = lines[index].match(/^\s*/).to_s
202
+ (
203
+ lines[0...index] +
204
+ file_list(indent + ' ') +
205
+ lines[index..-1]
206
+ ).join "\n"
207
+ end
208
+
162
209
  def replace_slot chunk
163
210
  chunk.gsub /^(\s*)#{slot}/ do
164
- indent = $1
165
- @files.map do |line|
166
- indent + line
167
- end.join "\n"
211
+ file_list($1).join "\n"
168
212
  end
169
213
  end
170
214
 
171
215
  private
172
216
 
217
+ def file_list indent
218
+ @files.map do |line|
219
+ indent + line
220
+ end
221
+ end
222
+
173
223
  def slot
174
224
  "<!-- ScriptStacker: #{@slot} //-->"
175
225
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class ScriptStacker
3
- VERSION = "0.0.1"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -1,3 +1,4 @@
1
+ require 'pry'
1
2
  require 'rack/scriptstacker'
2
3
 
3
4
  def smart_deindent str
@@ -11,13 +12,22 @@ end
11
12
 
12
13
  describe Rack::ScriptStacker do
13
14
  let(:js_files) { ['main.js', 'util.js'] }
14
- let(:vendor_js_files) { ['jquery.js', 'buttscript.js'] }
15
+ let(:vendor_js_files) { ['jquery.js', 'blogo_web.js'] }
15
16
  let(:css_files) { ['main.css', '_whatever.css'] }
16
- let(:middleware) do
17
- Rack::ScriptStacker.new app_with_body(body), configure_static: false do
17
+ let(:config) do
18
+ {
19
+ configure_static: false,
20
+ inject_mode: Rack::ScriptStacker::InjectMode::SLOT
21
+ }
22
+ end
23
+ let(:stack_spec) do
24
+ Proc.new {
18
25
  css 'static/css'
19
26
  javascript 'static/javascripts'
20
- end
27
+ }
28
+ end
29
+ let(:middleware) do
30
+ Rack::ScriptStacker.new app_with_body(body), config, &stack_spec
21
31
  end
22
32
 
23
33
  before :each do
@@ -32,7 +42,6 @@ describe Rack::ScriptStacker do
32
42
 
33
43
  context 'inactive' do
34
44
  let(:body) { '<div>whatever</div>' }
35
-
36
45
  it 'does not change without replacement slots' do
37
46
  expect(@response_body).to eq('<div>whatever</div>')
38
47
  end
@@ -47,7 +56,6 @@ describe Rack::ScriptStacker do
47
56
  </body>
48
57
  HTML
49
58
  end
50
-
51
59
  it 'injects tags' do
52
60
  expect(@response_body).to eq(smart_deindent(<<-HTML))
53
61
  <body>
@@ -67,7 +75,6 @@ describe Rack::ScriptStacker do
67
75
  </head>
68
76
  HTML
69
77
  end
70
-
71
78
  it 'injects tags' do
72
79
  expect(@response_body).to eq(smart_deindent(<<-HTML))
73
80
  <head>
@@ -78,36 +85,12 @@ describe Rack::ScriptStacker do
78
85
  end
79
86
  end
80
87
 
81
- context 'path normalization' do
82
- let(:middleware) do
83
- Rack::ScriptStacker.new app_with_body(body), configure_static: false do
84
- css 'static/css/' => '/stylesheets'
85
- javascript 'static/javascripts' => 'static/js'
86
- end
87
- end
88
- let(:body) do
89
- smart_deindent(<<-HTML)
90
- <!-- ScriptStacker: CSS //-->
91
- <!-- ScriptStacker: JAVASCRIPT //-->
92
- HTML
93
- end
94
-
95
- it 'does not mess up the paths' do
96
- expect(@response_body).to eq(smart_deindent(<<-HTML))
97
- <link rel="stylesheet" type="text/css" href="/stylesheets/main.css" />
98
- <link rel="stylesheet" type="text/css" href="/stylesheets/_whatever.css" />
99
- <script type="text/javascript" src="/static/js/main.js"></script>
100
- <script type="text/javascript" src="/static/js/util.js"></script>
101
- HTML
102
- end
103
- end
104
-
105
88
  context 'multiple specs for one stacker' do
106
- let(:middleware) do
107
- Rack::ScriptStacker.new app_with_body(body), configure_static: false do
89
+ let(:stack_spec) do
90
+ Proc.new {
108
91
  javascript 'vendor/javascripts'
109
92
  javascript 'static/javascripts'
110
- end
93
+ }
111
94
  end
112
95
  let(:body) do
113
96
  smart_deindent(<<-HTML)
@@ -117,13 +100,12 @@ describe Rack::ScriptStacker do
117
100
  </body>
118
101
  HTML
119
102
  end
120
-
121
103
  it 'injects tags in order' do
122
104
  expect(@response_body).to eq(smart_deindent(<<-HTML))
123
105
  <body>
124
106
  <div>lmao</div>
125
107
  <script type="text/javascript" src="/vendor/javascripts/jquery.js"></script>
126
- <script type="text/javascript" src="/vendor/javascripts/buttscript.js"></script>
108
+ <script type="text/javascript" src="/vendor/javascripts/blogo_web.js"></script>
127
109
  <script type="text/javascript" src="/static/javascripts/main.js"></script>
128
110
  <script type="text/javascript" src="/static/javascripts/util.js"></script>
129
111
  </body>
@@ -131,21 +113,92 @@ describe Rack::ScriptStacker do
131
113
  end
132
114
  end
133
115
 
134
- context 'Rack::Static' do
116
+ context 'static file auto-configuration' do
135
117
  let(:body) { '<div>whatever</div>' }
136
-
137
118
  it 'configures static automatically' do
138
119
  expect(Rack::Static).to receive(:new).with(
139
120
  duck_type(:call), {
140
121
  urls: ['/static/css/', '/static/javascripts/']
141
122
  }
142
123
  )
143
-
144
124
  Rack::ScriptStacker.new app_with_body(body), configure_static: true do
145
125
  css 'static/css'
146
126
  javascript 'static/javascripts'
147
127
  end
148
128
  end
149
129
  end
130
+
131
+ context 'auto-inject' do
132
+ let(:config) do
133
+ {
134
+ configure_static: false,
135
+ inject_mode: Rack::ScriptStacker::InjectMode::TAG
136
+ }
137
+ end
138
+ let(:body) do
139
+ smart_deindent(<<-HTML)
140
+ <html>
141
+ <head>
142
+ <title>OH RLY</title>
143
+ </head>
144
+ <body>
145
+ <div>ya rly</div>
146
+ </body>
147
+ </html>
148
+ HTML
149
+ end
150
+ it 'injects before the <head> and <body> tags' do
151
+ expect(@response_body).to eq(smart_deindent(<<-HTML))
152
+ <html>
153
+ <head>
154
+ <title>OH RLY</title>
155
+ <link rel="stylesheet" type="text/css" href="/static/css/main.css" />
156
+ <link rel="stylesheet" type="text/css" href="/static/css/_whatever.css" />
157
+ </head>
158
+ <body>
159
+ <div>ya rly</div>
160
+ <script type="text/javascript" src="/static/javascripts/main.js"></script>
161
+ <script type="text/javascript" src="/static/javascripts/util.js"></script>
162
+ </body>
163
+ </html>
164
+ HTML
165
+ end
166
+ end
167
+ end
168
+
169
+ describe Rack::ScriptStackerUtils::PathSpec do
170
+ let(:css_spec) { subject.new(
171
+ 'static/css/' => '/stylesheets'
172
+ )}
173
+ let(:js_spec) { subject.new(
174
+ 'static/javascripts' => 'static/js/'
175
+ )}
176
+
177
+ context 'serve path' do
178
+ it 'always serves with absolute paths' do
179
+ expect(js_spec.serve_path).to start_with('/')
180
+ end
181
+ it 'appends a slash when there is none, for concatenation simplicity' do
182
+ expect(css_spec.serve_path).to end_with('/')
183
+ end
184
+ it 'does not prepend a redundant slash' do
185
+ expect(css_spec.serve_path[0..1]).to_not start_with('//')
186
+ end
187
+ it 'does not append a redundant slash' do
188
+ expect(js_spec.serve_path[-2..-1]).to_not end_with('//')
189
+ end
190
+ end
191
+
192
+ context 'source path' do
193
+ it 'appends a slash when there is none, for concatenation simplicity' do
194
+ expect(js_spec.source_path).to end_with('/')
195
+ end
196
+ it 'does not prepend a slash, because local files are relative to pwd' do
197
+ expect(css_spec.source_path).to_not start_with('/')
198
+ end
199
+ it 'does not append a redundant slash' do
200
+ expect(css_spec.source_path).to_not end_with('//')
201
+ end
202
+ end
150
203
  end
151
204
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-scriptstacker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Cantor