leptris 1.9.319.0 → 1.9.320.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 +4 -4
- data/CHANGELOG.md +18 -0
- data/Rakefile +1 -1
- data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +23 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/descriptor/plan.c +35 -3
- data/vendor-src/libleptris/test/abi/test_descriptor.cpp +168 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e309fd6444a641db0b0f24f92aef96097860678c006a217a783724c499b7f50
|
|
4
|
+
data.tar.gz: d041122c9d4d1b366549b4dd2d81c8e6c8ee226cc8ddee62d30a6f9a7a582458
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae5b672a25949b41161419054c7ba635e2d234a5ea635a195ad5002d2aeaff4a4c18c3b0e98af5c23cb09c1fc7cec6967e4e866ea4b96c44031e0422fe1545fd
|
|
7
|
+
data.tar.gz: 6f454b0ad8bc92f31196fca9eb28369118cfe9ed81a7ccff3563a9dd903345ff145cdcb9549bcb6efc7d087718f143e1338704f4d6fdd63face059b74369f97f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## [1.9.320.0] - 2026-10-08
|
|
2
|
+
|
|
3
|
+
### Fixed — engine sync (libleptris 1.9.320)
|
|
4
|
+
|
|
5
|
+
- **Exact-URI child rows survive build-anchor GC (#1585)**: the
|
|
6
|
+
engine's plan build let child-row `ns_uri` ride a pointer into
|
|
7
|
+
the caller's build buffers instead of deep-copying — bindings
|
|
8
|
+
GC their anchors after build, so exact-URI child rows
|
|
9
|
+
deterministically stopped matching (uniword's
|
|
10
|
+
dc:title/dc:creator/dc:description hydrated nil). The engine
|
|
11
|
+
retains the URI; pinned with build + GC + walk.
|
|
12
|
+
- **Plain attribute rows match any qualification (#1586)**:
|
|
13
|
+
ns-unset attr rows try the exact wire spelling first, then any
|
|
14
|
+
qualification by local name (#758 parity) — plain rows bind
|
|
15
|
+
`w:name`/`w:val` exactly as the interpretive path does, top
|
|
16
|
+
level and nested; exact-URI rows keep their #1486 precedence.
|
|
17
|
+
Pinned at both depths.
|
|
18
|
+
|
|
1
19
|
## [1.9.319.0] - 2026-10-08
|
|
2
20
|
|
|
3
21
|
### Added — engine sync (libleptris 1.9.319)
|
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.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.320"
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.320] - 2026-10-08
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Descriptor: child-row exact-URI `ns_uri` retained at build
|
|
8
|
+
(#1585, PR #1587).** The build deep-copied child `ns_prefix` but
|
|
9
|
+
let `ns_uri` ride the struct copy — a pointer into the caller's
|
|
10
|
+
build buffers. Bindings GC their anchors (a warm walk plus one GC
|
|
11
|
+
makes it deterministic), after which exact-URI child rows
|
|
12
|
+
silently stopped matching and hydration answered nil (uniword's
|
|
13
|
+
`dc:title`/`dc:creator`/`dc:description`). The spec trashes the
|
|
14
|
+
caller's buffer after build and asserts the row still binds.
|
|
15
|
+
- **Descriptor: plain attribute rows match any qualification
|
|
16
|
+
(#1586, PR #1587).** `dp_attr_value`'s ns-unset path now mirrors
|
|
17
|
+
#758 — the exact wire spelling first, then any qualification by
|
|
18
|
+
local name — so plain rows bind `w:name`/`w:val` exactly as the
|
|
19
|
+
interpretive path does, top level and nested. Exact-URI rows keep
|
|
20
|
+
their #1486 precedence. A NESTED row's binding also consults the
|
|
21
|
+
TARGET plan's `ns_lenient` now (the #1115-consistent authority)
|
|
22
|
+
instead of the outer plan's.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
3
26
|
## [1.9.319] - 2026-10-08
|
|
4
27
|
|
|
5
28
|
### Added
|
|
@@ -285,6 +285,7 @@ static void dp_plan_free(LeptrisPlan p) {
|
|
|
285
285
|
for (uint32_t c = 0; c < d->child_count; c++) {
|
|
286
286
|
free((char *)d->child_plans[c].wire_name);
|
|
287
287
|
free((char *)d->child_plans[c].ns_prefix);
|
|
288
|
+
free((char *)d->child_plans[c].ns_uri);
|
|
288
289
|
for (uint16_t pi = 0;
|
|
289
290
|
pi < d->child_plans[c].predicate_count; pi++) {
|
|
290
291
|
free((char*)d->child_plans[c]
|
|
@@ -481,6 +482,15 @@ LEPTRIS_API LeptrisPlan leptris_plan_build(const leptris_plan_spec* spec,
|
|
|
481
482
|
sc->ns_prefix ? dp_strdup(sc->ns_prefix) : NULL;
|
|
482
483
|
if (sc->ns_prefix && !d->child_plans[c].ns_prefix)
|
|
483
484
|
goto oom;
|
|
485
|
+
/* #1585: ns_uri rides the struct copy otherwise —
|
|
486
|
+
* a dangling pointer into the caller's build
|
|
487
|
+
* buffers once they are reclaimed (bindings GC
|
|
488
|
+
* their anchors; the descriptor must outlive
|
|
489
|
+
* them). Same retention as attr rows (#1486). */
|
|
490
|
+
d->child_plans[c].ns_uri =
|
|
491
|
+
sc->ns_uri ? dp_strdup(sc->ns_uri) : NULL;
|
|
492
|
+
if (sc->ns_uri && !d->child_plans[c].ns_uri)
|
|
493
|
+
goto oom;
|
|
484
494
|
uint16_t pc = sc->predicate_count;
|
|
485
495
|
if (pc) {
|
|
486
496
|
if (!sc->predicates) goto oom;
|
|
@@ -598,8 +608,25 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
|
|
|
598
608
|
* namespace (XML namespaces §5.2) — EXACT never binds them. */
|
|
599
609
|
static const char* dp_attr_value(LeptrisElement elem,
|
|
600
610
|
const leptris_attr_plan* ap) {
|
|
601
|
-
if (!ap->ns_form)
|
|
602
|
-
|
|
611
|
+
if (!ap->ns_form) {
|
|
612
|
+
/* #1586, the plan-path mirror of #758: the exact wire
|
|
613
|
+
* spelling first, then any qualification by local name —
|
|
614
|
+
* plain rows are lenient exactly like element rows under
|
|
615
|
+
* ns_lenient. Exact-URI rows keep their #1486 semantics. */
|
|
616
|
+
const char* exact =
|
|
617
|
+
leptris_element_attribute(elem, ap->wire_name);
|
|
618
|
+
if (exact) return exact;
|
|
619
|
+
for (LeptrisAttribute at = leptris_element_first_attribute(elem);
|
|
620
|
+
at; at = leptris_attribute_next(at)) {
|
|
621
|
+
const char* qn = leptris_attribute_get_name(at);
|
|
622
|
+
if (!qn) continue;
|
|
623
|
+
const char* colon = strchr(qn, ':');
|
|
624
|
+
const char* local = colon ? colon + 1 : qn;
|
|
625
|
+
if (strcmp(local, ap->wire_name) == 0)
|
|
626
|
+
return leptris_attribute_get_value(elem, at);
|
|
627
|
+
}
|
|
628
|
+
return NULL;
|
|
629
|
+
}
|
|
603
630
|
for (LeptrisAttribute at = leptris_element_first_attribute(elem);
|
|
604
631
|
at; at = leptris_attribute_next(at)) {
|
|
605
632
|
const char* qn = leptris_attribute_get_name(at);
|
|
@@ -832,8 +859,13 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
|
|
|
832
859
|
LeptrisElement child = (LeptrisElement)n;
|
|
833
860
|
if (!dp_wire_name_matches(child, row->wire_name))
|
|
834
861
|
continue;
|
|
862
|
+
/* The TARGET plan's form governs (#1115) — so its
|
|
863
|
+
* lenient flag governs the leniency, not the outer
|
|
864
|
+
* plan's (#1586: a namespaced child under a plan
|
|
865
|
+
* that declares ns_lenient binds by local name). */
|
|
835
866
|
if (!dp_ns_binds(child, target->ns_form, target->ns_uri,
|
|
836
|
-
|
|
867
|
+
(target->flags &
|
|
868
|
+
LEPTRIS_PLAN_FLAG_NS_LENIENT) != 0))
|
|
837
869
|
continue;
|
|
838
870
|
/* Claim the element against later same-wire rows
|
|
839
871
|
* (predicate partitioning). */
|
|
@@ -1963,3 +1963,171 @@ TEST(Plan1563, PlainAttrRowAgreesNestedVsRoot) {
|
|
|
1963
1963
|
leptris_plan_free(root_plan);
|
|
1964
1964
|
leptris_plan_free(nested_plan);
|
|
1965
1965
|
}
|
|
1966
|
+
|
|
1967
|
+
// ---- #1585: child-row exact-URI ns_uri retention -------------------
|
|
1968
|
+
// The build copied child ns_prefix but left ns_uri pointing at the
|
|
1969
|
+
// CALLER's buffer — once the binding's build anchors are GC'd the
|
|
1970
|
+
// row dangles and silently stops matching. Retention is observable
|
|
1971
|
+
// without a GC: trash the caller's buffer after build; the row
|
|
1972
|
+
// must still bind.
|
|
1973
|
+
TEST(Plan1585, ChildRowNsUriIsRetained) {
|
|
1974
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
1975
|
+
char ns_uri_buf[64];
|
|
1976
|
+
strcpy(ns_uri_buf, "http://purl.org/dc/elements/1.1/");
|
|
1977
|
+
|
|
1978
|
+
leptris_child_plan kids[2] = {};
|
|
1979
|
+
kids[0].wire_name = "c";
|
|
1980
|
+
kids[0].kind = LEPTRIS_PLAN_KIND_SCALAR;
|
|
1981
|
+
kids[0].type_tag = 1;
|
|
1982
|
+
kids[0].child_plan_index = -1;
|
|
1983
|
+
kids[0].ns_form = LEPTRIS_PLAN_NS_EXACT;
|
|
1984
|
+
kids[0].ns_uri = ns_uri_buf;
|
|
1985
|
+
kids[0].ns_prefix = "d";
|
|
1986
|
+
kids[1].wire_name = "p";
|
|
1987
|
+
kids[1].kind = LEPTRIS_PLAN_KIND_SCALAR;
|
|
1988
|
+
kids[1].type_tag = 2;
|
|
1989
|
+
kids[1].child_plan_index = -1;
|
|
1990
|
+
leptris_element_plan plans[1] = {};
|
|
1991
|
+
plans[0].element_name = "r";
|
|
1992
|
+
plans[0].ns_form = LEPTRIS_PLAN_NS_ANY;
|
|
1993
|
+
plans[0].child_count = 2;
|
|
1994
|
+
plans[0].child_plans = kids;
|
|
1995
|
+
leptris_plan_spec spec = {};
|
|
1996
|
+
spec.abi_version = leptris_plan_abi_version();
|
|
1997
|
+
spec.plan_count = 1;
|
|
1998
|
+
spec.plans = plans;
|
|
1999
|
+
LeptrisPlan plan = leptris_plan_build(&spec, &st);
|
|
2000
|
+
ASSERT_NE(plan, nullptr);
|
|
2001
|
+
|
|
2002
|
+
/* Simulate the GC reclaiming the build anchor: the spec buffer
|
|
2003
|
+
* is now garbage. */
|
|
2004
|
+
memset(ns_uri_buf, 'X', sizeof(ns_uri_buf) - 1);
|
|
2005
|
+
|
|
2006
|
+
LeptrisDocument doc = parse(
|
|
2007
|
+
"<r xmlns:d=\"http://purl.org/dc/elements/1.1/\">"
|
|
2008
|
+
"<d:c>x</d:c><p>y</p></r>");
|
|
2009
|
+
ASSERT_NE(doc, nullptr);
|
|
2010
|
+
LeptrisPlanResult r =
|
|
2011
|
+
leptris_plan_walk(doc, leptris_document_root(doc), plan, &st);
|
|
2012
|
+
ASSERT_NE(r, nullptr);
|
|
2013
|
+
ASSERT_EQ(leptris_plan_value_count(r), 2u);
|
|
2014
|
+
EXPECT_STREQ(leptris_plan_value_string(leptris_plan_value_at(r, 0)),
|
|
2015
|
+
"x")
|
|
2016
|
+
<< "exact-URI child row lost its ns_uri";
|
|
2017
|
+
EXPECT_STREQ(leptris_plan_value_string(leptris_plan_value_at(r, 1)),
|
|
2018
|
+
"y");
|
|
2019
|
+
leptris_plan_result_free(r);
|
|
2020
|
+
leptris_plan_free(plan);
|
|
2021
|
+
leptris_document_free(doc);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// ---- #1586: plain attribute rows match any qualification -----------
|
|
2025
|
+
// The plan-path mirror of #758 (exact wire spelling first, then any
|
|
2026
|
+
// qualification by local name) — a plain attr row must bind w:name
|
|
2027
|
+
// the same way the interpretive path does, at top level and nested.
|
|
2028
|
+
TEST(Plan1586, PlainAttrRowLenientSpelling) {
|
|
2029
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
2030
|
+
const char* xml = "<r xmlns:w=\"http://w/1\" w:top=\"T\"/>";
|
|
2031
|
+
|
|
2032
|
+
leptris_attr_plan attrs[1] = {};
|
|
2033
|
+
attrs[0].wire_name = "top";
|
|
2034
|
+
attrs[0].kind = LEPTRIS_PLAN_KIND_SCALAR;
|
|
2035
|
+
attrs[0].type_tag = 1;
|
|
2036
|
+
leptris_element_plan plans[1] = {};
|
|
2037
|
+
plans[0].element_name = "r";
|
|
2038
|
+
plans[0].attribute_count = 1;
|
|
2039
|
+
plans[0].attribute_plans = attrs;
|
|
2040
|
+
plans[0].flags = LEPTRIS_PLAN_FLAG_NS_LENIENT;
|
|
2041
|
+
leptris_plan_spec spec = {};
|
|
2042
|
+
spec.abi_version = leptris_plan_abi_version();
|
|
2043
|
+
spec.plan_count = 1;
|
|
2044
|
+
spec.plans = plans;
|
|
2045
|
+
LeptrisPlan plan = leptris_plan_build(&spec, &st);
|
|
2046
|
+
ASSERT_NE(plan, nullptr);
|
|
2047
|
+
|
|
2048
|
+
LeptrisDocument doc = parse(xml);
|
|
2049
|
+
ASSERT_NE(doc, nullptr);
|
|
2050
|
+
LeptrisPlanResult r =
|
|
2051
|
+
leptris_plan_walk(doc, leptris_document_root(doc), plan, &st);
|
|
2052
|
+
ASSERT_NE(r, nullptr);
|
|
2053
|
+
EXPECT_STREQ(leptris_plan_value_attribute(r, "top"), "T");
|
|
2054
|
+
leptris_plan_result_free(r);
|
|
2055
|
+
leptris_document_free(doc);
|
|
2056
|
+
leptris_plan_free(plan);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
TEST(Plan1586, PlainAttrRowLenientUnderNestedRow) {
|
|
2060
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
2061
|
+
const char* xml = "<r xmlns:w=\"http://w/1\"><w:c w:n=\"N\"/></r>";
|
|
2062
|
+
|
|
2063
|
+
leptris_attr_plan c_attrs[1] = {};
|
|
2064
|
+
c_attrs[0].wire_name = "n";
|
|
2065
|
+
c_attrs[0].kind = LEPTRIS_PLAN_KIND_SCALAR;
|
|
2066
|
+
c_attrs[0].type_tag = 1;
|
|
2067
|
+
leptris_child_plan c_row = {};
|
|
2068
|
+
c_row.wire_name = "c";
|
|
2069
|
+
c_row.kind = LEPTRIS_PLAN_KIND_NESTED;
|
|
2070
|
+
c_row.child_plan_index = 1;
|
|
2071
|
+
c_row.ns_form = LEPTRIS_PLAN_NS_ANY;
|
|
2072
|
+
leptris_element_plan plans[2] = {};
|
|
2073
|
+
plans[0].element_name = "r";
|
|
2074
|
+
plans[0].child_count = 1;
|
|
2075
|
+
plans[0].child_plans = &c_row;
|
|
2076
|
+
plans[1].element_name = "c";
|
|
2077
|
+
plans[1].attribute_count = 1;
|
|
2078
|
+
plans[1].attribute_plans = c_attrs;
|
|
2079
|
+
plans[1].flags = LEPTRIS_PLAN_FLAG_NS_LENIENT;
|
|
2080
|
+
leptris_plan_spec spec = {};
|
|
2081
|
+
spec.abi_version = leptris_plan_abi_version();
|
|
2082
|
+
spec.plan_count = 2;
|
|
2083
|
+
spec.plans = plans;
|
|
2084
|
+
LeptrisPlan plan = leptris_plan_build(&spec, &st);
|
|
2085
|
+
ASSERT_NE(plan, nullptr);
|
|
2086
|
+
|
|
2087
|
+
LeptrisDocument doc = parse(xml);
|
|
2088
|
+
ASSERT_NE(doc, nullptr);
|
|
2089
|
+
LeptrisPlanResult r =
|
|
2090
|
+
leptris_plan_walk(doc, leptris_document_root(doc), plan, &st);
|
|
2091
|
+
ASSERT_NE(r, nullptr);
|
|
2092
|
+
ASSERT_EQ(leptris_plan_value_count(r), 1u);
|
|
2093
|
+
LeptrisPlanResult child = leptris_plan_value_at(r, 0);
|
|
2094
|
+
ASSERT_NE(child, nullptr);
|
|
2095
|
+
EXPECT_STREQ(leptris_plan_value_attribute(child, "n"), "N");
|
|
2096
|
+
leptris_plan_result_free(r);
|
|
2097
|
+
leptris_document_free(doc);
|
|
2098
|
+
leptris_plan_free(plan);
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
TEST(Plan1586, ExactUriRowStillWinsAndUnqualifiedStillBinds) {
|
|
2102
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
2103
|
+
/* exact-URI row keeps its #1486 semantics; unqualified wire
|
|
2104
|
+
* attr binds a plain row by the literal spelling. */
|
|
2105
|
+
const char* xml = "<r xmlns:w=\"http://w/1\" w:top=\"T\" top=\"U\"/>";
|
|
2106
|
+
leptris_attr_plan exact[1] = {};
|
|
2107
|
+
exact[0].wire_name = "top";
|
|
2108
|
+
exact[0].kind = LEPTRIS_PLAN_KIND_SCALAR;
|
|
2109
|
+
exact[0].type_tag = 1;
|
|
2110
|
+
exact[0].ns_form = LEPTRIS_PLAN_NS_EXACT;
|
|
2111
|
+
exact[0].ns_uri = "http://w/1";
|
|
2112
|
+
leptris_element_plan plans[1] = {};
|
|
2113
|
+
plans[0].element_name = "r";
|
|
2114
|
+
plans[0].attribute_count = 1;
|
|
2115
|
+
plans[0].attribute_plans = exact;
|
|
2116
|
+
leptris_plan_spec spec = {};
|
|
2117
|
+
spec.abi_version = leptris_plan_abi_version();
|
|
2118
|
+
spec.plan_count = 1;
|
|
2119
|
+
spec.plans = plans;
|
|
2120
|
+
LeptrisPlan plan = leptris_plan_build(&spec, &st);
|
|
2121
|
+
ASSERT_NE(plan, nullptr);
|
|
2122
|
+
LeptrisDocument doc = parse(xml);
|
|
2123
|
+
ASSERT_NE(doc, nullptr);
|
|
2124
|
+
LeptrisPlanResult r =
|
|
2125
|
+
leptris_plan_walk(doc, leptris_document_root(doc), plan, &st);
|
|
2126
|
+
ASSERT_NE(r, nullptr);
|
|
2127
|
+
/* The qualified spelling carries "T"; the exact row must take
|
|
2128
|
+
* it, not the unqualified "U". */
|
|
2129
|
+
EXPECT_STREQ(leptris_plan_value_attribute(r, "top"), "T");
|
|
2130
|
+
leptris_plan_result_free(r);
|
|
2131
|
+
leptris_document_free(doc);
|
|
2132
|
+
leptris_plan_free(plan);
|
|
2133
|
+
}
|
|
@@ -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.
|
|
4
|
+
"version": "1.9.320",
|
|
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",
|