palimpsest 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: bb9471fad6a53da1e1cc97a04c7883e81bfa6756
4
- data.tar.gz: ad2694a1fecbbe589e1bd2287d003398c0d4408c
3
+ metadata.gz: 786935525c8fd9d713375ffc0b13e52bae005eb7
4
+ data.tar.gz: 9b1f0e46de8b8d23fd44a55034e1ce87529c135a
5
5
  SHA512:
6
- metadata.gz: 44c0455bd09f9084fbb2b90cd24c97f833ccb2e70594788a0632846955db6a8dd23b43425ac4e68a740a1c0c0a5c9b8c5c05e91bc831c50a4fdaf6cc413aa2fd
7
- data.tar.gz: 6e9e38fa4cead06576012f74eda7cc94baf115578be4fd282a810ae7586d13dcbe19a0ccdc595e76cd4bf7ea51bd520a156023f6ad69228eb09a7264fd759431
6
+ metadata.gz: 7a405c655b0aa0024da55885d687e89cabe671705ba2dde83c1875ee911b04c297856dc97cf05ea85f79544ae35f49c6f8a7cb354993bb356d61c61e65e757ca
7
+ data.tar.gz: c139bd33702b70b8297f6f5eab2d0a1289f74249e95fd8fd68c184f1c1e7b5b8349f614ac3b7db538d836971c41c74597ced18ac2f98cdce2911058a382b31bd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Palimpsest ChangeLog
2
2
 
3
+ ## 0.1.0
4
+
5
+ - Lots of documentation and improvements.
6
+ - New classes: Site, Component, External.
7
+ - Environment support for new classes.
8
+
3
9
  ## 0.0.1
4
10
 
5
11
  Initial release.
data/README.md CHANGED
@@ -9,16 +9,20 @@ Palimpsest runs on top of any project and acts as a post processor for your code
9
9
  Features a [Sprockets](https://github.com/sstephenson/sprockets) asset pipeline
10
10
  and easy integration with [Kit](https://github.com/razor-x/kit).
11
11
 
12
+ [![Gem Version](https://badge.fury.io/rb/palimpsest.png)](http://badge.fury.io/rb/palimpsest)
12
13
  [![Dependency Status](https://gemnasium.com/razor-x/palimpsest.png)](https://gemnasium.com/razor-x/palimpsest)
13
14
  [![Build Status](https://travis-ci.org/razor-x/palimpsest.png?branch=master)](https://travis-ci.org/razor-x/palimpsest)
14
15
  [![Coverage Status](https://coveralls.io/repos/razor-x/palimpsest/badge.png)](https://coveralls.io/r/razor-x/palimpsest)
15
16
  [![Code Climate](https://codeclimate.com/github/razor-x/palimpsest.png)](https://codeclimate.com/github/razor-x/palimpsest)
17
+ [![githalytics.com alpha](https://cruel-carlota.pagodabox.com/428992451dfb452dbd522644cbb17f71 "githalytics.com")](http://githalytics.com/razor-x/palimpsest)
16
18
 
17
19
  ## Installation
18
20
 
19
21
  Add this line to your application's Gemfile:
20
22
 
21
- gem 'palimpsest'
23
+ ````ruby
24
+ gem 'palimpsest'
25
+ ````
22
26
 
23
27
  And then execute:
24
28
 
@@ -32,20 +36,131 @@ Or install it yourself as:
32
36
  $ gem install palimpsest
33
37
  ````
34
38
 
39
+ ## Documentation
40
+
41
+ The primary documentation for Palimpsest is this README and the YARD source documentation.
42
+
43
+ YARD documentation for all gem versions is hosted on the [Palimpsest gem page](https://rubygems.org/gems/palimpsest).
44
+ Documentation for the latest commits is hosted on [the RubyDoc.info project page](http://rubydoc.info/github/razor-x/palimpsest/frames).
45
+
35
46
  ## Usage
36
47
 
37
- **Palimpsest should not be considered stable until version 0.1.0 is released.**
48
+ **Palimpsest should not be considered stable until version 1.0.0 is released.**
38
49
 
39
- ### Documentation
50
+ This README will focus on examples of how to get your project working with Palimpsest through `Palimpsest::Environment`.
40
51
 
41
- _Comprehensive documentation and examples will be added to this REAME soon._
52
+ Palimpsest's classes are independently useful outside of `Palimpsest::Environment`, and each is
53
+ [well documented for that purpose](http://rubydoc.info/github/razor-x/palimpsest/frames).
42
54
 
43
- _For now, reference the YARD documentation below._
55
+ The first step is always
44
56
 
45
- The primary documentation for Palimpsest is this README and the YARD source documentation.
57
+ ````ruby
58
+ require 'palimpsest'
59
+ ````
60
+ ### Additional requirements
46
61
 
47
- YARD documentation for all gem versions is hosted on the [Palimpsest gem page](https://rubygems.org/gems/palimpsest).
48
- Documentation for the latest commits is hosted on [the RubyDoc.info project page](http://rubydoc.info/github/razor-x/palimpsest/frames).
62
+ Some optional Palimpsest features depend on gems not required by Palimpsest itself.
63
+ Include these in your project's Gemfile if you plan to use them.
64
+
65
+ For example, to use the `image_compression` option, add to your Gemfile
66
+
67
+ ````ruby
68
+ gem 'sprockets-image_compressor'
69
+ ````
70
+
71
+ and to your project
72
+
73
+ ````ruby
74
+ require 'sprockets-image_compressor'
75
+ ````
76
+
77
+ or if you set `js_compressor: uglifier` you must add to your Gemfile
78
+
79
+ ````ruby
80
+ gem 'uglifier'
81
+ ````
82
+
83
+ and to your project
84
+
85
+ ````ruby
86
+ require 'uglifier'
87
+ ````
88
+
89
+ Similarly you must include gems for any sprockets engines you want to use.
90
+
91
+ ### Creating and populating an environment
92
+
93
+ Create an environment with
94
+
95
+ ````ruby
96
+ environment = Palimpsest::Environment.new
97
+ ````
98
+ For most operations you will need to specify a `site` which can be any object which
99
+ responds to the methods `Palimpsest::Environment` assumes exists in some of its own methods.
100
+ A model class `Palimpsest::Site` is included which implements all possible expected methods.
101
+ Finally, the examples below assume default options for each class, but these can be overridden with `#options`.
102
+
103
+ ````ruby
104
+ site = Palimpsest::Site.new
105
+ site.name = 'my_app'
106
+ environment.site = site
107
+ ````
108
+
109
+ To populate the environment from a git repo,
110
+
111
+ ````ruby
112
+ site.repo = Grit::Repo.new '/path/to/project/repo'
113
+ environment.treeish = 'my_feature' # if you want something other then 'master'
114
+ environment.populate
115
+ ````
116
+ or to populate from a directory,
117
+
118
+ ````ruby
119
+ site.source = '/path/to/project/source'
120
+ environment.populate from :source
121
+ ````
122
+ Either way you will get a copy of your site in a new random working directory,
123
+
124
+ ````ruby
125
+ environment.directory #=> '/tmp/palimpsest_my_app_6025680'
126
+ ````
127
+
128
+ ### Working with the environment
129
+
130
+ If you project contains a file `palimpsest_config.yml`,
131
+ then its configuration is available with `environment.config`.
132
+
133
+ [**An example `palimpsest_config.yml`.**](http://rubydoc.info/github/razor-x/palimpsest/Palimpsest/Environment)
134
+
135
+ The configuration file tells Palimpsest how to behave when you ask it to manipulate the environment
136
+ and acts as a shortcut to working with the other Palimpsest classes directly.
137
+
138
+ If you made it this far, you can make Palimpsest do all sorts of magic to your code in the working directory.
139
+
140
+ For example, to search through you code for tags referencing assets,
141
+ process and save those assets with sprockets,
142
+ and replace the tags with references to the processed assets,
143
+
144
+ ````ruby
145
+ environment.compile_assets
146
+ ````
147
+ Check the [`Palimpsest::Environment` documentation](http://rubydoc.info/github/razor-x/palimpsest/Palimpsest/Environment)
148
+ for all available magic, and freely extend the class to add new magic applicable to your project.
149
+
150
+ ### Finishing with the environment
151
+
152
+ You can copy the current state of the environment to another directory with `Palimpsest::Environment#copy`.
153
+ By default, this will use `site.path` for the destination, or you can specify with
154
+
155
+ ````ruby
156
+ environment.copy dest: '/path/to/out/dir'
157
+ ````
158
+
159
+ To delete the working directory, use
160
+
161
+ ````ruby
162
+ environment.cleanup
163
+ ````
49
164
 
50
165
  ## Development
51
166
 
@@ -29,6 +29,9 @@ module Palimpsest
29
29
  # keyword to use in asset tag for inline assets
30
30
  inline: 'inline',
31
31
 
32
+ # if true, use sprockets-image_compressor with pngcrush and jpegoptim
33
+ image_compression: false,
34
+
32
35
  # if true, also generate a gzipped asset
33
36
  gzip: false,
34
37
 
@@ -40,7 +43,7 @@ module Palimpsest
40
43
  src_post: '%]',
41
44
 
42
45
  # allowed options for `Sprockets::Environment`
43
- sprockets_options: [ :js_compressor, :css_compressor ]
46
+ sprockets_options: [:js_compressor, :css_compressor]
44
47
  }
45
48
 
46
49
  # @!attribute directory
@@ -78,7 +81,12 @@ module Palimpsest
78
81
  options[:sprockets_options].each do |opt|
79
82
  sprockets.send "#{opt}=".to_sym, options[opt] if options[opt]
80
83
  end
81
- return self
84
+
85
+ if options[:image_compression]
86
+ Sprockets::ImageCompressor::Integration.setup sprockets
87
+ end
88
+
89
+ self
82
90
  end
83
91
 
84
92
  # Load paths into the sprockets environment.
@@ -87,7 +95,7 @@ module Palimpsest
87
95
  paths.each do |path|
88
96
  sprockets.append_path "#{directory + '/' unless directory.empty?}#{path}"
89
97
  end
90
- return self
98
+ self
91
99
  end
92
100
 
93
101
  # @return [Sprockets::Environment] sprockets environment with {#options} and {#paths} loaded
@@ -165,15 +173,15 @@ module Palimpsest
165
173
  # @param options [Hash] merged with {DEFAULT_OPTIONS}
166
174
  # (see #find_tags)
167
175
  def self.find_tags path, type=nil, options={}
168
- raise ArgumentError, 'path cannot be empty' if path.empty?
176
+ fail ArgumentError, 'path cannot be empty' if path.empty?
169
177
 
170
178
  options = DEFAULT_OPTIONS.merge options
171
179
  pre = Regexp.escape options[:src_pre]
172
180
  post= Regexp.escape options[:src_post]
173
181
 
174
- cmd = [ 'grep' ]
175
- cmd.concat [ '-l', '-I', '-r', '-E' ]
176
- cmd << \
182
+ cmd = ['grep']
183
+ cmd.concat %w(-l -I -r -E)
184
+ cmd <<
177
185
  if type.nil?
178
186
  pre + '(.*?)' + post
179
187
  else
@@ -0,0 +1,33 @@
1
+ module Palimpsest
2
+
3
+ # Use this class to store parts of your project in a location
4
+ # separate from the normal installed location.
5
+ #
6
+ # For example, put custom templates in `components/my_app/templates`
7
+ # which might later be installed to `apps/my_app/templates`.
8
+ #
9
+ # This is useful when `apps/my_app` is a separate project
10
+ # with its own repository loaded using {Palimpsest::External}.
11
+ class Component
12
+
13
+ # @!attribute source_path
14
+ # @return [String] source path for component
15
+ #
16
+ # @!attribute install_path
17
+ # @return [String] install path for component
18
+ attr_accessor :source_path, :install_path
19
+
20
+ def initialize source_path: '', install_path: ''
21
+ self.source_path = source_path
22
+ self.install_path = install_path
23
+ end
24
+
25
+ # Installs files in {#source_path} to {#install_path}
26
+ def install
27
+ fail RuntimeError if source_path.empty?
28
+ fail RuntimeError if install_path.empty?
29
+ FileUtils.mkdir_p install_path
30
+ FileUtils.mv Dir["#{source_path}/*"], install_path
31
+ end
32
+ end
33
+ end
@@ -1,9 +1,8 @@
1
- require 'grit'
2
-
3
1
  module Palimpsest
4
2
 
5
3
  # An environment is populated with the contents of
6
4
  # a site's repository at a specified commit.
5
+ # Alternatively, a single directory can be used to populate the environment.
7
6
  # The environment's files are rooted in a temporary {#directory}.
8
7
  # An environment is the primary way to interact with a site's files.
9
8
  #
@@ -13,11 +12,33 @@ module Palimpsest
13
12
  # Paths are all relative to the working {#directory}.
14
13
  #
15
14
  # ````yml
16
- # palimpsest_config.yml
15
+ # # example of palimpsest_config.yml
16
+ #
17
+ # # component settings
18
+ # :components:
19
+ # # all component paths are relative to the base
20
+ # :base: _components
21
+ #
22
+ # # list of components
23
+ # :paths:
24
+ # #- [ components_path, install_path ]
25
+ # - [ my_app/templates, apps/my_app/templates ]
26
+ # - [ my_app/extra, apps/my_app ]
17
27
  #
28
+ # # externals settings
29
+ # :externals:
30
+ # # server or local path that repos are under
31
+ # :server: "https://github.com/razor-x"
32
+ #
33
+ # # list of external repos
34
+ # :repos:
35
+ # #- [ name, install_path, branch, server (optional) ]
36
+ # - [ my_app, apps/my_app, master ]
37
+ # - [ sub_app, apps/my_app/sub_app, my_feature, "https://bitbucket.org/razorx" ]
18
38
  # # asset settings
19
39
  # :assets:
20
- # # all options are passed to `Palimpsest::Assets#options` and will use those defaults if unset
40
+ # # all options are passed to Assets#options
41
+ # # options will use defaults set in Palimpsest::Asset::DEFAULT_OPTIONS if unset here
21
42
  # # unless otherwise mentioned, options can be set or overridden per asset type
22
43
  # :options:
23
44
  # # opening and closing brackets for asset source tags
@@ -61,8 +82,10 @@ module Palimpsest
61
82
  # - assets/stylesheets
62
83
  # # images can be part of the asset pipeline
63
84
  # :images:
64
- # # options can be overridden per type
65
85
  # :options:
86
+ # # requires the sprockets-image_compressor gem
87
+ # :image_compression: true
88
+ # # options can be overridden per type
66
89
  # :output: images
67
90
  # :paths:
68
91
  # - assets/images
@@ -87,12 +110,9 @@ module Palimpsest
87
110
  # @!attribute treeish
88
111
  # @return [String] the reference used to pick the commit to build the environment with
89
112
  #
90
- # @!attribute [r] directory
91
- # @return [String] the environment's working directory
92
- #
93
113
  # @!attribute [r] populated
94
114
  # @return [Boolean] true if the site's repo has been extracted
95
- attr_reader :site, :treeish, :directory, :populated
115
+ attr_reader :site, :treeish, :populated
96
116
 
97
117
  def initialize site: nil, treeish: 'master', options: {}
98
118
  @populated = false
@@ -109,49 +129,71 @@ module Palimpsest
109
129
  @options = @options.merge options
110
130
  end
111
131
 
132
+ # @see Environment#site
112
133
  def site= site
113
- raise RuntimeError, "Cannot redefine 'site' once populated" if populated
134
+ fail RuntimeError, "Cannot redefine 'site' once populated" if populated
114
135
  @site = site
115
136
  end
116
137
 
138
+ # @see Environment#treeish
117
139
  def treeish= treeish
118
- raise RuntimeError, "Cannot redefine 'treeish' once populated" if populated
119
- raise TypeError unless treeish.is_a? String
140
+ fail RuntimeError, "Cannot redefine 'treeish' once populated" if populated
141
+ fail TypeError unless treeish.is_a? String
120
142
  @treeish = treeish
121
143
  end
122
144
 
145
+ # @return [String] the environment's working directory
123
146
  def directory
124
- raise RuntimeError if site.nil?
125
147
  if @directory.nil?
126
- @directory = Palimpsest::Utility.make_random_directory options[:tmp_dir], "#{options[:dir_prefix]}#{site.name}_"
148
+ name = site.nil? ? '' : site.name
149
+ @directory = Utility.make_random_directory options[:tmp_dir], "#{options[:dir_prefix]}#{name}_"
127
150
  else
128
151
  @directory
129
152
  end
130
153
  end
131
154
 
155
+ # Copy the contents of the working directory.
156
+ # @param dest [String] path to copy environment's files to
157
+ # @return [Environment] the current environment instance
158
+ def copy dest: site.path
159
+ FileUtils.cp_r Dir["#{directory}/*"], dest, preserve: true
160
+ self
161
+ end
162
+
132
163
  # Removes the environment's working directory.
164
+ # @return [Environment] the current environment instance
133
165
  def cleanup
134
166
  FileUtils.remove_entry_secure directory if @directory
135
167
  @directory = nil
168
+ @assets = []
169
+ @components = []
136
170
  @populated = false
137
- return self
171
+ self
138
172
  end
139
173
 
140
174
  # Extracts the site's files from repository to the working directory.
141
- def populate from: :repo
175
+ # @return [Environment] the current environment instance
176
+ def populate from: :auto
142
177
  cleanup if populated
143
- raise RuntimeError, "Cannot populate without 'site'" if site.nil?
178
+ fail RuntimeError, "Cannot populate without 'site'" if site.nil?
144
179
 
145
180
  case from
181
+ when :auto
182
+ if site.respond_to?(:repo) ? site.repo : nil
183
+ populate from: :repo
184
+ else
185
+ populate from: :source
186
+ end
146
187
  when :repo
147
- raise RuntimeError, "Cannot populate without 'treeish'" if treeish.empty?
148
- Palimpsest::Utility.extract_repo site.repo, treeish, directory
188
+ fail RuntimeError, "Cannot populate without 'treeish'" if treeish.empty?
189
+ Utility.extract_repo site.repo, treeish, directory
190
+ @populated = true
149
191
  when :source
150
- FileUtils.cp_r Dir["#{site.source}/*"], directory
192
+ FileUtils.cp_r Dir["#{site.source}/*"], directory, preserve: true
193
+ @populated = true
151
194
  end
152
195
 
153
- @populated = true
154
- return self
196
+ self
155
197
  end
156
198
 
157
199
  # @return [Hash] configuration loaded from {#options}`[:config_file]` under {#directory}
@@ -161,15 +203,65 @@ module Palimpsest
161
203
  validate_config if @config
162
204
  end
163
205
 
164
- # @return [Array<Palimpsest::Assets>] assets with settings and paths loaded from config
206
+ # @return [Array<Component>] components with paths loaded from config
207
+ def components
208
+ return @components if @components
209
+ return [] if config[:components].nil?
210
+ return [] if config[:components][:paths].nil?
211
+
212
+ @components = []
213
+
214
+ base = directory
215
+ base += config[:components][:base].nil? ? '' : '/' + config[:components][:base]
216
+
217
+ config[:components][:paths].each do |paths|
218
+ @components << Component.new(source_path: "#{base}/#{paths[0]}", install_path: "#{directory}/#{paths[1]}")
219
+ end
220
+
221
+ @components
222
+ end
223
+
224
+ # Install all components.
225
+ # @return [Environment] the current environment instance
226
+ def install_components
227
+ components.each { |c| c.install }
228
+ self
229
+ end
230
+
231
+ # @return [Array<External>] externals loaded from config
232
+ def externals
233
+ return @externals if @externals
234
+ return [] if config[:externals].nil?
235
+ return [] if config[:externals][:repos].nil?
236
+
237
+ @externals = []
238
+
239
+ config[:externals][:repos].each do |repo|
240
+ source = repo[3].nil? ? config[:externals][:server] : repo[3]
241
+ @externals << External.new(name: repo[0], source: source, branch: repo[2], install_path: "#{directory}/#{repo[1]}" )
242
+ end
243
+
244
+ @externals
245
+ end
246
+
247
+ # Install all externals.
248
+ # @return [Environment] the current environment instance
249
+ def install_externals
250
+ externals.each { |e| e.install }
251
+ self
252
+ end
253
+
254
+ # @return [Array<Assets>] assets with settings and paths loaded from config
165
255
  def assets
256
+ return @assets if @assets
257
+
166
258
  @assets = []
167
259
 
168
260
  config[:assets].each do |type, opt|
169
- next if [ :sources ].include? type
261
+ next if [:sources].include? type
170
262
  next if opt[:paths].nil?
171
263
 
172
- assets = Palimpsest::Assets.new directory: directory, paths: opt[:paths]
264
+ assets = Assets.new directory: directory, paths: opt[:paths]
173
265
  assets.options config[:assets][:options] unless config[:assets][:options].nil?
174
266
  assets.options opt[:options] unless opt[:options].nil?
175
267
  assets.type = type
@@ -187,12 +279,12 @@ module Palimpsest
187
279
  @sources_with_assets = []
188
280
 
189
281
  opts = {}
190
- [ :src_pre, :src_post ].each do |opt|
282
+ [:src_pre, :src_post].each do |opt|
191
283
  opts[opt] = config[:assets][:options][opt] unless config[:assets][:options][opt].nil?
192
284
  end unless config[:assets][:options].nil?
193
285
 
194
286
  config[:assets][:sources].each do |path|
195
- @sources_with_assets << Palimpsest::Assets.find_tags("#{directory}/#{path}", nil, opts)
287
+ @sources_with_assets << Assets.find_tags("#{directory}/#{path}", nil, opts)
196
288
  end
197
289
 
198
290
  @sources_with_assets.flatten
@@ -200,35 +292,57 @@ module Palimpsest
200
292
 
201
293
  # Finds all assets in {#sources_with_assets} and
202
294
  # generates the assets and updates the sources.
295
+ # @return [Environment] the current environment instance
203
296
  def compile_assets
204
297
  sources_with_assets.each do |file|
205
298
  source = File.read file
206
299
  assets.each { |a| a.update_source! source }
207
- Palimpsest::Utility.write source, file
300
+ Utility.write source, file, preserve: true
208
301
  end
209
- return self
302
+ self
210
303
  end
211
304
 
212
305
  private
213
306
 
214
307
  # Checks the config file for invalid settings.
215
- #
308
+ # @todo refactor this
216
309
  # - Checks that paths are not absolute or use `../` or `~/`.
217
310
  def validate_config
218
311
  message = 'bad path in config'
219
312
 
220
- def safe_path?(path) Palimpsest::Utility.safe_path?(path) end
221
-
222
313
  # Checks the option in the asset key.
223
314
  def validate_asset_options opts
224
315
  opts.each do |k,v|
225
- raise RuntimeError, 'bad option in config' if k == :sprockets_options
226
- raise RuntimeError, message if k == :output && ! safe_path?(v)
316
+ fail RuntimeError, 'bad option in config' if k == :sprockets_options
317
+ fail RuntimeError, message if k == :output && ! Utility.safe_path?(v)
227
318
  end
228
319
  end
229
320
 
230
- @config[:assets].each do |k, v|
321
+ @config[:external].each do |k, v|
322
+ next if k == :server
231
323
 
324
+ v.each do |repo|
325
+ fail RuntimeError, message unless Utility.safe_path? repo[1]
326
+ end unless v.nil?
327
+ end unless @config[:external].nil?
328
+
329
+ @config[:components].each do |k,v|
330
+ # process @config[:components][:base] then go to the next option
331
+ if k == :base
332
+ fail RuntimeError, message unless Utility.safe_path? v
333
+ next
334
+ end unless v.nil?
335
+
336
+ # process @config[:components][:paths]
337
+ if k == :paths
338
+ v.each do |path|
339
+ fail RuntimeError, message unless Utility.safe_path? path[0]
340
+ fail RuntimeError, message unless Utility.safe_path? path[1]
341
+ end
342
+ end
343
+ end unless @config[:components].nil?
344
+
345
+ @config[:assets].each do |k, v|
232
346
  # process @config[:assets][:options] then go to the next option
233
347
  if k == :options
234
348
  validate_asset_options v
@@ -238,7 +352,7 @@ module Palimpsest
238
352
  # process @config[:assets][:sources] then go to the next option
239
353
  if k == :sources
240
354
  v.each_with_index do |source, i|
241
- raise RuntimeError, message unless safe_path? source
355
+ fail RuntimeError, message unless Utility.safe_path? source
242
356
  end
243
357
  next
244
358
  end
@@ -253,10 +367,11 @@ module Palimpsest
253
367
 
254
368
  # process each asset path
255
369
  asset_value.each_with_index do |path, i|
256
- raise RuntimeError, message unless safe_path? path
370
+ fail RuntimeError, message unless Utility.safe_path? path
257
371
  end
258
372
  end
259
373
  end unless @config[:assets].nil?
374
+
260
375
  @config
261
376
  end
262
377
  end
@@ -0,0 +1,74 @@
1
+ module Palimpsest
2
+
3
+ # Use this class to manage external repositories you want to include in your project.
4
+ #
5
+ # Given a name, source and branch, the contents of the repository at the HEAD of the branch
6
+ # will be available as an {Environment} object through {#environment}.
7
+ #
8
+ # Use {#cleanup} to remove all files created by the {#External} object.
9
+ class External
10
+
11
+ # @!attribute name
12
+ # @return [String] repository name
13
+ #
14
+ # @!attribute source
15
+ # @return [String] base source url or path to external git repo (without name)
16
+ #
17
+ # @!attribute branch
18
+ # @return [String] branch to use for treeish
19
+ #
20
+ # @!attribute install_path
21
+ # @return [String] where the files will be installed to
22
+ attr_accessor :name, :source, :branch, :install_path
23
+
24
+ def initialize name: '', source: '', branch: 'master', install_path: ''
25
+ self.name = name
26
+ self.source = source
27
+ self.branch = branch
28
+ self.install_path = install_path
29
+ end
30
+
31
+ def repo_path
32
+ ( source.empty? || name.empty? ) ? '' : "#{source}/#{name}"
33
+ end
34
+
35
+ # @return [Environment] environment with contents of the repository at the HEAD of the branch
36
+ def environment
37
+ return @environment if @environment
38
+
39
+ site = Site.new repo: Grit::Repo.new(tmp_environment.directory)
40
+ @environment = Environment.new site: site, treeish: branch
41
+ end
42
+
43
+ # Copy the files to the {#install_path}.
44
+ # @return (see Environment#copy)
45
+ def install
46
+ environment.copy dest: install_path
47
+ end
48
+
49
+ # @return [External] the current external instance
50
+ def cleanup
51
+ environment.cleanup if @environment
52
+ @environment = nil
53
+
54
+ tmp_environment.cleanup if @tmp_environment
55
+ @tmp_environment = nil
56
+ self
57
+ end
58
+
59
+ # @return [Environment] temporary environment to hold the cloned repository
60
+ def tmp_environment
61
+ return @tmp_environment if @tmp_environment
62
+
63
+ fail RuntimeError if repo_path.empty?
64
+
65
+ Grit::Git.git_max_size = 200 * 1048576
66
+ Grit::Git.git_timeout = 200
67
+
68
+ @tmp_environment = Environment.new
69
+ gritty = Grit::Git.new tmp_environment.directory
70
+ gritty.clone( { branch: branch }, repo_path, tmp_environment.directory )
71
+ @tmp_environment
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,23 @@
1
+ module Palimpsest
2
+
3
+ # Model site object used by {Environment#site}.
4
+ class Site
5
+
6
+ # @!attribute name
7
+ # @return [String] name for this site
8
+ #
9
+ # @!attribute repo
10
+ # @return [Grit::Repo] grit repo for this site
11
+ #
12
+ # @!attribute source
13
+ # @return [String] path to source code for this site
14
+ attr_accessor :name, :repo, :source, :path
15
+
16
+ def initialize name: '', repo: nil, source: ''
17
+ self.name = name
18
+ self.repo = repo
19
+ self.source = source
20
+ self.path = path
21
+ end
22
+ end
23
+ end
@@ -38,8 +38,8 @@ module Palimpsest
38
38
  # @return [String] input path if valid
39
39
  def self.validate_path path, root=''
40
40
  case
41
- when path[/(\.\.\/|~\/)/] then raise RuntimeError
42
- when File.expand_path(path, root)[/^#{root}/].nil? then raise RuntimeError
41
+ when path[/(\.\.\/|~\/)/] then fail RuntimeError
42
+ when File.expand_path(path, root)[/^#{root}/].nil? then fail RuntimeError
43
43
  else path
44
44
  end
45
45
  end
@@ -48,22 +48,18 @@ module Palimpsest
48
48
  # @param [Grit::Repo] repo
49
49
  # @param [String] treeish
50
50
  # @param [String] directory
51
- def self.extract_repo repo, treeish, directory, files: nil
51
+ def self.extract_repo repo, treeish, directory
52
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
53
+ input.each { |e| input.extract_entry directory, e }
60
54
  end
61
55
 
62
56
  # Write contents to file.
63
57
  # @param contents [String]
64
58
  # @param file [String]
65
- def self.write contents, file
59
+ def self.write contents, file, preserve: false
60
+ original_time = File.mtime file if preserve
66
61
  File.open(file, 'w') { |f| f.write contents }
62
+ File.utime original_time, original_time, file if preserve
67
63
  end
68
64
  end
69
65
  end
@@ -1,4 +1,4 @@
1
1
  module Palimpsest
2
2
  # Palimpsest version.
3
- VERSION = '0.0.1'
3
+ VERSION = '0.1.0'
4
4
  end
data/lib/palimpsest.rb CHANGED
@@ -1,7 +1,12 @@
1
+ require 'grit'
2
+
1
3
  require 'palimpsest/version'
4
+ require 'palimpsest/utility'
5
+ require 'palimpsest/site'
2
6
  require 'palimpsest/assets'
7
+ require 'palimpsest/component'
8
+ require 'palimpsest/external'
3
9
  require 'palimpsest/environment'
4
- require 'palimpsest/utility'
5
10
 
6
11
  # No web framework, no problem:
7
12
  # Palimpsest gives any custom or legacy project # a modern workflow and toolset.
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Palimpsest::Component do
4
+
5
+ subject(:component) { Palimpsest::Component.new }
6
+
7
+ describe "#install" do
8
+
9
+ it "fails if no source path" do
10
+ component.install_path = 'install/path'
11
+ expect { component.install }.to raise_error RuntimeError
12
+ end
13
+
14
+ it "fails if no install path" do
15
+ component.source_path = 'src/path'
16
+ expect { component.install }.to raise_error RuntimeError
17
+ end
18
+
19
+ it "moves the component to the install path" do
20
+ component.source_path = 'src/path'
21
+ component.install_path = 'install/path'
22
+ allow(Dir).to receive(:[]).with('src/path/*').and_return( %w(src/path/1 src/path/2) )
23
+ expect(FileUtils).to receive(:mkdir_p).with('install/path')
24
+ expect(FileUtils).to receive(:mv).with(%w(src/path/1 src/path/2), 'install/path')
25
+ component.install
26
+ end
27
+ end
28
+ end
@@ -2,17 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe Palimpsest::Environment do
4
4
 
5
- let(:site_1) { double name: 'site_1' }
6
- let(:site_2) { double name: 'site_2' }
5
+ let(:site_1) { Palimpsest::Site.new name: 'site_1' }
6
+ let(:site_2) { Palimpsest::Site.new name: 'site_2' }
7
7
 
8
8
  subject(:environment) { Palimpsest::Environment.new }
9
9
 
10
- after :all do
11
- Dir.glob("#{Palimpsest::Environment.new.options[:tmp_dir]}/#{Palimpsest::Environment.new.options[:dir_prefix]}*").each do |dir|
12
- FileUtils.remove_entry_secure dir
13
- end
14
- end
15
-
16
10
  describe ".new" do
17
11
 
18
12
  it "sets default options" do
@@ -63,36 +57,33 @@ describe Palimpsest::Environment do
63
57
 
64
58
  describe "#directory" do
65
59
 
66
- context "when required values set" do
67
-
68
- subject(:environment) { Palimpsest::Environment.new site: site_1 }
69
-
70
- before :each do
71
- allow(Palimpsest::Utility).to receive(:make_random_directory).and_return('/tmp/rand_dir')
72
- end
60
+ before :each do
61
+ allow(Palimpsest::Utility).to receive(:make_random_directory).and_return('/tmp/rand_dir')
62
+ end
73
63
 
74
- context "when directory is unset" do
64
+ context "when directory is unset" do
75
65
 
76
- it "makes and returns a random directory" do
77
- expect(environment.directory).to eq '/tmp/rand_dir'
78
- end
66
+ it "makes and returns a random directory" do
67
+ expect(environment.directory).to eq '/tmp/rand_dir'
79
68
  end
69
+ end
80
70
 
81
- context "when directory is set" do
71
+ context "when directory is set" do
82
72
 
83
- it "returns the current directory" do
84
- expect(Palimpsest::Utility).to receive(:make_random_directory).once
85
- environment.directory
86
- expect(environment.directory).to eq '/tmp/rand_dir'
87
- end
73
+ it "returns the current directory" do
74
+ expect(Palimpsest::Utility).to receive(:make_random_directory).once
75
+ environment.directory
76
+ expect(environment.directory).to eq '/tmp/rand_dir'
88
77
  end
89
78
  end
79
+ end
90
80
 
91
- context "when required values are not set" do
92
-
93
- it "fails if site is not set" do
94
- expect { environment.directory }.to raise_error RuntimeError
95
- end
81
+ describe "#copy" do
82
+ it "moves the component to the install path" do
83
+ dir = environment.directory
84
+ allow(Dir).to receive(:[]).with("#{dir}/*").and_return( %W(#{dir}/path/1 #{dir}/path/2) )
85
+ expect(FileUtils).to receive(:cp_r).with( %W(#{dir}/path/1 #{dir}/path/2), '/dest/path', preserve: true)
86
+ environment.copy dest: '/dest/path'
96
87
  end
97
88
  end
98
89
 
@@ -106,6 +97,10 @@ describe Palimpsest::Environment do
106
97
  environment.cleanup
107
98
  expect(environment.instance_variable_get :@directory).to eq nil
108
99
  end
100
+
101
+ it "returns itself" do
102
+ expect(environment.cleanup).to be environment
103
+ end
109
104
  end
110
105
 
111
106
  describe "#populate" do
@@ -120,40 +115,40 @@ describe Palimpsest::Environment do
120
115
  subject(:environment) { Palimpsest::Environment.new site: site_1, treeish: 'master' }
121
116
 
122
117
  before :each do
123
- allow(site_1).to receive(:repo).and_return(double Grit::Repo)
118
+ site_1.repo = double Grit::Repo
124
119
  allow(Palimpsest::Utility).to receive :extract_repo
125
120
  end
126
121
 
127
122
  it "extracts the repo to the directory and sets populated true" do
128
123
  expect(Palimpsest::Utility).to receive(:extract_repo).with(site_1.repo, 'master', environment.directory)
129
- environment.populate
124
+ environment.populate from: :repo
130
125
  expect(environment.populated).to eq true
131
126
  end
132
127
 
133
128
  it "fails when missing treeish" do
134
129
  environment.site = site_1
135
130
  environment.treeish = ''
136
- expect { environment.populate }.to raise_error RuntimeError, /populate without/
131
+ expect { environment.populate from: :repo }.to raise_error RuntimeError, /populate without/
137
132
  end
138
133
 
139
134
  it "returns itself" do
140
- expect(environment.populate).to be environment
135
+ expect(environment.populate from: :repo).to be environment
141
136
  end
142
137
 
143
138
  it "will cleanup if populated" do
144
139
  environment.populate
145
140
  expect(environment).to receive :cleanup
146
- environment.populate
141
+ environment.populate from: :repo
147
142
  end
148
143
  end
149
144
 
150
- context "populate from directory" do
145
+ context "populate from source" do
151
146
 
152
- it "copies the source files to the directory" do
147
+ it "copies the source files to the directory preserving mtime" do
153
148
  environment.site = site_1
154
- allow(site_1).to receive(:source).and_return('/path/to/source')
149
+ site_1.source = '/path/to/source'
155
150
  allow(Dir).to receive(:[]).with('/path/to/source/*').and_return( %w(dir_1 dir_2) )
156
- expect(FileUtils).to receive(:cp_r).with( %w(dir_1 dir_2), environment.directory )
151
+ expect(FileUtils).to receive(:cp_r).with( %w(dir_1 dir_2), environment.directory, preserve: true )
157
152
  environment.populate from: :source
158
153
  end
159
154
  end
@@ -189,6 +184,16 @@ describe Palimpsest::Environment do
189
184
 
190
185
  let(:config) do
191
186
  YAML.load <<-EOF
187
+ :components:
188
+ :base: _components
189
+ :paths:
190
+ - [ my_app/templates, apps/my_app/templates ]
191
+ - [ my_app/extra, apps/my_app ]
192
+ :externals:
193
+ :server: "https://github.com/razor-x"
194
+ :repos:
195
+ - [ my_app, apps/my_app, master ]
196
+ - [ sub_app, apps/my_app/sub_app, my_feature, "https://bitbucket.org/razorx" ]
192
197
  :assets:
193
198
  :options:
194
199
  :output: compiled
@@ -219,6 +224,85 @@ describe Palimpsest::Environment do
219
224
  allow(environment).to receive(:config).and_return(config)
220
225
  end
221
226
 
227
+ describe "#components" do
228
+
229
+ it "returns an array" do
230
+ expect(environment.components).to be_a Array
231
+ end
232
+
233
+ it "contains components" do
234
+ expect(environment.components[0]).to be_a Palimpsest::Component
235
+ expect(environment.components[1]).to be_a Palimpsest::Component
236
+ end
237
+
238
+ it "sets the components source and install paths" do
239
+ expect(environment.components[0].source_path).to eq "#{environment.directory}/_components/my_app/templates"
240
+ expect(environment.components[0].install_path).to eq "#{environment.directory}/apps/my_app/templates"
241
+ end
242
+ end
243
+
244
+ describe "#install_components" do
245
+
246
+ it "installs the components" do
247
+ expect(environment.components[0]).to receive(:install)
248
+ expect(environment.components[1]).to receive(:install)
249
+ environment.install_components
250
+ end
251
+
252
+ it "returns itself" do
253
+ expect(environment.install_components).to be environment
254
+ end
255
+ end
256
+
257
+ describe "#externals" do
258
+
259
+ it "returns an array" do
260
+ expect(environment.externals).to be_a Array
261
+ end
262
+
263
+ it "contains externals" do
264
+ expect(environment.externals[0]).to be_a Palimpsest::External
265
+ expect(environment.externals[1]).to be_a Palimpsest::External
266
+ end
267
+
268
+ it "sets the externals repo path" do
269
+ expect(environment.externals[0].repo_path).to eq 'https://github.com/razor-x/my_app'
270
+ expect(environment.externals[1].repo_path).to eq 'https://bitbucket.org/razorx/sub_app'
271
+ end
272
+
273
+ it "sets the externals branch" do
274
+ expect(environment.externals[0].branch).to eq 'master'
275
+ expect(environment.externals[1].branch).to eq 'my_feature'
276
+ end
277
+
278
+ it "sets the install path" do
279
+ expect(environment.externals[0].install_path).to eq "#{environment.directory}/apps/my_app"
280
+ expect(environment.externals[1].install_path).to eq "#{environment.directory}/apps/my_app/sub_app"
281
+ end
282
+ end
283
+
284
+ describe "#install_externals" do
285
+
286
+ let(:external_1) { double Palimpsest::External }
287
+ let(:external_2) { double Palimpsest::External }
288
+
289
+ before :each do
290
+ allow(environment).to receive(:externals).and_return( [ external_1, external_2 ] )
291
+ allow(external_1).to receive(:install)
292
+ allow(external_2).to receive(:install)
293
+ end
294
+
295
+ it "installs the externals" do
296
+ expect(external_1).to receive(:install)
297
+ expect(external_2).to receive(:install)
298
+ environment.install_externals
299
+ end
300
+
301
+ it "returns itself" do
302
+ expect(environment.install_externals).to be environment
303
+ end
304
+ end
305
+
222
306
  describe "#assets" do
223
307
 
224
308
  subject(:assets) { environment.assets }
@@ -292,12 +376,12 @@ describe Palimpsest::Environment do
292
376
  expect(environment.compile_assets).to be environment
293
377
  end
294
378
 
295
- it "compiles the assets and writes the sources" do
379
+ it "compiles the assets and writes the sources while preserving mtime" do
296
380
  allow(environment).to receive(:sources_with_assets).and_return sources
297
381
  allow(File).to receive(:read).with(sources[0]).and_return('data_1')
298
382
  allow(File).to receive(:read).with(sources[1]).and_return('data_2')
299
- expect(Palimpsest::Utility).to receive(:write).with 'data_1', sources[0]
300
- expect(Palimpsest::Utility).to receive(:write).with 'data_2', sources[1]
383
+ expect(Palimpsest::Utility).to receive(:write).with 'data_1', sources[0], preserve: true
384
+ expect(Palimpsest::Utility).to receive(:write).with 'data_2', sources[1], preserve: true
301
385
  environment.compile_assets
302
386
  end
303
387
  end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe Palimpsest::External do
4
+
5
+ subject(:external) { Palimpsest::External.new name: 'my_app', source: 'path/to/source', branch: 'my_feature' }
6
+
7
+ let(:gritty) { double Grit::Git }
8
+ let(:repo) { double Grit::Repo }
9
+
10
+ before :each do
11
+ allow(Grit::Git).to receive(:new).and_return(gritty)
12
+ allow(Grit::Repo).to receive(:new).and_return(repo)
13
+ allow(gritty).to receive(:clone)
14
+ end
15
+
16
+ describe "#repo_path" do
17
+
18
+ it "gives the full path to the source repo" do
19
+ expect(external.repo_path).to eq 'path/to/source/my_app'
20
+ end
21
+ end
22
+
23
+ describe "#environment" do
24
+
25
+ it "returns a new environment" do
26
+ expect(external.environment).to be_a Palimpsest::Environment
27
+ end
28
+
29
+ it "sets the treeish for the environment" do
30
+ expect(external.environment.treeish).to eq 'my_feature'
31
+ end
32
+
33
+ it "sets the repo for the environment" do
34
+ expect(external.environment.site.repo).to equal repo
35
+ end
36
+ end
37
+
38
+ describe "#tmp_environment" do
39
+
40
+ it "fails if no repo path" do
41
+ external.name = ''
42
+ external.source = ''
43
+ expect { external.tmp_environment }.to raise_error RuntimeError
44
+ end
45
+
46
+ it "returns a new environment" do
47
+ expect(external.tmp_environment).to be_a Palimpsest::Environment
48
+ end
49
+
50
+ it "sets the treeish for the environment" do
51
+ expect(gritty).to receive(:clone).with( { branch: 'my_feature' }, external.repo_path, anything )
52
+ external.tmp_environment
53
+ end
54
+ end
55
+
56
+ describe "#cleanup" do
57
+
58
+ it "cleans environment" do
59
+ expect(external.environment).to receive(:cleanup)
60
+ external.cleanup
61
+ end
62
+ it "clears @environment" do
63
+ expect(external.instance_variable_get :@environment).to be_nil
64
+ external.cleanup
65
+ end
66
+
67
+ it "cleans tmp_environment" do
68
+ expect(external.tmp_environment).to receive(:cleanup)
69
+ external.cleanup
70
+ end
71
+
72
+ it "clears @tmp_environment" do
73
+ expect(external.instance_variable_get :@tmp_environment).to be_nil
74
+ external.cleanup
75
+ end
76
+ end
77
+
78
+ describe "#install" do
79
+
80
+ it "installs the external to the install path" do
81
+ external.install_path = 'path/to/install'
82
+ expect(external.environment).to receive(:copy).with(dest: 'path/to/install')
83
+ external.install
84
+ end
85
+ end
86
+ end
data/spec/spec_helper.rb CHANGED
@@ -12,4 +12,10 @@ SimpleCov.start
12
12
 
13
13
  RSpec.configure do |c|
14
14
  c.expect_with(:rspec) { |e| e.syntax = :expect }
15
+
16
+ c.after :suite do
17
+ Dir.glob("#{Palimpsest::Environment.new.options[:tmp_dir]}/#{Palimpsest::Environment.new.options[:dir_prefix]}*").each do |dir|
18
+ FileUtils.remove_entry_secure dir
19
+ end
20
+ end
15
21
  end
data/spec/utility_spec.rb CHANGED
@@ -46,11 +46,22 @@ describe Palimpsest::Utility do
46
46
  describe "write" do
47
47
 
48
48
  let(:file) { double File }
49
+ let(:mtime) { Time.now }
49
50
 
50
- it "writes to file" do
51
+ before :each do
51
52
  allow(File).to receive(:open).with('path/to/file', 'w').and_yield(file)
53
+ end
54
+
55
+ it "writes to file" do
52
56
  expect(file).to receive(:write).with('data')
53
57
  Palimpsest::Utility.write 'data', 'path/to/file'
54
58
  end
59
+
60
+ it "can preserve atime and mtime" do
61
+ allow(file).to receive(:write)
62
+ allow(File).to receive(:mtime).with('path/to/file').and_return(mtime)
63
+ expect(File).to receive(:utime).with(mtime, mtime, 'path/to/file')
64
+ Palimpsest::Utility.write 'data', 'path/to/file', preserve: true
65
+ end
55
66
  end
56
67
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: palimpsest
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
  - Evan Boyd Sosenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-25 00:00:00.000000000 Z
11
+ date: 2013-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -255,12 +255,17 @@ files:
255
255
  - Rakefile
256
256
  - lib/palimpsest.rb
257
257
  - lib/palimpsest/assets.rb
258
+ - lib/palimpsest/component.rb
258
259
  - lib/palimpsest/environment.rb
260
+ - lib/palimpsest/external.rb
261
+ - lib/palimpsest/site.rb
259
262
  - lib/palimpsest/utility.rb
260
263
  - lib/palimpsest/version.rb
261
264
  - palimpsest.gemspec
262
265
  - spec/assets_spec.rb
266
+ - spec/component_spec.rb
263
267
  - spec/environment_spec.rb
268
+ - spec/external_spec.rb
264
269
  - spec/spec_helper.rb
265
270
  - spec/utility_spec.rb
266
271
  homepage: https://github.com/razor-x/palimpsest
@@ -291,7 +296,9 @@ summary: Built flexible, simple, and customizable. Palimpsest runs on top of any
291
296
  and easy integration with Kit.
292
297
  test_files:
293
298
  - spec/assets_spec.rb
299
+ - spec/component_spec.rb
294
300
  - spec/environment_spec.rb
301
+ - spec/external_spec.rb
295
302
  - spec/spec_helper.rb
296
303
  - spec/utility_spec.rb
297
304
  has_rdoc: