scribble 1.0.0
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.
- 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
|