RIP 4: URI Scheme

Building on the work that Lorenz Leutgeb did in patch
rad:z3trNYnLWS11cJWC6BbxDs5niGo82/patch/19ba16634fbde97156255ed97069d0aa3ab2bf12,
the URI scheme is brought to a final RIP document, with full ABNF and Nearley
grammars as well as examples, and a couple of scripts to extract and test those
grammars against those examples.

Compared to the URL scheme in RIP 3, this RIP brings a subtly different URL form,
while also supporting the RIP 3 scheme (now considered legacy).  In short, these
two forms are supported by this RIP:

- rad://{rid}/{nid}                             (considered legacy)
- rad://{rad-auth}/{rid}[/{namespace}][/{resource-type-and-identifier}]

(rad-auth is the authority part, which can take the form of {nid}@{host-and-port}
or parts thereof)

This works, because the {rid} and the {nid} are distinguishable, as a {nid} always
starts with z6Mk, while a {rid} does start with a z, but never with z6Mk, and they
have different lengths (a {rid} is always 28 or 29 characters long, which a {nid}
is always 48 characters long).

Co-authored-by: Fintan Halpenny <fintan.halpenn@gmail.com>
Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
This commit is contained in:
Richard Levitte
2026-05-04 22:40:28 +02:00
committed by Lorenz Leutgeb
co-authored by Fintan Halpenny Lorenz Leutgeb
parent 7e993a9bec
commit 43301b47e5
60 changed files with 1848 additions and 3 deletions
+1
View File
@@ -1 +1,2 @@
node_modules/
result result
+208
View File
@@ -0,0 +1,208 @@
; This grammar is written using ABNF, as defined in RFC 5234
; (https://datatracker.ietf.org/doc/html/rfc5234), from which ALPHA, DIGIT
; and HEXDIG are borrowed
;
; Furthermore, this grammar is based on and specialises the generic URI
; specification (https://datatracker.ietf.org/doc/html/rfc3986) for Radicle,
; from which 'query', 'fragment', 'host', 'port', 'unreserved', 'pchar', ...
; are borrowed
; tag::overview[]
; RFC 3986 defines the syntax for 'query' and 'fragment'
rad = rad-scheme ":" rad-auth-and-resource rad-resource-type-and-identity [ "?" query ] [ "#" fragment ]
/ rad-scheme ":" rad-legacy
rad-scheme = "rad"
; RFC 3986 allows an absolute resource path without authority, i.e. a URI
; that looks like this: rad:/{RID}/{NID}
; We don't include that sort of URI in this scheme, because it's, strictly
; speaking, unnecessary in this context, and may seem confusing.
rad-auth-and-resource = "//" [ rad-auth ] "/" rad-resource ; abs. resource "path" with authority
/ rad-resource ; rel. resource "path"
; rad-legacy are URLs defined by RIP #3. That URL doesn't quite hold up
; against the general URI syntax defined in RFC3986, in so far that a node
; (identified with NID or NID@host) is the conceptual authority, not
; a repository.
; To be noted is that RIDs and NIDs are distinct enough that there should not
; be any ambiguity between rad-auth-and-resource and rad-legacy.
; Of all current implementations, this mostly impacts the git remote helper
; git-remote-rad.
rad-legacy = "//" rad-resource
; end::overview[]
; The NID is conceptually ambiguous from an RFC 3986 syntax perspective:
;
; authority = [ userinfo "@" ] host [ ":" port ]
;
; Is the NID 'userinfo' or 'host'? In a sense, it currently is a little bit
; of both. Furthermore, Radicle requires that if a hostname (+ port) is
; given, the NID must be given too. Therefore, instead of using 'authority'
; strictly as given by RFC 3986, we allow ourselves a slightly different
; syntax, which is fine. Do note also, that a 'host' can still be made to
; look like an NID, so programs dealing directly with the Radicle network
; will have to be prepared to handle that ambiguity appropriately.
; tag::authority[]
;
; TODO: There already are ways to resolve its NID from a hostname.
; For example, if `radicle-httpd` runs next to the node, then it can be
; queried for its NID via HTTPS. Also, there are ways to use DNS TXT records,
; which is also done for DNS-SD.
; We would therefore likely make the `rad-node` optional, or even omit it.
; If `rad-node` is sufficiently different from a DNS name (it probably is
; not), then we can even omit "@".
rad-auth = rad-node [ "@" rad-host-and-port ]
; TODO: Port might be optional in the future, once we have convinced IANA
; to assign one to us. There currently is no default port.
rad-host-and-port = host ":" port
; rad-node is a semantic symbol, to signify intent
rad-node = rad-nid
; An NID is an ID that is ultimately a public key in the Radicle network.
; This is used to uniquely represent a node, or a user (also a node), or a
; namespace within a repository.
; It is a [multibase] and [multicodec] encoded string, using [base58btc] as the multibase
; and the codec for the underlying Ed25519 public key.
;
; It is always 48 characters long, including the initial "z6Mk",
; the `z` corresponding to the base, and `6Mk` corresponding to the codec.
;
; [base58btc]: https://digitalbazaar.github.io/base58-spec/
; [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
; [multicodec]: https://github.com/multiformats/multicodec/
;
; TODO: New NIDs in the future? Even this might change, as we allow rotating
; keys for nodes. But this is even further out than stable IDs for users.
rad-nid = "z6Mk" 44( base58btc )
; end::authority[]
; tag::resource[]
rad-resource = rad-repository [ "/" rad-namespace ]
; rad-repository is a semantic symbol, to signify intent
rad-repository = rad-rid
; rad-namespace is a semantic symbol, to signify intent
rad-namespace = rad-nid
; An RID uniquely represents a git repository in the Radicle network.
; It is a [multibase] encoded string of a Git OID, using [base58btc] as the multibase.
;
; It is 28 or 29 characters long, including the initial multibase identifier "z".
;
; [base58btc]: https://digitalbazaar.github.io/base58-spec/
; [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
;
; TODO: In the near future (as soon as Git repositories with SHA-256 object
; format become popular, which will be in late 2026), we will have to extend
; the syntax of RIDs. Leave a note here so that implementors know this and
; make design decisions accordingly.
rad-rid = "z" 27*28( base58btc )
; end::resource[]
; tag::resource-type-and-identity[]
; 'rad-resource-type-and-identity' is empty by default, and more resource
; types and identities will be added further on, using the ABNF =/ operator,
; see RFC 5234 "3.3. Incremental Alternatives: Rule1 =/ Rule2"
rad-resource-type-and-identity = ""
; tag::git-type-and-identity[]
; For git specific resource types
rad-resource-type-and-identity =/ git-commit / git-tree / git-blob / git-tag
git-commit = "/" "commit" "/" git-obj-or-ref
git-tree = "/" "tree" "/" git-obj
git-blob = "/" "blob" "/" git-obj
git-tag = "/" "tag" "/" git-obj-or-ref
; Note: this rule will always be ambiguous, as something that looks like a
; git object ID could very well be a branch or a tag.
; Some git commands will warn when such refs are used, but not all.
git-obj-or-ref = git-obj / git-ref
; Currently, only sha1 identities are supported.
git-obj = git-sha1
; HEXDIG is defined in RFC 5234
git-sha1 = 40HEXDIG
; unreserved is defined in RFC 3986. Not that this syntax allows much
; more than what can be specified for a git reference. It's left to
; the implementation to parse and apply semantics properly.
; Our best reference for how to interpret and parse git references is
; https://git-scm.com/docs/protocol-common.html
git-ref = 1*unreserved *( "/" 1*unreserved )
; end::git-type-and-identity[]
; tag::rad-cob-type-and-identity[]
; For Radicle collaborative object resource types
rad-resource-type-and-identity =/ rad-cob / rad-cobs
rad-cob = "/" "cob" "/" rad-cob-type "/" rad-cob-obj
rad-cobs = "/" "cob" "/" rad-cob-type
; The COB type is like a reversed FQDN. The syntax spec is derived from
; https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
rad-cob-type = rad-cob-label 1*( "." rad-cob-label )
rad-cob-label = 1*( ALPHA / DIGIT ) *( "-" 1*( ALPHA / DIGIT ) )
; syntactically, radicle COB objects and object revisions are git objects
; semantically, they are as well, at least for now
rad-cob-obj = git-obj
; end::rad-cob-type-and-identity[]
; end::resource-type-and-identity[]
; tag::rfc5234[]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Copied from RFC 5234
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
DIGIT = %x30-39 ; 0-9
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
; Copied from RFC 3986
host = IP-literal / IPv4address / reg-name
port = *DIGIT
IP-literal = "[" ( IPv6address / IPvFuture ) "]"
IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
IPv6address = 6( h16 ":" ) ls32
/ "::" 5( h16 ":" ) ls32
/ [ h16 ] "::" 4( h16 ":" ) ls32
/ [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
/ [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
/ [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
/ [ *4( h16 ":" ) h16 ] "::" ls32
/ [ *5( h16 ":" ) h16 ] "::" h16
/ [ *6( h16 ":" ) h16 ] "::"
h16 = 1*4HEXDIG
ls32 = ( h16 ":" h16 ) / IPv4address
IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
dec-octet = DIGIT ; 0-9
/ %x31-39 DIGIT ; 10-99
/ "1" 2DIGIT ; 100-199
/ "2" %x30-34 DIGIT ; 200-249
/ "25" %x30-35 ; 250-255
reg-name = *( unreserved / pct-encoded / sub-delims )
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
query = *( pchar / "/" / "?" )
fragment = *( pchar / "/" / "?" )
pct-encoded = "%" HEXDIG HEXDIG
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
; end::rfc5234[]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Base58 Bitcoin Alphabet
base58btc = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+245
View File
@@ -0,0 +1,245 @@
@{%
const lstr = function(d) { return d.flat(5).join(''); }
%}
# In the ABNF grammar, 'rad-resource-type-and-identity' defaults to empty,
# which effectively renders it optional. Nearley doesn't allow empty rules,
# and multiple rules with the same name can be added anyway, so we make
# resource_type_and_identity optional here to get the same essential semantics.
rad -> scheme ":" auth_and_resource resource_type_and_identity:?
( "?" rad_query ):? ( "#" fragment ):?
{% ([s, , aar, rtai, q, f]) =>
({ scheme: s,
...aar,
...(rtai ? { resource: rtai} : {}),
...(q ? { query: q[1]} : {}),
...(f ? { fragment: f[1]} : {}) }) %}
| scheme ":" legacy
{% ([s, , l]) => ({ scheme: s, ...l }) %}
scheme -> "rad" {% lstr %}
auth_and_resource -> "//" auth:? "/" resource # absolute resource "path"
{% ([, auth = {}, , res]) => ({ ...auth, ...res }) %}
| resource # relative resource "path"
{% ([res]) => res %}
# 'legacy' are URLs defined by RIP #3. That URL doesn't quite hold up
# against the general URI syntax defined in RFC3986, in so far that a node
# (identified with NID or NID@host) is the conceptual authority, not
# a repository.
# To be noted is that RIDs and NIDs are distinct enough that there shouldn't
# be any ambiguity between rad-auth-and-resource and rad-legacy.
# Of all current implementations, this mostly impacts the git remote helper
# git-remote-rad.
legacy -> "//" resource
{% ([, res]) => res %}
# The NID is conceptually ambiguous from an RFC 3986 syntax perspective:
#
# authority = [ userinfo "@" ] host [ ":" port ]
#
# Is the NID 'userinfo' or 'host'? In a sense, it currently is a little bit
# of both. Furthermore, Radicle requires that if a hostname (+ port) is
# given, the NID must be given too. Therefore, instead of using 'authority'
# strictly as given by RFC 3986, we allow ourselves a slightly different
# syntax, which is fine. Do note also, that a 'host' can still be made to
# look like an NID, so programs dealing directly with the Radicle network
# will have to be prepared to handle that ambiguity appropriately.
#
# TODO: There already are ways to resolve its NID from a hostname.
# For example, if `radicle-httpd` runs next to the node, then it can be
# queried for its NID via HTTPS. Also, there are ways to use DNS TXT records,
# which is also done for DNS-SD.
# We would therefore likely make the `node` optional, or even omit it.
# If `node` is sufficiently different from a DNS name (it probably is
# not), then we can even omit "@".
auth -> node ("@" host_and_port):?
{% ([node, hap]) => ({ node: node, ...(hap ? { host: hap[1] } : {}) }) %}
host_and_port -> host ":" port {% lstr %}
# rad-node is a semantic symbol, to signify intent
node -> nid {% id %}
# An NID is an ID that is ultimately a public key in the Radicle network.
# This is used to uniquely represent a node, or a user (also a node), or a
# namespace within a repository.
# It is a [multibase] and [multicodec] encoded string, using [base58btc] as the multibase
# and the codec for the underlying Ed25519 public key.
#
# It is always 48 characters long, including the initial "z6Mk",
# the `z` corresponding to the base, and `6Mk` corresponding to the codec.
#
# [base58btc]: https://digitalbazaar.github.io/base58-spec/
# [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
# [multicodec]: https://github.com/multiformats/multicodec/
#
# TODO: New NIDs in the future? Even this might change, as we allow rotating
# keys for nodes. But this is even further out than stable IDs for users.
nid -> "z6Mk" b58x25 b58x10 b58x5 base58btc base58btc base58btc base58btc {% lstr %}
resource -> repository ( "/" namespace ):?
{% ([repo, ns]) => ({ repo: repo, ...(ns ? { namespace: ns[1] } : {}) }) %}
# repository is a semantic symbol, to signify intent
repository -> rid {% id %}
# namespace is a semantic symbol, to signify intent
namespace -> nid {% id %}
# An RID uniquely represents a git repository in the Radicle network.
# It is a [multibase] encoded string of a Git OID, using [base58btc] as the multibase.
#
# It is 28 or 29 characters long, including the initial multibase identifier "z".
#
# [base58btc]: https://digitalbazaar.github.io/base58-spec/
# [multibase]: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html
#
# TODO: In the near future (as soon as Git repositories with SHA-256 object
# format become popular, which will be in late 2026), we will have to extend
# the syntax of RIDs. Leave a note here so that implementors know this and
# make design decisions accordingly.
rid -> "z" b58x25 base58btc base58btc base58btc:?
{% (d,l,r) => {
s = d.join('')
// Check that the RID doesn't starts
// with "z6Mk", as that can only be
// an NID. Reject it if it is.
if (s.startsWith("z6Mk")) { return r; }
return s;
}
%}
# For git specific resource types
resource_type_and_identity -> git_commit {% id %}
resource_type_and_identity -> git_tree {% id %}
resource_type_and_identity -> git_blob {% id %}
resource_type_and_identity -> git_tag {% id %}
git_commit -> "/" "commit" "/" git_obj_or_ref {% ([, , , o]) => ({ type: "commit", ...o }) %}
git_tree -> "/" "tree" "/" git_obj {% ([, , , o]) => ({ type: "tree", obj: o }) %}
git_blob -> "/" "blob" "/" git_obj {% ([, , , o]) => ({ type: "blob", ref: o }) %}
git_tag -> "/" "tag" "/" git_obj_or_ref {% ([, , , o]) => ({ type: "tag", ...o }) %}
# Note: this rule will always be ambiguous, as something that looks like a
# git object ID could very well be a branch or a tag.
# Some git commands will warn when such refs are used, but not all.
git_obj_or_ref -> git_obj {% ([o]) => ({ obj: o }) %}
| git_ref {% ([r]) => ({ ref: r }) %}
# Currently, only sha1 identities are supported.
git_obj -> git_sha1 {% id %}
# a sha1 ID is 40 hex digits
git_sha1 -> hex8 hex8 hex8 hex8 hex8 {% lstr %}
# unreserved is defined in RFC 3986. Not that this syntax allows much
# more than what can be specified for a git reference. It's left to
# the implementation to parse and apply semantics properly.
# Our best reference for how to interpret and parse git references is
# https://git-scm.com/docs/protocol-common.html
git_ref -> unreserved:+ ( "/" unreserved:+ ):* {% lstr %}
# For Radicle collaborative object resource types
resource_type_and_identity -> rad_cob {% id %}
resource_type_and_identity -> rad_cobs {% id %}
rad_cob -> "/" "cob" "/" rad_cob_type "/" rad_cob_obj
{% ([, , , type, , obj]) =>
({ type: "cob", id: { type: type, obj: obj } }) %}
rad_cobs -> "/" "cob" "/" rad_cob_type
{% ([, , , type]) =>
({ type: "cobs", id: { type: type } }) %}
# The COB type is like a reversed FQDN. The syntax spec is derived from
# https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
rad_cob_type -> rad_cob_label ( "." rad_cob_label ):+ {% lstr %}
rad_cob_label -> alnum:+ ( "-" alnum:+ ):* {% lstr %}
# syntactically, radicle COB objects and object revisions are git objects
# semantically, they are as well, at least for now
rad_cob_obj -> git_obj {% id %}
# While the ABNF doesn't say anything special about 'query', we can allow
# it for ourselves here, since nearley does a bit more than just checking
# syntax.
rad_query -> rad_query_param ( "&" rad_query_param ):*
{% ([first, rest]) => {
newrest = rest ? rest : [];
newrest = newrest.reduce((tot, val) => { tot.push(val[1]); return tot }, []);
return ([first, ...newrest].reduce((tot, val) => {
if (tot[val['name']]) {
tot[val['name']].push(val['value']);
} else {
tot[val['name']] = [ val['value'] ];
}
return tot;
}, {}))
} %}
rad_query_param -> ( alpha alnum:* ) "=" ( rad_query_pchar | "/" | "?" ):*
{% ([name, , value]) => ({ name: lstr(name), value: lstr(value) }) %}
rad_query_pchar -> unreserved | pct_encoded | rad_query_sub_delims | ":" | "@"
rad_query_sub_delims -> "!" | "$" | "'" | "(" | ")"
| "*" | "+" | "," | ";" | "="
# Helper symbols
alnum -> [a-zA-Z0-9] {% id %}
alnum5 -> alnum alnum alnum alnum alnum {% function(d) { return d.join(''); } %}
alnum10 -> alnum5 alnum5 {% function(d) { return d.join(''); } %}
alnum25 -> alnum10 alnum10 alnum5 {% function(d) { return d.join(''); } %}
# Base58 Bitcoin Alphabet (excludes 0, O, I, l)
base58btc -> [1-9A-HJ-NP-Za-km-z] {% id %}
b58x5 -> base58btc base58btc base58btc base58btc base58btc {% function(d) { return d.join(''); } %}
b58x10 -> b58x5 b58x5 {% function(d) { return d.join(''); } %}
b58x25 -> b58x10 b58x10 b58x5 {% function(d) { return d.join(''); } %}
hex2 -> hexdig hexdig {% function(d) { return d.join(''); } %}
hex4 -> hex2 hex2 {% function(d) { return d.join(''); } %}
hex8 -> hex4 hex4 {% function(d) { return d.join(''); } %}
# ABNF defines ALPHA, DIGIT and HEXDIG
alpha -> [a-zA-Z] {% id %}
digit -> [0-9] {% id %}
hexdig -> [a-f0-9] {% id %}
# RFC 3986 defines the following (translated to nearley)
host -> IP_literal | IPv4address | reg_name
port -> [0-9]:+
IP_literal -> "[" ( IPv6address | IPvFuture ) "]"
IPvFuture -> "v" hexdig:+ "." ( unreserved | sub_delims | ":" ):+
IPv6address -> h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" ls32
| "::" h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" ls32
| h16 "::" h16 ":" h16 ":" h16 ":" h16 ":" ls32
| h16 ":" h16 "::" h16 ":" h16 ":" h16 ":" ls32
| h16 ":" h16 ":" h16 "::" h16 ":" h16 ":" ls32
| h16 ":" h16 ":" h16 ":" h16 "::" h16 ":" ls32
| h16 ":" h16 ":" h16 ":" h16 ":" h16 "::" ls32
| h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" h16 "::" h16
| h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" h16 ":" h16 "::"
h16 -> hexdig | hex2 | hex2 hexdig | hex4
ls32 -> ( h16 ":" h16 ) | IPv4address
IPv4address -> dec_octet "." dec_octet "." dec_octet "." dec_octet
dec_octet -> digit # 0-9
| [1-9] digit # 10-99
| "1" digit digit # 100-199
| "2" [0-4] digit # 200-249
| "25" [0-5] # 250-255
reg_name -> ( unreserved | pct_encoded | sub_delims ):*
pchar -> unreserved | pct_encoded | sub_delims | ":" | "@"
query -> ( pchar | "/" | "?" ):*
fragment -> ( pchar | "/" | "?" ):*
pct_encoded -> "%" hex2
unreserved -> alnum | "-" | "." | "_" | "~"
sub_delims -> "!" | "$" | "&" | "'" | "(" | ")"
| "*" | "+" | "," | ";" | "="
+1
View File
@@ -0,0 +1 @@
rad:z3gqcJUbbbbbbbbbbbbbbbCSGazv5
@@ -0,0 +1,6 @@
[
{
"scheme": "rad",
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
}
]
+1
View File
@@ -0,0 +1 @@
rad://z3gqcJUbbbbbbbbbbbbbbbCSGazv5
@@ -0,0 +1,6 @@
[
{
"scheme": "rad",
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
}
]
+1
View File
@@ -0,0 +1 @@
rad:///z3gqcJUbbbbbbbbbbbbbbbCSGazv5
@@ -0,0 +1,6 @@
[
{
"scheme": "rad",
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
}
]
+1
View File
@@ -0,0 +1 @@
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT
@@ -0,0 +1,7 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
}
]
+1
View File
@@ -0,0 +1 @@
rad://zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT
@@ -0,0 +1,7 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
}
]
+1
View File
@@ -0,0 +1 @@
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT
@@ -0,0 +1,7 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
}
]
+1
View File
@@ -0,0 +1 @@
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E
@@ -0,0 +1,7 @@
[
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E"
}
]
+1
View File
@@ -0,0 +1 @@
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT
@@ -0,0 +1,8 @@
[
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
}
]
+1
View File
@@ -0,0 +1 @@
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT@seed.example.com:8776/zk1arLZbbbbbbbbbbbbbbb26us45E
@@ -0,0 +1,8 @@
[
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"host": "seed.example.com:8776",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E"
}
]
+1
View File
@@ -0,0 +1 @@
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT@seed.example.com:8776/zk1arLZbbbbbbbbbbbbbbb26us45E/z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx
@@ -0,0 +1,9 @@
[
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"host": "seed.example.com:8776",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"namespace": "z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx"
}
]
+1
View File
@@ -0,0 +1 @@
rad:z3gqcJUbbbbbbbbbbbbbbbCSGazv5/commit/72db6dffffffffffffffffffffffffffffffffff
@@ -0,0 +1,18 @@
[
{
"scheme": "rad",
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5",
"resource": {
"type": "commit",
"ref": "72db6dffffffffffffffffffffffffffffffffff"
}
},
{
"scheme": "rad",
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5",
"resource": {
"type": "commit",
"obj": "72db6dffffffffffffffffffffffffffffffffff"
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:///z3gqcJUbbbbbbbbbbbbbbbCSGazv5/commit/72db6dffffffffffffffffffffffffffffffffff
@@ -0,0 +1,18 @@
[
{
"scheme": "rad",
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5",
"resource": {
"type": "commit",
"ref": "72db6dffffffffffffffffffffffffffffffffff"
}
},
{
"scheme": "rad",
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5",
"resource": {
"type": "commit",
"obj": "72db6dffffffffffffffffffffffffffffffffff"
}
}
]
+1
View File
@@ -0,0 +1 @@
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E/commit/e9cf5263ffffffffffffffffffffffffffffffff
@@ -0,0 +1,20 @@
[
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"ref": "e9cf5263ffffffffffffffffffffffffffffffff"
}
},
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"obj": "e9cf5263ffffffffffffffffffffffffffffffff"
}
}
]
+1
View File
@@ -0,0 +1 @@
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT@seed.example.com:8776/zk1arLZbbbbbbbbbbbbbbb26us45E/z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx/commit/72db6dffffffffffffffffffffffffffffffffff
@@ -0,0 +1,24 @@
[
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"host": "seed.example.com:8776",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"namespace": "z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx",
"resource": {
"type": "commit",
"ref": "72db6dffffffffffffffffffffffffffffffffff"
}
},
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"host": "seed.example.com:8776",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"namespace": "z6Mku8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbo9XVAx",
"resource": {
"type": "commit",
"obj": "72db6dffffffffffffffffffffffffffffffffff"
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/cob/org.example/e9cf5263ffffffffffffffffffffffffffffffff
@@ -0,0 +1,13 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "cob",
"id": {
"type": "org.example",
"obj": "e9cf5263ffffffffffffffffffffffffffffffff"
}
}
}
]
+1
View File
@@ -0,0 +1 @@
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E/cob/org.example/e9cf5263ffffffffffffffffffffffffffffffff
@@ -0,0 +1,14 @@
[
{
"scheme": "rad",
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "cob",
"id": {
"type": "org.example",
"obj": "e9cf5263ffffffffffffffffffffffffffffffff"
}
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/cob/org.example
@@ -0,0 +1,12 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "cobs",
"id": {
"type": "org.example"
}
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/tree/3eb47e9fffffffffffffffffffffffffffffffff?path=src
@@ -0,0 +1,15 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "tree",
"obj": "3eb47e9fffffffffffffffffffffffffffffffff"
},
"query": {
"path": [
"src"
]
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/tree/3eb47e9fffffffffffffffffffffffffffffffff?path=src
@@ -0,0 +1,15 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "tree",
"obj": "3eb47e9fffffffffffffffffffffffffffffffff"
},
"query": {
"path": [
"src"
]
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/commit/master?blob=README.md
@@ -0,0 +1,15 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"ref": "master"
},
"query": {
"blob": [
"README.md"
]
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/commit/master?blob=README.md
@@ -0,0 +1,15 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"ref": "master"
},
"query": {
"blob": [
"README.md"
]
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/commit/baz?path=foo/doc
@@ -0,0 +1,15 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"ref": "baz"
},
"query": {
"path": [
"foo/doc"
]
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/commit/baz?path=foo/doc
@@ -0,0 +1,15 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"ref": "baz"
},
"query": {
"path": [
"foo/doc"
]
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/commit/baz/foo?path=doc&path=src
@@ -0,0 +1,16 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"ref": "baz/foo"
},
"query": {
"path": [
"doc",
"src"
]
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/commit/baz/foo?path=doc&path=src
@@ -0,0 +1,16 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"ref": "baz/foo"
},
"query": {
"path": [
"doc",
"src"
]
}
}
]
+1
View File
@@ -0,0 +1 @@
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/commit/refs/notes/commit?blob=e9cf5263ffffffffffffffffffffffffffffffff
@@ -0,0 +1,15 @@
[
{
"scheme": "rad",
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
"resource": {
"type": "commit",
"ref": "refs/notes/commit"
},
"query": {
"blob": [
"e9cf5263ffffffffffffffffffffffffffffffff"
]
}
}
]
+42
View File
@@ -0,0 +1,42 @@
#! /bin/sh
set -euo pipefail
if ! command -v nearley-test > /dev/null; then
echo >&2 'The command `nearley-test` must be available to run this script. Consider running `npm install -g nearley`.'
exit 2
fi
if ! command -v yq > /dev/null; then
echo >&2 'The command `yq` must be available to run this script.'
exit 2
fi
readonly DATA="${1:-"$(git rev-parse --show-toplevel)/0004-general-uri-scheme/data"}"
readonly RAD_URI_NE="${DATA}/rad-uri.ne"
readonly RAD_URI_JS="$TMPDIR/rad-uri.js"
if ! nearleyc -o "$RAD_URI_JS" "$RAD_URI_NE"; then
exit 1
fi
readonly TMP="$(mktemp -d)"
FAILED=0
for EXPECTED in "${DATA}"/test/tc*-expected.json; do
INPUT="${EXPECTED%-expected.json}"
NAME="${INPUT##*/}"
ACTUAL="${TMP}/${NAME}.actual"
nearley-test -q -i "$(cat "$INPUT")" "$RAD_URI_JS" | yq > "${ACTUAL}"
if ! diff -u "$EXPECTED" "$ACTUAL" ; then
echo "FAIL: $NAME"
FAILED=1
else
echo "PASS: $NAME"
fi
done
[ "$FAILED" -eq 0 ]
@@ -0,0 +1,867 @@
= RIP 4: General `rad:` URI Scheme
Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>; Richard Levitte <richard@levitte.org>
:doctype: article
:toc: preamble
:toclevels: 3
:status: Draft
:copyright: CC0-1.0
:lang: en
--
In <<RIP2>>, a `rad:` URN is briefly discussed as a simple Radicle
identifier. In <<RIP3>>, the `rad://` URL is introduced, made to allow
`git-remote-rad` to be a fruitful interface between Git and Radicle.
In this RIP, we discuss some modifications that are needed to have a general
URI scheme that fits in the syntax defined by <<URI>>, as well as further
extensions that allow reference to more aspects of Radicle as well as Git on a
Radicle network.
All extensions and modifications are intended not to introduce impossible
clashes with the existing schemes.
--
== Status Quo
References to resources on Radicle (both traditional Git objects as well as
collaborative objects, such as patches and issues) are communicated between
individuals mostly by relying on installations of <<radicle-explorer>>,
a web application that provides an HTTPS interface to the Radicle network.
When Alice wants to direct Bob's attention to a specific issue that she filed
on Radicle, you will likely see her send a URI of the form
`https://app.example.com/nodes/seed.example.com/rad:…/issues/…` to Bob.
URIs using the `rad:` schema are exchanged more rarely, and almost exclusively
to refer to repositories as a whole, e.g. in order to invoke `rad clone`,
`git clone`, etc.
As of 2024-04-23, the Radicle team also opted to have `rad:…`
URIs in Zulip messages automatically replaced by links to
`https://app.radicle.xyz/nodes/garden.radicle.xyz/rad:…`, limiting the
usefulness of exchange of `rad:` URIs.<<zulip-mangle>>
== Terminology
Node ID:: a Node ID, or NID for short, is an identifier for a Radicle node and a Radicle user, at the time of writing.
It is a <<multibase>> (<<base58btc>>) and <<multicodec>> encoded Ed25519 public key.
Namespace:: a namespace, in the Radicle ecosystem, commonly refers to a Git namespace, i.e. `refs/namespaces`,
that uses a *Node ID* as the namespace, e.g., `refs/namespaces/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi`.
Repository ID:: a Repository ID, or RID for short, is an identifier for a Radicle repository.
It is a <<multibase>> (<<base58btc>>) encoded Git object identifier, at the time of writing.
== Challenges
:url-cgit: https://git.zx2c4.com/cgit/
:url-sourcegraph: https://sourcegraph.com/
Every user should be able to individually control how they want to interact
with the network. Users may prefer to interact with Radicle via…
* a command line interface in their shell.
* a specific client program, such as:
** VSCode with a suitable extension, <<radicle-vscode>>
** a JetBrains IDE with a suitable plugin, <<radicle-jetbrains>>.
* <<radicle-explorer>>, hosted locally or by relying on a specific hosting
provider.
* another custom program or handler, e.g. redirection to an instance of
{url-cgit}[cgit] or {url-sourcegraph}[Sourcegraph].
Exchanging `https:` URIs that point at a specific hosting provider of
<<radicle-explorer>> is not portable. Individuals that read/receive such a URI
might prefer to access the repository with another method and then must modify
the URI (extract repository address, etc.) before access.
For the case of a reference to a repository, this is rather simple, but as
soon as references to resources _within_ the repositories are exchanged, there
are currently no standardized options. The two most common ones are referring
to an instance of `radicle-explorer` by means of a `https:`-URI, or
communicating in natural language, often in a context where the repository ID
is clear to all communicating parties. They will simply say "patch c863fe".
Automatic rewriting of `rad:` URIs to a specific hosting provider of
`radicle-explorer` contributes to additional centralization of access to the
network. In case the specific hosting provider goes offline, is censored, or
censors content, the impact is multiplied.
== Intention
The intention with this RIP is to define a `rad:` URI scheme with which one
can refer to:
. a Git repository in the Radicle network, which also includes the namespace
for the contributions of a particular user (currently represented with a NID).
. optionally, a repository resource of some sort; to properly identify
the resource:
.. the resource's type
.. a resource identity, specific to the resource type
****
We recognise that the concept of namespace and how that relates to a user is
a bit ambiguous. A more detailed explanation should appear in another RIP.
For the moment, the user and their local node are identified with the same
identity, the NID. Since the namespace for a user uses its identity, it may
appear confusing. However, the fact remains that a namespace stores a user's
contribution to the repository it lives within.
In the future, users may be identified differently, and when that happens, the
namespace is expected to change accordingly, so that it is clear that it is the
user's contributions.
****
This can be seen as a hierarchy, expressed like this:
repo-id [ "/" namespace ] [ "/" resource-type "/" resource-id ]
The `rad:` URI scheme is intended to be used to refer to / represent
repositories and resources within repositories, in such a way that it
can be used by applications, but also be understood by humans wishing
to get to the resource being referred to with what knowledge tools
they have available (such as the commands `git` and `rad`).
It does not imply any particular network protocol, or even that all
the parts of the scheme must be implemeted by all applications, and
applications may also make some parts of the scheme more mandatory
than what this RIP implies.
As a very concrete example of an application that handles `rad:` URIs,
`git-remote-rad` can probably never be expected to handle more than a
repository and namespace, a `rad:` URI with more than that should be
considered an error. On the other hand, `git-remote-rad` requires the
presence of a namespace, i.e. makes it mandatory.
== Formalization and General Extension of the `rad:` URI Scheme
In this RIP, we denote the `rad:` scheme as a _URI_ scheme and try to
generally follow the nomenclature from <<URI>>.
=== The `web+rad:` URI Scheme
<<HTML>> defines that unless a URL scheme is known and on their safelist, it
may be registered as a custom URL handler if prefixed with `web+`. See their
section on <<HTML-registerhandler,registering handlers>>.
For this RIP, it means that `web+rad:` must be compatible with `rad:` in every
way, i.e prepending the string `"web+"` to a valid `rad:` URI must result in a
valid `web+rad:`-URI, and removing the first four symbols from a `web+rad:`
URI must result in a valid `rad:` URI.
This should make it as easy as possible for users to register URI handlers for
both `web+rad:` and `rad:` on their system, according to their preferences.
=== The Old `rad:` URI Scheme
The scheme as defined in <<RIP2>> and <<RIP3>> was really pretty simple, and could
be summarized with just a few lines of <<ABNF>>:
// [source,abnf]
[%nowrap]
----
rad = rad-scheme ":" rad-rid ; (RIP #2)
/ rad-scheme ":" "//" rad-rid [ "/" rad-nid ] ; (RIP #3)
rad-scheme = "rad"
rad-rid = "z" 27*28(ALPHA / DIGIT)
rad-nid = "z6Mk" 44(ALPHA / DIGIT)
----
This is workable in practice, but the URL form is arguably not aligning with
the ideas behind the 'authority' as described in <<URI-Authority>>. The
'authority' carries with it the idea of a host, possibly with some user
information. In a Radicle network, it can be argued that the NID represents
that much more than the RID does, but the NID used in the old `rad:` URI
scheme does not represent that either; it represents a namespace within a
repository.
=== The New `rad:` URI Scheme: General Approach
To align with <<URI, RFC 3986>>, the `rad:` scheme is renewed to include a
possible authority, which is the particular node / host that the data should
be found on. In most cases, (much like in `file:` URIs), the authority is
expected to be empty or not there at all.
Other than that, the new URI scheme is extended with additional syntax to
meet the <<Challenges, challenges>> given above.
// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=overview]
----
==== The Authority
The NID is conceptually ambiguous from an <<URI, RFC 3986>> syntax perspective:
// [source,abnf]
[%nowrap]
----
authority = [ userinfo "@" ] host [ ":" port ]
----
This does not quite align semantically with how Radicle nodes are identified.
Radicle nodes are identified with their NID, or with `{NID}@{hostname}:{port}`.
This does align with <<URI, RFC 3986>> syntax well enough, with just a little
bit of redefinition of the authority for use in Radicle context.
****
The authority is most closely related to the address given to the Radicle CLI
command `rad node connect`.
****
// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=authority]
----
==== The Repository
After the authority (if there is one) comes the repository to refer to.
With Radicle, the main anchoring resource is the repository, so its ID must
always be present, and specifying it alone refers to the synthesis of all
participants' contributions.
To refer to individual participants' separate contribution, their namespace
must also be included in the URI, separate from the repository ID with a slash
("/"). It shall be noted, though, that the namespace is only meaningful in
some specific circumstances, to be defined later.
****
Radicle repositories are _currently_ Git repositories. We expect that other
version control (VCS) implementations may be used as well, and in that case,
we expect the VCS implementation type to be reflected in the repository
identity.
****
// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=resource]
----
Here are a few examples of what a reference to a whole repository could look
like:
:tc00a: pass:c,q[footnote:tc00a[nearley-test result in link:data/test/tc00a-expected.json[]]]
:tc00b: pass:c,q[footnote:tc00b[nearley-test result in link:data/test/tc00b-expected.json[]]]
:tc00c: pass:c,q[footnote:tc00c[nearley-test result in link:data/test/tc00c-expected.json[]]]
:tc01a: pass:c,q[footnote:tc01a[nearley-test result in link:data/test/tc01a-expected.json[]]]
:tc01b: pass:c,q[footnote:tc01b[nearley-test result in link:data/test/tc01b-expected.json[]]]
:tc01c: pass:c,q[footnote:tc01c[nearley-test result in link:data/test/tc01c-expected.json[]]]
:tc02: pass:c,q[footnote:tc02[nearley-test result in link:data/test/tc02-expected.json[]]]
:tc03: pass:c,q[footnote:tc03[nearley-test result in link:data/test/tc03-expected.json[]]]
:tc04: pass:c,q[footnote:tc04[nearley-test result in link:data/test/tc04-expected.json[]]]
:tc05: pass:c,q[footnote:tc05[nearley-test result in link:data/test/tc05-expected.json[]]]
[options="header",cols="2a,1"]
|===
|URI
|Refers to
|
....
include::data/test/tc00a[]
....
{tc00a}
|Specific repo (RIP #2 URN form, still valid)
|
....
include::data/test/tc00b[]
....
{tc00b}
|Specific repo (RIP #3 URL form, considered legacy)
|
....
include::data/test/tc00c[]
....
{tc00c}
|Specific repo (new URI form with empty authority)
|
....
include::data/test/tc01a[]
....
{tc01a}
|Specific repo and namespace (new URI form, relative)
|
....
include::data/test/tc01b[]
....
{tc01b}
|Specific repo and namespace (RIP #3 URL form, considered legacy)
|
....
include::data/test/tc01c[]
....
{tc01c}
|Specific repo and namespace (new URI form with empty authority)
|
....
include::data/test/tc02[]
....
{tc02}
|Specific repo, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`
|
....
include::data/test/tc03[]
....
{tc03}
|Specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`
|
....
include::data/test/tc04[]
....
{tc04}
|Specific repo, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`
|
....
include::data/test/tc05[]
....
{tc05}
|Specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`
|===
(to test these URIs against a grammar, see <<Testing>> below)
==== The Resource Type and Identity
While <<The repository,the repository>> allows you to refer to a Git repository,
it may also be desirable to refer to one or more specific resources within
that repository. To do this, <<The repository,the repository>> may be followed
by a "/", a resource type and identity.
By default, the resource type and identity is nothing, left to be added
to further on in this RIP, or in future RIPs:
// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tags=resource-type-and-identity;!*]
----
==== The Query
The query part of the `rad:` URI inherits the syntax from <<URI>>.
The semantics of the query are to be defined with each resource type.
==== The Fragment
The fragment part of the URI inherits the syntax and semantics from <<URI>>.
=== Relative URIs
<<URI-Reference-Resolution>> discusses the resolution of relative (absolute
or rootless alike) URIs, based on a base URI.
This RIP has nothing specific to say about this, apart from supporting it.
== Resource Types and Identities
These are the resource types and associated identities defined in this
RIP. Future RIPs may amend them, as well as define new resource types
with associated identities.
=== Resource Types Specific to Git
There are four Git specific resource types:
* `commit`
* `tree`
* `blob`
* `tag`
In a `rad:` URI, these resource types are used to refer to Git object types
with the same name in a repository. These resource types must be followed by
an identifier that refers to a specific Git object of that type.
Some of these resource types may also be accompanied by <<The Query,query>>
parameters to further refine what the `rad:` URI refers to. This RIP defines
the following <<The Query,query>> parameters, all of which may be given
multiple times:
* `tree=dir1/dir2/…`, to "dig out" a tree given by the path.
* `blob=dir1/dir2/…`, to "dig out" a blob given by the path.
* `path=dir1/dir2/…`, to "dig out" a tree or a blob given by the path.
****
Applications are by and large allowed to define other <<The Query,query>>
parameters than what is defined here. We hope that future RIPs will help
standardise at least the most common ones.
A few possible examples that may be useful:
* `rad:{rid}/commit/{git-obj-or-ref}?history`, to view the history of a commit.
* `rad:{rid}/blob/{git-obj}?raw`, to view/download the raw contents of a blob,
where one might otherwise reasonably expected it to be wrapped in some
markup, such as HTML.
****
`commit`::
+
--
The identifier that follows this resource type may be:
* a Git object ID
* a Git reference (typically, a branch or a tag name)
In all cases, this resource type refers to a single commit object.
With this resource type, it makes sense to use the <<The Query,query>>
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
--
`tree`::
+
--
The identifier that follows this type may be must be a Git object identifier.
With this resource type, it makes sense to use the <<The Query,query>>
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
--
`blob`::
+
--
The identifier that follows this type must be a Git object identifier.
With this resource type, it makes no sense to use any of the <<The
Query,query>> defined above.
--
`tag`::
+
--
The identifier that follows this type may be:
* a Git object ID
* a Git reference (i.e. a tag name)
In all cases, this resource type refers to a single tag object.
With this resource type, it makes sense to use the <<The Query,query>>
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
--
In <<ABNF>> terms, the Git specific resource types are defined like this:
// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=git-type-and-identity]
----
Here are a few examples of what a reference to a Git specific resource could
look like:
:tc10a: pass:c,q[footnote:tc10a[nearley-test result in link:data/test/tc10a-expected.json[]]]
:tc10b: pass:c,q[footnote:tc10b[nearley-test result in link:data/test/tc10b-expected.json[]]]
:tc12: pass:c,q[footnote:tc12[nearley-test result in link:data/test/tc12-expected.json[]]]
:tc13: pass:c,q[footnote:tc13[nearley-test result in link:data/test/tc13-expected.json[]]]
[options="header",cols="2a,1"]
|===
|URI
|Refers to
|
....
include::data/test/tc10a[]
....
{tc10a}
|Specific commit in repo
|
....
include::data/test/tc10b[]
....
{tc10b}
|Specific commit in repo
|
....
include::data/test/tc12[]
....
{tc12}
|Specific commit on a specific node, using the explicit name for the commit object
|
....
include::data/test/tc13[]
....
{tc13}
|Specific commit in a specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`
|===
(to test these URIs against a grammar, see <<Testing>> below)
And with queries:
:tc40a: pass:c,q[footnote:tc40a[nearley-test result in link:data/test/tc40a-expected.json[]]]
:tc40b: pass:c,q[footnote:tc40b[nearley-test result in link:data/test/tc40b-expected.json[]]]
:tc41a: pass:c,q[footnote:tc41a[nearley-test result in link:data/test/tc41a-expected.json[]]]
:tc41b: pass:c,q[footnote:tc41b[nearley-test result in link:data/test/tc41b-expected.json[]]]
:tc42a: pass:c,q[footnote:tc42a[nearley-test result in link:data/test/tc42a-expected.json[]]]
:tc42b: pass:c,q[footnote:tc42b[nearley-test result in link:data/test/tc42b-expected.json[]]]
:tc43a: pass:c,q[footnote:tc43a[nearley-test result in link:data/test/tc43a-expected.json[]]]
:tc43b: pass:c,q[footnote:tc43b[nearley-test result in link:data/test/tc43b-expected.json[]]]
:tc44: pass:c,q[footnote:tc44[nearley-test result in link:data/test/tc44-expected.json[]]]
[options="header",cols="2a,1"]
|===
|URI
|Refers to
|
....
include::data/test/tc40a[]
....
{tc40a}
|The `src` directory of the repo at tree object `3eb47e9`
|
....
include::data/test/tc40b[]
....
{tc40b}
|The `src` directory of the repo at tree object `3eb47e9`
|
....
include::data/test/tc41a[]
....
{tc41a}
|The `README.md` file of the repo at the "master" reference
|
....
include::data/test/tc41b[]
....
{tc41b}
|The `README.md` file of the repo at the "master" reference
|
....
include::data/test/tc42a[]
....
{tc42a}
|The `foo/doc` directory of the repo at the "baz" reference
|
....
include::data/test/tc42b[]
....
{tc42b}
|The `foo/doc` directory of the repo at the "baz" reference
|
....
include::data/test/tc43a[]
....
{tc43a}
|The `doc` and `src` directories of the repo at the "baz/foo" reference.
|
....
include::data/test/tc43b[]
....
{tc43b}
|The `doc` and `src` directories directory of the repo at the "baz/foo" reference.
|
....
include::data/test/tc44[]
....
{tc44}
|The commit notes for commit `e9cf5263…`.
|===
(to test these URIs against a grammar, see <<Testing>> below)
****
// Git version that is current at the time of writing.
:gitversion: 2.54.0
// Base URL of Git documentation.
:gitdocs: https://git-scm.com/docs
// Allows for convenient reference to definitions in the Git glossary.
// "gitgloss" is just short for "a definition in the Git glossary".
:gitgloss: {gitdocs}/gitglossary/{gitversion}#Documentation/gitglossary.txt-
:gitrevparse: {gitdocs}/git-rev-parse/{gitversion}#Documentation/git-rev-parse.txt-
[discrete]
==== Addressing Git Resources by Reference Names
[discrete]
===== Types of Resources That May Be Addressed
Resources of the types "commit" and "tag", may, in Git jargon, be
"committish". There are at least two definitions in the Git documentation
that differ slightly:
[quote,'{gitgloss}commit-ishalsocommittish[Git glossary entry for "committish"]']
A {gitgloss}commit_object[commit object] or an {gitgloss}object[object]
that can be recursively {gitgloss}dereference[dereferenced] to a commit
object. The following are all commit-ishes:
a commit object,
a {gitgloss}tag_object[tag object] that points to a commit object,
a tag object that points to a tag object that points to a commit object,
etc.
[quote,'{gitrevparse}--verify[Git documentation of `git rev-parse --verify`]']
a commit-ish (i.e. a commit, or an annotated tag that points at a commit)
We thus restrict ourselves to only refer to commits and tags via references,
since no tree or blob object may ever be committish.
[discrete]
===== Ambiguity in Addressing by Reference Name
Note that Git references are ambiguous, even within one repository (or reference
namespace).
It is possible to create the following set of references in one namespace:
* `refs/heads/main`
* `refs/tags/main`
* `refs/heads/refs/heads/main`
We do not define how such ambiguity is to be resolved, but note that applications
will likely want to implement some mechanism for disambiguation.
See also {gitrevparse}refnameegmasterheadsmasterrefsheadsmaster[the section
_Specifying Revisions_ in the documentation of `git rev-parse`].
****
=== Resources Types Specific to Radicle Collaborative Objects
Alongside the usual Git objects, Radicle also implements a generalized
collaborative object (usually referred to as COB), identified with a COB type
and a COB object.
For general COB references in a `rad:` URI, these resource types are defined
to refer to a Radicle COB / multiple COBs:
`cob`::
+
--
In a `rad:` URI, this must be followed a slash (`/`) and the COB type. The
COB type _may_ be followed by another slash (`/`) and the COB object.
If the `rad:` URI includes the COB object, then it refers to a single COB.
If the `rad:` URI does not include the COB object, then it refers to the set
of COB objects of the given COB type.
--
`cob` resources may also be accompanied by a set of <<The Query,query>>
parameters to further refine what the `rad:` URI refers to.
****
Because we realise that we cannot know all future COB types and how they
function, we cannot do too much in terms of definitions.
We leave it to the COB owners as well as applications to work out what query params they
will handle, and hopefully, some of them can be standardised in future RIPs.
A hypothetical example: `rad:{RID}/cob/example.radicle.sparkle?q=glitter`.
****
In <<ABNF>> terms, the COB resource types are defined like this:
// [source, abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=rad-cob-type-and-identity]
----
Here are a few examples of what a reference to other collaborative objects
could look like:
:tc30: pass:c,q[footnote:tc30[nearley-test result in link:data/test/tc30-expected.json[]]]
:tc31: pass:c,q[footnote:tc31[nearley-test result in link:data/test/tc31-expected.json[]]]
:tc32: pass:c,q[footnote:tc32[nearley-test result in link:data/test/tc32-expected.json[]]]
[options="header",cols="2a,1"]
|===
|URI
|Refers to
|
....
include::data/test/tc30[]
....
{tc30}
|Specific COB of the type `org.example`
|
....
include::data/test/tc31[]
....
{tc31}
|Specific COB of the type `org.example` on specific node
|
....
include::data/test/tc32[]
....
{tc32}
|The set of COBs of the type `org.example`
|===
== Implementation Requirements and Recommendations
* Implementations may implement only part of this specification, depending on
what makes sense in the context of what they do. It is advisable, however, if
they can parse enough of a valid `rad:` URI to allow for <<Future work,future work>>.
For example, it is hard to imagine that `git-remote-rad` would ever support
more elaborate URLs than those involving an RID and an NID:
** `rad:{RID}`
** `rad:///{RID}`
** `rad:{RID}/{NID}`
** `rad:///{RID}/{NID}`
** `rad://{RID}/{NID}` (legacy)
* Implementations that receive a URI they cannot handle - because of
unrecognised resource type, identifier, or query params - should signal an
error.
+
For fragments, there is no recommendation, that is left entirely to the
authors of those softwares.
* Software for human interaction on the web should be able to automatically
convert between `rad:` and `web+rad:` to create links. For example, in a
markdown context, it would be quite viable for this:
+
`rad:z3gqcJU…CSGazv5/commit/72db6d…`
+
to be converted to:
+
`[rad:z3gqcJU…CSGazv5/commit/72db6d…](web+rad:z3gqcJU…CSGazv5/commit/72db6d…)`
* Implementations that are aware of the context may support relative URIs as
they see fit. For example, a program that handles Radicle patches and issues
could assume the base URI `rad:///{RID}` (given that `{RID}` is the ID of
the repository that is being handled), allowing comments to refer to issue and
other patches in the same repository with `patch/{PATCH-ID}` or `issue/{ISSUE-ID}`.
== Future Work
Future work may include:
* amending <<The authority,the authority>> with Radicle network
specification syntax (pending future formalization of that idea).
* amending <<The authority,the authority>> with multi-device IDs (also
pending formalization of that idea).
* adding more resource types, and their respective syntaxes
* extending existing resource types.
* adding other query types
* viewing the history/graph of the underlying Git repository (in analogy to `git log`/`git log --graph`).
* viewing revision ranges of the underlying Git repository (in analogy to `git diff`, `git log`).
== Closing Thoughts
This RIP started with a status quo and challenges. It is the hope of the
authors that the challenges are sufficiently addressed, while also leaving
doors open for more extensions in the future.
== Copyright
This document is licensed under the Creative Commons CC0 1.0 Universal license.
[bibliography]
== References
// https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/#bibliography
// says that anchors like [[[ABNF]]] should render to [ANBF], but experience
// says that this is not quite true. However, [[ABNF]][ABNF] seems to render
// what I hoped for. /RL
[[ABNF]][ABNF]:: https://datatracker.ietf.org/doc/html/rfc5234[RFC 5234 - Augmented BNF for Syntax Specifications: ABNF]
[[HTML]][HTML]:: Web Hypertext Application Technology Working Group: _HTML Living Standard_ (<https://html.spec.whatwg.org>)
[[HTML-registerhandler]][HTML-registerhandler]:: Sec. 8.9.1.4, _Custom scheme handlers: the `registerProtocolHandler()` method_ (<https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers>)
[[Nearley]][Nearley]:: Kartik Chandra's Nearley grammar (<https://nearley.js.org/>)
[[radicle-explorer]][radicle-explorer]:: Radicle Web Interface - link:web+rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5[rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5] +
(<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5>)
[[radicle-jetbrains]][radicle-jetbrains]:: Radicle JetBrains plugin - link:web+rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK[rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK] +
(<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK>)
[[radicle-vscode]][radicle-vscode]:: Radicle VSCode extension - link:web+rad:z3Makm6fsQQXmpSFE43DZqwupaEhk[rad:z3Makm6fsQQXmpSFE43DZqwupaEhk] +
(<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3Makm6fsQQXmpSFE43DZqwupaEhk>), +
link:web+vscode:extension/radicle-ide-plugins-team.radicle[vscode:extension/radicle-ide-plugins-team.radicle] +
(<https://marketplace.visualstudio.com/items?itemName=radicle-ide-plugins-team.radicle>)
[[RIP2]][RIP2]:: @cloudhead: Identity - link:web+rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/1c402116983be19e754fb14aa7ce38145f0a4b09?path=0002-identity.md[rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/1c402116983be19e754fb14aa7ce38145f0a4b09?path=0002-identity.md] +
(<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/1c402116983be19e754fb14aa7ce38145f0a4b09/0002-identity.md>)
[[RIP3]][RIP3]:: @fintohaps: Radicle Improvement Proposal #3: Storage Layout - link:web+rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/329dee9a4b65169ea3889a7da239892b705d0d68?path=0003-storage-layout.md#url[rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/329dee9a4b65169ea3889a7da239892b705d0d68?path=0003-storage-layout.md#url] +
(<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/329dee9a4b65169ea3889a7da239892b705d0d68/0003-storage-layout.md#url>)
[[URI]][URI]:: https://datatracker.ietf.org/doc/html/rfc3986[RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax]
[[URI-Authority]][URI-Authority]:: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2[RFC 3986 - 3.2. Authority]
[[URI-Reference-Resolution]][URI-Reference-Resolution]:: https://datatracker.ietf.org/doc/html/rfc3986#section-5[RFC 3986 - 5. Reference Resolution]
[[zulip-mangle]][zulip-mangle]:: Zulip Topic: _`rad:` gets mangled by Zulip_ (#general, 2024-04-23) (<https://radicle.zulipchat.com/#narrow/stream/369274-general/topic/.60rad.3A.60.20gets.20mangled.20by.20Zulip>)
[[base58btc]][base58btc]:: https://datatracker.ietf.org/doc/html/draft-msporny-base58-02[Base58 Specification]
[[multibase]][multibase]:: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html[Multibase Specification]
[[multicodec]][multicodec]:: https://github.com/multiformats/multicodec/[Multiformats: multicodec]
[appendix]
== Full <<ABNF>> grammar
[%nowrap]
----
include::data/rad-uri.abnf[tag=!rfc5234]
----
[appendix]
== Full <<Nearley>> grammar
[%nowrap]
----
include::data/rad-uri.ne[]
----
[appendix]
== Testing
All the examples sprinkled in this RIP are tested against the <<Nearley>>
grammar. Test inputs and expected parse results are in `data/test/`, where
each `tc__` file contains a valid URI and the corresponding
`tc__-expected.json` contains the expected parse result as JSON.
The test suite can be run with:
[,shell]
----
nix flake check
----
This compiles the grammar with `nearleyc`, runs `nearley-test` on each test
input, and compares the JSON output against the expected result.
To test a single URI manually:
[,shell]
[%nowrap]
----
# [one time] Compile the grammar into javascript
nearleyc -o rad-uri.js data/rad-uri.ne
# Test the chosen URI string ($URI)
nearley-test -q -i "$URI" rad-uri.js
----
`nearley-test` will return all possible results for the URI, which means that
where there are syntactic ambiguities, there will be more than one result.
For testing purposes, it is enough if one of the results match the expected
results linked from the footnotes associated with the examples in the tables
above.
+34 -3
View File
@@ -13,11 +13,42 @@
}: }:
flake-utils.lib.eachDefaultSystem (system: let flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
nearley = pkgs.buildNpmPackage {
pname = "nearley";
version = "2.20.1";
src = ./nix/nearley;
npmDepsHash = "sha256-a0RjAhSlzEbe6tM6QsZa06xKc0uzEe0cK6OsuVQSSzo=";
dontNpmBuild = true;
};
in { in {
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
buildInputs = [ buildInputs =
pkgs.asciidoctor (with pkgs; [
]; asciidoctor
nodejs
shellcheck
yq
])
++ [
nearley
];
}; };
checks.grammar =
pkgs.runCommand "grammar-check" {
nativeBuildInputs =
(with pkgs; [
nearley
nodejs
yq
])
++ [
nearley
];
} ''
${./0004-general-uri-scheme/data/test/uri-scheme-tester.sh} ${./0004-general-uri-scheme/data}
touch $out
'';
}); });
} }
+87
View File
@@ -0,0 +1,87 @@
{
"name": "nearley-wrapper",
"version": "2.20.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nearley-wrapper",
"version": "2.20.1",
"dependencies": {
"nearley": "^2.20.1"
},
"bin": {
"nearley-test": "node_modules/.bin/nearley-test",
"nearleyc": "node_modules/.bin/nearleyc"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/discontinuous-range": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
"integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==",
"license": "MIT"
},
"node_modules/moo": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz",
"integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==",
"license": "BSD-3-Clause"
},
"node_modules/nearley": {
"version": "2.20.1",
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
"integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
"license": "MIT",
"dependencies": {
"commander": "^2.19.0",
"moo": "^0.5.0",
"railroad-diagrams": "^1.0.0",
"randexp": "0.4.6"
},
"bin": {
"nearley-railroad": "bin/nearley-railroad.js",
"nearley-test": "bin/nearley-test.js",
"nearley-unparse": "bin/nearley-unparse.js",
"nearleyc": "bin/nearleyc.js"
},
"funding": {
"type": "individual",
"url": "https://nearley.js.org/#give-to-nearley"
}
},
"node_modules/railroad-diagrams": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
"integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==",
"license": "CC0-1.0"
},
"node_modules/randexp": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
"integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
"license": "MIT",
"dependencies": {
"discontinuous-range": "1.0.0",
"ret": "~0.1.10"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
"license": "MIT",
"engines": {
"node": ">=0.12"
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "nearley-wrapper",
"version": "2.20.1",
"dependencies": {
"nearley": "^2.20.1"
},
"bin": {
"nearleyc": "./node_modules/.bin/nearleyc",
"nearley-test": "./node_modules/.bin/nearley-test"
}
}