mirror of
https://seed.radicle.dev/rad:z3trNYnLWS11cJWC6BbxDs5niGo82.git
synced 2026-10-18 19:10:39 +08:00
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:
committed by
Lorenz Leutgeb
co-authored by
Fintan Halpenny
Lorenz Leutgeb
parent
7e993a9bec
commit
43301b47e5
@@ -1 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
result
|
result
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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 -> "!" | "$" | "&" | "'" | "(" | ")"
|
||||||
|
| "*" | "+" | "," | ";" | "="
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad:z3gqcJUbbbbbbbbbbbbbbbCSGazv5
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad://z3gqcJUbbbbbbbbbbbbbbbCSGazv5
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad:///z3gqcJUbbbbbbbbbbbbbbbCSGazv5
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"repo": "z3gqcJUbbbbbbbbbbbbbbbCSGazv5"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
|
||||||
|
"namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad://zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
|
||||||
|
"namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad:///zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
|
||||||
|
"namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
|
||||||
|
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad://z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT/zk1arLZbbbbbbbbbbbbbbb26us45E/z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"node": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT",
|
||||||
|
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
|
||||||
|
"namespace": "z6MksFbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS9wzpT"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rad:zk1arLZbbbbbbbbbbbbbbb26us45E/cob/org.example
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scheme": "rad",
|
||||||
|
"repo": "zk1arLZbbbbbbbbbbbbbbb26us45E",
|
||||||
|
"resource": {
|
||||||
|
"type": "cobs",
|
||||||
|
"id": {
|
||||||
|
"type": "org.example"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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
|
||||||
|
'';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+87
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user