dryml 1.1.0.pre0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +9 -0
- data/LICENSE.txt +22 -0
- data/README +36 -0
- data/Rakefile +47 -0
- data/TODO.txt +6 -0
- data/lib/dryml/dryml_builder.rb +140 -0
- data/lib/dryml/dryml_doc.rb +155 -0
- data/lib/dryml/dryml_generator.rb +271 -0
- data/lib/dryml/dryml_support_controller.rb +13 -0
- data/lib/dryml/helper.rb +69 -0
- data/lib/dryml/parser/attribute.rb +41 -0
- data/lib/dryml/parser/base_parser.rb +254 -0
- data/lib/dryml/parser/document.rb +57 -0
- data/lib/dryml/parser/element.rb +27 -0
- data/lib/dryml/parser/elements.rb +21 -0
- data/lib/dryml/parser/source.rb +58 -0
- data/lib/dryml/parser/text.rb +13 -0
- data/lib/dryml/parser/tree_parser.rb +67 -0
- data/lib/dryml/parser.rb +3 -0
- data/lib/dryml/part_context.rb +133 -0
- data/lib/dryml/scoped_variables.rb +42 -0
- data/lib/dryml/static_tags +98 -0
- data/lib/dryml/tag_parameters.rb +32 -0
- data/lib/dryml/taglib.rb +124 -0
- data/lib/dryml/template.rb +1021 -0
- data/lib/dryml/template_environment.rb +613 -0
- data/lib/dryml/template_handler.rb +187 -0
- data/lib/dryml.rb +291 -0
- data/taglibs/core.dryml +136 -0
- data/test/dryml.rdoctest +68 -0
- metadata +131 -0
data/CHANGES.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
CHANGES:
|
2
|
+
|
3
|
+
- Hobo::Dryml moved to Dryml
|
4
|
+
- Hobo.static_tags moved to Dryml.static_tags
|
5
|
+
- core.dryml moved to dryml/taglibs
|
6
|
+
- generator_directories moved from constant to parameter to Dryml.enable
|
7
|
+
- APPLICATION_TAGLIB not necessarily loaded automatically
|
8
|
+
- generator input and output directors no longer hardcoded, must be passed to #enable
|
9
|
+
- a bunch of functions in hobo.rb moved to dryml.rb
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2006 Tom Locke
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# DRYML
|
2
|
+
|
3
|
+
DRYML is the Don't Repeat Yourself Markup Language. It uses an
|
4
|
+
XML-like syntax and is best at creating XHTML documents. It could be
|
5
|
+
used to create other forms of plain-text documents, but the syntax is
|
6
|
+
not optimized for that and you may end up with extra carriage returns.
|
7
|
+
(Which would be a bug, so please send test cases).
|
8
|
+
|
9
|
+
DRYML was created for the Hobo project, but this is an extraction from
|
10
|
+
that project and can be used separately.
|
11
|
+
|
12
|
+
# How to use with Rails but without Hobo
|
13
|
+
|
14
|
+
- install both HoboSupport and Dryml as a plugin or gem
|
15
|
+
|
16
|
+
- create an `application.dryml`
|
17
|
+
|
18
|
+
$ mkdir app/views/taglibs
|
19
|
+
$ touch app/views/taglibs/application.dryml
|
20
|
+
|
21
|
+
- create `config/initializers/dryml.rb`
|
22
|
+
|
23
|
+
require 'dryml'
|
24
|
+
require 'dryml/template'
|
25
|
+
require 'dryml/dryml_generator'
|
26
|
+
Dryml.enable
|
27
|
+
|
28
|
+
Now you can use templates that end in ".dryml". Such templates will
|
29
|
+
ignore layouts.
|
30
|
+
|
31
|
+
# How to use outside of Rails
|
32
|
+
|
33
|
+
Dryml.render("<html><%= this %></html>", {:this => something})
|
34
|
+
|
35
|
+
See the [rdoc](http://fixme/doc/Dryml.html#render-instance_method) for
|
36
|
+
more information.
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '/lib')
|
6
|
+
$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '/../hobosupport/lib')
|
7
|
+
require 'dryml' # to get VERSION
|
8
|
+
|
9
|
+
RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']).sub(/.*\s.*/m, '"\&"')
|
10
|
+
RUBYDOCTEST = ENV['RUBYDOCTEST'] || "#{RUBY} -S rubydoctest"
|
11
|
+
|
12
|
+
desc "Default Task"
|
13
|
+
task :default => [ :test ]
|
14
|
+
|
15
|
+
# --- Testing --- #
|
16
|
+
|
17
|
+
desc "Run all tests"
|
18
|
+
task :test do |t|
|
19
|
+
files=Dir['test/*.rdoctest'].map {|f| File.expand_path(f)}.join(' ')
|
20
|
+
exit(1) if !system("#{RUBYDOCTEST} #{files}")
|
21
|
+
end
|
22
|
+
|
23
|
+
# --- RDOC --- #
|
24
|
+
|
25
|
+
require 'yard'
|
26
|
+
YARD::Rake::YardocTask.new do |t|
|
27
|
+
t.files = ['lib/**/*.rb', 'README', 'LICENSE.txt', 'CHANGES.txt']
|
28
|
+
end
|
29
|
+
|
30
|
+
# --- Packaging and Rubyforge & gemcutter & github--- #
|
31
|
+
|
32
|
+
require 'jeweler'
|
33
|
+
Jeweler::Tasks.new do |gemspec|
|
34
|
+
gemspec.version = Dryml::VERSION
|
35
|
+
gemspec.name = "dryml"
|
36
|
+
gemspec.email = "tom@tomlocke.com"
|
37
|
+
gemspec.summary = "The web app builder for Rails"
|
38
|
+
gemspec.homepage = "http://hobocentral.net/"
|
39
|
+
gemspec.authors = ["Tom Locke"]
|
40
|
+
gemspec.rubyforge_project = "hobo"
|
41
|
+
gemspec.add_dependency("hobosupport", ["= #{Dryml::VERSION}"])
|
42
|
+
gemspec.add_dependency("actionpack", [">= 2.2.2"])
|
43
|
+
end
|
44
|
+
Jeweler::GemcutterTasks.new
|
45
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
46
|
+
rubyforge.doc_task = false
|
47
|
+
end
|
data/TODO.txt
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module Dryml
|
2
|
+
|
3
|
+
class DRYMLBuilder
|
4
|
+
|
5
|
+
def initialize(template)
|
6
|
+
@template = template
|
7
|
+
@build_instructions = nil # set to [] on the first add_build_instruction
|
8
|
+
@part_names = []
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :template, :environment
|
12
|
+
|
13
|
+
def template_path
|
14
|
+
template.template_path
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def set_environment(environment)
|
19
|
+
@environment = environment
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def ready?(mtime, d=false)
|
24
|
+
@build_instructions && @last_build_mtime && @last_build_mtime >= mtime
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def start
|
29
|
+
@part_names.clear
|
30
|
+
@build_instructions = []
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def add_build_instruction(type, params)
|
35
|
+
@build_instructions << params.merge(:type => type)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def add_part(name, src, line_num)
|
40
|
+
raise DrymlException.new("duplicate part: #{name}", template_path, line_num) if name.in?(@part_names)
|
41
|
+
add_build_instruction(:def, :src => src, :line_num => line_num)
|
42
|
+
@part_names << name
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def <<(params)
|
47
|
+
@build_instructions << params
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def render_page_source(src, local_names)
|
52
|
+
locals = local_names.map{|l| "#{l} = __local_assigns__[:#{l}];"}.join(' ')
|
53
|
+
|
54
|
+
("def render_page(__page_this__, __local_assigns__); " +
|
55
|
+
"#{locals} new_object_context(__page_this__) do " +
|
56
|
+
src +
|
57
|
+
"; output_buffer; end; end")
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def erb_process(erb_src, method_def=false)
|
62
|
+
trim_mode = ActionView::TemplateHandlers::ERB.erb_trim_mode
|
63
|
+
erb = ERB.new(erb_src, nil, trim_mode, "output_buffer")
|
64
|
+
src = erb.src.split(';')[1..-2].join(';')
|
65
|
+
|
66
|
+
if method_def
|
67
|
+
src.sub /^\s*def.*?\(.*?\)/, '\0 __in_erb_template=true; '
|
68
|
+
else
|
69
|
+
"__in_erb_template=true; " + src
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def build(local_names, auto_taglibs, src_mtime)
|
75
|
+
|
76
|
+
auto_taglibs.each { |t| import_taglib(t) }
|
77
|
+
|
78
|
+
@build_instructions._?.each do |instruction|
|
79
|
+
name = instruction[:name]
|
80
|
+
case instruction[:type]
|
81
|
+
when :eval
|
82
|
+
@environment.class_eval(instruction[:src], template_path, instruction[:line_num])
|
83
|
+
|
84
|
+
when :def
|
85
|
+
src = erb_process(instruction[:src], true)
|
86
|
+
@environment.class_eval(src, template_path, instruction[:line_num])
|
87
|
+
|
88
|
+
when :render_page
|
89
|
+
method_src = render_page_source(erb_process(instruction[:src]), local_names)
|
90
|
+
@environment.compiled_local_names = local_names
|
91
|
+
@environment.class_eval(method_src, template_path, instruction[:line_num])
|
92
|
+
|
93
|
+
when :include
|
94
|
+
import_taglib(instruction)
|
95
|
+
|
96
|
+
when :module
|
97
|
+
import_module(name.constantize, instruction[:as])
|
98
|
+
|
99
|
+
when :set_theme
|
100
|
+
set_theme(name)
|
101
|
+
|
102
|
+
when :alias_method
|
103
|
+
@environment.send(:alias_method, instruction[:new], instruction[:old])
|
104
|
+
|
105
|
+
else
|
106
|
+
raise RuntimeError.new("DRYML: Unknown build instruction :#{instruction[:type]}, " +
|
107
|
+
"building #{template_path}")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
@last_build_mtime = src_mtime
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def import_taglib(options)
|
115
|
+
if options[:module]
|
116
|
+
import_module(options[:module].constantize, options[:as])
|
117
|
+
else
|
118
|
+
template_dir = File.dirname(template_path)
|
119
|
+
options = options.merge(:template_dir => template_dir)
|
120
|
+
|
121
|
+
taglib = Taglib.get(options)
|
122
|
+
taglib.import_into(@environment, options[:as])
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def import_module(mod, as=nil)
|
128
|
+
raise NotImplementedError if as
|
129
|
+
@environment.send(:include, mod)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def set_theme(name)
|
134
|
+
if Hobo.current_theme.nil? or Hobo.current_theme == name
|
135
|
+
Hobo.current_theme = name
|
136
|
+
import_taglib(:src => "taglibs/themes/#{name}/#{name}")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'rexml/xpath'
|
2
|
+
|
3
|
+
module Dryml
|
4
|
+
|
5
|
+
# DrymlDoc provides the facility to parse a directory tree of DRYML taglibs, building a collection of objects that provide metadata
|
6
|
+
module DrymlDoc
|
7
|
+
|
8
|
+
def self.load_taglibs(directory, taglib_class=DrymlDoc::Taglib)
|
9
|
+
dryml_files = Dir["#{directory}/**/*.dryml"]
|
10
|
+
|
11
|
+
dryml_files.map { |f| taglib_class.new(directory, f) }
|
12
|
+
end
|
13
|
+
|
14
|
+
CommentMethods = classy_module do
|
15
|
+
|
16
|
+
def comment_intro
|
17
|
+
comment && comment =~ /(.*?)^#/m ? $1 : comment
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def comment_rest
|
22
|
+
comment && comment[comment_intro.length..-1]
|
23
|
+
end
|
24
|
+
|
25
|
+
%w(comment comment_intro comment_rest).each do |m|
|
26
|
+
class_eval "def #{m}_html; Maruku.new(#{m}).to_html.gsub(/&/, '&'); end"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class Taglib
|
32
|
+
|
33
|
+
def initialize(home, filename, name=nil)
|
34
|
+
@name = name || filename.sub(/.dryml$/, '')[home.length+1..-1]
|
35
|
+
@doc = Dryml::Parser::Document.new(File.read(filename), filename)
|
36
|
+
parse_tag_defs
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :name, :doc, :tag_defs
|
40
|
+
|
41
|
+
def comment
|
42
|
+
first_node = doc[0][0]
|
43
|
+
doc.restore_erb_scriptlets(first_node.to_s.strip) if first_node.is_a?(REXML::Comment)
|
44
|
+
end
|
45
|
+
|
46
|
+
include CommentMethods
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def tagdef_class
|
51
|
+
self.class.parent.const_get('TagDef')
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_tag_defs
|
55
|
+
@tag_defs = []
|
56
|
+
REXML::XPath.match(doc, '/*/*[@tag]').each { |node| @tag_defs << tagdef_class.new(self, node) }
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class TagDef
|
62
|
+
|
63
|
+
def initialize(taglib, node)
|
64
|
+
@taglib = taglib
|
65
|
+
@node = node
|
66
|
+
end
|
67
|
+
|
68
|
+
attr_reader :taglib, :node
|
69
|
+
delegate :doc, :to => :taglib
|
70
|
+
|
71
|
+
|
72
|
+
def name
|
73
|
+
node.attributes['tag']
|
74
|
+
end
|
75
|
+
|
76
|
+
def source
|
77
|
+
doc.restore_erb_scriptlets(node.to_s).strip
|
78
|
+
end
|
79
|
+
|
80
|
+
# The contents of the XML comment, if any, immediately above the tag definition
|
81
|
+
def comment
|
82
|
+
@comment ||= begin
|
83
|
+
space = node.previous_sibling and
|
84
|
+
space.to_s.blank? && space.to_s.count("\n") == 1 and
|
85
|
+
comment_node = space.previous_sibling
|
86
|
+
|
87
|
+
if comment_node.is_a?(REXML::Comment)
|
88
|
+
doc.restore_erb_scriptlets(comment_node.to_s.strip)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
include CommentMethods
|
94
|
+
|
95
|
+
def no_doc?
|
96
|
+
comment =~ /^nodoc\b/
|
97
|
+
end
|
98
|
+
|
99
|
+
# An array of the arrtibute names defined by this tag
|
100
|
+
def attributes
|
101
|
+
(node.attributes['attrs'] || "").split(/\s*,\s*/).where_not.blank?
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# Returns a recursive array srtucture, where each item in the array is a pair: [parameter_name, sub_parameters]
|
106
|
+
# (sub-parameters is the same kind of structure)
|
107
|
+
def parameters(element=node)
|
108
|
+
result = []
|
109
|
+
element.elements.each do |e|
|
110
|
+
if (p = e.attributes['param'])
|
111
|
+
param_name = p == "&true" ? e.name : p
|
112
|
+
result << [param_name, parameters(e)]
|
113
|
+
else
|
114
|
+
result.concat(parameters(e))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
result
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# Is this the base definition of a polymorphic tag
|
122
|
+
def polymorphic?
|
123
|
+
node.attributes['polymorphic'].present?
|
124
|
+
end
|
125
|
+
|
126
|
+
# Is this an <extend>?
|
127
|
+
def extension?
|
128
|
+
node.name == "extend"
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
# The definition's 'for' attribute
|
133
|
+
def for_type
|
134
|
+
node.attributes['for']
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
# The name of the tag, if any, that this definition merges its parameters into
|
139
|
+
# That is, the tag with 'merge' or 'merge-params' declared
|
140
|
+
def merge_params
|
141
|
+
REXML::XPath.first(node, ".//*[@merge|@merge-params]")._?.name
|
142
|
+
end
|
143
|
+
|
144
|
+
# The name of the tag, if any, that this definition merges its attributes into
|
145
|
+
# That is, the tag with 'merge' or 'merge-attrs' declared
|
146
|
+
def merge_attrs
|
147
|
+
REXML::XPath.first(node, ".//*[@merge|@merge-attrs]")._?.name
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'set'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
require 'action_controller/dispatcher'
|
6
|
+
|
7
|
+
module Dryml
|
8
|
+
|
9
|
+
class DrymlGenerator
|
10
|
+
|
11
|
+
HEADER = "<!-- AUTOMATICALLY GENERATED FILE - DO NOT EDIT -->\n\n"
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :run_on_every_request
|
15
|
+
attr_accessor :output_directory
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.enable(generator_directories = [], output_directory = nil)
|
19
|
+
@output_directory = output_directory
|
20
|
+
@output_directory ||= "#{RAILS_ROOT}/app/views/taglibs/auto" if defined? :RAILS_ROOT
|
21
|
+
@generator_directories = generator_directories
|
22
|
+
|
23
|
+
# Unfortunately the dispatcher callbacks don't give us the hook we need (after routes are reloaded)
|
24
|
+
# so we have to alias_method_chain
|
25
|
+
ActionController::Dispatcher.class_eval do
|
26
|
+
|
27
|
+
if respond_to? :reload_application
|
28
|
+
#Rails 2.3
|
29
|
+
class << self
|
30
|
+
def reload_application_with_dryml_generators
|
31
|
+
reload_application_without_dryml_generators
|
32
|
+
DrymlGenerator.run unless Dryml::DrymlGenerator.run_on_every_request == false || Rails.env.production?
|
33
|
+
end
|
34
|
+
alias_method_chain :reload_application, :dryml_generators
|
35
|
+
end
|
36
|
+
else
|
37
|
+
#Rails <= 2.2
|
38
|
+
def reload_application_with_dryml_generators
|
39
|
+
reload_application_without_dryml_generators
|
40
|
+
DrymlGenerator.run unless Dryml::DrymlGenerator.run_on_every_request == false || Rails.env.production?
|
41
|
+
end
|
42
|
+
alias_method_chain :reload_application, :dryml_generators
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.run(generator_directories=nil, output_directory=nil)
|
48
|
+
@generator_directories ||= generator_directories
|
49
|
+
@output_directory ||= output_directory
|
50
|
+
@generator ||= DrymlGenerator.new(generator_directories || @generator_directories)
|
51
|
+
@generator.run
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def initialize(generator_directories=nil)
|
56
|
+
@templates = {}
|
57
|
+
@digests = {}
|
58
|
+
generator_directories ||= Dryml::DrymlGenerator.generator_directories
|
59
|
+
load_templates(generator_directories)
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_accessor :subsite
|
63
|
+
|
64
|
+
|
65
|
+
def load_templates(generator_directories)
|
66
|
+
generator_directories.each do |dir|
|
67
|
+
Dir["#{dir}/**/*.dryml.erb"].each do |f|
|
68
|
+
name = f[dir.length + 1..-11]
|
69
|
+
erb = File.read(f)
|
70
|
+
@templates[name] = ERB.new(erb, nil, '-').src
|
71
|
+
|
72
|
+
# Create output directories and parents as required
|
73
|
+
[nil, *Hobo.subsites].each do |s|
|
74
|
+
FileUtils.mkdir_p(File.dirname("#{output_dir s}/#{name}"))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def run
|
82
|
+
# FIXME
|
83
|
+
# Ensure all view hints loaded before running
|
84
|
+
subsites = [nil]
|
85
|
+
if defined?(:Hobo)
|
86
|
+
Hobo::Model.all_models.*.view_hints
|
87
|
+
subsites += [*Hobo.subsites]
|
88
|
+
end
|
89
|
+
|
90
|
+
subsites.each { |s| run_for_subsite(s) }
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def run_for_subsite(subsite)
|
95
|
+
self.subsite = subsite
|
96
|
+
@templates.each_pair do |name, src|
|
97
|
+
run_one(name, src)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def output_dir(s=subsite)
|
103
|
+
s ? "#{Dryml::DrymlGenerator.output_directory}/#{s}" : Dryml::DrymlGenerator.output_directory
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def run_one(name, src)
|
108
|
+
dryml = instance_eval(src, name)
|
109
|
+
if dryml_changed?(name, dryml)
|
110
|
+
out = HEADER + dryml
|
111
|
+
File.open("#{output_dir}/#{name}.dryml", 'w') { |f| f.write(out) }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def dryml_changed?(name, dryml)
|
117
|
+
key = "#{subsite}/#{name}"
|
118
|
+
d = digest dryml
|
119
|
+
if d != @digests[key]
|
120
|
+
@digests[key] = d
|
121
|
+
true
|
122
|
+
else
|
123
|
+
false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
def digest(s)
|
129
|
+
OpenSSL::Digest::SHA1.hexdigest(s)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
# --- Helper methods for the templates --- #
|
134
|
+
|
135
|
+
attr_reader :controller
|
136
|
+
|
137
|
+
|
138
|
+
def controllers
|
139
|
+
Hobo::ModelController.all_controllers(subsite).sort_by &:name
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
def models
|
144
|
+
Hobo::Model.all_models.sort_by &:name
|
145
|
+
end
|
146
|
+
|
147
|
+
def each_controller
|
148
|
+
controllers.each do |controller|
|
149
|
+
@controller = controller
|
150
|
+
yield
|
151
|
+
end
|
152
|
+
@controller = nil
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
def each_model
|
157
|
+
models.each do |model|
|
158
|
+
@model = model
|
159
|
+
yield
|
160
|
+
end
|
161
|
+
@model = nil
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def model
|
166
|
+
@model || @controller.model
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def model_name(*options)
|
171
|
+
name = :plural.in?(options) ? model.view_hints.model_name_plural : model.view_hints.model_name
|
172
|
+
name = name.titleize.downcase if :lowercase.in?(options)
|
173
|
+
name = name.camelize if :camel.in?(options)
|
174
|
+
name
|
175
|
+
end
|
176
|
+
|
177
|
+
# escape single quotes and backslashes for use in a single
|
178
|
+
# quoted string
|
179
|
+
def sq_escape(s)
|
180
|
+
s.gsub(/[\\]/, "\\\\\\\\").gsub(/'/, "\\\\'")
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def model_class
|
185
|
+
model.name.underscore.gsub('_', '-').gsub('/', '--')
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
def view_hints
|
190
|
+
model.view_hints
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def through_collection_names(klass=model)
|
195
|
+
klass.reflections.values.select do |refl|
|
196
|
+
refl.macro == :has_many && refl.options[:through]
|
197
|
+
end.map {|x| x.options[:through]}
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
def linkable?(*args)
|
202
|
+
options = args.extract_options!
|
203
|
+
options[:subsite] = subsite
|
204
|
+
klass, action = if args.length == 1
|
205
|
+
[model, args.first]
|
206
|
+
else
|
207
|
+
args
|
208
|
+
end
|
209
|
+
Hobo::ModelRouter.linkable?(klass, action, options)
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
def sortable_collection?(collection, model=self.model)
|
214
|
+
# There's no perfect way to detect for this, given that acts_as_list
|
215
|
+
# does not provide any metadata to reflect on, but if the :order
|
216
|
+
# option is the same as the target classes position_column, that's a
|
217
|
+
# pretty safe bet
|
218
|
+
if defined? ActiveRecord::Acts::List::InstanceMethods
|
219
|
+
refl = model.reflections[collection]
|
220
|
+
klass = refl.klass
|
221
|
+
klass < ActiveRecord::Acts::List::InstanceMethods &&
|
222
|
+
klass.new.position_column == refl.options[:order].to_s
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
def standard_fields(*args)
|
228
|
+
klass = args.first.is_a?(Class) ? args.shift : model
|
229
|
+
extras = args
|
230
|
+
|
231
|
+
fields = klass.attr_order.*.to_s & klass.content_columns.*.name
|
232
|
+
|
233
|
+
fields -= %w{created_at updated_at created_on updated_on deleted_at} unless extras.include?(:include_timestamps)
|
234
|
+
|
235
|
+
bt = extras.include?(:belongs_to)
|
236
|
+
hm = extras.include?(:has_many)
|
237
|
+
klass.reflections.values.sort_by { |refl| refl.name.to_s }.map do |refl|
|
238
|
+
fields << refl.name.to_s if bt && refl.macro == :belongs_to
|
239
|
+
fields << refl.name.to_s if hm && refl.macro == :has_many && refl.options[:accessible]
|
240
|
+
end
|
241
|
+
|
242
|
+
fields.reject! { |f| model.never_show? f }
|
243
|
+
fields
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
def creators
|
248
|
+
defined?(model::Lifecycle) ? model::Lifecycle.publishable_creators : []
|
249
|
+
end
|
250
|
+
|
251
|
+
def transitions
|
252
|
+
defined?(model::Lifecycle) ? model::Lifecycle.publishable_transitions : []
|
253
|
+
end
|
254
|
+
|
255
|
+
def creator_names
|
256
|
+
creators.map { |c| c.name.to_s }
|
257
|
+
end
|
258
|
+
|
259
|
+
def transition_names
|
260
|
+
transitions.map { |t| t.name.to_s }.uniq
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
def a_or_an(word)
|
265
|
+
(word =~ /^[aeiou]/i ? "an " : "a ") + word
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Dryml::DrymlSupportController < ActionController::Base
|
2
|
+
|
3
|
+
def edit_source
|
4
|
+
dryml_editor = ENV['DRYML_EDITOR']
|
5
|
+
if dryml_editor
|
6
|
+
file = File.join(RAILS_ROOT, params[:file])
|
7
|
+
command = dryml_editor.sub(":file", file).sub(":line", params[:line])
|
8
|
+
system(command)
|
9
|
+
end
|
10
|
+
render :nothing => true
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|