palimpsest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bb9471fad6a53da1e1cc97a04c7883e81bfa6756
4
+ data.tar.gz: ad2694a1fecbbe589e1bd2287d003398c0d4408c
5
+ SHA512:
6
+ metadata.gz: 44c0455bd09f9084fbb2b90cd24c97f833ccb2e70594788a0632846955db6a8dd23b43425ac4e68a740a1c0c0a5c9b8c5c05e91bc831c50a4fdaf6cc413aa2fd
7
+ data.tar.gz: 6e9e38fa4cead06576012f74eda7cc94baf115578be4fd282a810ae7586d13dcbe19a0ccdc595e76cd4bf7ea51bd520a156023f6ad69228eb09a7264fd759431
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .kateproject.d/
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.kateproject ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "name" : "palimpsest",
3
+ "files" : [ { "git" : 1 } ]
4
+ }
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm: [ 2.0.0 ]
3
+ script: rspec
data/.yardopts ADDED
@@ -0,0 +1,7 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
3
+ lib/**/*.rb
4
+ -
5
+ README.md
6
+ CHANGELOG.md
7
+ LICENSE.txt
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Palimpsest ChangeLog
2
+
3
+ ## 0.0.1
4
+
5
+ Initial release.
6
+
7
+ - Populating working environment from git repo or directory.
8
+ - Flexible asset pipeline with sprockets.
9
+ * `Palimpsest::Assets` class can be used standalone.
10
+ * Environment automatically compiles assets referenced in source files.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in palimpsest.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ guard :rspec, cli: '--color --format Fuubar' do
2
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
3
+ watch(%r{^lib/palimpsest/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch('spec/spec_helper.rb') { 'spec' }
6
+ end
7
+
8
+ guard :yard do
9
+ watch(%r{^lib/(.+)\.rb$})
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Evan Boyd Sosenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Palimpsest
2
+
3
+ by Evan Boyd Sosenko.
4
+
5
+ _No web framework, no problem: Palimpsest gives any custom or legacy project a modern workflow and toolset._
6
+
7
+ Built flexible, simple, and customizable.
8
+ Palimpsest runs on top of any project and acts as a post processor for your code.
9
+ Features a [Sprockets](https://github.com/sstephenson/sprockets) asset pipeline
10
+ and easy integration with [Kit](https://github.com/razor-x/kit).
11
+
12
+ [![Dependency Status](https://gemnasium.com/razor-x/palimpsest.png)](https://gemnasium.com/razor-x/palimpsest)
13
+ [![Build Status](https://travis-ci.org/razor-x/palimpsest.png?branch=master)](https://travis-ci.org/razor-x/palimpsest)
14
+ [![Coverage Status](https://coveralls.io/repos/razor-x/palimpsest/badge.png)](https://coveralls.io/r/razor-x/palimpsest)
15
+ [![Code Climate](https://codeclimate.com/github/razor-x/palimpsest.png)](https://codeclimate.com/github/razor-x/palimpsest)
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ gem 'palimpsest'
22
+
23
+ And then execute:
24
+
25
+ ````bash
26
+ $ bundle
27
+ ````
28
+
29
+ Or install it yourself as:
30
+
31
+ ````bash
32
+ $ gem install palimpsest
33
+ ````
34
+
35
+ ## Usage
36
+
37
+ **Palimpsest should not be considered stable until version 0.1.0 is released.**
38
+
39
+ ### Documentation
40
+
41
+ _Comprehensive documentation and examples will be added to this REAME soon._
42
+
43
+ _For now, reference the YARD documentation below._
44
+
45
+ The primary documentation for Palimpsest is this README and the YARD source documentation.
46
+
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).
49
+
50
+ ## Development
51
+
52
+ ### Source Repository
53
+
54
+ The [Palimpsest source](https://github.com/razor-x/palimpsest) is hosted at github.
55
+ To clone the project run
56
+
57
+ ````bash
58
+ $ git clone git@github.com:razor-x/palimpsest.git
59
+ ````
60
+
61
+ ## License
62
+
63
+ Palimpsest is licensed under the MIT license.
64
+
65
+ ## Warranty
66
+
67
+ This software is provided "as is" and without any express or
68
+ implied warranties, including, without limitation, the implied
69
+ warranties of merchantibility and fitness for a particular
70
+ purpose.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'bump/tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new :spec
6
+
7
+ task default: :spec
@@ -0,0 +1,189 @@
1
+ require 'active_support/inflector'
2
+ require 'open3'
3
+ require 'sprockets'
4
+
5
+ module Palimpsest
6
+
7
+ # Flexible asset pipeline using Sprockets.
8
+ # Paths are loaded into a `Sprockets::Environment` (relative to {#directory} if given).
9
+ # Asset tags are used in source code and replaced
10
+ # with generated asset path or compiled source if `inline` is used.
11
+ #
12
+ # For example, if type is set to `:javascripts` the following replacements would be made:
13
+ #
14
+ # [% javascript app %] -> app-9413c7f112033f0c6f2a8e8dd313399c18d93878.js
15
+ # [% javascript lib/jquery %] -> lib/jquery-e2a8cde3f5b3cdb011e38a673556c7a94729e0d1.js
16
+ # [% javascript inline tracking %] -> <compiled source of tracking.js asset>
17
+ #
18
+ class Assets
19
+
20
+ # Default {#options}.
21
+ DEFAULT_OPTIONS = {
22
+ # default path to output all saved assets (relative to directory)
23
+ output: '',
24
+
25
+ # assume assets will be served under this url
26
+ # e.g. `https://cdn.example.com/`
27
+ cdn: '',
28
+
29
+ # keyword to use in asset tag for inline assets
30
+ inline: 'inline',
31
+
32
+ # if true, also generate a gzipped asset
33
+ gzip: false,
34
+
35
+ # include hash in asset name
36
+ hash: true,
37
+
38
+ # opening and closing brackets for asset source tags
39
+ src_pre: '[%',
40
+ src_post: '%]',
41
+
42
+ # allowed options for `Sprockets::Environment`
43
+ sprockets_options: [ :js_compressor, :css_compressor ]
44
+ }
45
+
46
+ # @!attribute directory
47
+ # @return [String] directory which all paths will be relative to if set
48
+ #
49
+ # @!attribute paths
50
+ # @return [Array] paths to load into sprockets environment
51
+ #
52
+ # @!attribute type
53
+ # @return [Symbol] type of asset
54
+ attr_accessor :directory, :paths, :type
55
+
56
+ def initialize directory: '', options: {}, paths: {}
57
+ self.options options
58
+ self.directory = directory
59
+ self.paths = paths
60
+ end
61
+
62
+ # Uses {DEFAULT_OPTIONS} as initial value.
63
+ # @param options [Hash] merged with current options
64
+ # @return [Hash] current options
65
+ def options options={}
66
+ @options ||= DEFAULT_OPTIONS
67
+ @options = @options.merge options
68
+ end
69
+
70
+ # @return [Sprockets::Environment] the current sprockets environment
71
+ def sprockets
72
+ @sprockets ||= Sprockets::Environment.new
73
+ end
74
+
75
+ # Load options into the sprockets environment.
76
+ # Values are loaded from {#options}.
77
+ def load_options
78
+ options[:sprockets_options].each do |opt|
79
+ sprockets.send "#{opt}=".to_sym, options[opt] if options[opt]
80
+ end
81
+ return self
82
+ end
83
+
84
+ # Load paths into the sprockets environment.
85
+ # Values are loaded from {#paths}.
86
+ def load_paths
87
+ paths.each do |path|
88
+ sprockets.append_path "#{directory + '/' unless directory.empty?}#{path}"
89
+ end
90
+ return self
91
+ end
92
+
93
+ # @return [Sprockets::Environment] sprockets environment with {#options} and {#paths} loaded
94
+ def assets
95
+ unless @loaded
96
+ load_options
97
+ load_paths
98
+ end
99
+ @loaded = true
100
+ sprockets
101
+ end
102
+
103
+ # Write a target asset to file with a hashed name.
104
+ # @param target [String] logical path to asset
105
+ # @param gzip [Boolean] if the asset should be gzipped
106
+ # @param hash [Boolean] if the asset name should include the hash
107
+ # @return [String, nil] the relative path to the written asset or `nil` if no such asset
108
+ def write target, gzip: options[:gzip], hash: options[:hash]
109
+ asset = assets[target]
110
+
111
+ return if asset.nil?
112
+
113
+ name = hash ? asset.digest_path : asset.logical_path.to_s
114
+ name = "#{options[:output]}/#{name}" unless options[:output].empty?
115
+
116
+ path = directory.empty? ? '' : "#{directory}/"
117
+ path << name
118
+
119
+ asset.write_to "#{path}.gz", compress: true if gzip
120
+ asset.write_to path
121
+ name
122
+ end
123
+
124
+ # (see #update_source)
125
+ # @note this modifies the `source` `String` in place
126
+ def update_source! source
127
+ # e.g. /\[%\s+javascript\s+((\S+)\s?(\S+))\s+%\]/
128
+ regex = /#{Regexp.escape options[:src_pre]}\s+#{type.to_s.singularize}\s+((\S+)\s?(\S+))\s+#{Regexp.escape options[:src_post]}/
129
+ source.gsub! regex do
130
+ if $2 == options[:inline]
131
+ assets[$3].to_s
132
+ else
133
+ asset = write $1
134
+
135
+ # @todo raise warning or error if asset not found
136
+ p "asset not found: #{$1}" and next if asset.nil?
137
+
138
+ options[:cdn].empty? ? asset : options[:cdn] + asset
139
+ end
140
+ end
141
+ return true
142
+ end
143
+
144
+ # Replaces all asset tags in source string with asset path or asset source.
145
+ # Writes any referenced assets to disk.
146
+ # @param source [String] code to find and replace asset tags
147
+ # @return [String] copy of `source` with asset tags replaced
148
+ def update_source source
149
+ s = source
150
+ update_source! s
151
+ s
152
+ end
153
+
154
+ # Scans all non-binary files under `path` ({#directory} by default) for asset tags.
155
+ # Uses current asset {#type} (if set) and {#options}.
156
+ # @param path [String] where to look for source files
157
+ # @return [Array] files with asset tags
158
+ def find_tags path: directory
159
+ self.class.find_tags path, type, options
160
+ end
161
+
162
+ # Scans all non-binary files under `path` for asset tags.
163
+ # @param path [String] where to look for source files
164
+ # @param type [String, nil] only look for asset tags with this type (or any type if `nil`)
165
+ # @param options [Hash] merged with {DEFAULT_OPTIONS}
166
+ # (see #find_tags)
167
+ def self.find_tags path, type=nil, options={}
168
+ raise ArgumentError, 'path cannot be empty' if path.empty?
169
+
170
+ options = DEFAULT_OPTIONS.merge options
171
+ pre = Regexp.escape options[:src_pre]
172
+ post= Regexp.escape options[:src_post]
173
+
174
+ cmd = [ 'grep' ]
175
+ cmd.concat [ '-l', '-I', '-r', '-E' ]
176
+ cmd << \
177
+ if type.nil?
178
+ pre + '(.*?)' + post
179
+ else
180
+ pre + '\s+' + type.to_s + '\s+(.*?)' + post
181
+ end
182
+ cmd << path
183
+
184
+ files = []
185
+ Open3.capture2(*cmd).first.each_line { |l| files << l.chomp unless l.empty? }
186
+ files
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,263 @@
1
+ require 'grit'
2
+
3
+ module Palimpsest
4
+
5
+ # An environment is populated with the contents of
6
+ # a site's repository at a specified commit.
7
+ # The environment's files are rooted in a temporary {#directory}.
8
+ # An environment is the primary way to interact with a site's files.
9
+ #
10
+ # An environment loads a {#config} file from the working {#directory};
11
+ # by default, `palimpsest_config.yml`.
12
+ #
13
+ # Paths are all relative to the working {#directory}.
14
+ #
15
+ # ````yml
16
+ # palimpsest_config.yml
17
+ #
18
+ # # asset settings
19
+ # :assets:
20
+ # # all options are passed to `Palimpsest::Assets#options` and will use those defaults if unset
21
+ # # unless otherwise mentioned, options can be set or overridden per asset type
22
+ # :options:
23
+ # # opening and closing brackets for asset source tags
24
+ # # global option only: cannot be overridden per asset type
25
+ # :src_pre: "[%"
26
+ # :src_post: "%]"
27
+ #
28
+ # # relative directory to save compiled assets
29
+ # :output: compiled
30
+ #
31
+ # # assume assets will be served under here
32
+ # :cdn: https://cdn.example.com/
33
+ #
34
+ # # compiled asset names include a uniqe hash by default
35
+ # # this can be toggled off
36
+ # :hash: false
37
+ #
38
+ # # directories to scan for files with asset tags
39
+ # :sources:
40
+ # # putting assets/stylesheets first would allow asset tags,
41
+ # # e.g. for images, to be used in your stylesheets
42
+ # - assets/stylesheets
43
+ # - public
44
+ # - app/src
45
+ #
46
+ # # all other keys are asset types
47
+ # :javascripts:
48
+ # :options:
49
+ # :js_compressor: :uglifier
50
+ # # these paths are loaded into the sprockets environment
51
+ # :paths:
52
+ # - assets/javascripts
53
+ # - other/javascripts
54
+ #
55
+ # # this is another asset type which will have it's own namespace
56
+ # :stylesheets:
57
+ # :options:
58
+ # :css_compressor: :sass
59
+ #
60
+ # :paths:
61
+ # - assets/stylesheets
62
+ # # images can be part of the asset pipeline
63
+ # :images:
64
+ # # options can be overridden per type
65
+ # :options:
66
+ # :output: images
67
+ # :paths:
68
+ # - assets/images
69
+ # ````
70
+ class Environment
71
+
72
+ # Default {#options}.
73
+ DEFAULT_OPTIONS = {
74
+ # all environment's temporary directories will be rooted under here
75
+ tmp_dir: '/tmp',
76
+
77
+ # prepended to the name of the environment's working directory
78
+ dir_prefix: 'palimpsest_',
79
+
80
+ # name of config file to load, relative to environment's working directory
81
+ config_file: 'palimpsest_config.yml'
82
+ }
83
+
84
+ # @!attribute site
85
+ # @return site to build the environment with
86
+ #
87
+ # @!attribute treeish
88
+ # @return [String] the reference used to pick the commit to build the environment with
89
+ #
90
+ # @!attribute [r] directory
91
+ # @return [String] the environment's working directory
92
+ #
93
+ # @!attribute [r] populated
94
+ # @return [Boolean] true if the site's repo has been extracted
95
+ attr_reader :site, :treeish, :directory, :populated
96
+
97
+ def initialize site: nil, treeish: 'master', options: {}
98
+ @populated = false
99
+ self.options options
100
+ self.site = site if site
101
+ self.treeish = treeish
102
+ end
103
+
104
+ # Uses {DEFAULT_OPTIONS} as initial value.
105
+ # @param options [Hash] merged with current options
106
+ # @return [Hash] current options
107
+ def options options={}
108
+ @options ||= DEFAULT_OPTIONS
109
+ @options = @options.merge options
110
+ end
111
+
112
+ def site= site
113
+ raise RuntimeError, "Cannot redefine 'site' once populated" if populated
114
+ @site = site
115
+ end
116
+
117
+ def treeish= treeish
118
+ raise RuntimeError, "Cannot redefine 'treeish' once populated" if populated
119
+ raise TypeError unless treeish.is_a? String
120
+ @treeish = treeish
121
+ end
122
+
123
+ def directory
124
+ raise RuntimeError if site.nil?
125
+ if @directory.nil?
126
+ @directory = Palimpsest::Utility.make_random_directory options[:tmp_dir], "#{options[:dir_prefix]}#{site.name}_"
127
+ else
128
+ @directory
129
+ end
130
+ end
131
+
132
+ # Removes the environment's working directory.
133
+ def cleanup
134
+ FileUtils.remove_entry_secure directory if @directory
135
+ @directory = nil
136
+ @populated = false
137
+ return self
138
+ end
139
+
140
+ # Extracts the site's files from repository to the working directory.
141
+ def populate from: :repo
142
+ cleanup if populated
143
+ raise RuntimeError, "Cannot populate without 'site'" if site.nil?
144
+
145
+ case from
146
+ when :repo
147
+ raise RuntimeError, "Cannot populate without 'treeish'" if treeish.empty?
148
+ Palimpsest::Utility.extract_repo site.repo, treeish, directory
149
+ when :source
150
+ FileUtils.cp_r Dir["#{site.source}/*"], directory
151
+ end
152
+
153
+ @populated = true
154
+ return self
155
+ end
156
+
157
+ # @return [Hash] configuration loaded from {#options}`[:config_file]` under {#directory}
158
+ def config
159
+ populate unless populated
160
+ @config = YAML.load_file "#{directory}/#{options[:config_file]}"
161
+ validate_config if @config
162
+ end
163
+
164
+ # @return [Array<Palimpsest::Assets>] assets with settings and paths loaded from config
165
+ def assets
166
+ @assets = []
167
+
168
+ config[:assets].each do |type, opt|
169
+ next if [ :sources ].include? type
170
+ next if opt[:paths].nil?
171
+
172
+ assets = Palimpsest::Assets.new directory: directory, paths: opt[:paths]
173
+ assets.options config[:assets][:options] unless config[:assets][:options].nil?
174
+ assets.options opt[:options] unless opt[:options].nil?
175
+ assets.type = type
176
+ @assets << assets
177
+ end unless config[:assets].nil?
178
+
179
+ @assets
180
+ end
181
+
182
+ # @return [Array] all source files with asset tags
183
+ def sources_with_assets
184
+ return [] if config[:assets].nil?
185
+ return [] if config[:assets][:sources].nil?
186
+
187
+ @sources_with_assets = []
188
+
189
+ opts = {}
190
+ [ :src_pre, :src_post ].each do |opt|
191
+ opts[opt] = config[:assets][:options][opt] unless config[:assets][:options][opt].nil?
192
+ end unless config[:assets][:options].nil?
193
+
194
+ config[:assets][:sources].each do |path|
195
+ @sources_with_assets << Palimpsest::Assets.find_tags("#{directory}/#{path}", nil, opts)
196
+ end
197
+
198
+ @sources_with_assets.flatten
199
+ end
200
+
201
+ # Finds all assets in {#sources_with_assets} and
202
+ # generates the assets and updates the sources.
203
+ def compile_assets
204
+ sources_with_assets.each do |file|
205
+ source = File.read file
206
+ assets.each { |a| a.update_source! source }
207
+ Palimpsest::Utility.write source, file
208
+ end
209
+ return self
210
+ end
211
+
212
+ private
213
+
214
+ # Checks the config file for invalid settings.
215
+ #
216
+ # - Checks that paths are not absolute or use `../` or `~/`.
217
+ def validate_config
218
+ message = 'bad path in config'
219
+
220
+ def safe_path?(path) Palimpsest::Utility.safe_path?(path) end
221
+
222
+ # Checks the option in the asset key.
223
+ def validate_asset_options opts
224
+ 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)
227
+ end
228
+ end
229
+
230
+ @config[:assets].each do |k, v|
231
+
232
+ # process @config[:assets][:options] then go to the next option
233
+ if k == :options
234
+ validate_asset_options v
235
+ next
236
+ end unless v.nil?
237
+
238
+ # process @config[:assets][:sources] then go to the next option
239
+ if k == :sources
240
+ v.each_with_index do |source, i|
241
+ raise RuntimeError, message unless safe_path? source
242
+ end
243
+ next
244
+ end
245
+
246
+ # process each asset type in @config[:assets]
247
+ v.each do |asset_key, asset_value|
248
+ # process :options
249
+ if asset_key == :options
250
+ validate_asset_options asset_value
251
+ next
252
+ end unless asset_value.nil?
253
+
254
+ # process each asset path
255
+ asset_value.each_with_index do |path, i|
256
+ raise RuntimeError, message unless safe_path? path
257
+ end
258
+ end
259
+ end unless @config[:assets].nil?
260
+ @config
261
+ end
262
+ end
263
+ end