twig_ruby 0.0.8 → 0.0.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48285eaf20e2ccc979242da5a4441263cfc17cbf8acdec2e4f7e28ce22b21f5d
4
- data.tar.gz: e2e60724e210affecd7bb85075e1f21190ad224e15c9f4bbc581a49f58c6fb76
3
+ metadata.gz: 0c74aeae1c40b1a21026a07bf01374c5065997a0005025d5bd208111736d455b
4
+ data.tar.gz: 9e50006a3dceda02a14e3c2f0c5098480eb7f527d55152671d1dc2c0a5d3d871
5
5
  SHA512:
6
- metadata.gz: 9107303138c19f49920a2c969b1ce45623ce0e7ce2013ec98ebab9e1204bf66b97898bd3158ad289d441c555dfc7ecd26f40669ae2a77c85511d23204ef02eb5
7
- data.tar.gz: a202be7e8ef2c866fbe1c669d206e2e5a1647aa58947f5fa62176b6aaf608431e061075c4568679c93b1555d9ea86d993231874f6cc59b3d768b703eb45f520f
6
+ metadata.gz: dd3aa6fe4c411313f4ebaad61001870ccbf0c6fd4c9029941233e6f562c1a6c2d20142394bd80ac46bd8fefb727cece46a73353b7c2a90750d5f31c8a3bde335
7
+ data.tar.gz: 2dbb3fd08c278d2413de33d8a2b995dab7b40fa0f0ab7c4cbb2df1491d8db36f7ef66352ab434f0aec32e40d95687f669074a22a14875f11c02720fab64ec300
@@ -5,6 +5,10 @@ module Twig
5
5
  class Core < Base
6
6
  DEFAULT_TRIM_CHARS = " \t\n\r\0\x0B"
7
7
 
8
+ # Returned by subscript_key when nothing matched. A sentinel rather than
9
+ # nil, because nil is a perfectly good hash key and a perfectly good value.
10
+ KEY_NOT_FOUND = Object.new.freeze
11
+
8
12
  class << self
9
13
  include ActiveSupport::NumberHelper
10
14
  end
@@ -860,25 +864,64 @@ module Twig
860
864
  raise Error::Runtime, "Invalid regular expression passed to matches: #{e.message}"
861
865
  end
862
866
 
867
+ # The key or index that +attribute+ matches on a subscriptable object, or
868
+ # KEY_NOT_FOUND when it matches none.
869
+ #
870
+ # Returning the key rather than the value is the point: it lets the caller
871
+ # subscript exactly once. Fetching first and testing the result instead —
872
+ # as `object[attribute] || object[attribute.to_s]` did — can't tell a key
873
+ # holding nil or false from a key that isn't there, so { 'billable' =>
874
+ # false } came back as nil.
875
+ #
876
+ # @return [Object] the matching key, or KEY_NOT_FOUND
877
+ def self.subscript_key(object, attribute)
878
+ if object.is_a?(Array)
879
+ return KEY_NOT_FOUND unless attribute.is_a?(Integer)
880
+
881
+ # Ruby indexes from the end with a negative, so -1 is the last element
882
+ # while anything below -length is as absent as anything above length.
883
+ in_bounds = attribute >= -object.length && attribute < object.length
884
+
885
+ return in_bounds ? attribute : KEY_NOT_FOUND
886
+ end
887
+
888
+ return KEY_NOT_FOUND unless object.respond_to?(:key?)
889
+ return attribute if object.key?(attribute)
890
+
891
+ # A template writes h.foo, h['foo'] and h[:foo] for the same Ruby hash,
892
+ # so both forms of the name count as a match.
893
+ symbol = attribute.to_sym if attribute.respond_to?(:to_sym)
894
+ return symbol if !symbol.nil? && object.key?(symbol)
895
+
896
+ string = attribute.to_s if attribute.respond_to?(:to_s)
897
+ return string if !string.nil? && object.key?(string)
898
+
899
+ KEY_NOT_FOUND
900
+ end
901
+
902
+ # Names the keys that do exist rather than dumping the object, which for
903
+ # anything the size of a request's parameters buries the one absent key
904
+ # under everything else.
905
+ #
906
+ # @return [String]
907
+ def self.key_not_found_message(object, attribute)
908
+ keys = object.respond_to?(:keys) ? " with keys \"#{object.keys.join(', ')}\"" : ''
909
+
910
+ "Key \"#{attribute}\" for sequence/mapping#{keys} does not exist."
911
+ end
912
+
863
913
  # @param [Environment] environment
864
914
  def self.get_attribute(
865
915
  environment, source, object, attribute, type, arguments: {}, defined_test: false,
866
916
  ignore_strict_check: false, lineno: -1, &
867
917
  )
868
918
  if type == Template::ARRAY_CALL || object.respond_to?(:[])
869
- if object.respond_to?(:[]) && (
870
- (object.is_a?(Array) && attribute.is_a?(Integer) && attribute < object.length) ||
871
- (
872
- object.respond_to?(:key?) && (
873
- object.key?(attribute) ||
874
- (attribute.respond_to?(:to_sym) && object.key?(attribute.to_sym)) ||
875
- (attribute.respond_to?(:to_s) && object.key?(attribute.to_s))
876
- )
877
- )
878
- )
919
+ key = object.respond_to?(:[]) ? subscript_key(object, attribute) : KEY_NOT_FOUND
920
+
921
+ unless key.equal?(KEY_NOT_FOUND)
879
922
  return true if defined_test
880
923
 
881
- return object[attribute] || (attribute.is_a?(String) ? object[attribute.to_sym] : object[attribute.to_s])
924
+ return object[key]
882
925
  end
883
926
 
884
927
  if type == Template::ARRAY_CALL
@@ -890,7 +933,11 @@ module Twig
890
933
  return
891
934
  end
892
935
 
893
- raise Error::Runtime, "Can't find key #{attribute} in #{object.inspect}."
936
+ raise Error::Runtime.new(
937
+ key_not_found_message(object, attribute),
938
+ lineno,
939
+ source
940
+ )
894
941
  end
895
942
  end
896
943
 
@@ -925,19 +972,22 @@ module Twig
925
972
 
926
973
  kwargs = kwargs.transform_keys(&:to_sym)
927
974
 
975
+ # public_send, not send: respond_to? above already excludes private and
976
+ # protected methods, but an object with a loose respond_to_missing?
977
+ # can report true for one. Templates only reach the public interface.
928
978
  if positional.length.positive? && kwargs.empty?
929
- object.send(attribute, *positional, &)
979
+ object.public_send(attribute, *positional, &)
930
980
  elsif positional.empty? && kwargs.length.positive?
931
- object.send(attribute, **kwargs, &)
981
+ object.public_send(attribute, **kwargs, &)
932
982
  elsif positional.length.positive? && kwargs.length.positive?
933
- object.send(attribute, *positional, **kwargs, &)
983
+ object.public_send(attribute, *positional, **kwargs, &)
934
984
  else
935
- case object
936
- when Hash, Array
937
- object[attribute]
938
- else
939
- object.send(attribute, &)
940
- end
985
+ # Reached only when the object responds to the attribute and the
986
+ # key/index lookup above did not match, so subscripting a Hash or
987
+ # Array here could only ever miss — it raised TypeError on arrays
988
+ # ({{ list.any? }}) and returned nil on hashes, silently rendering
989
+ # nothing. Collections dispatch methods like any other object.
990
+ object.public_send(attribute, &)
941
991
  end
942
992
  # Constant could be nil but we should return if we find it
943
993
  elsif (constant = get_constant(object, attribute.to_s)) && constant[0] == :found
@@ -952,8 +1002,7 @@ module Twig
952
1002
  message = if object.nil?
953
1003
  "Impossible to access an attribute (\"#{attribute}\") on a null variable."
954
1004
  elsif object.respond_to?(:[]) && !object.is_a?(String)
955
- keys = object.respond_to?(:keys) ? "with keys \"#{object.keys.join(', ')}\"" : ''
956
- "Key \"#{attribute}\" for sequence/mapping #{keys} does not exist."
1005
+ key_not_found_message(object, attribute)
957
1006
  else
958
1007
  "Impossible to access an attribute (\"#{attribute}\") on a #{object.class} " \
959
1008
  "variable (\"#{object}\")."
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ VERSION = '0.0.9'
5
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twig_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig Blanchette
8
8
  - Fabian Potencier
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-17 00:00:00.000000000 Z
11
+ date: 2026-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -217,6 +217,7 @@ files:
217
217
  - lib/twig/twig_function.rb
218
218
  - lib/twig/twig_test.rb
219
219
  - lib/twig/util/callable_arguments_extractor.rb
220
+ - lib/twig/version.rb
220
221
  - lib/twig_ruby.rb
221
222
  homepage: https://rubygems.org/gems/twig-ruby
222
223
  licenses: