marv 0.2.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.
- data/.document +5 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +140 -0
- data/LICENSE +20 -0
- data/README.md +49 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/bin/marv +12 -0
- data/features/step_definitions/marv_steps.rb +38 -0
- data/features/support/env.rb +17 -0
- data/layouts/config/config.tt +19 -0
- data/layouts/default/functions/functions.php.erb +34 -0
- data/layouts/default/javascripts/admin.js +1 -0
- data/layouts/default/javascripts/theme.js +1 -0
- data/layouts/default/stylesheets/_header.scss.erb +18 -0
- data/layouts/default/stylesheets/_reset.scss +5 -0
- data/layouts/default/stylesheets/_typography.scss +5 -0
- data/layouts/default/stylesheets/style.css.scss.erb +32 -0
- data/layouts/default/templates/404.php.erb +10 -0
- data/layouts/default/templates/archive.php.erb +11 -0
- data/layouts/default/templates/attachment.php.erb +32 -0
- data/layouts/default/templates/comments.php +2 -0
- data/layouts/default/templates/footer.php +9 -0
- data/layouts/default/templates/header.php.erb +30 -0
- data/layouts/default/templates/index.php +4 -0
- data/layouts/default/templates/page.php +14 -0
- data/layouts/default/templates/partials/loop.php.erb +36 -0
- data/layouts/default/templates/search.php.erb +12 -0
- data/layouts/default/templates/sidebar.php +9 -0
- data/layouts/default/templates/single.php.erb +30 -0
- data/lib/guard/marv/assets.rb +31 -0
- data/lib/guard/marv/config.rb +34 -0
- data/lib/guard/marv/functions.rb +36 -0
- data/lib/guard/marv/templates.rb +28 -0
- data/lib/marv.rb +11 -0
- data/lib/marv/builder.rb +282 -0
- data/lib/marv/cli.rb +86 -0
- data/lib/marv/config.rb +61 -0
- data/lib/marv/engines.rb +12 -0
- data/lib/marv/error.rb +8 -0
- data/lib/marv/generator.rb +138 -0
- data/lib/marv/guard.rb +65 -0
- data/lib/marv/project.rb +162 -0
- data/lib/marv/version.rb +3 -0
- data/marv.gemspec +145 -0
- data/spec/lib/marv/config_spec.rb +79 -0
- data/spec/lib/marv/project_spec.rb +34 -0
- data/spec/spec_helper.rb +13 -0
- metadata +404 -0
data/lib/marv/builder.rb
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'sprockets'
|
2
|
+
require 'sprockets-sass'
|
3
|
+
require 'sass'
|
4
|
+
require 'less'
|
5
|
+
require 'zip'
|
6
|
+
require 'marv/engines'
|
7
|
+
|
8
|
+
module Marv
|
9
|
+
class Builder
|
10
|
+
def initialize(project)
|
11
|
+
@project = project
|
12
|
+
@task = project.task
|
13
|
+
@templates_path = @project.templates_path
|
14
|
+
@assets_path = @project.assets_path
|
15
|
+
@functions_path = @project.functions_path
|
16
|
+
@includes_path = @project.includes_path
|
17
|
+
@extras_path = @project.extras_path
|
18
|
+
@package_path = @project.package_path
|
19
|
+
|
20
|
+
init_sprockets
|
21
|
+
end
|
22
|
+
|
23
|
+
# Runs all the methods necessary to build a completed project
|
24
|
+
def build
|
25
|
+
clean_build_directory
|
26
|
+
copy_templates
|
27
|
+
copy_functions
|
28
|
+
copy_includes
|
29
|
+
copy_extras
|
30
|
+
build_assets
|
31
|
+
end
|
32
|
+
|
33
|
+
# Use the rubyzip library to build a zip from the generated source
|
34
|
+
def zip(filename=nil)
|
35
|
+
filename = filename || File.basename(@project.root)
|
36
|
+
project_base = File.basename(@project.root)
|
37
|
+
|
38
|
+
zip_filename = File.join(File.basename(@package_path), "#{filename}.zip")
|
39
|
+
# Create a temporary file for RubyZip to write to
|
40
|
+
temp_filename = "#{zip_filename}.tmp"
|
41
|
+
|
42
|
+
File.delete(temp_filename) if File.exists?(temp_filename)
|
43
|
+
|
44
|
+
# Wrapping the zip creation in Thor's create_file to get "overwrite" prompts
|
45
|
+
# Note: I could be overcomplicating this
|
46
|
+
@task.create_file(zip_filename) do
|
47
|
+
Zip::File.open(temp_filename, Zip::File::CREATE) do |zip|
|
48
|
+
# Get all filenames in the build directory recursively
|
49
|
+
filenames = Dir[File.join(@project.build_path, '**', '*')]
|
50
|
+
|
51
|
+
# Remove the build directory path from the filename
|
52
|
+
filenames.collect! {|path| path.gsub(/#{@project.build_path}\//, '')}
|
53
|
+
|
54
|
+
# Add each file in the build directory to the zip file
|
55
|
+
filenames.each do |filename|
|
56
|
+
zip.add File.join(project_base, filename), File.join(@project.build_path, filename)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Give Thor contents of zip file for "overwrite" prompt
|
61
|
+
File.open(temp_filename, 'rb') { |f| f.read }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Clean up the temp file
|
65
|
+
File.delete(temp_filename)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Empty out the build directory
|
69
|
+
def clean_build_directory
|
70
|
+
FileUtils.rm_rf Dir.glob(File.join(@project.build_path, '*'))
|
71
|
+
end
|
72
|
+
|
73
|
+
def clean_templates
|
74
|
+
# TODO: cleaner way of removing templates only?
|
75
|
+
Dir.glob(File.join(@project.build_path, '*.php')).each do |path|
|
76
|
+
FileUtils.rm path unless path.include?('functions.php')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def copy_templates
|
81
|
+
template_paths.each do |template_path|
|
82
|
+
# Skip directories
|
83
|
+
next if File.directory?(template_path)
|
84
|
+
|
85
|
+
if template_path.end_with?('.erb')
|
86
|
+
# Chop the .erb extension off the filename
|
87
|
+
destination = File.join(@project.build_path, File.basename(template_path).slice(0..-5))
|
88
|
+
|
89
|
+
write_erb(template_path, destination)
|
90
|
+
else
|
91
|
+
# Regular old copy of PHP-only files
|
92
|
+
FileUtils.cp template_path, @project.build_path
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def clean_functions
|
98
|
+
FileUtils.rm File.join(@project.build_path, 'functions.php')
|
99
|
+
FileUtils.rm_rf File.join(@project.build_path, 'functions')
|
100
|
+
end
|
101
|
+
|
102
|
+
def copy_functions
|
103
|
+
functions_erb_path = File.join(@functions_path, 'functions.php.erb')
|
104
|
+
functions_php_path = File.join(@functions_path, 'functions.php')
|
105
|
+
|
106
|
+
if File.exists?(functions_erb_path)
|
107
|
+
destination = File.join(@project.build_path, 'functions.php')
|
108
|
+
write_erb(functions_erb_path, destination)
|
109
|
+
elsif File.exists?(functions_php_path)
|
110
|
+
FileUtils.cp functions_php_path, @project.build_path
|
111
|
+
end
|
112
|
+
|
113
|
+
functions_paths = Dir.glob(File.join(@functions_path, '*')).reject do |filename|
|
114
|
+
[functions_erb_path, functions_php_path].include?(filename)
|
115
|
+
end
|
116
|
+
|
117
|
+
unless functions_paths.empty?
|
118
|
+
# Create the includes folder in the build directory
|
119
|
+
FileUtils.mkdir_p(File.join(@project.build_path, 'functions'))
|
120
|
+
|
121
|
+
# Iterate over all files in source/functions, skipping the actual functions.php file
|
122
|
+
paths = Dir.glob(File.join(@functions_path, '**', '*')).reject {|filename| [functions_erb_path, functions_php_path].include?(filename) }
|
123
|
+
|
124
|
+
copy_paths_with_erb(paths, @functions_path, File.join(@project.build_path, 'functions'))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def clean_includes
|
129
|
+
FileUtils.rm_rf File.join(@project.build_path, 'includes')
|
130
|
+
end
|
131
|
+
|
132
|
+
def copy_includes
|
133
|
+
unless Dir.glob(File.join(@includes_path, '*')).empty?
|
134
|
+
# Create the includes folder in the build directory
|
135
|
+
FileUtils.mkdir(File.join(@project.build_path, 'includes'))
|
136
|
+
|
137
|
+
# Iterate over all files in source/includes, so we can exclude if necessary
|
138
|
+
paths = Dir.glob(File.join(@includes_path, '**', '*'))
|
139
|
+
copy_paths_with_erb(paths, @includes_path, File.join(@project.build_path, 'includes'))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def copy_extras
|
144
|
+
unless Dir.glob(File.join(@extras_path, '*')).empty?
|
145
|
+
|
146
|
+
# Iterate over all files in source/extras, so we can exclude if necessary
|
147
|
+
paths = Dir.glob(File.join(@extras_path, '**', '*'))
|
148
|
+
copy_paths_with_erb(paths, @extras_path, File.join(@project.build_path, ''))
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def clean_images
|
153
|
+
FileUtils.rm_rf File.join(@project.build_path, 'images')
|
154
|
+
end
|
155
|
+
|
156
|
+
def build_assets
|
157
|
+
[['style.css'], ['ie.css'], ['javascripts', 'theme.js'], ['javascripts', 'admin.js']].each do |asset|
|
158
|
+
destination = File.join(@project.build_path, asset)
|
159
|
+
|
160
|
+
sprocket = @sprockets.find_asset(asset.last)
|
161
|
+
|
162
|
+
# Catch any sprockets errors and continue the process
|
163
|
+
begin
|
164
|
+
@task.shell.mute do
|
165
|
+
FileUtils.mkdir_p(File.dirname(destination)) unless File.directory?(File.dirname(destination))
|
166
|
+
sprocket.write_to(destination) unless sprocket.nil?
|
167
|
+
|
168
|
+
if @project.config[:compress_js] && destination.end_with?('.js')
|
169
|
+
require "yui/compressor"
|
170
|
+
|
171
|
+
# Grab the initial sprockets output
|
172
|
+
sprockets_output = File.open(destination, 'r').read
|
173
|
+
|
174
|
+
# Re-write the file, minified
|
175
|
+
File.open(destination, 'w') do |file|
|
176
|
+
file.write(YUI::JavaScriptCompressor.new.compress(sprockets_output))
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
rescue Exception => e
|
181
|
+
@task.say "Error while building #{asset.last}:"
|
182
|
+
@task.say e.message, Thor::Shell::Color::RED
|
183
|
+
File.open(destination, 'w') do |file|
|
184
|
+
file.puts(e.message)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Re-initializing sprockets to prevent further errors
|
188
|
+
# TODO: This is done for lack of a better solution
|
189
|
+
init_sprockets
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Copy the images directory over
|
194
|
+
FileUtils.cp_r(File.join(@assets_path, 'images'), @project.build_path) if File.exists?(File.join(@assets_path, 'images'))
|
195
|
+
|
196
|
+
# Check for screenshot and move it into main build directory
|
197
|
+
Dir.glob(File.join(@project.build_path, 'images', '*')).each do |filename|
|
198
|
+
if filename.index(/screenshot\.(png|jpg|jpeg|gif)/)
|
199
|
+
FileUtils.mv(filename, @project.build_path + File::SEPARATOR )
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def copy_paths_with_erb(paths, source_dir, destination_dir)
|
207
|
+
paths.each do |path|
|
208
|
+
# Remove source directory from full file path to get the relative path
|
209
|
+
relative_path = path.gsub(source_dir, '')
|
210
|
+
|
211
|
+
destination = File.join(destination_dir, relative_path)
|
212
|
+
|
213
|
+
if destination.end_with?('.erb')
|
214
|
+
# Remove the .erb extension if the path was an erb file
|
215
|
+
destination = destination.slice(0..-5)
|
216
|
+
# And process it as an erb
|
217
|
+
write_erb(path, destination)
|
218
|
+
else
|
219
|
+
# Otherwise, we simply move the file over
|
220
|
+
FileUtils.mkdir_p(destination) if File.directory?(path)
|
221
|
+
FileUtils.cp path, destination unless File.directory?(path)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def init_sprockets
|
227
|
+
@sprockets = Sprockets::Environment.new
|
228
|
+
|
229
|
+
['javascripts', 'stylesheets', 'lib'].each do |dir|
|
230
|
+
@sprockets.append_path File.join(@assets_path, dir)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Add assets/styleshets to load path for Less Engine
|
234
|
+
Tilt::LessTemplateWithPaths.load_path = File.join(@assets_path, 'stylesheets')
|
235
|
+
|
236
|
+
@sprockets.register_engine '.less', Tilt::LessTemplateWithPaths
|
237
|
+
|
238
|
+
# Passing the @project instance variable to the Sprockets::Context instance
|
239
|
+
# used for processing the asset ERB files. Ruby meta-programming, FTW.
|
240
|
+
@sprockets.context_class.instance_exec(@project) do |project|
|
241
|
+
define_method :config do
|
242
|
+
project.config
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def template_paths
|
248
|
+
Dir.glob(File.join(@templates_path, '**', '*'))
|
249
|
+
end
|
250
|
+
|
251
|
+
# Generate a unique filename for the zip output
|
252
|
+
def get_output_filename(basename)
|
253
|
+
package_path_base = File.basename(@package_path)
|
254
|
+
filename = File.join(package_path_base, "#{basename}.zip")
|
255
|
+
|
256
|
+
i = 1
|
257
|
+
while File.exists?(filename)
|
258
|
+
filename = File.join(package_path_base, "#{basename}(#{i}).zip")
|
259
|
+
i += 1
|
260
|
+
end
|
261
|
+
|
262
|
+
filename
|
263
|
+
end
|
264
|
+
|
265
|
+
protected
|
266
|
+
|
267
|
+
# Write an .erb from source to destination, catching and reporting errors along the way
|
268
|
+
def write_erb(source, destination)
|
269
|
+
begin
|
270
|
+
@task.shell.mute do
|
271
|
+
@task.create_file(destination) do
|
272
|
+
@project.parse_erb(source)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
rescue Exception => e
|
276
|
+
@task.say "Error while building #{File.basename(source)}:"
|
277
|
+
@task.say e.message + "\n", Thor::Shell::Color::RED
|
278
|
+
exit
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
data/lib/marv/cli.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'yaml'
|
3
|
+
require 'guard/marv/assets'
|
4
|
+
require 'guard/marv/config'
|
5
|
+
require 'guard/marv/templates'
|
6
|
+
require 'guard/marv/functions'
|
7
|
+
|
8
|
+
module Marv
|
9
|
+
class CLI < Thor
|
10
|
+
include Thor::Actions
|
11
|
+
|
12
|
+
def self.source_root
|
13
|
+
File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'layouts'))
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "create DIRECTORY", "Creates a Marv project"
|
17
|
+
def create(dir)
|
18
|
+
theme = {}
|
19
|
+
theme[:name] = dir
|
20
|
+
|
21
|
+
project = Marv::Project.create(dir, theme, self)
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "link PATH", "Create a symbolic link to the compilation directory"
|
25
|
+
long_desc "This command will symlink the compiled version of the theme to the specified path.\n\n"+
|
26
|
+
"To compile the theme use the `marv watch` command"
|
27
|
+
def link(path)
|
28
|
+
project = Marv::Project.new('.', self)
|
29
|
+
|
30
|
+
FileUtils.mkdir_p project.build_path unless File.directory?(project.build_path)
|
31
|
+
|
32
|
+
do_link(project, path)
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "watch", "Start watch process"
|
36
|
+
long_desc "Watches the source directory in your project for changes, and reflects those changes in a compile folder"
|
37
|
+
method_option :config, :type => :string, :desc => "Name of alternate config file"
|
38
|
+
def watch
|
39
|
+
project = Marv::Project.new('.', self, nil, options[:config])
|
40
|
+
|
41
|
+
# Empty the build directory before starting up to clean out old files
|
42
|
+
FileUtils.rm_rf project.build_path
|
43
|
+
FileUtils.mkdir_p project.build_path
|
44
|
+
|
45
|
+
Marv::Guard.start(project, self)
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "build DIRECTORY", "Build your theme into specified directory"
|
49
|
+
method_option :config, :type => :string, :desc => "Name of alternate config file"
|
50
|
+
def build(dir='build')
|
51
|
+
project = Marv::Project.new('.', self, nil, options[:config])
|
52
|
+
|
53
|
+
builder = Builder.new(project)
|
54
|
+
builder.build
|
55
|
+
|
56
|
+
Dir.glob(File.join(dir, '**', '*')).each do |file|
|
57
|
+
shell.mute { remove_file(file) }
|
58
|
+
end
|
59
|
+
|
60
|
+
directory(project.build_path, dir)
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "package FILENAME", "Compile and zip your project to FILENAME.zip"
|
64
|
+
method_option :config, :type => :string, :desc => "Name of alternate config file"
|
65
|
+
def package(filename=nil)
|
66
|
+
project = Marv::Project.new('.', self, nil, options[:config])
|
67
|
+
|
68
|
+
builder = Builder.new(project)
|
69
|
+
builder.build
|
70
|
+
builder.zip(filename)
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
def do_link(project, path)
|
75
|
+
begin
|
76
|
+
project.link(path)
|
77
|
+
rescue LinkSourceDirNotFound
|
78
|
+
say_status :error, "The path #{File.dirname(path)} does not exist", :red
|
79
|
+
exit 2
|
80
|
+
rescue Errno::EEXIST
|
81
|
+
say_status :error, "The path #{path} already exsts", :red
|
82
|
+
exit 2
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/marv/config.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Marv
|
2
|
+
# Reads/Writes a configuration file in the user's home directory
|
3
|
+
#
|
4
|
+
class Config
|
5
|
+
|
6
|
+
@config
|
7
|
+
|
8
|
+
attr_accessor :config
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
@config = {
|
12
|
+
:theme => {
|
13
|
+
:author => nil,
|
14
|
+
:author_url => nil,
|
15
|
+
},
|
16
|
+
:links => []
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Provides access to the config using the Hash square brackets
|
21
|
+
def [](var)
|
22
|
+
@config[var]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Allows modifying variables through hash square brackets
|
26
|
+
def []=(var, value)
|
27
|
+
@config[var] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the path to the user's configuration file
|
31
|
+
def config_file
|
32
|
+
@config_file ||= File.expand_path(File.join('~', '.watch', 'config.yml'))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Writes the configuration file
|
36
|
+
def write(options={})
|
37
|
+
# If we're unit testing then it helps to use a
|
38
|
+
# StringIO object instead of a file buffer
|
39
|
+
io = options[:io] || File.open(self.config_file, 'w')
|
40
|
+
|
41
|
+
io.write JSON.generate(@config)
|
42
|
+
|
43
|
+
io.close
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Loads config declarations in user's home dir
|
49
|
+
#
|
50
|
+
# If file does not exist then it will be created
|
51
|
+
def read
|
52
|
+
return write unless File.exists?(self.config_file)
|
53
|
+
|
54
|
+
data = File.open(self.config_file).read
|
55
|
+
|
56
|
+
@config = data.empty? ? {} : JSON.parse(data)
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/marv/engines.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Tilt
|
2
|
+
class LessTemplateWithPaths < LessTemplate
|
3
|
+
class << self
|
4
|
+
attr_accessor :load_path
|
5
|
+
end
|
6
|
+
|
7
|
+
def prepare
|
8
|
+
parser = ::Less::Parser.new(:filename => eval_file, :line => line, :paths => [self.class.load_path])
|
9
|
+
@engine = parser.parse(data)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|