leptris 1.9.240.0 → 1.9.241.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91e18b7955af279de38382142b10d20f972c5d8fb1ceb02216efdcae5b572e8d
4
- data.tar.gz: f30f6e5564aac12de9a5a074be4ebd706bb6fba196911fbbd752229507763b0a
3
+ metadata.gz: 6ad2fa0b842ba466a3264f4bf15b7aef98b02916e54c83faae4a88c3fc56f93a
4
+ data.tar.gz: 9f013ffbfc83ebc2bd6d848dc6a52518121eb0f51a1f74241c259a7c0e31829c
5
5
  SHA512:
6
- metadata.gz: 3cc5846626e6feac81081470224e77a47da5e6094c4d57c7444d2135837aea33cdb799d34437cd717f0a4f7ae28f9df2abd0f8a3772d204693b4c27871bc4d2f
7
- data.tar.gz: fa24557f80e5182058a320a7cf87fae1644f324750a767c971aa0a836afe2fd611fc610b0f4e57d34a862b6eabe689b13c146b8db5a98fe25f37c68dfbbee882
6
+ metadata.gz: 0f0bebfda63c162db6237698ee5e8e4b7639985bf8f64c76f3b88eb436eb9e6ea701d139e976cb6c6b4930bb5e0ed3eba739ef260bd267c006cbc5e708d2b64c
7
+ data.tar.gz: 113065e13a279548868058dd223b4a62d75b025a88b659a897c83ca35f6142780999c283225cd6463349518c45197cb08c9510ee57b459f5269e9f82ec069cc1
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to Leptris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.241.0] - 2026-09-25
9
+
10
+ ### Fixed
11
+
12
+ - Vendored libleptris 1.9.241 (riding 1.9.240 + 1.9.241):
13
+ - The interleaved parse lane now applies the same §2.11 EOL
14
+ normalization as the classic lane (#1357) — both lanes
15
+ spec-identical; the lane stays env-gated off by default.
16
+ - xquery: order by — only a genuinely empty-sequence key takes
17
+ the empty mode (an empty STRING key sorts by value);
18
+ fn:distinct-values compares xs:duration-family members in the
19
+ value space and spells survivors canonically.
20
+ - xquery: shortest round-trip plain spelling for numbers — the
21
+ 15-digit plain budget collided distinct decimals
22
+ (1.2000000000000001 vs 1.2); the branch now discovers minimal
23
+ significant digits and renders fixed-point uncapped.
24
+ - Repairs the Rakefile engine pin, left at 1.9.239 by the last
25
+ release's version-only bump, back in lockstep with version.rb.
26
+
8
27
  ## [1.9.239.0] - 2026-09-25
9
28
 
10
29
  ### Fixed
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  # Pin for `rake compile` and the platform-gem builds. Keep in lockstep
9
9
  # with .github/workflows/build.yml (which calls `rake compile`) and the
10
10
  # CHANGELOG when libleptris releases.
11
- LIBLEPTRIS_VERSION = "1.9.239"
11
+ LIBLEPTRIS_VERSION = "1.9.241"
12
12
  # Vendored alongside libleptris for fn:normalize-unicode (TODO
13
13
  # .restructure/20): built per platform with a RELOCATABLE @rpath
14
14
  # install name, loaded by ffi.rb before libleptris so the
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.240.0"
4
+ VERSION = "1.9.241.0"
5
5
  end
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.239
4
+ - libleptris v1.9.241
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,30 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.241] - 2026-09-25
4
+
5
+ ### Fixed
6
+
7
+ - xquery: shortest round-trip plain spelling for XQuery numbers.
8
+ The plain branch kept a 15-digit budget, so `1.2000000000000001`
9
+ and `1.2` collided into one spelling and fn:distinct-values
10
+ deduped distinct xs:decimals. The branch now discovers the minimal
11
+ significant digits via the same e-form loop (float-aware) and
12
+ renders fixed-point with `decimals = prec - exp10` — uncapped, or
13
+ tiny plain-range values (`1e-18`) truncate to "0".
14
+ - QT3 corpus: distinct-values 93→94 adopted; pins-off reds 67→59.
15
+
16
+
17
+
18
+ ## [1.9.240] - 2026-09-25
19
+
20
+ ### Fixed
21
+
22
+ - order-by empty-string keys, duration-typed distinct-values (xquery)
23
+ - qualifier casts in the plan free path (descriptor)
24
+ - §2.11 EOL normalization in the interleaved lane (#326) (dom)
25
+
26
+
27
+
3
28
  ## [1.9.239] - 2026-09-24
4
29
 
5
30
  ### Added
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.239
6
+ VERSION 1.9.241
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -244,7 +244,7 @@ static void dp_plan_free(LeptrisPlan p) {
244
244
  for (uint32_t i = 0; i < p->plan_count; i++) {
245
245
  dp_plan* d = &p->plans[i];
246
246
  for (uint32_t a = 0; a < d->attribute_count; a++) {
247
- free(d->attribute_plans[a].wire_name);
247
+ free((char *)d->attribute_plans[a].wire_name);
248
248
  for (uint16_t pi = 0;
249
249
  pi < d->attribute_plans[a].predicate_count; pi++) {
250
250
  free((char*)d->attribute_plans[a]
@@ -255,7 +255,7 @@ static void dp_plan_free(LeptrisPlan p) {
255
255
  free((leptris_attr_predicate*)d->attribute_plans[a].predicates);
256
256
  }
257
257
  for (uint32_t c = 0; c < d->child_count; c++) {
258
- free(d->child_plans[c].wire_name);
258
+ free((char *)d->child_plans[c].wire_name);
259
259
  for (uint16_t pi = 0;
260
260
  pi < d->child_plans[c].predicate_count; pi++) {
261
261
  free((char*)d->child_plans[c]
@@ -2560,12 +2560,22 @@ static struct leptris_document* dp_il_build(
2560
2560
  pool, a->val_len + 1);
2561
2561
  if (!norm) { free(smap); free(rdepth);
2562
2562
  free(lc); goto oom_pool; }
2563
+ /* §2.11 (#326): CRLF is ONE line break — one
2564
+ * space, same collapse as the classic lane. */
2565
+ size_t nw = 0;
2563
2566
  for (uint32_t w = 0; w < a->val_len; w++) {
2564
2567
  char ch = scratch[a->val_off + w];
2565
- norm[w] = (ch == '\t' || ch == '\n' ||
2566
- ch == '\r') ? ' ' : ch;
2568
+ if (ch == '\r') {
2569
+ norm[nw++] = ' ';
2570
+ if (w + 1 < a->val_len &&
2571
+ scratch[a->val_off + w + 1] == '\n')
2572
+ w++;
2573
+ } else {
2574
+ norm[nw++] = (ch == '\t' || ch == '\n')
2575
+ ? ' ' : ch;
2576
+ }
2567
2577
  }
2568
- norm[a->val_len] = '\0';
2578
+ norm[nw] = '\0';
2569
2579
  leptris_attr_value_set_heap(
2570
2580
  at, leptris_sv_from_cstr(norm));
2571
2581
  } else {
@@ -2672,12 +2682,37 @@ static struct leptris_document* dp_il_build(
2672
2682
  } else {
2673
2683
  LeptrisTextNode* tn = &tblk[ntext++];
2674
2684
  const char* content = scratch + r->off;
2685
+ size_t content_len = r->len;
2675
2686
  scratch[r->off + r->len] = '\0';
2687
+ /* §2.11 (#326), interleaved lane: same pre-expansion
2688
+ * CRLF/CR -> LF normalization as the classic lane;
2689
+ * CR-free runs stay in scratch. */
2690
+ if (memchr(content, '\r', content_len)) {
2691
+ char* norm =
2692
+ (char*)leptris_pool_alloc(pool, content_len + 1);
2693
+ if (!norm) { free(smap); free(rdepth);
2694
+ free(lc); goto oom_pool; }
2695
+ char* w = norm;
2696
+ const char* rr = content;
2697
+ const char* r_end = content + content_len;
2698
+ while (rr < r_end) {
2699
+ if (*rr == '\r') {
2700
+ *w++ = '\n';
2701
+ if (rr + 1 < r_end && rr[1] == '\n') rr++;
2702
+ } else {
2703
+ *w++ = *rr;
2704
+ }
2705
+ rr++;
2706
+ }
2707
+ *w = '\0';
2708
+ content = norm;
2709
+ content_len = (size_t)(w - norm);
2710
+ }
2676
2711
  tn->base.type = LEPTRIS_NODE_TYPE_TEXT;
2677
2712
  tn->base.frozen = 1;
2678
2713
  tn->base.line = r->line;
2679
2714
  leptris_textnode_set_content_ptr_doc(tn, content, doc);
2680
- tn->content_len = r->len;
2715
+ tn->content_len = (uint32_t)content_len;
2681
2716
  /* (slice 4: pool/borrowed fields gone; the il scratch
2682
2717
  * run is already NUL-terminated at carve) */
2683
2718
  tn->parent_off = 0;
@@ -136,17 +136,32 @@ char* xpath_number_to_string_xq_typed(double number, int float_prec) {
136
136
  snprintf(work, sizeof(work), "%sE%s", mant, exp);
137
137
  return leptris_strdup(work);
138
138
  } else {
139
- /* Fraction budget: integer digits first, the fraction spends
140
- * the remaining significant digits (33 places floor for the
141
- * 1e-18 tail). */
142
- int integer_place = (int)floor(log10(absolute_value));
143
- int fraction_place = integer_place > 0
144
- ? 15 - integer_place - 1
145
- : 15 - integer_place;
146
- if (fraction_place > 33) fraction_place = 33;
147
- if (fraction_place < 0) fraction_place = 0;
148
- size = (int)snprintf(work, sizeof(work), "%0.*f",
149
- fraction_place, number);
139
+ /* Shortest round-trip PLAIN spelling: discover the minimal
140
+ * significant-digit count via the e-form (float-aware like
141
+ * the scientific branch), then render fixed-point. The old
142
+ * 15-digit budget truncated the 17th digit, colliding
143
+ * distinct xs:decimals ("1.2" vs "1.2000000000000002")
144
+ * into one spelling. */
145
+ char mant[64];
146
+ int prec;
147
+ for (prec = 1; prec <= 17; prec++) {
148
+ snprintf(mant, sizeof(mant), "%.*e", prec, number);
149
+ double back = strtod(mant, NULL);
150
+ if (float_prec) back = (double)((float)back);
151
+ if (back == number) break;
152
+ }
153
+ int exp10 = 0;
154
+ char* e = strchr(mant, 'e');
155
+ if (e) exp10 = atoi(e + 1);
156
+ /* %.*e prints `prec` digits AFTER the mantissa point, so
157
+ * fixed-point decimals = prec - exp10 ("2.5e+00" -> 1). */
158
+ int decimals = prec - exp10;
159
+ if (decimals < 0) decimals = 0;
160
+ /* tiny plain-range values (1e-18) need exp10 digits of
161
+ * fraction — no 17 cap here or they truncate to "0" */
162
+ if (decimals > 40) decimals = 40;
163
+ size = (int)snprintf(work, sizeof(work), "%.*f",
164
+ decimals, number);
150
165
  }
151
166
 
152
167
  char* start = work;
@@ -485,6 +485,79 @@ static struct leptris_xpath_result* fn_index_of(XPathContext* ctx,
485
485
  return out;
486
486
  }
487
487
 
488
+ /* ISO-8601 duration value space: (months, seconds). Returns 1 when
489
+ * s is a duration lexical form. */
490
+ static int dur_ms_parse(const char* s, double* out_m, double* out_s) {
491
+ const char* p = s;
492
+ int neg = (*p == '-');
493
+ if (neg) p++;
494
+ if (*p != 'P') return 0;
495
+ p++;
496
+ double m = 0, sec = 0;
497
+ int in_time = 0, any = 0;
498
+ while (*p) {
499
+ if (*p == 'T') {
500
+ in_time = 1;
501
+ p++;
502
+ any = 0;
503
+ continue;
504
+ }
505
+ if (!isdigit((unsigned char)*p) && *p != '.') return 0;
506
+ double v = strtod(p, (char**)&p);
507
+ char d = *p;
508
+ if (!d) return 0;
509
+ p++;
510
+ double sign = neg ? -1.0 : 1.0;
511
+ if (!in_time) {
512
+ if (d == 'Y') { m += sign * v * 12; any = 1; }
513
+ else if (d == 'M') { m += sign * v; any = 1; }
514
+ else if (d == 'D') { sec += sign * v * 86400; any = 1; }
515
+ else return 0;
516
+ } else {
517
+ if (d == 'H') { sec += sign * v * 3600; any = 1; }
518
+ else if (d == 'M') { sec += sign * v * 60; any = 1; }
519
+ else if (d == 'S') { sec += sign * v; any = 1; }
520
+ else return 0;
521
+ }
522
+ }
523
+ if (!any) return 0;
524
+ *out_m = m;
525
+ *out_s = sec;
526
+ return 1;
527
+ }
528
+
529
+ /* XSD canonical duration spelling: whole-value sign, no zero
530
+ * components, all-zero -> "PT0S". */
531
+ static char* dur_canonical(const char* s) {
532
+ double m, sec;
533
+ if (!dur_ms_parse(s, &m, &sec)) return NULL;
534
+ int neg = (m < 0 || sec < 0);
535
+ double am = fabs(m), as = fabs(sec);
536
+ long long years = (long long)(am / 12);
537
+ long long months = (long long)am - years * 12;
538
+ long long days = (long long)(as / 86400);
539
+ double rem = as - days * 86400;
540
+ long long hours = (long long)(rem / 3600);
541
+ rem -= hours * 3600;
542
+ long long mins = (long long)(rem / 60);
543
+ double secs = rem - mins * 60;
544
+ if (secs > 0 && secs == (double)(long long)secs)
545
+ secs = (double)(long long)secs;
546
+ char buf[96];
547
+ int w = snprintf(buf, sizeof(buf), "%sP", neg ? "-" : "");
548
+ if (years) w += snprintf(buf + w, sizeof(buf) - w, "%lldY", years);
549
+ if (months) w += snprintf(buf + w, sizeof(buf) - w, "%lldM", months);
550
+ if (days) w += snprintf(buf + w, sizeof(buf) - w, "%lldD", days);
551
+ if (hours || mins || secs || (!years && !months && !days)) {
552
+ w += snprintf(buf + w, sizeof(buf) - w, "T");
553
+ if (hours) w += snprintf(buf + w, sizeof(buf) - w, "%lldH", hours);
554
+ if (mins) w += snprintf(buf + w, sizeof(buf) - w, "%lldM", mins);
555
+ if (secs || (!hours && !mins))
556
+ w += snprintf(buf + w, sizeof(buf) - w, "%.12gS", secs);
557
+ }
558
+ return leptris_strdup(buf);
559
+ }
560
+
488
561
  static struct leptris_xpath_result* fn_distinct_values(XPathContext* ctx,
489
562
  XPathASTNode** args, size_t n) {
490
563
  size_t cnt;
@@ -501,11 +574,28 @@ static struct leptris_xpath_result* fn_distinct_values(XPathContext* ctx,
501
574
  if (out)
502
575
  for (size_t k = 0; k < cnt; k++) {
503
576
  int dup = 0;
504
- for (size_t j = 0; j < k && !dup; j++)
505
- if (atom_seq_eq_n(items[k], items[j], 1)) dup = 1;
506
- /* first occurrence keeps its original spelling
507
- * (distinct-values((1, 2.0, 3, 2)) = 1, 2.0, 3) */
508
- if (!dup) seq_push_str(out, items[k]);
577
+ double mk = 0, sk = 0;
578
+ int dk = dur_ms_parse(items[k], &mk, &sk);
579
+ for (size_t j = 0; j < k && !dup; j++) {
580
+ if (dk) {
581
+ /* xs:duration eq compares the value space
582
+ * (months, seconds) across the duration
583
+ * subtypes (cbcl-distinct-values-013) */
584
+ double mj = 0, sj = 0;
585
+ if (dur_ms_parse(items[j], &mj, &sj) &&
586
+ mj == mk && sj == sk)
587
+ dup = 1;
588
+ } else if (atom_seq_eq_n(items[k], items[j], 1)) {
589
+ dup = 1;
590
+ }
591
+ }
592
+ if (!dup) {
593
+ /* duration survivors spell canonically
594
+ * (dayTimeDuration P0D -> "PT0S") */
595
+ char* push = dk ? dur_canonical(items[k]) : NULL;
596
+ seq_push_str(out, push ? push : items[k]);
597
+ free(push);
598
+ }
509
599
  }
510
600
  free_items(items, cnt);
511
601
  return out;
@@ -2898,13 +2898,26 @@ static LeptrisXPathResult xq_eval_impl(
2898
2898
  for (size_t k = 0; k < q->nkeys; k++) {
2899
2899
  struct leptris_xpath_result* r =
2900
2900
  evaluate_expr(ctx, q->keys[k].key);
2901
- char* ks = r ? xpath_to_string(r) : NULL;
2901
+ /* Only a genuinely EMPTY SEQUENCE key
2902
+ * takes the empty-mode; an empty STRING
2903
+ * key is a real key and sorts by value
2904
+ * ("" < "a" — cbcl-distinct-values-010).
2905
+ * NULL slot = empty sequence. */
2906
+ int empty_seq =
2907
+ !r || (r->type == XPATH_RESULT_NODESET &&
2908
+ (!r->value.nodeset_value ||
2909
+ r->value.nodeset_value->count ==
2910
+ 0));
2911
+ char* ks =
2912
+ empty_seq ? NULL
2913
+ : (r ? xpath_to_string(r)
2914
+ : NULL);
2902
2915
  if (ti == 0)
2903
2916
  q->keys[k].strmode =
2904
2917
  (r &&
2905
2918
  r->type == XPATH_RESULT_STRING);
2906
2919
  xpath_result_free(r);
2907
- tuples[ti].keys[k] = ks ? ks : strdup("");
2920
+ tuples[ti].keys[k] = ks;
2908
2921
  }
2909
2922
  }
2910
2923
  }
@@ -2936,8 +2949,8 @@ static LeptrisXPathResult xq_eval_impl(
2936
2949
  const char* ka = tuples[j - 1].keys[k];
2937
2950
  const char* kb = tmp.keys[k];
2938
2951
  int c;
2939
- int ea = !ka || !ka[0];
2940
- int eb = !kb || !kb[0];
2952
+ int ea = !ka;
2953
+ int eb = !kb;
2941
2954
  if (ea || eb) {
2942
2955
  /* empty-sequence keys order by the
2943
2956
  * key's empty mode (greatest default) */
@@ -892,9 +892,8 @@ TEST(Qt3Subset, FnStringJoin) {
892
892
  TEST(Qt3Subset, FnIndexOf) {
893
893
  /* Reds are the typed-atom class: NaN/INF members and
894
894
  * xs:date/dateTime compare via the string channel. */
895
- run_test_set("fn/index-of.xml", {}, 42, {},
895
+ run_test_set("fn/index-of.xml", {}, 43, {},
896
896
  {
897
- "K-SeqIndexOfFunc-17",
898
897
  "fn-indexof-mix-args-013",
899
898
 
900
899
  });
@@ -914,7 +913,7 @@ TEST(Qt3Subset, FnRemove) {
914
913
  /* Reds: instance-of on typed sequences + the typed-member class. */
915
914
  run_test_set("fn/remove.xml", {}, 23, {},
916
915
  {
917
- "K-SeqRemoveFunc-8",
916
+ "fn-remove-mix-args-010", /* float member in a mixed pair loses float-aware spelling */
918
917
  "fn-remove-mix-args-017",
919
918
 
920
919
  });
@@ -933,10 +932,9 @@ TEST(Qt3Subset, FnReverse) {
933
932
  TEST(Qt3Subset, FnSubsequence) {
934
933
  /* Reds: typed members under assert-xml (float/boolean/dateTime
935
934
  * spellings) + start/length NaN rules. */
936
- run_test_set("fn/subsequence.xml", {}, 61, {},
935
+ run_test_set("fn/subsequence.xml", {}, 65, {},
937
936
  {
938
937
  "K-SeqSubsequenceFunc-34",
939
- "cbcl-subsequence-001",
940
938
  "cbcl-subsequence-003",
941
939
  "cbcl-subsequence-005",
942
940
  "cbcl-subsequence-011",
@@ -945,9 +943,6 @@ TEST(Qt3Subset, FnSubsequence) {
945
943
  "cbcl-subsequence-014",
946
944
  "cbcl-subsequence-019",
947
945
  "cbcl-subsequence-025",
948
- "cbcl-subsequence-026",
949
- "fn-subsequence-mix-args-011",
950
- "fn-subsequence-mix-args-016",
951
946
  "fn-subsequence-mix-args-025",
952
947
 
953
948
  });
@@ -959,19 +954,14 @@ TEST(Qt3Subset, FnDistinctValues) {
959
954
  /* mixed-args-012 (decimal-vs-float dedup) diverges ONLY inside
960
955
  * the LTO'd test binary — identical query passes standalone and
961
956
  * via the public API; CI's non-LTO/gcc legs are the arbiter. */
962
- run_test_set("fn/distinct-values.xml", {}, 89, {},
957
+ run_test_set("fn/distinct-values.xml", {}, 94, {},
963
958
  {
964
959
  "cbcl-distinct-values-002",
965
960
  "cbcl-distinct-values-002b",
966
961
  "cbcl-distinct-values-007",
967
- "cbcl-distinct-values-010",
968
- "cbcl-distinct-values-011",
969
- "cbcl-distinct-values-013",
970
- "cbcl-distinct-values-016",
971
962
  "fn-distinct-values-1",
972
963
  "fn-distinct-values-2",
973
964
  "fn-distinct-values-mixed-args-010",
974
- "fn-distinct-values-mixed-args-012",
975
965
  "fn-distinct-values-mixed-args-018",
976
966
 
977
967
  });
@@ -981,7 +971,7 @@ TEST(Qt3Subset, FnDeepEqual) {
981
971
  /* Reds: type-aware atomic equality (xs:float ne xs:double,
982
972
  * xs:date ne string, NaN = NaN inside typed wrappers) — needs
983
973
  * the typed-atom model. */
984
- run_test_set("fn/deep-equal.xml", {}, 192, {},
974
+ run_test_set("fn/deep-equal.xml", {}, 193, {},
985
975
  {
986
976
  "K2-SeqDeepEqualFunc-14",
987
977
  "K2-SeqDeepEqualFunc-15",
@@ -1002,7 +992,6 @@ TEST(Qt3Subset, FnDeepEqual) {
1002
992
  "cbcl-deep-equal-002",
1003
993
  "cbcl-deep-equal-003",
1004
994
  "cbcl-deep-equal-004",
1005
- "cbcl-deep-equal-010",
1006
995
  "fn-deep-equal-arrays-1",
1007
996
  "fn-deep-equal-arrays-14",
1008
997
  "fn-deep-equal-arrays-15",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
3
3
  "name": "leptris",
4
- "version": "1.9.239",
4
+ "version": "1.9.241",
5
5
  "description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
6
6
  "homepage": "https://github.com/leptris/leptris",
7
7
  "license": "MIT",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.240.0
4
+ version: 1.9.241.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.