teptris 0.2.52 → 0.2.54

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: 2537428f82b791a55389c0be4bb2eaa2115f118238839705a913e51da11e6af1
4
- data.tar.gz: a95d1d2b3f582803ef44c0bacbc1ec9172a9cf044c30cf56a488963f575c4007
3
+ metadata.gz: d87bfa72c18905691d707f6025a6a38c50145e3b3fbdd852ae684a23b02cf5de
4
+ data.tar.gz: da1577dbc5917511ea6013c686b1ae9e33cdc8e344af69bbd73f884d9e658814
5
5
  SHA512:
6
- metadata.gz: 2a8d04664b9cb79842b466a7bc4e877e167ff61aaa58258c2e2419363d601d75ad759913f439c3f722c0b3ceccee603548414dcbcf828aa169acd00793fad92c
7
- data.tar.gz: 27633d7ecf115edbb86be8b669552b8bdd4ccc427aacd1c775dea017a4a143aa7df267a4dfb0ffa59de3f1e827f0056f6e6989f16e022ee808d277ee2e32bcc8
6
+ metadata.gz: 3207652a5292daeafb0f790cb91da4497f9f83d860dc13cdbef03c0fe8d728daa69fd5e012cfd529b6a678e14298c9af1e6e9c5fd36715a74590922ccb336e02
7
+ data.tar.gz: 0e193983d8e3913b8b23c4d337fd6f1874cb8992fd7d28a82f7dddceba96d93a1038ec1cff203ac437281ab2b9c44a853092b54caefb669cb8bbfbcfeab896d0
data/Steepfile ADDED
@@ -0,0 +1,10 @@
1
+ target :lib do
2
+ signature "sig"
3
+ library "date"
4
+
5
+ check "lib"
6
+
7
+ configure_code_diagnostics do |hash|
8
+ # keep the lane green while signatures evolve
9
+ end
10
+ end
@@ -256,28 +256,6 @@ static void *parse_without_gvl(void *p)
256
256
  return NULL;
257
257
  }
258
258
 
259
- static teptris_document *parse_or_raise(VALUE str) {
260
- teptris_document *doc = NULL;
261
- /* frozen strings cannot take the tmp lock (ruby 4.0 raises
262
- * FrozenError - chilled literals); a dup is movable-page-safe the
263
- * same way, at one input copy for frozen callers only */
264
- volatile VALUE pin = OBJ_FROZEN(str) ? rb_str_dup(str) : str;
265
- rb_str_locktmp(pin);
266
- struct gvl_parse_arg arg = {RSTRING_PTR(pin), (size_t)RSTRING_LEN(pin),
267
- &doc, TEPTRIS_OK};
268
- rb_thread_call_without_gvl(parse_without_gvl, &arg, RUBY_UBF_IO, NULL);
269
- rb_str_unlocktmp(pin);
270
- teptris_status st = arg.st;
271
- if (st != TEPTRIS_OK) {
272
- const teptris_error *e = teptris_document_error(doc);
273
- VALUE ex = rb_exc_new(eParseError, e->message, (long)strlen(e->message));
274
- rb_iv_set(ex, "@line", SIZET2NUM(e->line));
275
- rb_iv_set(ex, "@column", SIZET2NUM(e->column));
276
- teptris_document_free(doc);
277
- rb_exc_raise(ex);
278
- }
279
- return doc;
280
- }
281
259
 
282
260
  static VALUE ext_load(int argc, VALUE *argv, VALUE self) {
283
261
  VALUE str, opts;
@@ -292,11 +270,29 @@ static VALUE ext_load(int argc, VALUE *argv, VALUE self) {
292
270
  if (RTEST(ft)) flags |= FMT_FORBID_TIME;
293
271
  if (RTEST(fd)) flags |= FMT_FORBID_DATE;
294
272
  }
295
- teptris_document *doc = parse_or_raise(str);
273
+ /* the parse is pure C over a private copy, GVL-free; the copy
274
+ * must outlive materialization - the document's strings are
275
+ * zero-copy views into it (a real UAF on glibc when freed early) */
276
+ long len = RSTRING_LEN(str);
277
+ char *cbuf = ALLOC_N(char, (size_t)len + 1);
278
+ memcpy(cbuf, RSTRING_PTR(str), (size_t)len);
279
+ teptris_document *doc = NULL;
280
+ struct gvl_parse_arg arg = {cbuf, (size_t)len, &doc, TEPTRIS_OK};
281
+ rb_thread_call_without_gvl(parse_without_gvl, &arg, RUBY_UBF_IO, NULL);
282
+ if (arg.st != TEPTRIS_OK) {
283
+ const teptris_error *e = teptris_document_error(doc);
284
+ VALUE ex = rb_exc_new(eParseError, e->message, (long)strlen(e->message));
285
+ rb_iv_set(ex, "@line", SIZET2NUM(e->line));
286
+ rb_iv_set(ex, "@column", SIZET2NUM(e->column));
287
+ xfree(cbuf);
288
+ teptris_document_free(doc);
289
+ rb_exc_raise(ex);
290
+ }
296
291
  keycache kc;
297
292
  kc_init(&kc);
298
293
  VALUE out = obj_from_node(teptris_document_root(doc), flags, &kc);
299
294
  kc_free(&kc);
295
+ xfree(cbuf);
300
296
  teptris_document_free(doc);
301
297
  return out;
302
298
  }
@@ -741,7 +737,8 @@ static VALUE cLazyOwner, cLazyValue;
741
737
 
742
738
  typedef struct {
743
739
  teptris_document *doc;
744
- VALUE input; /* the borrowed views' buffer */
740
+ VALUE input; /* single-doc path: owned frozen copy (GC-marked) */
741
+ char *buf; /* batch path: owned input copy the doc's views use */
745
742
  } lazy_owner;
746
743
 
747
744
  typedef struct {
@@ -752,12 +749,18 @@ typedef struct {
752
749
  static void lazy_owner_free(void *p) {
753
750
  lazy_owner *o = (lazy_owner *)p;
754
751
  if (o->doc != NULL) teptris_document_free(o->doc);
752
+ if (o->buf != NULL) xfree(o->buf);
755
753
  xfree(o);
756
754
  }
757
755
  static size_t lazy_owner_size(const void *p) { return sizeof(lazy_owner); }
756
+ static void lazy_owner_mark(void *p)
757
+ {
758
+ lazy_owner *o = (lazy_owner *)p;
759
+ if (o->input) rb_gc_mark(o->input); /* roots the single-path copy */
760
+ }
758
761
  static const rb_data_type_t lazy_owner_type = {
759
762
  "TeptrisExt/LazyOwner",
760
- {NULL, lazy_owner_free, lazy_owner_size, NULL, 0},
763
+ {lazy_owner_mark, lazy_owner_free, lazy_owner_size, NULL, 0},
761
764
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY};
762
765
 
763
766
  static void lazy_value_mark(void *p) {
@@ -792,7 +795,7 @@ static VALUE ext_load_lazy(VALUE self, VALUE toml) {
792
795
  VALUE owner = TypedData_Make_Struct(cLazyOwner, lazy_owner,
793
796
  &lazy_owner_type, o);
794
797
  o->doc = NULL;
795
- o->input = rb_str_new_frozen(toml); /* borrowed views: own a copy */
798
+ o->input = rb_str_new_frozen(toml); /* owned frozen copy */
796
799
  teptris_status st = teptris_parse(RSTRING_PTR(o->input),
797
800
  (size_t)RSTRING_LEN(o->input),
798
801
  NULL, &o->doc);
@@ -973,25 +976,53 @@ static unsigned flags_from_opts(VALUE opts) {
973
976
  /* Parse-only batch (returns an Array of Lazy wrappers, one per doc):
974
977
  * eager parse, deferred materialization. Same per-doc error semantics
975
978
  * as ext_load_lazy. */
979
+ struct batch_parse_arg {
980
+ const char **data;
981
+ size_t *lens;
982
+ size_t n;
983
+ teptris_document **docs;
984
+ teptris_status *statuses;
985
+ teptris_status batch;
986
+ };
987
+
988
+ static void *batch_parse_without_gvl(void *p)
989
+ {
990
+ struct batch_parse_arg *a = (struct batch_parse_arg *)p;
991
+ a->batch = teptris_parse_batch(a->data, a->lens, a->n, NULL,
992
+ a->docs, a->statuses);
993
+ return NULL;
994
+ }
995
+
976
996
  static VALUE ext_load_lazy_batch(VALUE self, VALUE strings) {
977
997
  Check_Type(strings, T_ARRAY);
978
998
  long n = RARRAY_LEN(strings);
979
999
  if (n == 0) return rb_ary_new();
980
1000
  batch_scratch *s = batch_scratch_alloc(n);
981
1001
  if (s == NULL) rb_raise(eError, "out of memory batching %ld documents", n);
1002
+ /* the batch parse is pure C: pin every input (one tmp lock per
1003
+ * string - repeated objects dup; frozen strings cannot take the
1004
+ * lock, yet compaction can move them) and release the GVL for the
1005
+ * whole parse */
1006
+ /* the batch parse is pure C over private copies: a Ruby-string
1007
+ * buffer can move under a concurrent compacting GC, and locktmp
1008
+ * does not pin on every platform - copies are C memory, immovable
1009
+ * by construction. Each LazyOwner owns its copy (freed with the
1010
+ * owner): the lazy nodes view these buffers until materialized. */
1011
+ char **bufs = ALLOC_N(char *, n);
982
1012
  for (long i = 0; i < n; i++) {
983
1013
  VALUE str = rb_ary_entry(strings, i);
984
1014
  StringValue(str); /* raises TypeError if not coercible */
985
- s->data[i] = RSTRING_PTR(str);
986
- s->lens[i] = (size_t)RSTRING_LEN(str);
987
- }
988
- teptris_status batch =
989
- teptris_parse_batch(s->data, s->lens, (size_t)n, NULL,
990
- s->docs, s->statuses);
991
- if (batch != TEPTRIS_OK) {
992
- batch_scratch_free(s);
993
- rb_raise(eError, "batch parse failed (status %d)", (int)batch);
1015
+ long len = RSTRING_LEN(str);
1016
+ bufs[i] = ALLOC_N(char, (size_t)len + 1);
1017
+ memcpy(bufs[i], RSTRING_PTR(str), (size_t)len);
1018
+ s->data[i] = bufs[i];
1019
+ s->lens[i] = (size_t)len;
994
1020
  }
1021
+ struct batch_parse_arg arg = {s->data, s->lens, (size_t)n,
1022
+ s->docs, s->statuses, TEPTRIS_OK};
1023
+ rb_thread_call_without_gvl(batch_parse_without_gvl, &arg,
1024
+ RUBY_UBF_IO, NULL);
1025
+ teptris_status batch = arg.batch;
995
1026
  long first_fail = -1;
996
1027
  for (long i = 0; i < n; i++) {
997
1028
  if (s->statuses[i] != TEPTRIS_OK && first_fail == -1) first_fail = i;
@@ -1012,7 +1043,7 @@ static VALUE ext_load_lazy_batch(VALUE self, VALUE strings) {
1012
1043
  VALUE owner = TypedData_Make_Struct(cLazyOwner, lazy_owner,
1013
1044
  &lazy_owner_type, o);
1014
1045
  o->doc = s->docs[i];
1015
- o->input = rb_ary_entry(strings, i);
1046
+ o->buf = bufs[i]; /* the owner owns this copy */
1016
1047
  rb_ary_push(out, lazy_wrap(owner, teptris_document_root(s->docs[i])));
1017
1048
  }
1018
1049
  batch_scratch_free(s);
@@ -1053,35 +1084,29 @@ static VALUE ext_load_slice(int argc, VALUE *argv, VALUE self) {
1053
1084
  if (n == 0) return rb_ary_new();
1054
1085
  unsigned flags = flags_from_opts(opts);
1055
1086
 
1056
- volatile VALUE *pins = ALLOC_N(VALUE, n);
1087
+ /* the GVL-free parse reads private copies: a Ruby-string buffer
1088
+ * can move under a concurrent compacting GC, and locktmp does not
1089
+ * pin on every platform (windows ruby 3.3 parsed garbage). Copies
1090
+ * are C memory - immovable by construction. Freed after
1091
+ * materialization (the docs view them until then). */
1057
1092
  const char **data = ALLOC_N(const char *, n);
1058
1093
  size_t *lens = ALLOC_N(size_t, n);
1059
1094
  teptris_document **docs = ALLOC_N(teptris_document *, n);
1060
1095
  teptris_status *stats = ALLOC_N(teptris_status, n);
1061
- /* the tmp lock is one-per-string: repeated objects in the slice
1062
- * get a dup before pinning (frozen inputs dup for the same reason
1063
- * - they cannot take the lock, yet compaction can move them) */
1064
- VALUE seen = rb_hash_new();
1096
+ char **bufs = ALLOC_N(char *, n);
1065
1097
  long i;
1066
1098
  for (i = 0; i < n; i++) {
1067
1099
  VALUE str = rb_ary_entry(strs, i);
1068
1100
  StringValue(str);
1069
- VALUE id = rb_obj_id(str);
1070
- if (!NIL_P(rb_hash_aref(seen, id))) {
1071
- str = rb_str_dup(str);
1072
- } else {
1073
- rb_hash_aset(seen, id, Qtrue);
1074
- if (OBJ_FROZEN(str)) str = rb_str_dup(str);
1075
- }
1076
- pins[i] = str;
1077
- rb_str_locktmp(pins[i]);
1078
- data[i] = RSTRING_PTR(pins[i]);
1079
- lens[i] = (size_t)RSTRING_LEN(pins[i]);
1101
+ long len = RSTRING_LEN(str);
1102
+ bufs[i] = ALLOC_N(char, (size_t)len + 1);
1103
+ memcpy(bufs[i], RSTRING_PTR(str), (size_t)len);
1104
+ data[i] = bufs[i];
1105
+ lens[i] = (size_t)len;
1080
1106
  }
1081
1107
  struct slice_parse_arg arg = {n, data, lens, docs, stats};
1082
1108
  rb_thread_call_without_gvl(slice_parse_without_gvl, &arg,
1083
1109
  RUBY_UBF_IO, NULL);
1084
- for (i = 0; i < n; i++) rb_str_unlocktmp(pins[i]);
1085
1110
 
1086
1111
  VALUE out = rb_ary_new_capa(n);
1087
1112
  keycache kc;
@@ -1091,7 +1116,7 @@ static VALUE ext_load_slice(int argc, VALUE *argv, VALUE self) {
1091
1116
  const char *msg = "parse failed (out of memory)";
1092
1117
  VALUE ex = rb_exc_new(eParseError, msg, (long)strlen(msg));
1093
1118
  kc_free(&kc);
1094
- xfree(pins); xfree(data); xfree(lens); xfree(docs); xfree(stats);
1119
+ xfree(data); xfree(lens); xfree(docs); xfree(stats);
1095
1120
  rb_exc_raise(ex);
1096
1121
  }
1097
1122
  const teptris_error *e = teptris_document_error(docs[i]);
@@ -1103,7 +1128,7 @@ static VALUE ext_load_slice(int argc, VALUE *argv, VALUE self) {
1103
1128
  long j;
1104
1129
  for (j = i; j < n; j++) teptris_document_free(docs[j]);
1105
1130
  kc_free(&kc);
1106
- xfree(pins); xfree(data); xfree(lens); xfree(docs); xfree(stats);
1131
+ xfree(data); xfree(lens); xfree(docs); xfree(stats);
1107
1132
  rb_exc_raise(ex);
1108
1133
  }
1109
1134
  }
@@ -1113,7 +1138,7 @@ static VALUE ext_load_slice(int argc, VALUE *argv, VALUE self) {
1113
1138
  teptris_document_free(docs[i]);
1114
1139
  }
1115
1140
  kc_free(&kc);
1116
- xfree(pins); xfree(data); xfree(lens); xfree(docs); xfree(stats);
1141
+ xfree(data); xfree(lens); xfree(docs); xfree(stats);
1117
1142
  return out;
1118
1143
  }
1119
1144
 
@@ -32,7 +32,9 @@ class Teptris::Descriptor
32
32
  # siblings that follow a :nested child into the sub-plan's range —
33
33
  # the 0.2.29 bug (every array-of-tables element grew stray nil
34
34
  # rows).
35
+ # @type var plans: Array[Hash[Symbol, untyped]]
35
36
  plans = []
37
+ # @type var index_of: Hash[Integer, Integer]
36
38
  index_of = {}
37
39
  collect = lambda do |t|
38
40
  unless index_of.key?(t.object_id)
data/lib/teptris/toml.rb CHANGED
@@ -25,6 +25,7 @@ module Teptris
25
25
  unless %i[native string].include?(datetime_policy)
26
26
  raise ArgumentError, "datetime_policy must be :native or :string"
27
27
  end
28
+ # @type var opts: Hash[Symbol, untyped]
28
29
  opts = {}
29
30
  opts[:string_datetimes] = true if datetime_policy == :string
30
31
  if permitted_classes && datetime_policy == :native
@@ -69,6 +70,7 @@ module Teptris
69
70
  # Repeated objects are dup'd once each HERE: a repeat landing in
70
71
  # two slices would double-lock across threads (the slice-level
71
72
  # guard cannot see across slices); frozen dup'ing is C-side.
73
+ # @type var seen: Hash[Integer, bool]
72
74
  seen = {}
73
75
  tomls = tomls.map do |s|
74
76
  next s.dup if seen[s.object_id]
@@ -77,6 +79,7 @@ module Teptris
77
79
  end
78
80
  per = (n.to_f / threads).ceil
79
81
  results = Array.new(n)
82
+ # @type var first_error: [Integer, untyped]?
80
83
  first_error = nil
81
84
  mutex = Mutex.new
82
85
  threads.times.map do |ti|
@@ -157,6 +160,7 @@ module Teptris
157
160
  end
158
161
 
159
162
  def _safe_load_opts(safe_load, datetime_policy)
163
+ # @type var opts: Hash[Symbol, untyped]
160
164
  opts = {}
161
165
  opts[:string_datetimes] = true if datetime_policy == :string
162
166
  if safe_load && datetime_policy == :native
@@ -1,3 +1,3 @@
1
1
  module Teptris
2
- VERSION = "0.2.52"
2
+ VERSION = "0.2.54"
3
3
  end
data/sig/teptris.rbs ADDED
@@ -0,0 +1,131 @@
1
+ module Teptris
2
+ VERSION: String
3
+
4
+ # mirrored from the engine's teptris_status (lib/teptris.rb)
5
+ TEPTRIS_OK: Integer
6
+
7
+ class Error < StandardError
8
+ end
9
+
10
+ class ParseError < Error
11
+ attr_reader line: Integer
12
+ attr_reader column: Integer
13
+
14
+ def initialize: (String message, ?Integer line, ?Integer column) -> void
15
+ end
16
+
17
+ module TOML
18
+ VERSION: String
19
+
20
+ # Eager load: String -> Hash with String keys. Datetime contract
21
+ # (the tomlib shape): Time with zone for offset datetimes, local
22
+ # Time for local datetimes, Date for dates, "HH:MM:SS" String for
23
+ # local times.
24
+ def self.load: (String toml) -> Hash[String, untyped]
25
+
26
+ def self.load_file: (String path) -> Hash[String, untyped]
27
+
28
+ def self.safe_load: (String toml, ?permitted_classes: Array[Class]?,
29
+ ?datetime_policy: :native | :string)
30
+ -> Hash[String, untyped]
31
+
32
+ # One parse; host objects materialize only along accessed paths.
33
+ def self.load_lazy: (String toml) -> TeptrisExt::LazyValue
34
+
35
+ def self.load_batch: (Array[String] tomls, ?safe_load: Array[Class]?,
36
+ ?datetime_policy: :native | :string,
37
+ ?threads: Integer)
38
+ -> Array[Hash[String, untyped]]
39
+
40
+ def self.load_lazy_batch: (Array[String] tomls)
41
+ -> Array[TeptrisExt::LazyValue]
42
+
43
+ def self.load_files: (Array[String] paths, ?safe_load: Array[Class]?,
44
+ ?datetime_policy: :native | :string)
45
+ -> Array[Hash[String, untyped]]
46
+
47
+ def self.load_schema: (String toml, Descriptor descriptor)
48
+ -> Hash[String, untyped]
49
+
50
+ def self.dump: (untyped obj) -> String
51
+
52
+ def self.engine_version: () -> String
53
+
54
+ def self._validate_batch!: (untyped input, untyped safe_load,
55
+ :native | :string datetime_policy,
56
+ ?kind: Symbol) -> void
57
+
58
+ def self._safe_load_opts: (untyped safe_load,
59
+ :native | :string datetime_policy)
60
+ -> Hash[Symbol, untyped]?
61
+ end
62
+
63
+ # Tree-shaped schema descriptor: compile once with .build, then
64
+ # materialize documents against it with #walk — unplanned keys are
65
+ # never materialized (the py twin is teptris.Descriptor).
66
+ class Descriptor
67
+ KINDS: Hash[Symbol, Integer]
68
+
69
+ def self.build: (Hash[Symbol, untyped] tree) -> Descriptor
70
+
71
+ attr_reader rows: Array[[String, Integer, Integer]]
72
+ attr_reader first_row: Array[Integer]
73
+
74
+ def walk: (String toml) -> Hash[String, untyped]
75
+ end
76
+ end
77
+
78
+ # The C extension's surface (defined in ext/teptris_ext). The gem's
79
+ # public API is Teptris::TOML; these module functions are the binding
80
+ # layer underneath it.
81
+ module TeptrisExt
82
+ # Lazy wrapper: one parse, scalars materialize on access, to_h/to_a
83
+ # flatten to the eager shapes.
84
+ class LazyValue
85
+ def []: (String key) -> (LazyValue | untyped)
86
+ | (Integer index) -> (LazyValue | untyped)
87
+
88
+ def at: (Integer index) -> (LazyValue | untyped)
89
+
90
+ def dig: (*String keys) -> (LazyValue | untyped)
91
+
92
+ def kind: () -> String
93
+
94
+ def size: () -> Integer
95
+
96
+ def each: () { (LazyValue | untyped) -> void } -> LazyValue
97
+ | () -> Enumerator[LazyValue | untyped, LazyValue]
98
+
99
+ def value: () -> untyped
100
+
101
+ def to_h: () -> Hash[String, untyped]
102
+
103
+ def to_a: () -> Array[untyped]
104
+
105
+ def ==: (untyped other) -> bool
106
+
107
+ include Enumerable[LazyValue | untyped]
108
+ end
109
+
110
+ def self.load: (String toml, ?Hash[Symbol, untyped]? opts)
111
+ -> Hash[String, untyped]
112
+
113
+ def self.load_lazy: (String toml) -> LazyValue
114
+
115
+ def self.load_batch: (Array[String] tomls, Hash[Symbol, untyped]? opts)
116
+ -> Array[Hash[String, untyped]]
117
+
118
+ def self.load_slice: (Array[String] tomls, Hash[Symbol, untyped]? opts)
119
+ -> Array[Hash[String, untyped]]
120
+
121
+ def self.load_lazy_batch: (Array[String] tomls) -> Array[LazyValue]
122
+
123
+ def self.dump: (untyped obj) -> String
124
+
125
+ def self.engine_version: () -> String
126
+
127
+ def self.plan_build: (Array[[String, Integer, Integer]] rows,
128
+ Array[Integer] first_row) -> untyped
129
+
130
+ def self.plan_emit: (untyped handle, String toml) -> Hash[String, untyped]
131
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: teptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.52
4
+ version: 0.2.54
5
5
  platform: ruby
6
6
  authors:
7
7
  - teptris contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-01 00:00:00.000000000 Z
11
+ date: 2026-10-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A native C extension over libteptris, a C99 TOML 1.1 parser/emitter.
14
14
  API shape mirrors the tomlib gem.
@@ -18,6 +18,7 @@ extensions:
18
18
  - ext/teptris_ext/extconf.rb
19
19
  extra_rdoc_files: []
20
20
  files:
21
+ - Steepfile
21
22
  - ext/teptris_ext/engine/LICENSE.md
22
23
  - ext/teptris_ext/engine/README.md
23
24
  - ext/teptris_ext/engine/src/include/teptris/plan.h
@@ -56,6 +57,7 @@ files:
56
57
  - lib/teptris/error.rb
57
58
  - lib/teptris/toml.rb
58
59
  - lib/teptris/version.rb
60
+ - sig/teptris.rbs
59
61
  homepage: https://github.com/leptris/teptris-ruby
60
62
  licenses:
61
63
  - MIT