palimpsest 0.0.1 → 0.1.0

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