scribble 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +455 -0
  6. data/Rakefile +2 -0
  7. data/lib/scribble.rb +44 -0
  8. data/lib/scribble/block.rb +25 -0
  9. data/lib/scribble/converter.rb +10 -0
  10. data/lib/scribble/errors.rb +24 -0
  11. data/lib/scribble/method.rb +91 -0
  12. data/lib/scribble/methods/if.rb +26 -0
  13. data/lib/scribble/methods/layout.rb +25 -0
  14. data/lib/scribble/methods/partial.rb +14 -0
  15. data/lib/scribble/methods/times.rb +11 -0
  16. data/lib/scribble/nodes/call.rb +55 -0
  17. data/lib/scribble/nodes/ending.rb +6 -0
  18. data/lib/scribble/nodes/node.rb +24 -0
  19. data/lib/scribble/nodes/value.rb +16 -0
  20. data/lib/scribble/objects/boolean.rb +33 -0
  21. data/lib/scribble/objects/fixnum.rb +53 -0
  22. data/lib/scribble/objects/nil.rb +21 -0
  23. data/lib/scribble/objects/string.rb +62 -0
  24. data/lib/scribble/parsing/nester.rb +49 -0
  25. data/lib/scribble/parsing/parser.rb +132 -0
  26. data/lib/scribble/parsing/reporter.rb +71 -0
  27. data/lib/scribble/parsing/transform.rb +87 -0
  28. data/lib/scribble/partial.rb +41 -0
  29. data/lib/scribble/registry.rb +95 -0
  30. data/lib/scribble/support/context.rb +98 -0
  31. data/lib/scribble/support/matcher.rb +74 -0
  32. data/lib/scribble/support/unmatched.rb +70 -0
  33. data/lib/scribble/support/utilities.rb +49 -0
  34. data/lib/scribble/template.rb +61 -0
  35. data/lib/scribble/version.rb +3 -0
  36. data/scribble.gemspec +22 -0
  37. data/test/all.rb +23 -0
  38. data/test/errors_test.rb +94 -0
  39. data/test/methods/if_test.rb +49 -0
  40. data/test/methods/layout_test.rb +71 -0
  41. data/test/methods/partial_test.rb +85 -0
  42. data/test/methods/times_test.rb +10 -0
  43. data/test/objects/boolean_test.rb +162 -0
  44. data/test/objects/fixnum_test.rb +236 -0
  45. data/test/objects/nil_test.rb +83 -0
  46. data/test/objects/string_test.rb +268 -0
  47. data/test/parsing/parsing_test.rb +234 -0
  48. data/test/registry_test.rb +264 -0
  49. data/test/template_test.rb +51 -0
  50. data/test/test_helper.rb +65 -0
  51. metadata +127 -0
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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,11 @@
1
+ module Scribble
2
+ module Methods
3
+ class Times < Block
4
+ register :times, on: Fixnum
5
+
6
+ def times
7
+ render * @receiver
8
+ end
9
+ end
10
+ end
11
+ 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,6 @@
1
+ module Scribble
2
+ module Nodes
3
+ class Ending < Node
4
+ end
5
+ end
6
+ 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,16 @@
1
+ module Scribble
2
+ module Nodes
3
+ class Value < Node
4
+ attr_reader :value
5
+
6
+ def initialize slice, value
7
+ super(slice)
8
+ @value = value
9
+ end
10
+
11
+ def evaluate context, **options
12
+ @value
13
+ end
14
+ end
15
+ end
16
+ 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