scribble 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +455 -0
- data/Rakefile +2 -0
- data/lib/scribble.rb +44 -0
- data/lib/scribble/block.rb +25 -0
- data/lib/scribble/converter.rb +10 -0
- data/lib/scribble/errors.rb +24 -0
- data/lib/scribble/method.rb +91 -0
- data/lib/scribble/methods/if.rb +26 -0
- data/lib/scribble/methods/layout.rb +25 -0
- data/lib/scribble/methods/partial.rb +14 -0
- data/lib/scribble/methods/times.rb +11 -0
- data/lib/scribble/nodes/call.rb +55 -0
- data/lib/scribble/nodes/ending.rb +6 -0
- data/lib/scribble/nodes/node.rb +24 -0
- data/lib/scribble/nodes/value.rb +16 -0
- data/lib/scribble/objects/boolean.rb +33 -0
- data/lib/scribble/objects/fixnum.rb +53 -0
- data/lib/scribble/objects/nil.rb +21 -0
- data/lib/scribble/objects/string.rb +62 -0
- data/lib/scribble/parsing/nester.rb +49 -0
- data/lib/scribble/parsing/parser.rb +132 -0
- data/lib/scribble/parsing/reporter.rb +71 -0
- data/lib/scribble/parsing/transform.rb +87 -0
- data/lib/scribble/partial.rb +41 -0
- data/lib/scribble/registry.rb +95 -0
- data/lib/scribble/support/context.rb +98 -0
- data/lib/scribble/support/matcher.rb +74 -0
- data/lib/scribble/support/unmatched.rb +70 -0
- data/lib/scribble/support/utilities.rb +49 -0
- data/lib/scribble/template.rb +61 -0
- data/lib/scribble/version.rb +3 -0
- data/scribble.gemspec +22 -0
- data/test/all.rb +23 -0
- data/test/errors_test.rb +94 -0
- data/test/methods/if_test.rb +49 -0
- data/test/methods/layout_test.rb +71 -0
- data/test/methods/partial_test.rb +85 -0
- data/test/methods/times_test.rb +10 -0
- data/test/objects/boolean_test.rb +162 -0
- data/test/objects/fixnum_test.rb +236 -0
- data/test/objects/nil_test.rb +83 -0
- data/test/objects/string_test.rb +268 -0
- data/test/parsing/parsing_test.rb +234 -0
- data/test/registry_test.rb +264 -0
- data/test/template_test.rb +51 -0
- data/test/test_helper.rb +65 -0
- metadata +127 -0
data/Rakefile
ADDED
data/lib/scribble.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Support
|
2
|
+
|
3
|
+
require_relative 'scribble/support/context'
|
4
|
+
require_relative 'scribble/support/utilities'
|
5
|
+
require_relative 'scribble/support/matcher'
|
6
|
+
require_relative 'scribble/support/unmatched'
|
7
|
+
|
8
|
+
# Parsing
|
9
|
+
|
10
|
+
require_relative 'scribble/parsing/parser'
|
11
|
+
require_relative 'scribble/parsing/transform'
|
12
|
+
require_relative 'scribble/parsing/nester'
|
13
|
+
require_relative 'scribble/parsing/reporter'
|
14
|
+
|
15
|
+
# Node types
|
16
|
+
|
17
|
+
require_relative 'scribble/nodes/node'
|
18
|
+
require_relative 'scribble/nodes/call'
|
19
|
+
require_relative 'scribble/nodes/ending'
|
20
|
+
require_relative 'scribble/nodes/value'
|
21
|
+
|
22
|
+
# Main components
|
23
|
+
|
24
|
+
require_relative 'scribble/registry'
|
25
|
+
require_relative 'scribble/method'
|
26
|
+
require_relative 'scribble/block'
|
27
|
+
require_relative 'scribble/partial'
|
28
|
+
require_relative 'scribble/template'
|
29
|
+
require_relative 'scribble/errors'
|
30
|
+
require_relative 'scribble/converter'
|
31
|
+
|
32
|
+
# Object registrations
|
33
|
+
|
34
|
+
require_relative 'scribble/objects/boolean'
|
35
|
+
require_relative 'scribble/objects/fixnum'
|
36
|
+
require_relative 'scribble/objects/nil'
|
37
|
+
require_relative 'scribble/objects/string'
|
38
|
+
|
39
|
+
# Method registrations
|
40
|
+
|
41
|
+
require_relative 'scribble/methods/if'
|
42
|
+
require_relative 'scribble/methods/layout'
|
43
|
+
require_relative 'scribble/methods/partial'
|
44
|
+
require_relative 'scribble/methods/times'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Scribble
|
2
|
+
class Block < Method
|
3
|
+
include Support::Context
|
4
|
+
|
5
|
+
def self.block?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
def nodes
|
10
|
+
@nodes || @call.nodes
|
11
|
+
end
|
12
|
+
|
13
|
+
def split_nodes
|
14
|
+
nodes.take_while.with_index do |node, index|
|
15
|
+
if node.split?
|
16
|
+
@nodes = nodes.drop index + 1
|
17
|
+
node.evaluate self, allow_split: true, allow_block: false
|
18
|
+
false
|
19
|
+
else
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Scribble
|
2
|
+
def self.converter from_to, &block
|
3
|
+
Object.new.tap do |converter|
|
4
|
+
converter.define_singleton_method(:from) { from_to.keys.first }
|
5
|
+
converter.define_singleton_method(:to) { from_to.values.first }
|
6
|
+
|
7
|
+
converter.define_singleton_method :convert, &block
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Scribble
|
2
|
+
module Errors
|
3
|
+
class Error < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
class Syntax < Error
|
7
|
+
end
|
8
|
+
|
9
|
+
class Undefined < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
class Arity < Error
|
13
|
+
end
|
14
|
+
|
15
|
+
class Argument < Error
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnlocatedArgument < Error
|
19
|
+
end
|
20
|
+
|
21
|
+
class NotFound < Error
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Scribble
|
2
|
+
class Method
|
3
|
+
def initialize receiver, call, context
|
4
|
+
@receiver, @call, @context = receiver, call, context
|
5
|
+
end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :method_name, :receiver_class, :signature
|
9
|
+
|
10
|
+
# Setup instance variables
|
11
|
+
|
12
|
+
def setup receiver_class, method_name, signature
|
13
|
+
raise "Method name needs to be a Symbol, got #{method_name.inspect}" unless method_name.is_a?(Symbol)
|
14
|
+
@receiver_class, @method_name, @signature = receiver_class, method_name, signature
|
15
|
+
end
|
16
|
+
|
17
|
+
# Insert method into a registry
|
18
|
+
|
19
|
+
def eql? other
|
20
|
+
receiver_class == other.receiver_class && method_name == other.method_name && signature == other.signature
|
21
|
+
end
|
22
|
+
|
23
|
+
def insert registry
|
24
|
+
raise "Duplicate method #{method_name} on #{receiver_class}" if registry.methods.any? { |method| eql? method }
|
25
|
+
raise "Method #{method_name} must be a #{'non-' if split?}split method" unless [nil, split?].include? registry.split?(method_name)
|
26
|
+
raise "Method #{method_name} must be a #{'non-' if block?}block method" unless [nil, block?].include? registry.block?(method_name)
|
27
|
+
registry.methods << self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Setup method and insert into default registry
|
31
|
+
|
32
|
+
def register method_name, *signature, on: Template::Context
|
33
|
+
setup on, method_name, signature
|
34
|
+
insert Registry.instance
|
35
|
+
end
|
36
|
+
|
37
|
+
# Subclass, setup and insert into registry
|
38
|
+
|
39
|
+
def implement receiver_class, method_name, signature, registry, as: nil, to: nil, cast: nil, returns: nil, split: nil
|
40
|
+
Class.new(self) do
|
41
|
+
setup receiver_class, method_name, signature
|
42
|
+
|
43
|
+
raise "Received multiple implementation options for method" unless [as, to, cast, returns].compact.size <= 1
|
44
|
+
raise "Method option :as requires String, got #{as.inspect}" unless as.nil? || as.is_a?(String)
|
45
|
+
raise "Method option :to requires Proc, got #{to.inspect}" unless to.nil? || to.is_a?(Proc)
|
46
|
+
raise "Method option :cast must be 'to_boolean' or 'to_string'" unless [nil, 'to_boolean', 'to_string'].include? cast
|
47
|
+
raise "Method option :split must be true" unless [nil, true].include? split
|
48
|
+
|
49
|
+
# Redefine split?
|
50
|
+
define_singleton_method :split? do
|
51
|
+
true
|
52
|
+
end if split
|
53
|
+
|
54
|
+
# Implement method
|
55
|
+
send :define_method, method_name do |*args|
|
56
|
+
if to
|
57
|
+
@receiver.instance_exec *args, &to
|
58
|
+
elsif cast
|
59
|
+
registry.evaluate method_name, registry.send(cast, @receiver), args, @call, @context
|
60
|
+
elsif !returns.nil?
|
61
|
+
returns
|
62
|
+
else
|
63
|
+
@receiver.send (as || method_name), *args
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end.insert registry
|
67
|
+
end
|
68
|
+
|
69
|
+
# Default split and block
|
70
|
+
|
71
|
+
def split?; false; end
|
72
|
+
def block?; false; end
|
73
|
+
|
74
|
+
# Arity
|
75
|
+
|
76
|
+
def min_arity
|
77
|
+
@min_arity ||= signature.reduce 0 do |min_arity, element|
|
78
|
+
element.is_a?(Array) ? min_arity : min_arity + 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def max_arity
|
83
|
+
@max_arity ||= signature.reduce 0 do |max_arity, element|
|
84
|
+
if max_arity
|
85
|
+
element.is_a?(Array) ? element[1] && max_arity + element[1] : max_arity + 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Scribble
|
2
|
+
module Methods
|
3
|
+
class If < Block
|
4
|
+
register :if, Object
|
5
|
+
|
6
|
+
def if object
|
7
|
+
@paths = []
|
8
|
+
send :elsif, object
|
9
|
+
|
10
|
+
render(nodes: @paths.map { |condition, nodes| nodes if condition }.compact.first || [])
|
11
|
+
end
|
12
|
+
|
13
|
+
method :elsif, Object, split: true
|
14
|
+
|
15
|
+
def elsif object
|
16
|
+
@paths.unshift [Registry.to_boolean(object), split_nodes]
|
17
|
+
end
|
18
|
+
|
19
|
+
method :else, split: true
|
20
|
+
|
21
|
+
def else
|
22
|
+
@paths.unshift [true, nodes]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Scribble
|
2
|
+
module Methods
|
3
|
+
class Layout < Block
|
4
|
+
register :layout, String, [Object, 1]
|
5
|
+
|
6
|
+
def layout name, object = nil
|
7
|
+
template.load(name, self).tap do |partial_context|
|
8
|
+
raise Errors::NotFound.new("Layout partial '#{name}' not found #{@call.line_and_column}") if partial_context.nil?
|
9
|
+
partial_context.set_variable name.to_sym, object if object
|
10
|
+
@render_format = partial_context.format
|
11
|
+
end.render
|
12
|
+
end
|
13
|
+
|
14
|
+
method :content
|
15
|
+
|
16
|
+
def content
|
17
|
+
render context: @context
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_format
|
21
|
+
@render_format || super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Scribble
|
2
|
+
module Methods
|
3
|
+
class Partial < Method
|
4
|
+
register :partial, String, [Object, 1]
|
5
|
+
|
6
|
+
def partial name, object = nil
|
7
|
+
@context.template.load(name, @context).tap do |partial_context|
|
8
|
+
raise Errors::NotFound.new("Partial '#{name}' not found #{@call.line_and_column}") if partial_context.nil?
|
9
|
+
partial_context.set_variable name.to_sym, object if object
|
10
|
+
end.render
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Scribble
|
2
|
+
module Nodes
|
3
|
+
class Call < Node
|
4
|
+
attr_reader :name, :args, :allow_variable
|
5
|
+
|
6
|
+
attr_accessor :receiver, :nodes
|
7
|
+
|
8
|
+
def initialize slice, name, args: [], receiver: nil, allow_variable: false
|
9
|
+
super slice
|
10
|
+
@name, @args, @receiver, @allow_variable = name, args, receiver, allow_variable
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate context, allow_block: true, allow_split: false
|
14
|
+
disallow_split unless allow_split
|
15
|
+
disallow_block unless allow_block
|
16
|
+
|
17
|
+
if @receiver
|
18
|
+
Registry.evaluate @name, evaluated_receiver(context), evaluated_args(context), self, context
|
19
|
+
else
|
20
|
+
context.evaluate self, evaluated_args(context), context
|
21
|
+
end
|
22
|
+
rescue Errors::UnlocatedArgument => e
|
23
|
+
raise Errors::Argument.new("#{e.message} #{line_and_column}")
|
24
|
+
rescue Support::Unmatched => e
|
25
|
+
raise e.to_error self
|
26
|
+
end
|
27
|
+
|
28
|
+
def block?
|
29
|
+
Registry.block? name
|
30
|
+
end
|
31
|
+
|
32
|
+
def split?
|
33
|
+
Registry.split? name
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def disallow_split
|
39
|
+
raise Errors::Syntax.new "Unexpected '#{name}' #{line_and_column}" if split?
|
40
|
+
end
|
41
|
+
|
42
|
+
def disallow_block
|
43
|
+
raise Errors::Syntax.new "Unexpected '#{name}' #{line_and_column}; block methods can't be arguments" if block?
|
44
|
+
end
|
45
|
+
|
46
|
+
def evaluated_receiver context
|
47
|
+
@evaluated_receiver ||= @receiver.evaluate context, allow_block: false
|
48
|
+
end
|
49
|
+
|
50
|
+
def evaluated_args context
|
51
|
+
@evaluated_args ||= args.map { |arg| arg.evaluate context, allow_block: false }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Scribble
|
2
|
+
module Nodes
|
3
|
+
class Node
|
4
|
+
attr_reader :slice
|
5
|
+
|
6
|
+
def initialize slice
|
7
|
+
@slice = slice
|
8
|
+
end
|
9
|
+
|
10
|
+
def block?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def split?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def line_and_column
|
19
|
+
line, column = slice.line_and_column
|
20
|
+
"at line #{line} column #{column}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Scribble
|
2
|
+
Registry.for TrueClass do
|
3
|
+
method :equals, TrueClass, returns: true
|
4
|
+
method :equals, FalseClass, returns: false
|
5
|
+
method :differs, TrueClass, returns: false
|
6
|
+
method :differs, FalseClass, returns: true
|
7
|
+
end
|
8
|
+
|
9
|
+
Registry.for FalseClass do
|
10
|
+
method :equals, TrueClass, returns: false
|
11
|
+
method :equals, FalseClass, returns: true
|
12
|
+
method :differs, TrueClass, returns: true
|
13
|
+
method :differs, FalseClass, returns: false
|
14
|
+
end
|
15
|
+
|
16
|
+
Registry.for TrueClass, FalseClass do
|
17
|
+
name 'boolean'
|
18
|
+
|
19
|
+
to_boolean { self }
|
20
|
+
to_string { to_s }
|
21
|
+
|
22
|
+
# Logical operators
|
23
|
+
method :or, Object, to: ->(object) { self | Scribble::Registry.to_boolean(object) }
|
24
|
+
method :and, Object, to: ->(object) { self & Scribble::Registry.to_boolean(object) }
|
25
|
+
|
26
|
+
# Equality
|
27
|
+
method :equals, Object, returns: false
|
28
|
+
method :differs, Object, returns: true
|
29
|
+
|
30
|
+
# Unary not
|
31
|
+
method :not, to: -> { !self }
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Scribble
|
2
|
+
Registry.for Fixnum do
|
3
|
+
name 'number'
|
4
|
+
|
5
|
+
to_boolean { !zero? }
|
6
|
+
to_string { to_s }
|
7
|
+
|
8
|
+
# Logical operators
|
9
|
+
method :or, Object, cast: 'to_boolean'
|
10
|
+
method :and, Object, cast: 'to_boolean'
|
11
|
+
|
12
|
+
# Equality
|
13
|
+
method :equals, Fixnum, as: '=='
|
14
|
+
method :differs, Fixnum, as: '!='
|
15
|
+
method :equals, Object, returns: false
|
16
|
+
method :differs, Object, returns: true
|
17
|
+
|
18
|
+
# Comparisons
|
19
|
+
method :greater, Fixnum, as: '>'
|
20
|
+
method :greater_or_equal, Fixnum, as: '>='
|
21
|
+
method :less, Fixnum, as: '<'
|
22
|
+
method :less_or_equal, Fixnum, as: '<='
|
23
|
+
|
24
|
+
# Calculations
|
25
|
+
method :add, Fixnum, as: '+'
|
26
|
+
method :subtract, Fixnum, as: '-'
|
27
|
+
method :multiply, Fixnum, as: '*'
|
28
|
+
method :remainder, Fixnum, as: '%'
|
29
|
+
|
30
|
+
method :divide, Fixnum, to: ->(n) {
|
31
|
+
begin
|
32
|
+
self / n
|
33
|
+
rescue ZeroDivisionError
|
34
|
+
raise Errors::UnlocatedArgument.new "Division by zero"
|
35
|
+
end
|
36
|
+
}
|
37
|
+
|
38
|
+
# Unary operators
|
39
|
+
method :negative, to: -> { -self }
|
40
|
+
method :not, cast: 'to_boolean'
|
41
|
+
|
42
|
+
# String manipulations
|
43
|
+
method :add, String, cast: 'to_string'
|
44
|
+
method :multiply, String, to: ->(string) { Support::Utilities.repeat string, self }
|
45
|
+
|
46
|
+
# Unary methods
|
47
|
+
method :abs
|
48
|
+
method :odd, as: 'odd?'
|
49
|
+
method :even, as: 'even?'
|
50
|
+
method :zero, as: 'zero?'
|
51
|
+
method :nonzero, to: -> { self != 0 }
|
52
|
+
end
|
53
|
+
end
|