Initial import

master
Jakub Šťastný aka Botanicus 2010-10-20 14:42:17 +01:00
commit 4cc4e303ab
44 changed files with 691 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/*.gem
.DS_Store
.rvmrc

3
CHANGELOG Normal file
View File

@ -0,0 +1,3 @@
= Version 0.0.1
* Items
* Generators

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2010 Jakub Šťastný aka Botanicus
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

18
README.textile Normal file
View File

@ -0,0 +1,18 @@
h1. Why you should be interested in ace?
In Ace, every page is an instance
Typically I want to define methods, like @post.excerpt
There are also *generators* available for easier generating items on the fly.
Ace has *template inheritance*. I love template inheritance, it's more flexible pattern than layouts.
Tasks for deployment included.
h1. The boot process
# load @boot.rb@ where the
# load the rules (controllers / globs mapping)
# load & instantiate the items: only the renderables (concrete post)
# run the filters, layoutin' ... actually this can be defined in the controller
# match the routes, write the files

1
TODO Normal file
View File

@ -0,0 +1 @@
- ace-gen myproject

53
ace.gemspec Executable file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env gem build
# encoding: utf-8
require "base64"
require File.expand_path("../lib/ace/version", __FILE__)
Gem::Specification.new do |s|
s.name = "ace"
s.version = Ace::VERSION
s.authors = ["Jakub Šťastný aka Botanicus"]
s.homepage = "http://github.com/botanicus/ace"
s.summary = "Ace is highly flexible static pages generator with template inheritance."
s.description = "" # TODO: long description
s.cert_chain = nil
s.email = Base64.decode64("c3Rhc3RueUAxMDFpZGVhcy5jeg==\n")
s.has_rdoc = true
# files
s.files = `git ls-files`.split("\n")
s.executables = Dir["bin/*"].map(&File.method(:basename))
s.default_executable = "ace"
s.require_paths = ["lib"]
# Ruby version
# Current JRuby with --1.9 switch has RUBY_VERSION set to "1.9.2dev"
# and RubyGems don't play well with it, so we have to set minimal
# Ruby version to 1.9, even if it actually is 1.9.1
s.required_ruby_version = ::Gem::Requirement.new("~> 1.9")
# Dependencies
# RubyGems has runtime dependencies (add_dependency) and
# development dependencies (add_development_dependency)
# Ace isn't a monolithic framework, so you might want
# to use just one specific part of it, so it has no sense
# to specify dependencies for the whole gem. If you want
# to install everything what you need for start with Ace,
# just run gem install ace --development
s.add_dependency "template-inheritance"
s.add_development_dependency "simple-templater", ">= 0.0.1.2"
begin
require "changelog"
rescue LoadError
warn "You have to have changelog gem installed for post install message"
else
s.post_install_message = CHANGELOG.new.version_changes
end
# RubyForge
s.rubyforge_project = "ace"
end

6
ace.pre.gemspec Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env gem build
# encoding: utf-8
eval(File.read("ace.gemspec")).tap do |specification|
specification.version = "#{specification.version}.pre"
end

76
bin/ace Executable file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env ruby
# encoding: utf-8
if RUBY_VERSION < "1.9.1"
abort "Ace requires Ruby 1.9."
end
base = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
libdir = File.expand_path(File.join(File.dirname(base), "..", "lib"))
# because of system installation, there is bin/../lib, but not bin/../lib/ace
if File.directory?(File.join(libdir, "ace"))
$:.unshift(libdir) unless $:.include?(libdir)
end
require "ace"
require "ace/dsl"
if File.join(Dir.pwd, "boot.rb")
require File.join(Dir.pwd, "boot.rb")
else
abort "No boot.rb!"
end
if File.join(Dir.pwd, "rules.rb")
path = File.join(Dir.pwd, "rules.rb")
code = File.read(path)
rules = Ace::DSL.new
begin
rules.instance_eval(code)
rescue Exception => exception
puts "Error in DSL: #{exception.message}"
puts exception.backtrace
exit 1
end
else
abort "No rules.rb!"
end
rules.rules.each do |klass, files|
puts "#{klass} #{files.inspect}"
files.each do |file|
if File.binread(file).match(/^-{3,5}\s*$/)
raw_item = Ace::RawItem.new(file).tap(&:parse)
item = klass.create(raw_item.metadata, raw_item.content)
else
item = klass.create(Hash.new, File.read(file))
end
item.original_path = file
end
end
puts
rules.generators.each do |generator_klass|
puts "Running #{generator_klass}"
generator = generator_klass.new
begin
if generator.respond_to?(:run)
generator.run
else
abort "Generator #{generator.inspect} doesn't respond to the #run method!"
end
rescue Exception => exception
puts "Error in generator #{generator.inspect}: #{exception.message}"
puts exception.backtrace
exit 1
end
end
puts
Ace::Item.instances.each do |item|
puts "Generating #{item.output_path}"
item.save!
end

32
bin/ace-gen Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env ruby
# encoding: utf-8
if RUBY_VERSION < "1.9.1"
abort "Ace requires Ruby 1.9."
end
base = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
libdir = File.expand_path(File.join(File.dirname(base), "..", "lib"))
# because of system installation, there is bin/../lib, but not bin/../lib/ace
if File.directory?(File.join(libdir, "ace"))
$:.unshift(libdir) unless $:.include?(libdir)
end
begin
require "simple-templater"
rescue LoadError
abort "You have to install simple-templater first!"
end
begin
templater = SimpleTemplater.new(:ace)
generator = templater.find(:project)
generator.run(ARGV)
rescue SimpleTemplater::TargetAlreadyExist => exception
abort exception.message
rescue Interrupt
exit
rescue Exception => exception
abort "Exception #{exception.inspect} occured during running generator #{generator.inspect}\n#{exception.backtrace.join("\n")}"
end

20
example/app/posts.rb Normal file
View File

@ -0,0 +1,20 @@
# encoding: utf-8
require "nokogiri"
require "ace/filters"
# Inheritted methods:
# - content
# - metadata
# - config
class Post < Ace::Item
before Ace::LayoutFilter, layout: "post.html"
def document
Nokogiri::HTML(self.content)
end
def excerpt
self.document.css("p.excerpt")
end
end

28
example/app/tags.rb Normal file
View File

@ -0,0 +1,28 @@
# encoding: utf-8
class Tag < Ace::Item
before Ace::LayoutFilter, layout: "tag.html"
end
class TagPagesGenerator
def tags
Post.instances.inject(Hash.new) do |buffer, post|
if tags = post.metadata[:tags]
tags.each do |tag|
buffer[tag] ||= Array.new
buffer[tag] << post
end
end
buffer
end
end
def run
self.tags.each do |tag_title, items|
tag_name = tag_title.downcase.gsub(" ", "-")
metadata = {title: tag_title, timestamp: Time.now}
tag = Tag.create(metadata, items)
tag.output_path = "output/tags/#{tag_name}.html"
end
end
end

6
example/boot.rb Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env ace
# encoding: utf-8
Dir["app/**/*.rb"].each do |file|
load file
end

2
example/config.yml Normal file
View File

@ -0,0 +1,2 @@
title: ""
base_url: ""

View File

@ -0,0 +1 @@
h1 { color: red; }

View File

@ -0,0 +1,3 @@
window.onload = function () {
console.log("I don't do nothing really, I'm just pretending to be a useful asset.");
};

View File

@ -0,0 +1,5 @@
- extends "base.html"
- Post.each do |post|
%h2= post.title
= post.excerpt

View File

View File

@ -0,0 +1,29 @@
---
title: Node.js Asynchronous JavaScript Framework
timestamp: 2010-09-16
tags: ["Development", "JavaScript", "Node.js"]
---
<p class="excerpt">
Node.js is an evented I/O framework for the V8 JavaScript engine. It is intended for writing scalable network programs such as web servers.
</p>
<p>
Node.js is similar in purpose to Twisted for Python, Perl Object Environment for Perl, and EventMachine for Ruby. Unlike most JavaScript, it is not executed in a web browser, but it is rather related to server-side JavaScript. Node.js implements some CommonJS specifications[1]. Node.js includes a REPL environment for interactive testing.
</p>
<pre>
var sys = require('sys'),
http = require('http');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}).listen(8000);
sys.puts('Server running at http://127.0.0.1:8000/');
</pre>
<p>
<em>From <a href="http://en.wikipedia.org/wiki/Node.js">Wikipedia.org</em>.
</p>

View File

@ -0,0 +1,21 @@
---
title: Ruby Programming Language
timestamp: 2010-09-14
tags: ["Development", "Ruby"]
---
<p class="excerpt">
Ruby is a dynamic, reflective, general purpose object-oriented programming language that combines syntax inspired by Perl with Smalltalk-like features. Ruby originated in Japan during the mid-1990s and was first developed and designed by Yukihiro "Matz" Matsumoto. It was influenced primarily by Perl, Smalltalk, Eiffel, and Lisp.
</p>
<p>
Ruby supports multiple programming paradigms, including functional, object oriented, imperative and reflective. It also has a dynamic type system and automatic memory management; it is therefore similar in varying respects to Python, Perl, Lisp, Dylan, Pike, and CLU.
</p>
<p>
The standard 1.8.7 implementation is written in C, as a single-pass interpreted language. There is currently no specification of the Ruby language, so the original implementation is considered to be the de facto reference. As of 2010[update], there are a number of complete or upcoming alternative implementations of the Ruby language, including YARV, JRuby, Rubinius, IronRuby, MacRuby, and HotRuby, each of which takes a different approach, with IronRuby, JRuby and MacRuby providing just-in-time compilation and MacRuby also providing ahead-of-time compilation. The official 1.9 branch uses YARV, as will 2.0 (development), and will eventually supersede the slower Ruby MRI.
</p>
<p>
<em>From <a href="http://en.wikipedia.org/wiki/Ruby_%28programming_language%29">Wikipedia.org</em>.
</p>

View File

@ -0,0 +1,5 @@
!!!
%html
%head
%body
#main= block(:body)

View File

@ -0,0 +1,5 @@
- extends "base.html"
- block(:body) do
%h1 My Coooooool Bloogiiiiseeeeek!
= item.content

View File

@ -0,0 +1,6 @@
- extends "base.html"
- block(:body) do
%h1 My Coooooool Bloogiiiiseeeeek!
- item.content.each do |post|
= post.metadata[:title]

View File

View File

@ -0,0 +1 @@
h1 { color: red; }

View File

@ -0,0 +1,3 @@
window.onload = function () {
console.log("I don't do nothing really, I'm just pretending to be a useful asset.");
};

View File

@ -0,0 +1,32 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>
<div id='main'>
<h1>My Coooooool Bloogiiiiseeeeek!</h1>
<p class="excerpt">
Node.js is an evented I/O framework for the V8 JavaScript engine. It is intended for writing scalable network programs such as web servers.
</p>
<p>
Node.js is similar in purpose to Twisted for Python, Perl Object Environment for Perl, and EventMachine for Ruby. Unlike most JavaScript, it is not executed in a web browser, but it is rather related to server-side JavaScript. Node.js implements some CommonJS specifications[1]. Node.js includes a REPL environment for interactive testing.
</p>
<pre>
var sys = require('sys'),
http = require('http');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}).listen(8000);
sys.puts('Server running at http://127.0.0.1:8000/');
</pre>
<p>
<em>From <a href="http://en.wikipedia.org/wiki/Node.js">Wikipedia.org</em>.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>
<div id='main'>
<h1>My Coooooool Bloogiiiiseeeeek!</h1>
<p class="excerpt">
Ruby is a dynamic, reflective, general purpose object-oriented programming language that combines syntax inspired by Perl with Smalltalk-like features. Ruby originated in Japan during the mid-1990s and was first developed and designed by Yukihiro "Matz" Matsumoto. It was influenced primarily by Perl, Smalltalk, Eiffel, and Lisp.
</p>
<p>
Ruby supports multiple programming paradigms, including functional, object oriented, imperative and reflective. It also has a dynamic type system and automatic memory management; it is therefore similar in varying respects to Python, Perl, Lisp, Dylan, Pike, and CLU.
</p>
<p>
The standard 1.8.7 implementation is written in C, as a single-pass interpreted language. There is currently no specification of the Ruby language, so the original implementation is considered to be the de facto reference. As of 2010[update], there are a number of complete or upcoming alternative implementations of the Ruby language, including YARV, JRuby, Rubinius, IronRuby, MacRuby, and HotRuby, each of which takes a different approach, with IronRuby, JRuby and MacRuby providing just-in-time compilation and MacRuby also providing ahead-of-time compilation. The official 1.9 branch uses YARV, as will 2.0 (development), and will eventually supersede the slower Ruby MRI.
</p>
<p>
<em>From <a href="http://en.wikipedia.org/wiki/Ruby_%28programming_language%29">Wikipedia.org</em>.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>
<div id='main'>
<h1>My Coooooool Bloogiiiiseeeeek!</h1>
content
</div>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>
<div id='main'>
<h1>My Coooooool Bloogiiiiseeeeek!</h1>
Node.js Asynchronous JavaScript Framework
Ruby Programming Language
</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>
<div id='main'>
<h1>My Coooooool Bloogiiiiseeeeek!</h1>
Node.js Asynchronous JavaScript Framework
</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>
<div id='main'>
<h1>My Coooooool Bloogiiiiseeeeek!</h1>
Node.js Asynchronous JavaScript Framework
</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>
<div id='main'>
<h1>My Coooooool Bloogiiiiseeeeek!</h1>
Ruby Programming Language
</div>
</body>
</html>

8
example/rules.rb Normal file
View File

@ -0,0 +1,8 @@
# encoding: utf-8
# filters
rule Post, "posts/*.html"
rule Ace::Asset, "assets/**/*"
# generators
generator TagPagesGenerator#, "/tags/:slug"

0
example/tasks.rb Executable file
View File

130
lib/ace.rb Normal file
View File

@ -0,0 +1,130 @@
# encoding: utf-8
# === The boot process === #
# 1) load the app
# 2) load the rules (controllers / globs mapping)
# 3) load & instantiate all the renderable items
# 4) render all the items (here the filters & layouting run)
# 5) match the routes, write the files
require "yaml"
require "fileutils"
module Ace
class RawItem
attr_accessor :path, :metadata, :content
def initialize(path)
@data = File.read(path)
end
def parse
pieces = @data.split(/^-{3,5}\s*$/)
if pieces.size < 3
raise RuntimeError.new(
"The file '#{content_filename}' appears to start with a metadata section (three or five dashes at the top) but it does not seem to be in the correct format."
)
end
# Parse
self.metadata = YAML.load(pieces[1]).inject(Hash.new) { |metadata, pair| metadata.merge(pair[0].to_sym => pair[1]) } || Hash.new
self.content = pieces[2..-1].join.strip
end
end
# This class represents the items which will be
# eventually rendered like concrete posts, tags etc.
class Item
@@subclasses ||= Array.new
def self.inherited(subclass)
@@subclasses << subclass
end
@@instances ||= Array.new
def self.instances
@@instances
end
def self.before_filters
@before_filters ||= Array.new
end
def self.before(filter, *args)
self.before_filters << filter.new(*args)
end
def self.after_filters
@after_filters ||= Array.new
end
def self.after(filter, *args)
self.after_filters << filter.new(*args)
end
def self.create(metadata, content)
self.new(metadata, content).tap(&:register)
end
# Content can be anything, not just a string.
attr_accessor :metadata, :content
attr_accessor :original_path
def initialize(metadata, content)
@metadata = metadata
@content = content
end
def config
@config ||= begin
YAML::load_file("config.yml").inject(Hash.new) do |hash, pair|
hash.merge!(pair[0].to_sym => pair[1])
end
end
end
def register
instances = self.class.instances
unless instances.include?(self)
self.class.instances << self
end
end
def unregister
self.class.instances.delete(self)
end
def render
output = self.class.before_filters.inject(self.content) do |buffer, filter|
filter.call(self, buffer)
end
self.class.after_filters.inject(output) do |buffer, filter|
filter.call(self, buffer)
end
end
attr_writer :output_path
def output_path
@output_path ||= begin
unless self.original_path.nil?
self.original_path.sub("content", "output")
end
end
end
def save!
content = self.render.chomp # so filters can influence output_path
FileUtils.mkdir_p File.dirname(self.output_path)
File.open(self.output_path, "w") do |file|
file.puts content
end
end
end
class Asset < Item
end
module Helpers
def link_to(anchor, path_or_item, options = nil)
end
end
end

21
lib/ace/dsl.rb Normal file
View File

@ -0,0 +1,21 @@
# encoding: utf-8
module Ace
class DSL
attr_accessor :rules, :generators
def initialize
@rules, @generators = Hash.new, Array.new
end
def rule(klass, *globs)
paths = globs.map { |glob| Dir.glob("content/#{glob}") }
files = paths.flatten.select { |path| File.file?(path) }
self.rules[klass] ||= Array.new
self.rules[klass].push(*files)
end
def generator(klass)
self.generators << klass
end
end
end

9
lib/ace/filters.rb Normal file
View File

@ -0,0 +1,9 @@
# encoding: utf-8
module Ace
class Filter
def initialize(options = Hash.new)
@options = options
end
end
end

18
lib/ace/filters/haml.rb Normal file
View File

@ -0,0 +1,18 @@
# encoding: utf-8
require "haml"
require "ace/filters"
module Ace
class HamlFilter < Filter
# http://haml.hamptoncatlin.com/docs/rdoc/classes/Haml/Engine.html
def call(item, content)
if item.output_path && item.output_path.end_with?(".haml")
item.output_path.sub!(/\.haml$/, "")
end
engine = Haml::Engine.new(content)
engine.render(item.extend(Ace::Helpers))
end
end
end

19
lib/ace/filters/layout.rb Normal file
View File

@ -0,0 +1,19 @@
# encoding: utf-8
require "ace/filters"
require "template-inheritance"
TemplateInheritance::Template.paths << File.join(Dir.pwd, "layouts")
module Ace
class LayoutFilter < Filter
def initialize(options)
@path = options[:layout]
end
def call(item, content)
template = TemplateInheritance::Template.new(@path)
return template.render(item: item)
end
end
end

5
lib/ace/version.rb Normal file
View File

@ -0,0 +1,5 @@
# encoding: utf-8
module Ace
VERSION = "0.0.1"
end

View File

@ -0,0 +1,3 @@
---
:full: yes
:flat: no

View File

@ -0,0 +1,9 @@
# encoding: utf-8
# This hook will be executed after templater finish in context of current generator object.
# Current directory is what you just generated, unless this is flat generator.
unless RUBY_PLATFORM.match(/mswin|mingw/)
sh "chmod +x boot.rb"
sh "chmod +x tasks.rb"
end

View File

@ -0,0 +1,9 @@
# encoding: utf-8
# This hook will be executed in context of current generator object before templater start to generate new files.
# You can update context hash and register hooks. Don't forget to use merge! instead of merge, because you are
# manipulating with one object, rather than returning new one.
hook do |generator, context|
# TODO
end

6
simple-templater.scope Normal file
View File

@ -0,0 +1,6 @@
# encoding: utf-8
SimpleTemplater.scope(:ace) do
path = File.expand_path("../project_generator", __FILE__)
SimpleTemplater.register(:ace, :project, File.expand_path(path))
end