liquid-c 3.0.0 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f2140b7f3e49dc914ab2d474dee3bca94c6f5923
4
- data.tar.gz: d0d2ef0d9a4ab5d291b560e9d35c3391a7b456d8
3
+ metadata.gz: c625a10bf29068a8d124df52ea012868d936f2ea
4
+ data.tar.gz: 537c2e49a4a6b65c693e7f51d2432d0f969dcb3f
5
5
  SHA512:
6
- metadata.gz: b019264cea1628bbdb4194685d660a378107ce68561a549324d7171f088491219fa0b95970175cb9aa7078e68c2603c58dde78a5e61beac03b0cd49d2dc8082e
7
- data.tar.gz: ddf7b182c306787f2c31451b3af77f6dae6b22c83c79fddfbaba5e4243a1f54c3419607f3e4629a24fd13bc79ea1d462ed1c07eb77e3424094bdaf57bddb19c0
6
+ metadata.gz: 5390b71dc89234cd139d6e3234473cc00c200e37380ba8a140c1e49c58fe1d7db68dd98ee79d8d023cf4994c9c7b472031504bde9b2bc27312f9f0bb04a56902
7
+ data.tar.gz: 99565b5eb0e6458afa0b9d7608b67160b1972892c6fa2bf2a4850eca80428b25f49743a57e9380ce6294448442f462109c917c7a702f647597a78d4443215402
data/.travis.yml CHANGED
@@ -1,6 +1,12 @@
1
1
  language: ruby
2
+
2
3
  rvm:
3
- - 2.0.0
4
- - 2.1.0
5
- gemfile:
6
- - Gemfile
4
+ - 2.0
5
+ - 2.1
6
+ - 2.2
7
+ - ruby-head
8
+
9
+ sudo: false
10
+
11
+ notifications:
12
+ disable: true
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ gemspec
4
4
 
5
5
  gem 'liquid', github: 'Shopify/liquid', branch: 'master'
6
6
 
7
+
7
8
  group :test do
8
9
  gem 'spy', '0.4.1'
9
10
  gem 'benchmark-ips'
data/ext/liquid_c/block.c CHANGED
@@ -9,9 +9,10 @@ static ID
9
9
  intern_blank,
10
10
  intern_is_blank,
11
11
  intern_clear,
12
- intern_tags,
12
+ intern_registered_tags,
13
13
  intern_parse,
14
- intern_square_brackets;
14
+ intern_square_brackets,
15
+ intern_set_line_number;
15
16
 
16
17
  static int is_id(int c)
17
18
  {
@@ -24,6 +25,14 @@ inline static const char *read_while(const char *start, const char *end, int (fu
24
25
  return start;
25
26
  }
26
27
 
28
+ inline static const char *read_while_end(const char *start, const char *end, int (func)(int))
29
+ {
30
+ end--;
31
+ while (start < end && func((unsigned char) *end)) end--;
32
+ end++;
33
+ return end;
34
+ }
35
+
27
36
  static VALUE rb_block_parse(VALUE self, VALUE tokens, VALUE options)
28
37
  {
29
38
  tokenizer_t *tokenizer;
@@ -34,6 +43,9 @@ static VALUE rb_block_parse(VALUE self, VALUE tokens, VALUE options)
34
43
  VALUE nodelist = rb_ivar_get(self, intern_nodelist);
35
44
 
36
45
  while (true) {
46
+ if (tokenizer->line_number != 0) {
47
+ rb_funcall(options, intern_set_line_number, 1, UINT2NUM(tokenizer->line_number));
48
+ }
37
49
  tokenizer_next(tokenizer, &token);
38
50
 
39
51
  switch (token.type) {
@@ -51,7 +63,14 @@ static VALUE rb_block_parse(VALUE self, VALUE tokens, VALUE options)
51
63
  }
52
64
  case TOKEN_RAW:
53
65
  {
54
- VALUE str = rb_enc_str_new(token.str, token.length, utf8_encoding);
66
+ const char *start = token.str, *end = token.str + token.length, *token_start = start, *token_end = end;
67
+
68
+ if(token.lstrip)
69
+ token_start = read_while(start, end, rb_isspace);
70
+ if(token.rstrip)
71
+ token_end = read_while_end(token_start, end, rb_isspace);
72
+
73
+ VALUE str = rb_enc_str_new(token_start, token_end - token_start, utf8_encoding);
55
74
  rb_ary_push(nodelist, str);
56
75
 
57
76
  if (rb_ivar_get(self, intern_blank) == Qtrue) {
@@ -64,7 +83,7 @@ static VALUE rb_block_parse(VALUE self, VALUE tokens, VALUE options)
64
83
  }
65
84
  case TOKEN_VARIABLE:
66
85
  {
67
- VALUE args[2] = {rb_enc_str_new(token.str + 2, token.length - 4, utf8_encoding), options};
86
+ VALUE args[2] = {rb_enc_str_new(token.str + 2 + token.lstrip, token.length - 4 - token.lstrip - token.rstrip, utf8_encoding), options};
68
87
  VALUE var = rb_class_new_instance(2, args, cLiquidVariable);
69
88
  rb_ary_push(nodelist, var);
70
89
  rb_ivar_set(self, intern_blank, Qfalse);
@@ -72,7 +91,7 @@ static VALUE rb_block_parse(VALUE self, VALUE tokens, VALUE options)
72
91
  }
73
92
  case TOKEN_TAG:
74
93
  {
75
- const char *start = token.str + 2, *end = token.str + token.length - 2;
94
+ const char *start = token.str + 2 + token.lstrip, *end = token.str + token.length - 2 - token.rstrip;
76
95
 
77
96
  // Imitate \s*(\w+)\s*(.*)? regex
78
97
  const char *name_start = read_while(start, end, rb_isspace);
@@ -81,7 +100,7 @@ static VALUE rb_block_parse(VALUE self, VALUE tokens, VALUE options)
81
100
  VALUE tag_name = rb_enc_str_new(name_start, name_end - name_start, utf8_encoding);
82
101
 
83
102
  if (tags == Qnil)
84
- tags = rb_funcall(cLiquidTemplate, intern_tags, 0);
103
+ tags = rb_funcall(self, intern_registered_tags, 0);
85
104
 
86
105
  VALUE tag_class = rb_funcall(tags, intern_square_brackets, 1, tag_name);
87
106
 
@@ -112,9 +131,10 @@ void init_liquid_block()
112
131
  intern_blank = rb_intern("@blank");
113
132
  intern_is_blank = rb_intern("blank?");
114
133
  intern_clear = rb_intern("clear");
115
- intern_tags = rb_intern("tags");
134
+ intern_registered_tags = rb_intern("registered_tags");
116
135
  intern_parse = rb_intern("parse");
117
136
  intern_square_brackets = rb_intern("[]");
137
+ intern_set_line_number = rb_intern("line_number=");
118
138
 
119
139
  VALUE cLiquidBlockBody = rb_const_get(mLiquid, rb_intern("BlockBody"));
120
140
  rb_define_method(cLiquidBlockBody, "c_parse", rb_block_parse, 2);
@@ -5,13 +5,14 @@
5
5
  #include "parser.h"
6
6
  #include "block.h"
7
7
 
8
- VALUE mLiquid, cLiquidSyntaxError, cLiquidVariable, cLiquidTemplate;
8
+ VALUE mLiquid, mLiquidC, cLiquidSyntaxError, cLiquidVariable, cLiquidTemplate;
9
9
  rb_encoding *utf8_encoding;
10
10
 
11
11
  void Init_liquid_c(void)
12
12
  {
13
13
  utf8_encoding = rb_utf8_encoding();
14
14
  mLiquid = rb_define_module("Liquid");
15
+ mLiquidC = rb_define_module_under(mLiquid, "C");
15
16
  cLiquidSyntaxError = rb_const_get(mLiquid, rb_intern("SyntaxError"));
16
17
  cLiquidVariable = rb_const_get(mLiquid, rb_intern("Variable"));
17
18
  cLiquidTemplate = rb_const_get(mLiquid, rb_intern("Template"));
@@ -5,7 +5,7 @@
5
5
  #include <ruby/encoding.h>
6
6
  #include <stdbool.h>
7
7
 
8
- extern VALUE mLiquid, cLiquidSyntaxError, cLiquidVariable, cLiquidTemplate;
8
+ extern VALUE mLiquid, mLiquidC, cLiquidSyntaxError, cLiquidVariable, cLiquidTemplate;
9
9
  extern rb_encoding *utf8_encoding;
10
10
 
11
11
  #endif
@@ -2,7 +2,7 @@
2
2
  #include "parser.h"
3
3
  #include "lexer.h"
4
4
 
5
- static VALUE cLiquidRangeLookup, cLiquidVariableLookup, cRange, symBlank, symEmpty;
5
+ static VALUE cLiquidRangeLookup, cLiquidVariableLookup, cRange, vLiquidExpressionLiterals;
6
6
  static ID idToI, idEvaluate;
7
7
 
8
8
  void init_parser(parser_t *p, const char *str, const char *end)
@@ -119,12 +119,10 @@ static VALUE parse_variable(parser_t *p)
119
119
  }
120
120
  }
121
121
 
122
- if (RARRAY_LEN(lookups) == 0 && TYPE(name) == T_STRING) {
123
- if (rstring_eq(name, "nil") || rstring_eq(name, "null")) return Qnil;
124
- if (rstring_eq(name, "true")) return Qtrue;
125
- if (rstring_eq(name, "false")) return Qfalse;
126
- if (rstring_eq(name, "blank")) return symBlank;
127
- if (rstring_eq(name, "empty")) return symEmpty;
122
+ if (RARRAY_LEN(lookups) == 0) {
123
+ VALUE undefined = FIXNUM_P(-1);
124
+ VALUE literal = rb_hash_lookup2(vLiquidExpressionLiterals, name, undefined);
125
+ if (literal != undefined) return literal;
128
126
  }
129
127
 
130
128
  VALUE args[4] = {Qfalse, name, lookups, INT2FIX(command_flags)};
@@ -185,8 +183,6 @@ void init_liquid_parser(void)
185
183
  {
186
184
  idToI = rb_intern("to_i");
187
185
  idEvaluate = rb_intern("evaluate");
188
- symBlank = ID2SYM(rb_intern("blank?"));
189
- symEmpty = ID2SYM(rb_intern("empty?"));
190
186
 
191
187
  cLiquidRangeLookup = rb_const_get(mLiquid, rb_intern("RangeLookup"));
192
188
  cRange = rb_const_get(rb_cObject, rb_intern("Range"));
@@ -194,5 +190,7 @@ void init_liquid_parser(void)
194
190
 
195
191
  VALUE cLiquidExpression = rb_const_get(mLiquid, rb_intern("Expression"));
196
192
  rb_define_singleton_method(cLiquidExpression, "c_parse", rb_parse_expression, 1);
193
+
194
+ vLiquidExpressionLiterals = rb_const_get(cLiquidExpression, rb_intern("LITERALS"));
197
195
  }
198
196
 
@@ -38,7 +38,7 @@ static VALUE tokenizer_allocate(VALUE klass)
38
38
  return obj;
39
39
  }
40
40
 
41
- static VALUE tokenizer_initialize_method(VALUE self, VALUE source)
41
+ static VALUE tokenizer_initialize_method(VALUE self, VALUE source, VALUE line_numbers)
42
42
  {
43
43
  tokenizer_t *tokenizer;
44
44
 
@@ -48,6 +48,10 @@ static VALUE tokenizer_initialize_method(VALUE self, VALUE source)
48
48
  tokenizer->source = source;
49
49
  tokenizer->cursor = RSTRING_PTR(source);
50
50
  tokenizer->length = RSTRING_LEN(source);
51
+ tokenizer->lstrip_flag = 0;
52
+ // tokenizer->line_number keeps track of the current line number or it is 0
53
+ // to indicate that line numbers aren't being calculated
54
+ tokenizer->line_number = RTEST(line_numbers) ? 1 : 0;
51
55
  return Qnil;
52
56
  }
53
57
 
@@ -63,6 +67,8 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
63
67
 
64
68
  token->str = cursor;
65
69
  token->type = TOKEN_RAW;
70
+ token->lstrip = 0;
71
+ token->rstrip = 0;
66
72
 
67
73
  while (cursor < last) {
68
74
  if (*cursor++ != '{')
@@ -71,12 +77,20 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
71
77
  char c = *cursor++;
72
78
  if (c != '%' && c != '{')
73
79
  continue;
74
- if (cursor - tokenizer->cursor > 2) {
80
+ if (cursor <= last && *cursor == '-') {
81
+ cursor++;
82
+ token->rstrip = 1;
83
+ }
84
+ if (cursor - tokenizer->cursor > (ptrdiff_t)(2 + token->rstrip)) {
75
85
  token->type = TOKEN_RAW;
76
- cursor -= 2;
86
+ cursor -= 2 + token->rstrip;
87
+ token->lstrip = tokenizer->lstrip_flag;
88
+ tokenizer->lstrip_flag = 0;
77
89
  goto found;
78
90
  }
79
91
  token->type = TOKEN_INVALID;
92
+ token->lstrip = token->rstrip;
93
+ token->rstrip = 0;
80
94
  if (c == '%') {
81
95
  while (cursor < last) {
82
96
  if (*cursor++ != '%')
@@ -87,10 +101,13 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
87
101
  if (c != '}')
88
102
  continue;
89
103
  token->type = TOKEN_TAG;
104
+ if(cursor[-3] == '-')
105
+ token->rstrip = tokenizer->lstrip_flag = 1;
90
106
  goto found;
91
107
  }
92
108
  // unterminated tag
93
109
  cursor = tokenizer->cursor + 2;
110
+ tokenizer->lstrip_flag = 0;
94
111
  goto found;
95
112
  } else {
96
113
  while (cursor < last) {
@@ -102,18 +119,33 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
102
119
  goto found;
103
120
  }
104
121
  token->type = TOKEN_VARIABLE;
122
+ if(cursor[-3] == '-')
123
+ token->rstrip = tokenizer->lstrip_flag = 1;
105
124
  goto found;
106
125
  }
107
126
  // unterminated variable
108
127
  cursor = tokenizer->cursor + 2;
128
+ tokenizer->lstrip_flag = 0;
109
129
  goto found;
110
130
  }
111
131
  }
112
132
  cursor = last + 1;
133
+ token->lstrip = tokenizer->lstrip_flag;
134
+ tokenizer->lstrip_flag = 0;
113
135
  found:
114
136
  token->length = cursor - tokenizer->cursor;
115
137
  tokenizer->cursor += token->length;
116
138
  tokenizer->length -= token->length;
139
+
140
+ if (tokenizer->line_number) {
141
+ const char *cursor = token->str;
142
+ const char *end = token->str + token->length;
143
+ while (cursor < end) {
144
+ if (*cursor == '\n')
145
+ tokenizer->line_number++;
146
+ cursor++;
147
+ }
148
+ }
117
149
  }
118
150
 
119
151
  static VALUE tokenizer_shift_method(VALUE self)
@@ -129,11 +161,23 @@ static VALUE tokenizer_shift_method(VALUE self)
129
161
  return rb_enc_str_new(token.str, token.length, utf8_encoding);
130
162
  }
131
163
 
164
+ static VALUE tokenizer_line_number_method(VALUE self)
165
+ {
166
+ tokenizer_t *tokenizer;
167
+ Tokenizer_Get_Struct(self, tokenizer);
168
+
169
+ if (tokenizer->line_number == 0)
170
+ return Qnil;
171
+
172
+ return UINT2NUM(tokenizer->line_number);
173
+ }
174
+
132
175
  void init_liquid_tokenizer()
133
176
  {
134
- cLiquidTokenizer = rb_define_class_under(mLiquid, "Tokenizer", rb_cObject);
177
+ cLiquidTokenizer = rb_define_class_under(mLiquidC, "Tokenizer", rb_cObject);
135
178
  rb_define_alloc_func(cLiquidTokenizer, tokenizer_allocate);
136
- rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 1);
179
+ rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 2);
137
180
  rb_define_method(cLiquidTokenizer, "shift", tokenizer_shift_method, 0);
181
+ rb_define_method(cLiquidTokenizer, "line_number", tokenizer_line_number_method, 0);
138
182
  }
139
183
 
@@ -13,12 +13,16 @@ typedef struct token {
13
13
  enum token_type type;
14
14
  const char *str;
15
15
  long length;
16
+ unsigned int lstrip;
17
+ unsigned int rstrip;
16
18
  } token_t;
17
19
 
18
20
  typedef struct tokenizer {
19
21
  VALUE source;
20
22
  const char *cursor;
21
23
  long length;
24
+ unsigned int line_number;
25
+ unsigned int lstrip_flag;
22
26
  } tokenizer_t;
23
27
 
24
28
  extern VALUE cLiquidTokenizer;
data/lib/liquid/c.rb CHANGED
@@ -12,16 +12,12 @@ module Liquid
12
12
  end
13
13
  end
14
14
 
15
- Liquid::Template.class_eval do
16
- private
17
-
18
- alias_method :ruby_tokenize, :tokenize
19
-
20
- def tokenize(source)
21
- if Liquid::C.enabled && !@line_numbers
22
- Liquid::Tokenizer.new(source.to_s)
15
+ Liquid::Tokenizer.class_eval do
16
+ def self.new(source, line_numbers = false)
17
+ if Liquid::C.enabled
18
+ Liquid::C::Tokenizer.new(source.to_s, line_numbers)
23
19
  else
24
- ruby_tokenize(source)
20
+ super
25
21
  end
26
22
  end
27
23
  end
@@ -30,7 +26,7 @@ Liquid::BlockBody.class_eval do
30
26
  alias_method :ruby_parse, :parse
31
27
 
32
28
  def parse(tokens, options)
33
- if Liquid::C.enabled && !options[:line_numbers] && !options[:profile]
29
+ if Liquid::C.enabled && !options[:profile]
34
30
  c_parse(tokens, options) { |t, m| yield t, m }
35
31
  else
36
32
  ruby_parse(tokens, options) { |t, m| yield t, m }
@@ -43,7 +39,7 @@ Liquid::Variable.class_eval do
43
39
  alias_method :ruby_strict_parse, :strict_parse
44
40
 
45
41
  def lax_parse(markup)
46
- stats = @options[:stats_callbacks]
42
+ stats = options[:stats_callbacks]
47
43
  stats[:variable_parse].call if stats
48
44
 
49
45
  if Liquid::C.enabled
@@ -1,5 +1,5 @@
1
1
  module Liquid
2
2
  module C
3
- VERSION = "3.0.0"
3
+ VERSION = "4.0.0.rc1"
4
4
  end
5
5
  end
data/liquid-c.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.name = "liquid-c"
8
8
  spec.version = Liquid::C::VERSION
9
9
  spec.authors = ["Justin Li", "Dylan Thacker-Smith"]
10
- spec.email = ["jli@shopify.com", "Dylan.Smith@shopify.com"]
10
+ spec.email = ["gems@shopify.com"]
11
11
  spec.summary = "Liquid performance extension in C"
12
12
  spec.homepage = ""
13
13
  spec.license = "MIT"
@@ -23,5 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "bundler", "~> 1.5"
24
24
  spec.add_development_dependency "rake"
25
25
  spec.add_development_dependency 'rake-compiler'
26
+ spec.add_development_dependency 'minitest'
26
27
  spec.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
27
28
  end
@@ -33,7 +33,7 @@ class TokenizerTest < MiniTest::Unit::TestCase
33
33
  private
34
34
 
35
35
  def tokenize(source)
36
- tokenizer = Liquid::Tokenizer.new(source)
36
+ tokenizer = Liquid::C::Tokenizer.new(source, false)
37
37
  tokens = []
38
38
  while t = tokenizer.shift
39
39
  tokens << t
@@ -35,7 +35,7 @@ class VariableTest < MiniTest::Unit::TestCase
35
35
  assert_equal [123.4, []], variable_parse('123.4')
36
36
 
37
37
  assert_equal [lookup('[blank]'), []], variable_parse('[blank]')
38
- assert_equal [lookup(false, true, [:blank?], 0), []], variable_parse('[true][blank]')
38
+ assert_equal [lookup(false, true, [Liquid::Expression::LITERALS['blank']], 0), []], variable_parse('[true][blank]')
39
39
  assert_equal [lookup('[true][blank]'), []], variable_parse('[true][blank]')
40
40
  assert_equal [lookup('x["size"]'), []], variable_parse('x["size"]')
41
41
  end
@@ -84,17 +84,21 @@ class VariableTest < MiniTest::Unit::TestCase
84
84
  variable_fallback: lambda { variable_fallbacks += 1 }
85
85
  }
86
86
 
87
- Liquid::Variable.new('abc', error_mode: :lax, stats_callbacks: callbacks)
87
+ create_variable('abc', error_mode: :lax, stats_callbacks: callbacks)
88
88
  assert_equal 1, variable_parses
89
89
  assert_equal 0, variable_fallbacks
90
90
 
91
- Liquid::Variable.new('@!#', error_mode: :lax, stats_callbacks: callbacks)
91
+ create_variable('@!#', error_mode: :lax, stats_callbacks: callbacks)
92
92
  assert_equal 2, variable_parses
93
93
  assert_equal 1, variable_fallbacks
94
94
  end
95
95
 
96
96
  private
97
97
 
98
+ def create_variable(markup, options={})
99
+ Liquid::Variable.new(markup, Liquid::ParseContext.new(options))
100
+ end
101
+
98
102
  def variable_parse(markup)
99
103
  name = Liquid::Variable.c_strict_parse(markup, filters = [])
100
104
  [name, filters]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid-c
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 4.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Li
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-01-31 00:00:00.000000000 Z
12
+ date: 2016-09-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: liquid
@@ -67,6 +67,20 @@ dependencies:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: minitest
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
70
84
  - !ruby/object:Gem::Dependency
71
85
  name: stackprof
72
86
  requirement: !ruby/object:Gem::Requirement
@@ -83,8 +97,7 @@ dependencies:
83
97
  version: '0'
84
98
  description:
85
99
  email:
86
- - jli@shopify.com
87
- - Dylan.Smith@shopify.com
100
+ - gems@shopify.com
88
101
  executables: []
89
102
  extensions:
90
103
  - ext/liquid_c/extconf.rb
@@ -132,12 +145,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
145
  version: '0'
133
146
  required_rubygems_version: !ruby/object:Gem::Requirement
134
147
  requirements:
135
- - - ">="
148
+ - - ">"
136
149
  - !ruby/object:Gem::Version
137
- version: '0'
150
+ version: 1.3.1
138
151
  requirements: []
139
152
  rubyforge_project:
140
- rubygems_version: 2.2.2
153
+ rubygems_version: 2.4.5
141
154
  signing_key:
142
155
  specification_version: 4
143
156
  summary: Liquid performance extension in C