├── .document ├── .gitignore ├── .gitmodules ├── .rspec ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── bin └── forge ├── features ├── step_definitions │ └── forge_steps.rb └── support │ └── env.rb ├── forge.gemspec ├── layouts ├── config │ └── config.tt └── default │ ├── functions │ └── functions.php.erb │ ├── javascripts │ ├── admin.js │ └── theme.js │ ├── stylesheets │ ├── _header.scss.erb │ ├── _reset.scss │ ├── _typography.scss │ └── style.css.scss.erb │ └── templates │ ├── 404.php.erb │ ├── archive.php.erb │ ├── attachment.php.erb │ ├── comments.php │ ├── footer.php │ ├── header.php.erb │ ├── index.php │ ├── page.php │ ├── partials │ └── loop.php.erb │ ├── search.php.erb │ ├── sidebar.php │ └── single.php.erb ├── lib ├── forge.rb ├── forge │ ├── builder.rb │ ├── cli.rb │ ├── config.rb │ ├── engines.rb │ ├── error.rb │ ├── generator.rb │ ├── guard.rb │ ├── project.rb │ └── version.rb └── guard │ └── forge │ ├── assets.rb │ ├── config.rb │ ├── functions.rb │ └── templates.rb └── spec ├── lib └── forge │ ├── config_spec.rb │ └── project_spec.rb └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # for aruba testing 2 | tmp 3 | 4 | # rcov generated 5 | coverage 6 | 7 | # rdoc generated 8 | rdoc 9 | 10 | # yard generated 11 | doc 12 | .yardoc 13 | 14 | # bundler 15 | .bundle 16 | 17 | # jeweler generated 18 | pkg 19 | 20 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 21 | # 22 | # * Create a file at ~/.gitignore 23 | # * Include files you want ignored 24 | # * Run: git config --global core.excludesfile ~/.gitignore 25 | # 26 | # After doing this, these files will be ignored in all your git projects, 27 | # saving you from having to 'pollute' every project you touch with them 28 | # 29 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 30 | # 31 | # For MacOS: 32 | # 33 | #.DS_Store 34 | 35 | # For TextMate 36 | #*.tmproj 37 | #tmtags 38 | 39 | # For emacs: 40 | #*~ 41 | #\#* 42 | #.\#* 43 | 44 | # For vim: 45 | #*.swp 46 | 47 | # For redcar: 48 | #.redcar 49 | 50 | # For rubinius: 51 | #*.rbc 52 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "layouts/lib/struts"] 2 | path = layouts/lib/struts 3 | url = git@github.com:jestro/struts.git 4 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | == 0.4.0 == 2 | - Added lib to assets folder on generation 3 | - Allow multiple functions files in the source/functions folder, including subfolders 4 | 5 | == 0.3.0 == 6 | - Changed configuration over to Ruby from JSON 7 | - Fixed some bugs that caused forge watch to crash 8 | 9 | == 0.2.0 == 10 | - Added preliminary LESS support 11 | - Added ERB processing on templates, functions and assets with filenames that end with .erb 12 | - Adding --config=filename flag for specifying an alternate config file 13 | - Scaffolding template cleanup 14 | 15 | == 0.1.3 == 16 | - Fixed bug where forge watch crashed when a file was at the root of the includes folder 17 | 18 | == 0.1.2 == 19 | - "build" and "package" commands were not copying includes 20 | - Removing hidden files from includes folder, if they exist 21 | - Updating rack gem - silences the warning it threw in 1.3.4 22 | 23 | == 0.1.1 == 24 | - LiveReload support 25 | - Option for "template" in config, for child themes 26 | - Additions and corrections to config template -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "thor" 4 | gem 'guard' 5 | gem "sprockets" 6 | gem "rubyzip" 7 | gem "json" 8 | gem "sass" 9 | gem "sprockets-sass" 10 | gem "compass" 11 | gem 'rack' 12 | gem "less" 13 | gem "rb-fsevent" 14 | gem "yui-compressor" 15 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | chunky_png (1.4.0) 5 | coderay (1.1.3) 6 | commonjs (0.2.7) 7 | compass (0.12.2) 8 | chunky_png (~> 1.2) 9 | fssm (>= 0.2.7) 10 | sass (~> 3.1) 11 | ffi (1.14.2) 12 | formatador (0.2.5) 13 | fssm (0.2.10) 14 | guard (2.16.2) 15 | formatador (>= 0.2.4) 16 | listen (>= 2.7, < 4.0) 17 | lumberjack (>= 1.0.12, < 2.0) 18 | nenv (~> 0.1) 19 | notiffany (~> 0.0) 20 | pry (>= 0.9.12) 21 | shellany (~> 0.0) 22 | thor (>= 0.18.1) 23 | hike (1.2.3) 24 | json (2.5.1) 25 | less (2.6.0) 26 | commonjs (~> 0.2.7) 27 | listen (3.4.0) 28 | rb-fsevent (~> 0.10, >= 0.10.3) 29 | rb-inotify (~> 0.9, >= 0.9.10) 30 | lumberjack (1.2.8) 31 | method_source (1.0.0) 32 | multi_json (1.15.0) 33 | nenv (0.3.0) 34 | notiffany (0.1.3) 35 | nenv (~> 0.1) 36 | shellany (~> 0.0) 37 | pry (0.13.1) 38 | coderay (~> 1.1) 39 | method_source (~> 1.0) 40 | rack (1.6.13) 41 | rb-fsevent (0.10.4) 42 | rb-inotify (0.10.1) 43 | ffi (~> 1.0) 44 | rubyzip (2.3.0) 45 | sass (3.7.4) 46 | sass-listen (~> 4.0.0) 47 | sass-listen (4.0.0) 48 | rb-fsevent (~> 0.9, >= 0.9.4) 49 | rb-inotify (~> 0.9, >= 0.9.7) 50 | shellany (0.0.1) 51 | sprockets (2.12.5) 52 | hike (~> 1.2) 53 | multi_json (~> 1.0) 54 | rack (~> 1.0) 55 | tilt (~> 1.1, != 1.3.0) 56 | sprockets-sass (1.3.1) 57 | sprockets (~> 2.0) 58 | tilt (~> 1.1) 59 | thor (1.0.1) 60 | tilt (1.4.1) 61 | yui-compressor (0.12.0) 62 | 63 | PLATFORMS 64 | x86_64-darwin-20 65 | 66 | DEPENDENCIES 67 | compass 68 | guard 69 | json 70 | less 71 | rack 72 | rb-fsevent 73 | rubyzip 74 | sass 75 | sprockets 76 | sprockets-sass 77 | thor 78 | yui-compressor 79 | 80 | BUNDLED WITH 81 | 2.2.4 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jestro LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Forge is a toolkit for bootstrapping and developing WordPress themes. 2 | 3 | [Forge website](http://forge.thethemefoundry.com/) 4 | 5 | [User's manual](http://forge.thethemefoundry.com/manual) 6 | 7 | ----- 8 | 9 | Current Version: **0.5.0** 10 | 11 | Install Forge (requires [Ruby](http://www.ruby-lang.org/) and [RubyGems](http://rubygems.org/)): 12 | 13 | $ gem install forge 14 | 15 | Create your new theme project: 16 | 17 | $ forge create your_theme 18 | 19 | Change to your new project directory: 20 | 21 | $ cd your_theme 22 | 23 | Link to your WordPress theme folder: 24 | 25 | $ forge link /path/to/wordpress/wp-content/themes/your_theme 26 | 27 | Watch for changes and start developing! 28 | 29 | $ forge watch 30 | 31 | Press Ctrl + C to exit watch mode 32 | 33 | Build your theme into the build_here directory: 34 | 35 | $ forge build build_here 36 | 37 | Package your theme as your_theme.zip: 38 | 39 | $ forge package your_theme 40 | 41 | Get a little help with the Forge commands: 42 | 43 | $ forge help 44 | 45 | See the [user's manual](http://forge.thethemefoundry.com/manual) for more information. 46 | 47 | ----- 48 | 49 | **Note for upgrading existing projects to 0.5.0:** 50 | 51 | As of version 0.5.0, Forge no longer generates the header of your **style.css** file based on the values in `config.rb`. 52 | 53 | Instead, you need to write your own header and include it in **style.css.scss** yourself to generate a valid theme stylesheet. 54 | 55 | If you have any questions on migrating, open a new issue and we'll help you sort it out! -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 17 | gem.name = "forge" 18 | gem.homepage = "http://forge.thethemefoundry.com" 19 | gem.license = "MIT" 20 | gem.summary = %Q{A tool for developing wordpress themes} 21 | gem.description = %Q{A toolkit for bootstrapping and developing WordPress themes.} 22 | gem.email = "aadams@jestro.com" 23 | gem.authors = ["Andy Adams", "Drew Strojny", "Matt Button"] 24 | # dependencies defined in Gemfile 25 | gem.files.include Dir.glob('**/*') 26 | end 27 | Jeweler::RubygemsDotOrgTasks.new 28 | 29 | require 'rspec/core' 30 | require 'rspec/core/rake_task' 31 | RSpec::Core::RakeTask.new(:spec) do |spec| 32 | spec.pattern = FileList['spec/**/*_spec.rb'] 33 | end 34 | 35 | RSpec::Core::RakeTask.new(:rcov) do |spec| 36 | spec.pattern = 'spec/**/*_spec.rb' 37 | spec.rcov = true 38 | end 39 | 40 | require 'cucumber/rake/task' 41 | Cucumber::Rake::Task.new(:features) 42 | 43 | task :default => :spec 44 | 45 | require 'rdoc/task' 46 | RDoc::Task.new do |rdoc| 47 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 48 | 49 | rdoc.rdoc_dir = 'rdoc' 50 | rdoc.title = "forge #{version}" 51 | rdoc.rdoc_files.include('README*') 52 | rdoc.rdoc_files.include('lib/**/*.rb') 53 | end 54 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.5.0 -------------------------------------------------------------------------------- /bin/forge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'pathname' 4 | require 'rubygems' 5 | 6 | file_path = Pathname.new(__FILE__).realpath 7 | libdir = File.join(File.dirname(File.dirname(file_path)), "lib") 8 | $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) 9 | 10 | require 'forge' 11 | 12 | Forge::CLI.start -------------------------------------------------------------------------------- /features/step_definitions/forge_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^I am in a forge project named "([^"]*)"$/ do |name| 2 | cli = Forge::CLI.new 3 | 4 | cli.shell.mute do 5 | Forge::Project.create(File.join(current_dir, name), {:name => name}, cli) 6 | end 7 | 8 | cd name 9 | end 10 | 11 | Given /^a WordPress installation exists at "([^"]*)"$/ do |directory| 12 | create_dir directory 13 | end 14 | 15 | Then /^the file "([^"]*)" should contain:$/ do |filename, content_partials| 16 | content_partials.raw.each do |content| 17 | check_file_content(filename, content[0], true) 18 | end 19 | end 20 | 21 | Then /^the forge skeleton should be created in directory "([^"]*)"$/ do |base_dir| 22 | skeleton_dirs = [ 23 | ['assets', 'images'], 24 | ['assets', 'javascripts'], 25 | ['assets', 'stylesheets'], 26 | 27 | ['functions'], 28 | 29 | ['templates', 'core'], 30 | ['templates', 'custom', 'pages'], 31 | ['templates', 'custom', 'partials'] 32 | ] 33 | 34 | skeleton_dirs.each do |skeleton_dir| 35 | full_path = File.join(base_dir, skeleton_dir) 36 | check_directory_presence([full_path], true) 37 | end 38 | end -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | begin 3 | Bundler.setup(:default, :development) 4 | rescue Bundler::BundlerError => e 5 | $stderr.puts e.message 6 | $stderr.puts "Run `bundle install` to install missing gems" 7 | exit e.status_code 8 | end 9 | 10 | file_path = Pathname.new(__FILE__).realpath 11 | libdir = File.join(File.dirname(File.dirname(File.dirname(file_path))), "lib") 12 | $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) 13 | 14 | require 'forge' 15 | 16 | require 'rspec/expectations' 17 | require 'aruba/cucumber' -------------------------------------------------------------------------------- /forge.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "forge" 8 | s.version = "0.5.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Andy Adams", "Drew Strojny", "Matt Button"] 12 | s.date = "2012-10-02" 13 | s.description = "A toolkit for bootstrapping and developing WordPress themes." 14 | s.email = "aadams@jestro.com" 15 | s.executables = ["forge"] 16 | s.extra_rdoc_files = [ 17 | "LICENSE", 18 | "README.md" 19 | ] 20 | s.files = [ 21 | ".document", 22 | ".gitmodules", 23 | ".rspec", 24 | "CHANGELOG.md", 25 | "Gemfile", 26 | "Gemfile.lock", 27 | "LICENSE", 28 | "README.md", 29 | "Rakefile", 30 | "VERSION", 31 | "bin/forge", 32 | "features/step_definitions/forge_steps.rb", 33 | "features/support/env.rb", 34 | "forge.gemspec", 35 | "layouts/config/config.tt", 36 | "layouts/default/functions/functions.php.erb", 37 | "layouts/default/javascripts/admin.js", 38 | "layouts/default/javascripts/theme.js", 39 | "layouts/default/stylesheets/_header.scss.erb", 40 | "layouts/default/stylesheets/_reset.scss", 41 | "layouts/default/stylesheets/_typography.scss", 42 | "layouts/default/stylesheets/style.css.scss.erb", 43 | "layouts/default/templates/404.php.erb", 44 | "layouts/default/templates/archive.php.erb", 45 | "layouts/default/templates/attachment.php.erb", 46 | "layouts/default/templates/comments.php", 47 | "layouts/default/templates/footer.php", 48 | "layouts/default/templates/header.php.erb", 49 | "layouts/default/templates/index.php", 50 | "layouts/default/templates/page.php", 51 | "layouts/default/templates/partials/loop.php.erb", 52 | "layouts/default/templates/search.php.erb", 53 | "layouts/default/templates/sidebar.php", 54 | "layouts/default/templates/single.php.erb", 55 | "lib/forge.rb", 56 | "lib/forge/builder.rb", 57 | "lib/forge/cli.rb", 58 | "lib/forge/config.rb", 59 | "lib/forge/engines.rb", 60 | "lib/forge/error.rb", 61 | "lib/forge/generator.rb", 62 | "lib/forge/guard.rb", 63 | "lib/forge/project.rb", 64 | "lib/forge/version.rb", 65 | "lib/guard/forge/assets.rb", 66 | "lib/guard/forge/config.rb", 67 | "lib/guard/forge/functions.rb", 68 | "lib/guard/forge/templates.rb", 69 | "spec/lib/forge/config_spec.rb", 70 | "spec/lib/forge/project_spec.rb", 71 | "spec/spec_helper.rb" 72 | ] 73 | s.homepage = "http://forge.thethemefoundry.com" 74 | s.licenses = ["MIT"] 75 | s.require_paths = ["lib"] 76 | s.rubygems_version = "1.8.10" 77 | s.summary = "A tool for developing wordpress themes" 78 | 79 | if s.respond_to? :specification_version then 80 | s.specification_version = 3 81 | 82 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 83 | s.add_runtime_dependency(%q) 84 | s.add_runtime_dependency(%q) 85 | s.add_runtime_dependency(%q) 86 | s.add_runtime_dependency(%q) 87 | s.add_runtime_dependency(%q) 88 | s.add_runtime_dependency(%q) 89 | s.add_runtime_dependency(%q) 90 | s.add_runtime_dependency(%q) 91 | s.add_runtime_dependency(%q) 92 | s.add_runtime_dependency(%q) 93 | s.add_runtime_dependency(%q) 94 | s.add_runtime_dependency(%q) 95 | s.add_runtime_dependency(%q) 96 | s.add_runtime_dependency(%q) 97 | s.add_development_dependency(%q) 98 | s.add_development_dependency(%q) 99 | s.add_development_dependency(%q) 100 | s.add_development_dependency(%q) 101 | s.add_development_dependency(%q) 102 | else 103 | s.add_dependency(%q) 104 | s.add_dependency(%q) 105 | s.add_dependency(%q) 106 | s.add_dependency(%q) 107 | s.add_dependency(%q) 108 | s.add_dependency(%q) 109 | s.add_dependency(%q) 110 | s.add_dependency(%q) 111 | s.add_dependency(%q) 112 | s.add_dependency(%q) 113 | s.add_dependency(%q) 114 | s.add_dependency(%q) 115 | s.add_dependency(%q) 116 | s.add_dependency(%q) 117 | s.add_dependency(%q) 118 | s.add_dependency(%q) 119 | s.add_dependency(%q) 120 | s.add_dependency(%q) 121 | s.add_dependency(%q) 122 | end 123 | else 124 | s.add_dependency(%q) 125 | s.add_dependency(%q) 126 | s.add_dependency(%q) 127 | s.add_dependency(%q) 128 | s.add_dependency(%q) 129 | s.add_dependency(%q) 130 | s.add_dependency(%q) 131 | s.add_dependency(%q) 132 | s.add_dependency(%q) 133 | s.add_dependency(%q) 134 | s.add_dependency(%q) 135 | s.add_dependency(%q) 136 | s.add_dependency(%q) 137 | s.add_dependency(%q) 138 | s.add_dependency(%q) 139 | s.add_dependency(%q) 140 | s.add_dependency(%q) 141 | s.add_dependency(%q) 142 | s.add_dependency(%q) 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /layouts/config/config.tt: -------------------------------------------------------------------------------- 1 | # WordPress theme information is stored in source/assets/stylesheets/_header.scss as of Forge version 0.5 2 | 3 | # JavaScript compression 4 | # config[:compress_js] = false 5 | 6 | # Enable livereload 7 | # config[:livereload] = false 8 | 9 | # Compass configuration can also go here. 10 | # See http://compass-style.org/help/tutorials/configuration-reference/ for some of the options 11 | # Note: Most options (especially path-related options) will have no effect on Forge 12 | 13 | # Compass.configuration do |compass| 14 | # compass.line_comments = true 15 | # compass.output_style = :expanded # use :compressed for minified version 16 | # end 17 | 18 | # You can also include additional Compass frameworks by requiring them: 19 | # require 'stitch' -------------------------------------------------------------------------------- /layouts/default/functions/functions.php.erb: -------------------------------------------------------------------------------- 1 | _enqueue_scripts' ); 4 | 5 | if ( ! function_exists( '<%= theme_id %>_enqueue_scripts' ) ) : 6 | 7 | /** 8 | * Add theme styles and scripts here 9 | */ 10 | function <%= theme_id %>_enqueue_scripts() { 11 | 12 | if ( ! is_admin() ) { 13 | wp_enqueue_style( 14 | '<%= theme_id %>-style', 15 | get_bloginfo( 'stylesheet_url' ) 16 | ); 17 | } 18 | 19 | } 20 | 21 | endif; // <%= theme_id %>_enqueue_scripts 22 | 23 | add_action( 'after_setup_theme', '<%= theme_id %>_setup' ); 24 | 25 | if ( ! function_exists( '<%= theme_id %>_setup' ) ) : 26 | 27 | /** 28 | * Set up your theme here 29 | */ 30 | function <%= theme_id %>_setup() { 31 | add_theme_support( 'post-thumbnails' ); 32 | } 33 | 34 | endif; // <%= theme_id %>_setup 35 | -------------------------------------------------------------------------------- /layouts/default/javascripts/admin.js: -------------------------------------------------------------------------------- 1 | // Theme Options JavaScript goes here -------------------------------------------------------------------------------- /layouts/default/javascripts/theme.js: -------------------------------------------------------------------------------- 1 | // Regular theme JavaScript goes here -------------------------------------------------------------------------------- /layouts/default/stylesheets/_header.scss.erb: -------------------------------------------------------------------------------- 1 | /** 2 | * Theme Name: <%= config[:name] %> 3 | * Theme URI: <%= config[:uri] %> 4 | * Author: <%= config[:author] %> 5 | * Author URI: <%= config[:author_uri] %> 6 | * Description: <%= config[:description] if config[:description] %> 7 | * Version: <%= config[:version_number] if config[:version_number] %> 8 | <% if config[:template] -%> 9 | * Template: <%= config[:template] %> 10 | <% end -%> 11 | * License: <%= config[:license_name] if config[:license_name] %> 12 | * License URI: <%= config[:license_uri] if config[:license_uri] %> 13 | * Tags: <%= config[:tags].join(", ") if config[:tags] %> 14 | <%- unless config[:comments].nil? || config[:comments].empty? -%> 15 | * 16 | * <%= config[:comments] %> 17 | <%- end -%> 18 | */ 19 | -------------------------------------------------------------------------------- /layouts/default/stylesheets/_reset.scss: -------------------------------------------------------------------------------- 1 | /* _reset.scss 2 | * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ 3 | 4 | @import "blueprint/reset"; 5 | @include blueprint-global-reset; -------------------------------------------------------------------------------- /layouts/default/stylesheets/_typography.scss: -------------------------------------------------------------------------------- 1 | /* _typography.scss 2 | * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ 3 | 4 | @import "blueprint/typography"; 5 | @include blueprint-typography; -------------------------------------------------------------------------------- /layouts/default/stylesheets/style.css.scss.erb: -------------------------------------------------------------------------------- 1 | // WordPress theme header 2 | @import "header"; 3 | 4 | // This is your master stylesheet 5 | 6 | // Use SASS to include CSS partials 7 | @import "reset"; 8 | @import "typography"; 9 | @import "blueprint/grid"; 10 | 11 | // Add some basic styling 12 | .clear { 13 | clear: both; 14 | } 15 | 16 | #container { 17 | @include container; 18 | @include prepend(1); 19 | border-left: 1px solid; 20 | border-right: 1px solid; 21 | } 22 | 23 | #content { 24 | @include column(15); 25 | @include border(#555, 2px); 26 | } 27 | 28 | #sidebar { 29 | @include column(7); 30 | @include prepend(1); 31 | @include last; 32 | } -------------------------------------------------------------------------------- /layouts/default/templates/404.php.erb: -------------------------------------------------------------------------------- 1 | 2 |

3 | ' ); ?> 4 |

5 |
6 |

' ); ?>

7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /layouts/default/templates/archive.php.erb: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

' ); ?>

5 | 6 | 7 |

' ); ?>

8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /layouts/default/templates/attachment.php.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 31 | 32 | -------------------------------------------------------------------------------- /layouts/default/templates/comments.php: -------------------------------------------------------------------------------- 1 | 2 |

3 | Proudly powered by WordPress 4 |

5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /layouts/default/templates/header.php.erb: -------------------------------------------------------------------------------- 1 | 2 | > 3 | 4 | 5 | <?php wp_title(); ?> 6 | 7 | 8 | 9 | 10 | 11 | > 12 |
13 | 31 | -------------------------------------------------------------------------------- /layouts/default/templates/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /layouts/default/templates/page.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 |
8 | 9 |

10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /layouts/default/templates/partials/loop.php.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 |
5 |

6 | 7 | 8 | 9 |

10 |

' ) ); ?>

11 |

12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | ' )); ?> 20 | ' ), '

', '

' ); ?> 21 |
22 | 25 |
26 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /layouts/default/templates/search.php.erb: -------------------------------------------------------------------------------- 1 | 2 |

" ), get_search_query() ); ?>

3 | 4 | 5 | 6 |
7 |

' ), get_search_query());?>

8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /layouts/default/templates/sidebar.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /layouts/default/templates/single.php.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
> 5 |

6 |
7 | ' ), get_the_author() ); ?> 8 |
9 |
10 | 13 | 14 | ' ), '

', '

' ); ?> 15 | 16 |
17 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /lib/forge.rb: -------------------------------------------------------------------------------- 1 | require 'forge/error' 2 | 3 | module Forge 4 | ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 5 | autoload :Guard, 'forge/guard' 6 | autoload :CLI, 'forge/cli' 7 | autoload :Project, 'forge/project' 8 | autoload :Builder, 'forge/builder' 9 | autoload :Generator, 'forge/generator' 10 | autoload :Config, 'forge/config' 11 | end -------------------------------------------------------------------------------- /lib/forge/builder.rb: -------------------------------------------------------------------------------- 1 | require 'sprockets' 2 | require 'sprockets-sass' 3 | require 'sass' 4 | require 'less' 5 | require 'zip' 6 | require 'forge/engines' 7 | 8 | module Forge 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 | @package_path = @project.package_path 18 | 19 | init_sprockets 20 | end 21 | 22 | # Runs all the methods necessary to build a completed project 23 | def build 24 | clean_build_directory 25 | copy_templates 26 | copy_functions 27 | copy_includes 28 | build_assets 29 | end 30 | 31 | # Use the rubyzip library to build a zip from the generated source 32 | def zip(filename=nil) 33 | filename = filename || File.basename(@project.root) 34 | project_base = File.basename(@project.root) 35 | 36 | zip_filename = File.join(File.basename(@package_path), "#{filename}.zip") 37 | # Create a temporary file for RubyZip to write to 38 | temp_filename = "#{zip_filename}.tmp" 39 | 40 | File.delete(temp_filename) if File.exists?(temp_filename) 41 | 42 | # Wrapping the zip creation in Thor's create_file to get "overwrite" prompts 43 | # Note: I could be overcomplicating this 44 | @task.create_file(zip_filename) do 45 | Zip::ZipFile.open(temp_filename, Zip::ZipFile::CREATE) do |zip| 46 | # Get all filenames in the build directory recursively 47 | filenames = Dir[File.join(@project.build_path, '**', '*')] 48 | 49 | # Remove the build directory path from the filename 50 | filenames.collect! {|path| path.gsub(/#{@project.build_path}\//, '')} 51 | 52 | # Add each file in the build directory to the zip file 53 | filenames.each do |filename| 54 | zip.add File.join(project_base, filename), File.join(@project.build_path, filename) 55 | end 56 | end 57 | 58 | # Give Thor contents of zip file for "overwrite" prompt 59 | File.open(temp_filename, 'rb') { |f| f.read } 60 | end 61 | 62 | # Clean up the temp file 63 | File.delete(temp_filename) 64 | end 65 | 66 | # Empty out the build directory 67 | def clean_build_directory 68 | FileUtils.rm_rf Dir.glob(File.join(@project.build_path, '*')) 69 | end 70 | 71 | def clean_templates 72 | # TODO: cleaner way of removing templates only? 73 | Dir.glob(File.join(@project.build_path, '*.php')).each do |path| 74 | FileUtils.rm path unless path.include?('functions.php') 75 | end 76 | end 77 | 78 | def copy_templates 79 | template_paths.each do |template_path| 80 | # Skip directories 81 | next if File.directory?(template_path) 82 | 83 | if template_path.end_with?('.erb') 84 | # Chop the .erb extension off the filename 85 | destination = File.join(@project.build_path, File.basename(template_path).slice(0..-5)) 86 | 87 | write_erb(template_path, destination) 88 | else 89 | # Regular old copy of PHP-only files 90 | FileUtils.cp template_path, @project.build_path 91 | end 92 | end 93 | end 94 | 95 | def clean_functions 96 | FileUtils.rm File.join(@project.build_path, 'functions.php') 97 | FileUtils.rm_rf File.join(@project.build_path, 'functions') 98 | end 99 | 100 | def copy_functions 101 | functions_erb_path = File.join(@functions_path, 'functions.php.erb') 102 | functions_php_path = File.join(@functions_path, 'functions.php') 103 | 104 | if File.exists?(functions_erb_path) 105 | destination = File.join(@project.build_path, 'functions.php') 106 | write_erb(functions_erb_path, destination) 107 | elsif File.exists?(functions_php_path) 108 | FileUtils.cp functions_php_path, @project.build_path 109 | end 110 | 111 | functions_paths = Dir.glob(File.join(@functions_path, '*')).reject do |filename| 112 | [functions_erb_path, functions_php_path].include?(filename) 113 | end 114 | 115 | unless functions_paths.empty? 116 | # Create the includes folder in the build directory 117 | FileUtils.mkdir_p(File.join(@project.build_path, 'functions')) 118 | 119 | # Iterate over all files in source/functions, skipping the actual functions.php file 120 | paths = Dir.glob(File.join(@functions_path, '**', '*')).reject {|filename| [functions_erb_path, functions_php_path].include?(filename) } 121 | 122 | copy_paths_with_erb(paths, @functions_path, File.join(@project.build_path, 'functions')) 123 | end 124 | end 125 | 126 | def clean_includes 127 | FileUtils.rm_rf File.join(@project.build_path, 'includes') 128 | end 129 | 130 | def copy_includes 131 | unless Dir.glob(File.join(@includes_path, '*')).empty? 132 | # Create the includes folder in the build directory 133 | FileUtils.mkdir(File.join(@project.build_path, 'includes')) 134 | 135 | # Iterate over all files in source/includes, so we can exclude if necessary 136 | paths = Dir.glob(File.join(@includes_path, '**', '*')) 137 | copy_paths_with_erb(paths, @includes_path, File.join(@project.build_path, 'includes')) 138 | end 139 | end 140 | 141 | def clean_images 142 | FileUtils.rm_rf File.join(@project.build_path, 'images') 143 | end 144 | 145 | def build_assets 146 | [['style.css'], ['ie.css'], ['javascripts', 'theme.js'], ['javascripts', 'admin.js']].each do |asset| 147 | destination = File.join(@project.build_path, asset) 148 | 149 | sprocket = @sprockets.find_asset(asset.last) 150 | 151 | # Catch any sprockets errors and continue the process 152 | begin 153 | @task.shell.mute do 154 | FileUtils.mkdir_p(File.dirname(destination)) unless File.directory?(File.dirname(destination)) 155 | sprocket.write_to(destination) unless sprocket.nil? 156 | 157 | if @project.config[:compress_js] && destination.end_with?('.js') 158 | require "yui/compressor" 159 | 160 | # Grab the initial sprockets output 161 | sprockets_output = File.open(destination, 'r').read 162 | 163 | # Re-write the file, minified 164 | File.open(destination, 'w') do |file| 165 | file.write(YUI::JavaScriptCompressor.new.compress(sprockets_output)) 166 | end 167 | end 168 | end 169 | rescue Exception => e 170 | @task.say "Error while building #{asset.last}:" 171 | @task.say e.message, Thor::Shell::Color::RED 172 | File.open(destination, 'w') do |file| 173 | file.puts(e.message) 174 | end 175 | 176 | # Re-initializing sprockets to prevent further errors 177 | # TODO: This is done for lack of a better solution 178 | init_sprockets 179 | end 180 | end 181 | 182 | # Copy the images directory over 183 | FileUtils.cp_r(File.join(@assets_path, 'images'), @project.build_path) if File.exists?(File.join(@assets_path, 'images')) 184 | 185 | # Check for screenshot and move it into main build directory 186 | Dir.glob(File.join(@project.build_path, 'images', '*')).each do |filename| 187 | if filename.index(/screenshot\.(png|jpg|jpeg|gif)/) 188 | FileUtils.mv(filename, @project.build_path + File::SEPARATOR ) 189 | end 190 | end 191 | end 192 | 193 | private 194 | 195 | def copy_paths_with_erb(paths, source_dir, destination_dir) 196 | paths.each do |path| 197 | # Remove source directory from full file path to get the relative path 198 | relative_path = path.gsub(source_dir, '') 199 | 200 | destination = File.join(destination_dir, relative_path) 201 | 202 | if destination.end_with?('.erb') 203 | # Remove the .erb extension if the path was an erb file 204 | destination = destination.slice(0..-5) 205 | # And process it as an erb 206 | write_erb(path, destination) 207 | else 208 | # Otherwise, we simply move the file over 209 | FileUtils.mkdir_p(destination) if File.directory?(path) 210 | FileUtils.cp path, destination unless File.directory?(path) 211 | end 212 | end 213 | end 214 | 215 | def init_sprockets 216 | @sprockets = Sprockets::Environment.new 217 | 218 | ['javascripts', 'stylesheets', 'lib'].each do |dir| 219 | @sprockets.append_path File.join(@assets_path, dir) 220 | end 221 | 222 | # Add assets/styleshets to load path for Less Engine 223 | Tilt::LessTemplateWithPaths.load_path = File.join(@assets_path, 'stylesheets') 224 | 225 | @sprockets.register_engine '.less', Tilt::LessTemplateWithPaths 226 | 227 | # Passing the @project instance variable to the Sprockets::Context instance 228 | # used for processing the asset ERB files. Ruby meta-programming, FTW. 229 | @sprockets.context_class.instance_exec(@project) do |project| 230 | define_method :config do 231 | project.config 232 | end 233 | end 234 | end 235 | 236 | def template_paths 237 | Dir.glob(File.join(@templates_path, '**', '*')) 238 | end 239 | 240 | # Generate a unique filename for the zip output 241 | def get_output_filename(basename) 242 | package_path_base = File.basename(@package_path) 243 | filename = File.join(package_path_base, "#{basename}.zip") 244 | 245 | i = 1 246 | while File.exists?(filename) 247 | filename = File.join(package_path_base, "#{basename}(#{i}).zip") 248 | i += 1 249 | end 250 | 251 | filename 252 | end 253 | 254 | protected 255 | 256 | # Write an .erb from source to destination, catching and reporting errors along the way 257 | def write_erb(source, destination) 258 | begin 259 | @task.shell.mute do 260 | @task.create_file(destination) do 261 | @project.parse_erb(source) 262 | end 263 | end 264 | rescue Exception => e 265 | @task.say "Error while building #{File.basename(source)}:" 266 | @task.say e.message + "\n", Thor::Shell::Color::RED 267 | exit 268 | end 269 | end 270 | end 271 | end 272 | -------------------------------------------------------------------------------- /lib/forge/cli.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | require 'yaml' 3 | require 'guard/forge/assets' 4 | require 'guard/forge/config' 5 | require 'guard/forge/templates' 6 | require 'guard/forge/functions' 7 | 8 | module Forge 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 Forge project" 17 | def create(dir) 18 | theme = {} 19 | theme[:name] = dir 20 | 21 | project = Forge::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 `forge watch` command" 27 | def link(path) 28 | project = Forge::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 = Forge::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 | Forge::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 = Forge::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 = Forge::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 87 | -------------------------------------------------------------------------------- /lib/forge/config.rb: -------------------------------------------------------------------------------- 1 | module Forge 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('~', '.forge', '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 62 | -------------------------------------------------------------------------------- /lib/forge/engines.rb: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /lib/forge/error.rb: -------------------------------------------------------------------------------- 1 | module Forge 2 | class Error < StandardError 3 | end 4 | 5 | # Raised when the link source could not be found 6 | class LinkSourceDirNotFound < Error 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/forge/generator.rb: -------------------------------------------------------------------------------- 1 | 2 | module Forge 3 | class Generator 4 | class << self 5 | def run(project, layout='default') 6 | generator = self.new(project, layout) 7 | generator.run 8 | end 9 | end 10 | 11 | def initialize(project, layout='default') 12 | @project = project 13 | @task = project.task 14 | @layout = layout 15 | end 16 | 17 | def create_structure 18 | # Create the build directory for Forge output 19 | @task.empty_directory @project.build_path 20 | 21 | source_paths = [ 22 | ['assets', 'images'], 23 | ['assets', 'javascripts'], 24 | ['assets', 'stylesheets'], 25 | ['assets', 'lib'], 26 | 27 | ['functions'], 28 | 29 | ['includes'], 30 | 31 | ['templates', 'pages'], 32 | ['templates', 'partials'], 33 | ] 34 | 35 | # Build out Forge structure in the source directory 36 | source_paths.each do |path| 37 | @task.empty_directory File.join(@project.source_path, path) 38 | end 39 | 40 | self 41 | end 42 | 43 | def copy_stylesheets 44 | source = File.expand_path(File.join(self.layout_path, 'stylesheets')) 45 | target = File.expand_path(File.join(@project.assets_path, 'stylesheets')) 46 | 47 | render_directory(source, target) 48 | 49 | self 50 | end 51 | 52 | def copy_javascript 53 | source = File.expand_path(File.join(self.layout_path, 'javascripts')) 54 | target = File.expand_path(File.join(@project.assets_path, 'javascripts')) 55 | 56 | render_directory(source, target) 57 | 58 | self 59 | end 60 | 61 | def copy_templates 62 | source = File.expand_path(File.join(self.layout_path, 'templates')) 63 | target = File.expand_path(File.join(@project.source_path, 'templates')) 64 | 65 | render_directory(source, target) 66 | 67 | self 68 | end 69 | 70 | def copy_functions 71 | source = File.expand_path(File.join(self.layout_path, 'functions', 'functions.php.erb')) 72 | target = File.expand_path(File.join(@project.source_path, 'functions', 'functions.php')) 73 | 74 | write_template(source, target) 75 | end 76 | 77 | def layout_path 78 | @layout_path ||= File.join(Forge::ROOT, 'layouts', @layout) 79 | end 80 | 81 | def run 82 | write_config 83 | create_structure 84 | copy_stylesheets 85 | copy_javascript 86 | copy_templates 87 | copy_functions 88 | return self 89 | end 90 | 91 | def write_config 92 | unless File.exists?(@project.global_config_file) 93 | @task.shell.mute do 94 | @task.create_file(@project.global_config_file) do 95 | "# Place your global configuration values here\n# config[:livereload] = true" 96 | end 97 | end 98 | end 99 | 100 | write_template(['config', 'config.tt'], @project.config_file) 101 | 102 | self 103 | end 104 | 105 | def write_template(source, target) 106 | source = File.join(source) 107 | template = File.expand_path(@task.find_in_source_paths((source))) 108 | target = File.expand_path(File.join(target)) 109 | 110 | @task.create_file target do 111 | @project.parse_erb(template) 112 | end 113 | end 114 | 115 | protected 116 | def render_directory(source, target) 117 | Dir.glob("#{source}/**/*") do |file| 118 | unless File.directory?(file) 119 | source_file = file.gsub(source, '') 120 | target_file = File.join(target, source_file) 121 | 122 | if source_file.end_with? ".erb" 123 | target_file = target_file.slice(0..-5) 124 | 125 | content = @project.parse_erb(file) 126 | else 127 | content = File.open(file).read 128 | end 129 | 130 | @task.create_file target_file do 131 | content 132 | end 133 | end 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/forge/guard.rb: -------------------------------------------------------------------------------- 1 | require 'guard' 2 | require 'guard/compat/plugin' 3 | 4 | module Forge 5 | module Guard 6 | 7 | class << self 8 | attr_accessor :project, :task, :builder 9 | end 10 | 11 | def self.add_guard(&block) 12 | @additional_guards ||= [] 13 | @additional_guards << block 14 | end 15 | 16 | def self.start(project, task, options={}, livereload={}) 17 | @project = project 18 | @task = task 19 | @builder = Builder.new(project) 20 | 21 | options_hash = "" 22 | options.each do |k,v| 23 | options_hash << ", :#{k} => '#{v}'" 24 | end 25 | 26 | assets_path = @project.assets_path.gsub(/#{@project.root}\//, '') 27 | source_path = @project.source_path.gsub(/#{@project.root}\//, '') 28 | config_file = @project.config_file.gsub(/#{@project.root}\//, '') 29 | 30 | guardfile_contents = %Q{ 31 | guard 'forgeconfig'#{options_hash} do 32 | watch("#{config_file}") 33 | end 34 | guard 'forgeassets' do 35 | watch(%r{#{assets_path}/javascripts/*}) 36 | watch(%r{#{assets_path}/stylesheets/*}) 37 | watch(%r{#{assets_path}/images/*}) 38 | end 39 | guard 'forgetemplates' do 40 | watch(%r{#{source_path}/templates/*}) 41 | watch(%r{#{source_path}/partials/*}) 42 | end 43 | guard 'forgefunctions' do 44 | watch(%r{#{source_path}/functions/*}) 45 | watch(%r{#{source_path}/includes/*}) 46 | end 47 | } 48 | 49 | if @project.config[:livereload] 50 | guardfile_contents << %Q{ 51 | guard 'livereload' do 52 | watch(%r{#{source_path}/*}) 53 | end 54 | } 55 | end 56 | 57 | (@additional_guards || []).each do |block| 58 | result = block.call(options, livereload) 59 | guardfile_contents << result unless result.nil? 60 | end 61 | ::Guard.start({ :guardfile_contents => guardfile_contents }).join 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/forge/project.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'compass' 3 | 4 | module Forge 5 | class Project 6 | class << self 7 | def create(root, config, task) 8 | root = File.expand_path(root) 9 | 10 | project = self.new(root, task, config) 11 | Generator.run(project) 12 | 13 | project 14 | end 15 | end 16 | 17 | attr_accessor :root, :config, :task 18 | 19 | def initialize(root, task, config={}, config_file=nil) 20 | @root = File.expand_path(root) 21 | @config = config || {} 22 | @task = task 23 | @config_file = config_file 24 | 25 | self.load_config if @config.empty? 26 | end 27 | 28 | def assets_path 29 | @assets_path ||= File.join(self.source_path, 'assets') 30 | end 31 | 32 | def build_path 33 | File.join(self.root, '.forge', 'build') 34 | end 35 | 36 | def source_path 37 | File.join(self.root, 'source') 38 | end 39 | 40 | def package_path 41 | File.join(self.root, 'package') 42 | end 43 | 44 | def templates_path 45 | File.join(self.source_path, 'templates') 46 | end 47 | 48 | def functions_path 49 | File.join(self.source_path, 'functions') 50 | end 51 | 52 | def includes_path 53 | File.join(self.source_path, 'includes') 54 | end 55 | 56 | def config_file 57 | @config_file ||= File.join(self.root, 'config.rb') 58 | end 59 | 60 | def global_config_file 61 | @global_config_file ||= File.join(ENV['HOME'], '.forge', 'config.rb') 62 | end 63 | 64 | # Create a symlink from source to the project build dir 65 | def link(source) 66 | source = File.expand_path(source) 67 | 68 | unless File.directory?(File.dirname(source)) 69 | raise Forge::LinkSourceDirNotFound 70 | end 71 | 72 | @task.link_file build_path, source 73 | end 74 | 75 | def theme_id 76 | File.basename(self.root).gsub(/\W/, '_') 77 | end 78 | 79 | def load_config 80 | config = {} 81 | 82 | # Check for global (user) config.rb 83 | if File.exists?(self.global_config_file) 84 | config.merge!(load_ruby_config(self.global_config_file)) 85 | end 86 | 87 | # Check for config.rb 88 | if File.exists?(self.config_file) 89 | config.merge!(load_ruby_config(self.config_file)) 90 | else 91 | # Old format of config file 92 | if File.exists?(File.join(self.root, 'config.json')) 93 | config.merge!(convert_old_config) 94 | else 95 | raise Error, "Could not find the config file, are you sure you're in a 96 | forge project directory?" 97 | end 98 | end 99 | 100 | @config = config 101 | end 102 | 103 | def get_binding 104 | binding 105 | end 106 | 107 | def parse_erb(file) 108 | ERB.new(::File.binread(file), nil, '-', '@output_buffer').result(binding) 109 | end 110 | 111 | private 112 | 113 | def convert_old_config 114 | require 'json' 115 | 116 | # Let the user know what is going to happen 117 | @task.say("It looks like you are using the old JSON-format config. Forge will now try converting your config to the new Ruby format.") 118 | @task.ask(" Press any key to continue...") 119 | 120 | begin 121 | old_file_name = File.join(self.root, 'config.json') 122 | # Parse the old config format, convert keys to symbols 123 | @config = JSON.parse(File.open(old_file_name).read).inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo} 124 | 125 | @task.create_file(@config_file) do 126 | # Find the config.tt template, and parse it using ERB 127 | config_template_path = @task.find_in_source_paths(File.join(['config', 'config.tt'])) 128 | parse_erb(File.expand_path(config_template_path)) 129 | end 130 | rescue Exception => e 131 | @task.say "Error while building new config file:", Thor::Shell::Color::RED 132 | @task.say e.message, Thor::Shell::Color::RED 133 | @task.say "You'll need to either fix the error and try again, or manually convert your config.json file to Ruby format (config.rb)" 134 | exit 135 | end 136 | 137 | @task.say "Success! Double-check that all your config values were moved over, and you can now delete config.json.", Thor::Shell::Color::GREEN 138 | 139 | # We now have a Ruby config file, so we can continue loading as normal 140 | return load_ruby_config(self.config_file) 141 | end 142 | 143 | def load_ruby_config(file) 144 | config = {} 145 | 146 | begin 147 | # Config file is just executed as straight ruby 148 | eval(File.read(file)) 149 | rescue Exception => e 150 | @task.say "Error while evaluating config file:" 151 | @task.say e.message, Thor::Shell::Color::RED 152 | end 153 | 154 | return config 155 | end 156 | 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /lib/forge/version.rb: -------------------------------------------------------------------------------- 1 | module Forge 2 | VERSION = "0.1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/guard/forge/assets.rb: -------------------------------------------------------------------------------- 1 | require 'guard' 2 | require 'guard/compat/plugin' 3 | 4 | module Guard 5 | class ForgeAssets < Plugin 6 | 7 | def initialize(watchers=[], options={}) 8 | super 9 | end 10 | 11 | def start 12 | UI.info "Building all assets" 13 | ::Forge::Guard.builder.build_assets 14 | end 15 | 16 | # Called on Ctrl-\ signal 17 | # This method should be principally used for long action like running all specs/tests/... 18 | def run_all 19 | UI.info "Rebuilding all assets" 20 | ::Forge::Guard.builder.clean_images 21 | ::Forge::Guard.builder.build_assets 22 | end 23 | 24 | # Called on file(s) modifications 25 | def run_on_change(paths) 26 | UI.info "Assets have changed, rebuilding..." 27 | ::Forge::Guard.builder.clean_images 28 | ::Forge::Guard.builder.build_assets 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/guard/forge/config.rb: -------------------------------------------------------------------------------- 1 | require 'guard' 2 | require 'guard/compat/plugin' 3 | 4 | module Guard 5 | class ForgeConfig < Plugin 6 | def initialize(watchers=[], options={}) 7 | super 8 | end 9 | 10 | # Called on Ctrl-Z signal 11 | # This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/... 12 | def reload 13 | UI.info "Reloading project config" 14 | ::Forge::Guard.project.load_config 15 | end 16 | 17 | # Called on Ctrl-\ signal 18 | # This method should be principally used for long action like running all specs/tests/... 19 | def run_all 20 | UI.info "Reloading project config" 21 | ::Forge::Guard.project.load_config 22 | true 23 | end 24 | 25 | # Called on file(s) modifications 26 | def run_on_change(paths) 27 | UI.info "Project config changed, reloading" 28 | ::Forge::Guard.project.load_config 29 | ::Forge::Guard.builder = ::Forge::Builder.new(::Forge::Guard.project) 30 | # Rebuild everything if the config changes 31 | ::Forge::Guard.builder.build 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/guard/forge/functions.rb: -------------------------------------------------------------------------------- 1 | require 'guard' 2 | require 'guard/compat/plugin' 3 | 4 | module Guard 5 | class ForgeFunctions < Plugin 6 | def initialize(watchers=[], options={}) 7 | super 8 | end 9 | 10 | def start 11 | UI.info "Copying functions over" 12 | ::Forge::Guard.builder.copy_functions 13 | ::Forge::Guard.builder.copy_includes 14 | end 15 | 16 | def run_all 17 | UI.info "Rebuilding all functions" 18 | ::Forge::Guard.builder.clean_functions 19 | ::Forge::Guard.builder.copy_functions 20 | ::Forge::Guard.builder.clean_includes 21 | ::Forge::Guard.builder.copy_includes 22 | end 23 | 24 | # Called on file(s) modifications 25 | def run_on_change(paths) 26 | UI.info "Functions have changed, copying over" 27 | ::Forge::Guard.builder.clean_functions 28 | ::Forge::Guard.builder.copy_functions 29 | ::Forge::Guard.builder.clean_includes 30 | ::Forge::Guard.builder.copy_includes 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/guard/forge/templates.rb: -------------------------------------------------------------------------------- 1 | require 'guard' 2 | require 'guard/compat/plugin' 3 | 4 | module Guard 5 | class ForgeTemplates < Plugin 6 | def initialize(watchers=[], options={}) 7 | super 8 | end 9 | 10 | def start 11 | UI.info "Copying templates over" 12 | ::Forge::Guard.builder.copy_templates 13 | end 14 | 15 | def run_all 16 | UI.info "Rebuilding all templates" 17 | ::Forge::Guard.builder.clean_templates 18 | ::Forge::Guard.builder.copy_templates 19 | end 20 | 21 | # Called on file(s) modifications 22 | def run_on_change(paths) 23 | UI.info "Templates have changed, copying over" 24 | ::Forge::Guard.builder.clean_templates 25 | ::Forge::Guard.builder.copy_templates 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/lib/forge/config_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'forge/config' 3 | 4 | describe Forge::Config do 5 | 6 | def config_file 7 | File.expand_path(File.join('~', '.forge', 'config.yml')) 8 | end 9 | 10 | before(:each) do 11 | @buffer = StringIO.new 12 | @config = Forge::Config.new 13 | end 14 | 15 | it "should provide empty config object by default" do 16 | @config.config.should == { 17 | :theme => { 18 | :author => nil, 19 | :author_url => nil 20 | }, 21 | :links => [] 22 | } 23 | end 24 | 25 | it "should allow access to config variables through []" do 26 | @config[:links].should == [] 27 | end 28 | 29 | it "should allow config options to be changed through []" do 30 | @config[:links] = ['/var/www/wordpress'] 31 | @config[:links].should == ['/var/www/wordpress'] 32 | end 33 | 34 | it "should store config in ~/.forge/config.yml" do 35 | @config.config_file.should == config_file 36 | end 37 | 38 | it "should write default configuration to yaml" do 39 | @config.write(:io => @buffer) 40 | @buffer.string.should == "---\n:theme:\n :author: !!null \n :author_url: !!null \n:links: []\n" 41 | end 42 | 43 | describe :write do 44 | it "should dump any changes made to the config" do 45 | @config[:theme][:author] = "Matt Button" 46 | @config[:theme][:author_url] = "http://that-matt.com" 47 | 48 | @config.write(:io => @buffer) 49 | @buffer.string.should == "---\n:theme:\n :author: Matt Button\n :author_url: http://that-matt.com\n:links: []\n" 50 | end 51 | end 52 | 53 | describe :read do 54 | it "should call #write if the file does not exist" do 55 | File.should_receive(:exists?).with(config_file).and_return(false) 56 | Psych.should_not_receive(:load_file) 57 | 58 | @config.should_receive(:write) 59 | @config.read 60 | end 61 | 62 | it "should not call #write if the config file exists" do 63 | File.should_receive(:exists?).with(config_file).and_return(true) 64 | Psych.should_receive(:load_file).with(config_file) 65 | 66 | @config.should_not_receive(:write) 67 | @config.read 68 | end 69 | 70 | it "should load config from yaml file" do 71 | File.stub!(:exists?).and_return(true) 72 | Psych.stub!(:load_file).and_return({:theme => {:author => 'Drew'}, :links => []}) 73 | 74 | @config.read 75 | @config[:links].should == [] 76 | @config[:theme].should == {:author => 'Drew'} 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/lib/forge/project_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'forge/project' 3 | 4 | describe Forge::Project do 5 | before(:each) do 6 | @project = Forge::Project.new('/tmp/', nil, {:name => 'Hello'}) 7 | end 8 | 9 | describe :config_file do 10 | it "should create an expanded path to the config file" do 11 | @project.config_file.should == '/tmp/config.json' 12 | end 13 | end 14 | 15 | describe :assets_path do 16 | it "should create an expanded path to the assets folder" do 17 | @project.assets_path.should == '/tmp/assets' 18 | end 19 | end 20 | 21 | 22 | describe :build_dir do 23 | it "should create an expanded path to the forge build directory" do 24 | @project.build_dir.should == '/tmp/.forge' 25 | end 26 | end 27 | 28 | describe :theme_id do 29 | it "should be the same as the project folder" do 30 | @project.theme_id.should == 'tmp' 31 | end 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | require 'rspec' 4 | require 'forge' 5 | 6 | p "HEY" 7 | # Requires supporting files with custom matchers and macros, etc, 8 | # in ./support/ and its subdirectories. 9 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 10 | 11 | RSpec.configure do |config| 12 | 13 | end 14 | --------------------------------------------------------------------------------