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