Commit

Author:

Hash:

Timestamp:

+134 -58 +/-6 browse

Kevin Schoon [me@kevinschoon.com]

33cab614cdef0909208e1bf5144150eccab473b2

Thu, 04 Dec 2025 17:03:21 +0000 (3 days ago)

further clean up specifications
1diff --git a/content/project-atom-feed.md b/content/project-atom-feed.md
2index 3816681..d1ffc3c 100644
3--- a/content/project-atom-feed.md
4+++ b/content/project-atom-feed.md
5 @@ -4,17 +4,17 @@ title: Atom Feed
6
7 # Atom Feeds
8
9- Your forge should expose an [Atom](https://www.rfc-editor.org/rfc/rfc4287)
10+ A forge SHOULD expose an [Atom](https://www.rfc-editor.org/rfc/rfc4287)
11 feed in order for other peers to subscribe to interesting code projects that
12- are developed on your forge. The server should expose projects ordered by those
13- which have been recently updated. THe heuristic you use to determine what has
14- been updated depends on your forge. A common way to do this may be to simply
15- look at your VCS's concept of a commit and order updates with that.
16+ are developed on your forge. The server SHOULD expose projects ordered by those
17+ which have been recently updated. The heuristic used to determine what has
18+ been updated depends on the implementor forge. A common way to do this may be
19+ to simply look at your VCS's concept of a commit and order updates with that.
20
21 ## Determine if a Host Supports Forge Feed
22
23- In order to participate in ForgeFeed your forge MUST present an HTML link
24- element such as below at the root domain of your forge. For example,
25+ In order to participate as an Atom enabled ForgeFeed the forge MUST present an
26+ HTML link element such as below at the root domain of your forge. For example,
27 `code.example.org` MUST have a link element present in it's html header:
28
29 ```html
30 @@ -73,10 +73,11 @@ Below is an example Atom feed with an optional repository section (described bel
31
32 ### ForgeFeed Extension
33
34- In order to facilitate discovery by external indexes it is highly recommended
35- that your server implement the webfinger [project specification](/webfinger-project)
36- so that repository indexes may populate their state with rich information
37- about code repositories hosted on your server.
38+ In order to facilitate discovery by external indexes the server SHOULD implement
39+ implement the webfinger [project specification](/webfinger-project) as well as
40+ the [repository specification](/webfinger-repository) so that repository indexes
41+ may populate their state with rich information about code repositories hosted
42+ on your server.
43
44 The extension currently supports only a single field called `project`.
45
46 @@ -94,7 +95,7 @@ of the server which is providing the feed.
47 Forge-feed enabled Atom feeds have no support for sharing private projects
48 and any project that is not shared publicly on the internet must be hidden
49 from the Atom activity feed stream. If your forge provides the ability to change
50- a project from public to private it must be understood that clients may
51+ a project from public to private it should be understood that clients may
52 already have cached versions of your project data.
53
54 ### Recommendations for Enumerating Project Events
55 diff --git a/content/webfinger-project.md b/content/webfinger-project.md
56index 2f7dc4c..6eba516 100644
57--- a/content/webfinger-project.md
58+++ b/content/webfinger-project.md
59 @@ -2,18 +2,26 @@
60 title: Project Discovery via WebFinger
61 ---
62
63- A project refers to a software project which may contain multiple repositories
64- as well as other resources such as mailing lists, bug trackers, links to
65- chatrooms, etc. NOTE that some forges do not make a distinction between a
66- project and a repository. If your forge does not then you MAY choose to expose
67- repositories as a project with a single repository resource link.
68+ A project refers to a peice of software which may be spread across multiple
69+ code [repositories](/webfinger-repository). For example a consider a fictious
70+ programming language which may be composed of several components such as a
71+ compiler, a standard library, example codebases, a marketing website, etc.
72+ When the architecture of such a project spans multiple repositories then it
73+ is likely they should be represented as a project.
74+
75+ Sometimes however the scope of a project is much smaller such that it is
76+ entirely contained within one single repository, alternatively a large project
77+ might choose to version it's code all in one logical repository. If your forge
78+ supports it you MAY present a single repository as both an identifiable project
79+ and also a repository.
80
81 # Project URI
82
83 A project URI identifies a software project and optionally the host that is
84- resides on. This value is similar to
85- [RFC7565](https://datatracker.ietf.org/doc/html/rfc7565). The slug and hostname
86- part MUST match the URI path specification as defined in
87+ resides on. The value serves to contain enough information such that a client
88+ with a simple URL parser may easily parse the identifier. A project URI MUST
89+ be a valid [RFC7565](https://datatracker.ietf.org/doc/html/rfc7565) URI. The
90+ slug and hostname parts MUST match the URI path specification as defined in
91 [RFC3986-3.3](https://www.rfc-editor.org/rfc/rfc3986#section-3.3) while the
92 hostname, if specified, must match
93 [RFC3986-3.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2).
94 @@ -26,7 +34,8 @@ hostname, if specified, must match
95
96 ### Slug
97
98- The Slug represents a unique string that identifies a project at a particular code forge.
99+ The Slug represents a unique string that identifies a project at a particular
100+ code forge.
101
102 ### Hostname
103
104 @@ -38,25 +47,26 @@ https://example.org/.well-known/webfinger?resource=project://spartacus
105 https://example.org/.well-known/webfinger?resource=project://spartacus@example.org
106 ```
107
108- TODO: Make this an actual spec, for now, some Python:
109+ ## Parsing
110+
111+ An example parser written in the Python programming language. Any programming
112+ language which implements a valid RFC7565 parser should be sufficent for
113+ reading a project URI.
114
115 ```python
116- from urllib.parse import urlparse, quote_plus
117+ from urllib.parse import urlparse
118
119- def from_string(text):
120+ def parse_project(text):
121 url = urlparse(text)
122+ if url.scheme != "project": # schema must be repository://
123+ raise Exception("Wrong scheme, should be project://")
124 if not url.path:
125- return (None, None)
126+ raise Exception("Missing slug part")
127 split = url.path.split("@", 1)
128 if len(split) == 2:
129- return (split[0], split[1])
130+ domain = urlparse(f"ignore://{split[1]}")
131+ return (split[0], domain.netloc)
132 return (split[0], None)
133-
134- def to_string(slug, domain=None):
135- if domain:
136- return quote_plus(f"project://{slug}@{domain}")
137- else:
138- return quote_plus(f"project://{slug}")
139 ```
140
141
142 @@ -90,6 +100,9 @@ an avatar for use in other applications.
143
144 #### `http://feed-forge.org/rel/chatroom`
145
146+ A chatroom refers to an interactive chat environment for real time
147+ collaboration among project contributors.
148+
149 ```json
150 {
151 "rel": "http://webfinger.net/rel/chatroom",
152 @@ -180,7 +193,7 @@ Reference to a VCS managed code repository.
153 The following [property identifiers](https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.3) are available
154 for use within a project resource.
155
156- ```txt
157+ ```text
158 http://forge-feed.org/ns/label
159 http://forge-feed.org/ns/repository-uri
160 http://forge-feed.org/ns/vcs-type
161 @@ -218,7 +231,7 @@ Identifies VCS types, valid strings are:
162 ## Example Multi-Repository Query
163
164 A [WebFinger](https://webfinger.net/spec/) query may be used to identify
165- detailed information about a public repository at a particular forge. Here is
166+ detailed information about a public project at a particular forge. Here is
167 an example response about a fictitious project which has two code repositories
168 associated with it as well as chat links, and a bug tracking system.
169
170 diff --git a/content/webfinger-repository.md b/content/webfinger-repository.md
171index a7181dd..f1cc33e 100644
172--- a/content/webfinger-repository.md
173+++ b/content/webfinger-repository.md
174 @@ -2,18 +2,19 @@
175 title: Repository Discovery via WebFinger
176 ---
177
178- A repository refers to a version control managed source code which may be
179- browsed over HTTP or downloaded with specific tooling.
180+ A repository refers to a version control managed source code which MAY be
181+ browsable over HTTP or accessed with specific tooling.
182
183 ## Repository URI
184
185- A repository URI identifies a code repository and optionally the host that is
186- resides on. This value is similar to
187- [RFC7565](https://datatracker.ietf.org/doc/html/rfc7565). The slug and hostname
188- part MUST match the URI path specification as defined in
189+ A repository URI identifies a VCS resource and optionally the host that is
190+ resides on. The value serves to contain enough information such that a client
191+ with a simple URL parser may easily parse the identifier. A repository URI MUST
192+ be a valid [RFC7565](https://datatracker.ietf.org/doc/html/rfc7565) URI. The
193+ slug and hostname parts MUST match the URI path specification as defined in
194 [RFC3986-3.3](https://www.rfc-editor.org/rfc/rfc3986#section-3.3) while the
195 hostname, if specified, must match
196- [RFC3986-3.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2)
197+ [RFC3986-3.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2).
198
199 repository-uri = prefix slug hostname
200
201 @@ -37,25 +38,26 @@ https://example.org/.well-known/webfinger?resource=repository://spartacus/game
202 https://example.org/.well-known/webfinger?resource=repository://spartacus/game@example.org
203 ```
204
205- TODO: Make this an actual spec, for now, some Python:
206+ ## Parsing
207+
208+ An example parser written in the Python programming language. Any programming
209+ language which implements a valid RFC7565 parser should be sufficent for
210+ reading a project URI.
211
212 ```python
213- from urllib.parse import urlparse, quote_plus
214+ from urllib.parse import urlparse
215
216 def from_string(text):
217 url = urlparse(text)
218+ if url.scheme != "repository": # schema must be repository://
219+ raise Exception("Wrong scheme, should be repository://")
220 if not url.path:
221- return (None, None)
222+ raise Exception("Missing slug part")
223 split = url.path.split("@", 1)
224 if len(split) == 2:
225- return (split[0], split[1])
226+ domain = urlparse(f"ignore://{split[1]}")
227+ return (split[0], domain.netloc)
228 return (split[0], None)
229-
230- def to_string(slug, domain=None):
231- if domain:
232- return quote_plus(f"repository://{slug}@{domain}")
233- else:
234- return quote_plus(f"repository://{slug}")
235 ```
236
237
238 @@ -64,7 +66,7 @@ def to_string(slug, domain=None):
239 The following [Relation Types](https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.1) are
240 available for use within a repository resource.
241
242- ```
243+ ```text
244 http://forge-feed.org/rel/avatar
245 http://forge-feed.org/rel/clone
246 http://forge-feed.org/rel/description
247 @@ -155,9 +157,9 @@ Links to issue tracking systems.
248 ### Property Identifiers
249
250 The following [property identifiers](https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.3) are available
251- for use within a project resource.
252+ for use within a repository resource.
253
254- ```
255+ ```text
256 http://forge-feed.org/ns/label
257 http://forge-feed.org/ns/project-uri
258 http://forge-feed.org/ns/spdx-identifier
259 @@ -185,7 +187,7 @@ Identifies VCS types, valid strings are:
260 svn (Apache Subversion) subversion.apache.org
261
262
263- ## WebFinger Query
264+ ## Example Repository WebFinger Query
265
266 A [WebFinger](https://webfinger.net/spec/) query may be used to identify
267 detailed information about a public repository at a particular forge. Here is
268 diff --git a/parser.py b/parser.py
269new file mode 100644
270index 0000000..15f52a3
271--- /dev/null
272+++ b/parser.py
273 @@ -0,0 +1,28 @@
274+ from urllib.parse import urlparse, quote_plus
275+
276+
277+ def from_string(text):
278+ url = urlparse(text)
279+ if not url.path:
280+ return (None, None)
281+ split = url.path.split("@", 1)
282+ if len(split) == 2:
283+ return (split[0], split[1])
284+ return (split[0], None)
285+
286+
287+ def to_string(slug, domain=None):
288+ if domain:
289+ return quote_plus(f"repository:{slug}@{domain}")
290+ else:
291+ return quote_plus(f"repository:{slug}")
292+
293+
294+ print(from_string("repository://fuu/bar@example.org"))
295+ print(from_string("repository://fuu/bar/baz/qux@example.org"))
296+ print(from_string("repository://fuu/bar/baz/qux"))
297+ print(from_string("repository://~hello/world"))
298+ print(from_string("repository://~hello/world@example.org"))
299+ print(from_string("repository://~hello/world@example.org@aaaa"))
300+ print(to_string("fuu/bar/baz", None))
301+ print(to_string("fuu/bar/baz", "example.org"))
302 diff --git a/project-uri.py b/project-uri.py
303new file mode 100644
304index 0000000..4e4c9ba
305--- /dev/null
306+++ b/project-uri.py
307 @@ -0,0 +1,30 @@
308+ from urllib.parse import urlparse, quote_plus
309+
310+
311+ def from_string(text):
312+ url = urlparse(text)
313+ if url.scheme != "project": # schema must be repository://
314+ raise Exception("Wrong scheme, should be project://")
315+ if not url.path:
316+ raise Exception("Missing slug part")
317+ split = url.path.split("@", 1)
318+ if len(split) == 2:
319+ domain = urlparse(f"ignore://{split[1]}")
320+ return (split[0], domain.netloc)
321+ return (split[0], None)
322+
323+
324+ def to_string(slug, domain=None):
325+ if domain:
326+ return quote_plus(f"project:{slug}@{domain}")
327+ else:
328+ return quote_plus(f"project:{slug}")
329+
330+
331+ print(from_string("project://fuu/bar@example.org"))
332+ print(from_string("project://fuu/bar/baz/qux@example.org"))
333+ print(from_string("project://fuu/bar/baz/qux"))
334+ print(from_string("project://~hello/world"))
335+ print(from_string("project://~hello/world@example.org"))
336+ print(to_string("fuu/bar/baz", None))
337+ print(to_string("fuu/bar/baz", "example.org"))
338 diff --git a/repository-uri.py b/repository-uri.py
339index 15f52a3..3264e18 100644
340--- a/repository-uri.py
341+++ b/repository-uri.py
342 @@ -3,11 +3,14 @@ from urllib.parse import urlparse, quote_plus
343
344 def from_string(text):
345 url = urlparse(text)
346+ if url.scheme != "repository": # schema must be repository://
347+ raise Exception("Wrong scheme, should be repository://")
348 if not url.path:
349- return (None, None)
350+ raise Exception("Missing slug part")
351 split = url.path.split("@", 1)
352 if len(split) == 2:
353- return (split[0], split[1])
354+ domain = urlparse(f"ignore://{split[1]}")
355+ return (split[0], domain.netloc)
356 return (split[0], None)
357
358
359 @@ -23,6 +26,5 @@ print(from_string("repository://fuu/bar/baz/qux@example.org"))
360 print(from_string("repository://fuu/bar/baz/qux"))
361 print(from_string("repository://~hello/world"))
362 print(from_string("repository://~hello/world@example.org"))
363- print(from_string("repository://~hello/world@example.org@aaaa"))
364 print(to_string("fuu/bar/baz", None))
365 print(to_string("fuu/bar/baz", "example.org"))