palimpsest 0.0.1
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 +7 -0
- data/.gitignore +18 -0
- data/.kateproject +4 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +4 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +20 -0
- data/README.md +70 -0
- data/Rakefile +7 -0
- data/lib/palimpsest/assets.rb +189 -0
- data/lib/palimpsest/environment.rb +263 -0
- data/lib/palimpsest/utility.rb +69 -0
- data/lib/palimpsest/version.rb +4 -0
- data/lib/palimpsest.rb +9 -0
- data/palimpsest.gemspec +40 -0
- data/spec/assets_spec.rb +360 -0
- data/spec/environment_spec.rb +305 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/utility_spec.rb +56 -0
- metadata +297 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'archive/tar/minitar'
|
2
|
+
|
3
|
+
module Palimpsest
|
4
|
+
|
5
|
+
# Utility functions for Palimpsest.
|
6
|
+
class Utility
|
7
|
+
|
8
|
+
# Make a random directory.
|
9
|
+
# @param [String] root directory to place random directory
|
10
|
+
# @param [String] prefix prepended to random directory name
|
11
|
+
# @param [String, nil] dir the random directory name (used recursively)
|
12
|
+
# @return [String] path to created random directory
|
13
|
+
def self.make_random_directory root, prefix, dir = nil
|
14
|
+
path = "#{root}/#{prefix}#{dir}" unless dir.nil?
|
15
|
+
if path.nil? or File.exists? path
|
16
|
+
make_random_directory root, prefix, Random.rand(10000000)
|
17
|
+
else
|
18
|
+
FileUtils.mkdir(path).first
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Forbids use of `../` and `~/` in path.
|
23
|
+
# Forbids absolute paths.
|
24
|
+
# @param [String] path
|
25
|
+
# @return [Boolean]
|
26
|
+
def self.safe_path? path
|
27
|
+
case
|
28
|
+
when path[/(\.\.\/|~\/)/] then return false
|
29
|
+
when path[/^\//] then return false
|
30
|
+
else return true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks that a path is really rooted under a given root directory.
|
35
|
+
# Forbids use of `../` and `~/` in path.
|
36
|
+
# @param [String] path
|
37
|
+
# @param [String] root directory where path should be rooted under
|
38
|
+
# @return [String] input path if valid
|
39
|
+
def self.validate_path path, root=''
|
40
|
+
case
|
41
|
+
when path[/(\.\.\/|~\/)/] then raise RuntimeError
|
42
|
+
when File.expand_path(path, root)[/^#{root}/].nil? then raise RuntimeError
|
43
|
+
else path
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Extracts a git repo to a directory.
|
48
|
+
# @param [Grit::Repo] repo
|
49
|
+
# @param [String] treeish
|
50
|
+
# @param [String] directory
|
51
|
+
def self.extract_repo repo, treeish, directory, files: nil
|
52
|
+
input = Archive::Tar::Minitar::Input.new StringIO.new(repo.archive_tar treeish)
|
53
|
+
input.each do |entry|
|
54
|
+
if files.nil?
|
55
|
+
input.extract_entry directory, entry
|
56
|
+
else
|
57
|
+
input.extract_entry directory, entry if files.include? entry.name
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Write contents to file.
|
63
|
+
# @param contents [String]
|
64
|
+
# @param file [String]
|
65
|
+
def self.write contents, file
|
66
|
+
File.open(file, 'w') { |f| f.write contents }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/palimpsest.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'palimpsest/version'
|
2
|
+
require 'palimpsest/assets'
|
3
|
+
require 'palimpsest/environment'
|
4
|
+
require 'palimpsest/utility'
|
5
|
+
|
6
|
+
# No web framework, no problem:
|
7
|
+
# Palimpsest gives any custom or legacy project # a modern workflow and toolset.
|
8
|
+
module Palimpsest
|
9
|
+
end
|
data/palimpsest.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'palimpsest/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'palimpsest'
|
8
|
+
spec.version = Palimpsest::VERSION
|
9
|
+
spec.authors = ['Evan Boyd Sosenko']
|
10
|
+
spec.email = ['razorx@evansosenko.com']
|
11
|
+
spec.description = %q{No web framework, no problem: Palimpsest gives any custom or legacy project a modern workflow and toolset.}
|
12
|
+
spec.summary = %q{Built flexible, simple, and customizable. Palimpsest runs on top of any project and acts as a post processor for your code. Features a Sprockets asset pipeline and easy integration with Kit.}
|
13
|
+
spec.homepage = 'https://github.com/razor-x/palimpsest'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'activesupport', '~> 4.0.0'
|
22
|
+
spec.add_dependency 'archive-tar-minitar', '~> 0.5.2'
|
23
|
+
spec.add_dependency 'grit', '~> 2.5.0'
|
24
|
+
spec.add_dependency 'sprockets', '~> 2.10.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.1'
|
28
|
+
spec.add_development_dependency 'bump', '~> 0.4'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'yard', '0.8.7.2'
|
31
|
+
spec.add_development_dependency 'redcarpet', '3.0.0'
|
32
|
+
spec.add_development_dependency 'github-markup', '0.7.5'
|
33
|
+
|
34
|
+
spec.add_development_dependency 'rspec', '~> 2.14.1'
|
35
|
+
spec.add_development_dependency 'simplecov', '~> 0.7.1'
|
36
|
+
spec.add_development_dependency 'coveralls', '~> 0.7.0'
|
37
|
+
spec.add_development_dependency 'fuubar', '~> 1.2.1'
|
38
|
+
spec.add_development_dependency 'guard-rspec', '~> 3.1.0'
|
39
|
+
spec.add_development_dependency 'guard-yard', '~> 2.1.0'
|
40
|
+
end
|
data/spec/assets_spec.rb
ADDED
@@ -0,0 +1,360 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Palimpsest::Assets do
|
4
|
+
|
5
|
+
let(:config) do
|
6
|
+
YAML.load <<-EOF
|
7
|
+
:options:
|
8
|
+
:js_compressor: :uglifier
|
9
|
+
:not_a_good_setting: :some_value
|
10
|
+
:paths:
|
11
|
+
- assets/javascripts
|
12
|
+
- other/javascripts
|
13
|
+
EOF
|
14
|
+
end
|
15
|
+
|
16
|
+
subject(:assets) { Palimpsest::Assets.new }
|
17
|
+
|
18
|
+
describe ".new" do
|
19
|
+
|
20
|
+
it "sets default options" do
|
21
|
+
expect(assets.options).to eq Palimpsest::Assets::DEFAULT_OPTIONS
|
22
|
+
end
|
23
|
+
|
24
|
+
it "merges default options" do
|
25
|
+
assets = Palimpsest::Assets.new options: { src_pre: '{{' }
|
26
|
+
expect(assets.options).to eq Palimpsest::Assets::DEFAULT_OPTIONS.merge(src_pre: '{{')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#options" do
|
31
|
+
|
32
|
+
it "merges with default options" do
|
33
|
+
assets.options[:src_pre] = '{{'
|
34
|
+
expect(assets.options).to eq Palimpsest::Assets::DEFAULT_OPTIONS.merge(src_pre: '{{')
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can be called twice and merge options" do
|
38
|
+
assets.options[:src_pre] = '{{'
|
39
|
+
assets.options[:src_post] = '}}'
|
40
|
+
expect(assets.options).to eq Palimpsest::Assets::DEFAULT_OPTIONS.merge(src_pre: '{{', src_post: '}}')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#sprockets" do
|
45
|
+
|
46
|
+
it "returns a new sprockets environment" do
|
47
|
+
expect(assets.sprockets).to be_a Sprockets::Environment
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#load_options" do
|
52
|
+
|
53
|
+
subject(:assets) { Palimpsest::Assets.new options: config[:options] }
|
54
|
+
|
55
|
+
it "returns itself" do
|
56
|
+
expect(assets.load_options).to be assets
|
57
|
+
end
|
58
|
+
|
59
|
+
it "sets the options for sprockets" do
|
60
|
+
expect(assets.sprockets).to receive(:js_compressor=).with(:uglifier)
|
61
|
+
assets.load_options
|
62
|
+
end
|
63
|
+
|
64
|
+
it "does not load an unset setting" do
|
65
|
+
expect(assets.sprockets).to_not receive(:css_compressor)
|
66
|
+
assets.load_options
|
67
|
+
end
|
68
|
+
|
69
|
+
it "does not load an invalid setting" do
|
70
|
+
expect(assets.sprockets).to_not receive(:not_a_good_setting)
|
71
|
+
assets.load_options
|
72
|
+
end
|
73
|
+
|
74
|
+
context "no options" do
|
75
|
+
|
76
|
+
it "does not fail when options not set" do
|
77
|
+
expect { assets.load_options }.to_not raise_error
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#load_paths" do
|
83
|
+
|
84
|
+
subject(:assets) { Palimpsest::Assets.new paths: config[:paths] }
|
85
|
+
|
86
|
+
it "returns itself" do
|
87
|
+
expect(assets.load_paths).to be assets
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when directory set" do
|
91
|
+
|
92
|
+
it "loads the paths for the given set into the sprockets environment" do
|
93
|
+
assets.directory = '/tmp/root_dir'
|
94
|
+
expect(assets.sprockets).to receive(:append_path).with('/tmp/root_dir/assets/javascripts')
|
95
|
+
expect(assets.sprockets).to receive(:append_path).with('/tmp/root_dir/other/javascripts')
|
96
|
+
assets.load_paths
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when no directory set" do
|
101
|
+
|
102
|
+
it "loads the paths for the given set into the sprockets environment" do
|
103
|
+
expect(assets.sprockets).to receive(:append_path).with('assets/javascripts')
|
104
|
+
expect(assets.sprockets).to receive(:append_path).with('other/javascripts')
|
105
|
+
assets.load_paths
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "when no paths set" do
|
110
|
+
|
111
|
+
it "should not fail" do
|
112
|
+
assets.paths = {}
|
113
|
+
expect { assets.load_paths }.to_not raise_error
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#assets" do
|
119
|
+
|
120
|
+
subject(:assets) { Palimpsest::Assets.new paths: config[:paths] }
|
121
|
+
|
122
|
+
it "should load options" do
|
123
|
+
expect(assets).to receive :load_options
|
124
|
+
assets.assets
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should load paths" do
|
128
|
+
expect(assets).to receive :load_paths
|
129
|
+
assets.assets
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should not load options and paths twice" do
|
133
|
+
expect(assets).to receive(:load_options).once
|
134
|
+
expect(assets).to receive(:load_paths).once
|
135
|
+
assets.assets
|
136
|
+
assets.assets
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should return compiled assets" do
|
140
|
+
expect(assets.assets).to equal assets.sprockets
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "#write" do
|
145
|
+
|
146
|
+
let(:asset) { double Sprockets::Asset }
|
147
|
+
let(:name) { 'lib/app.js' }
|
148
|
+
|
149
|
+
before :each do
|
150
|
+
assets.options hash: false
|
151
|
+
allow(assets.assets).to receive(:[]).with('lib/app').and_return(asset)
|
152
|
+
allow(asset).to receive(:logical_path).and_return(name)
|
153
|
+
end
|
154
|
+
|
155
|
+
context "asset not found" do
|
156
|
+
it "returns nil" do
|
157
|
+
allow(assets.assets).to receive(:[]).with('not_here').and_return(nil)
|
158
|
+
expect(assets.write 'not_here').to be nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "output is set with no directory set" do
|
163
|
+
|
164
|
+
it "writes to relative path and returns the relative path" do
|
165
|
+
assets.options output: 'compiled'
|
166
|
+
expect(asset).to receive(:write_to).with("compiled/#{name}")
|
167
|
+
expect(assets.write 'lib/app').to eq "compiled/#{name}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context "output is set with directory set" do
|
172
|
+
|
173
|
+
it "writes to relative path under directory and returns the relative path" do
|
174
|
+
assets.options output: 'compiled'
|
175
|
+
assets.directory = '/tmp/dir'
|
176
|
+
expect(asset).to receive(:write_to).with("/tmp/dir/compiled/#{name}")
|
177
|
+
expect(assets.write 'lib/app').to eq "compiled/#{name}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "no output is set with directory set and returns the relative path" do
|
182
|
+
|
183
|
+
it "writes to relative path under directory and returns the relative path" do
|
184
|
+
assets.directory = '/tmp/dir'
|
185
|
+
expect(asset).to receive(:write_to).with("/tmp/dir/#{name}")
|
186
|
+
expect(assets.write 'lib/app').to eq name
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "no output is set with no directory set" do
|
191
|
+
|
192
|
+
it "writes to relative path and returns the relative path" do
|
193
|
+
expect(asset).to receive(:write_to).with(name)
|
194
|
+
expect(assets.write 'lib/app').to eq name
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context "when gzip true" do
|
199
|
+
|
200
|
+
it "still returns the non-gzipped asset name" do
|
201
|
+
allow(asset).to receive(:write_to)
|
202
|
+
expect(assets.write 'lib/app', gzip: true).to eq name
|
203
|
+
end
|
204
|
+
|
205
|
+
it "it gzips the assets as well" do
|
206
|
+
expect(asset).to receive(:write_to).at_most(:once).with("#{name}.gz", compress: true)
|
207
|
+
expect(asset).to receive(:write_to).at_most(:once).with(name)
|
208
|
+
assets.write 'lib/app', gzip: true
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context "when hash true" do
|
213
|
+
|
214
|
+
it "hashes the file name" do
|
215
|
+
allow(asset).to receive(:digest_path).and_return('app-cb5a921a4e7663347223c41cd2fa9e11.js')
|
216
|
+
expect(asset).to receive(:write_to).with('app-cb5a921a4e7663347223c41cd2fa9e11.js')
|
217
|
+
assets.write 'lib/app', hash: true
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe ".find_tags and #find_tags" do
|
223
|
+
|
224
|
+
let(:grep) { [ 'grep', '-l', '-I', '-r', '-E' ] }
|
225
|
+
let(:matches) { [ "first/match_1\nsecond/match_2" ] }
|
226
|
+
|
227
|
+
describe ".find_tags" do
|
228
|
+
|
229
|
+
let(:regex) { '\[%(.*?)%\]' }
|
230
|
+
|
231
|
+
it "fails if path is empty" do
|
232
|
+
expect { assets.find_tags '' }.to raise_error ArgumentError
|
233
|
+
end
|
234
|
+
|
235
|
+
it "greps in the path" do
|
236
|
+
expect(Open3).to receive(:capture2).with(*grep, regex, '/the/path').and_return(matches)
|
237
|
+
Palimpsest::Assets.find_tags '/the/path'
|
238
|
+
end
|
239
|
+
|
240
|
+
it "greps for only the asset tag for the given type" do
|
241
|
+
expect(Open3).to receive(:capture2).with(*grep, '\[%\s+javascript\s+(.*?)%\]', '/the/path').and_return(matches)
|
242
|
+
Palimpsest::Assets.find_tags '/the/path', :javascript
|
243
|
+
end
|
244
|
+
|
245
|
+
it "merges options" do
|
246
|
+
expect(Open3).to receive(:capture2).with(*grep, '\(%\s+javascript\s+(.*?)%\]', '/the/path').and_return(matches)
|
247
|
+
Palimpsest::Assets.find_tags '/the/path', :javascript, src_pre: '(%'
|
248
|
+
end
|
249
|
+
|
250
|
+
it "returns an array of results" do
|
251
|
+
allow(Open3).to receive(:capture2).with(*grep, regex, '/the/path').and_return(matches)
|
252
|
+
expect(Palimpsest::Assets.find_tags '/the/path').to eq [ 'first/match_1', 'second/match_2' ]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe "#find_tags" do
|
257
|
+
|
258
|
+
it "uses the type as the type" do
|
259
|
+
assets.type = :javascript
|
260
|
+
expect(Palimpsest::Assets).to receive(:find_tags).with(anything, :javascript, anything)
|
261
|
+
assets.find_tags path: '/the/path'
|
262
|
+
end
|
263
|
+
|
264
|
+
it "uses the options as the options" do
|
265
|
+
expect(Palimpsest::Assets).to receive(:find_tags).with(anything, anything, assets.options)
|
266
|
+
assets.find_tags path: '/the/path'
|
267
|
+
end
|
268
|
+
|
269
|
+
it "uses the directory as the path" do
|
270
|
+
assets.directory = '/the/directory'
|
271
|
+
expect(Palimpsest::Assets).to receive(:find_tags).with('/the/directory', anything, anything)
|
272
|
+
assets.find_tags
|
273
|
+
end
|
274
|
+
|
275
|
+
it "can use an alternative path" do
|
276
|
+
assets.directory = '/the/directory'
|
277
|
+
expect(Palimpsest::Assets).to receive(:find_tags).with('/the/path', anything, anything)
|
278
|
+
assets.find_tags path: '/the/path'
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "#update_source and #update_source!" do
|
284
|
+
|
285
|
+
let(:asset) { double Sprockets::Asset }
|
286
|
+
|
287
|
+
let(:source) do
|
288
|
+
<<-EOF
|
289
|
+
<head>
|
290
|
+
<script src="[% javascript app %]"></script>
|
291
|
+
<script src="[% javascript vendor/modernizr %]"></script>
|
292
|
+
<script>
|
293
|
+
[% javascript inline vendor/tracking %]
|
294
|
+
</script>
|
295
|
+
</head>
|
296
|
+
EOF
|
297
|
+
end
|
298
|
+
|
299
|
+
let(:result) do
|
300
|
+
<<-EOF
|
301
|
+
<head>
|
302
|
+
<script src="app-1234.js"></script>
|
303
|
+
<script src="vendor/modernizr-5678.js"></script>
|
304
|
+
<script>
|
305
|
+
alert('track');
|
306
|
+
</script>
|
307
|
+
</head>
|
308
|
+
EOF
|
309
|
+
end
|
310
|
+
|
311
|
+
let(:result_with_cdn) do
|
312
|
+
<<-EOF
|
313
|
+
<head>
|
314
|
+
<script src="https://cdn.example.com/app-1234.js"></script>
|
315
|
+
<script src="https://cdn.example.com/vendor/modernizr-5678.js"></script>
|
316
|
+
<script>
|
317
|
+
alert('track');
|
318
|
+
</script>
|
319
|
+
</head>
|
320
|
+
EOF
|
321
|
+
end
|
322
|
+
|
323
|
+
before :each do
|
324
|
+
assets.type = :javascripts
|
325
|
+
allow(assets).to receive(:write).with('app').and_return('app-1234.js')
|
326
|
+
allow(assets).to receive(:write).with('vendor/modernizr').and_return('vendor/modernizr-5678.js')
|
327
|
+
allow(assets.assets).to receive(:[]).with('vendor/tracking').and_return(asset)
|
328
|
+
allow(asset).to receive(:to_s).and_return(%q{alert('track');})
|
329
|
+
end
|
330
|
+
|
331
|
+
describe "#update_source!" do
|
332
|
+
|
333
|
+
it "replaces asset tags in sources" do
|
334
|
+
assets.update_source! source
|
335
|
+
expect(source).to eq result
|
336
|
+
end
|
337
|
+
|
338
|
+
context "cdn option set" do
|
339
|
+
|
340
|
+
it "uses cdn when it replaces asset tags in sources" do
|
341
|
+
assets.options cdn: 'https://cdn.example.com/'
|
342
|
+
assets.update_source! source
|
343
|
+
expect(source).to eq result_with_cdn
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe "#update_source" do
|
349
|
+
|
350
|
+
it "replaces asset tags in sources" do
|
351
|
+
expect(assets.update_source source).to eq result
|
352
|
+
end
|
353
|
+
|
354
|
+
it "does not change input string" do
|
355
|
+
assets.update_source source
|
356
|
+
expect(source).to eq source
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|