palimpsest 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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