Author:
Hash:
Timestamp:
+38 -6 +/-3 browse
Kevin Schoon [me@kevinschoon.com]
c6b20ec7fe389afc3392ed633358131123bb9c0e
Mon, 01 Dec 2025 23:14:00 +0000 (1 month ago)
| 1 | diff --git a/ayllu/src/license.rs b/ayllu/src/license.rs |
| 2 | index 34a6444..31e6736 100644 |
| 3 | --- a/ayllu/src/license.rs |
| 4 | +++ b/ayllu/src/license.rs |
| 5 | @@ -14,6 +14,7 @@ static LICENSE_PATHS: &[&str] = &["LICENSE", "COPYING", "LICENSE.md"]; |
| 6 | |
| 7 | // TODO: super naive license detection |
| 8 | // see https://github.com/OpenSourceOrg/licenses |
| 9 | + // TODO: https://crates.io/crates/spdx/0.12.0 |
| 10 | pub fn detect(repository: &Wrapper, commit: Option<String>) -> Result<Option<String>, Error> { |
| 11 | for path in LICENSE_PATHS { |
| 12 | let path = Path::new(path); |
| 13 | diff --git a/ayllu/src/web2/routes/finger.rs b/ayllu/src/web2/routes/finger.rs |
| 14 | index 56317d0..bcd29b6 100644 |
| 15 | --- a/ayllu/src/web2/routes/finger.rs |
| 16 | +++ b/ayllu/src/web2/routes/finger.rs |
| 17 | @@ -8,23 +8,26 @@ use webfinger_rs::{Link, WebFingerRequest, WebFingerResponse}; |
| 18 | |
| 19 | use crate::{ |
| 20 | config::{Collection, Config}, |
| 21 | + license, |
| 22 | web2::error::Error, |
| 23 | }; |
| 24 | use ayllu_git::Scanner; |
| 25 | |
| 26 | const FORGE_FEED_REL_AVATAR: &str = "http://forge-feed.org/rel/avatar"; |
| 27 | - // const FORGE_FEED_REL_TICKETING: &str = "http://forge-feed.org/rel/ticketing-system"; |
| 28 | + const FORGE_FEED_REL_TICKETING: &str = "http://forge-feed.org/rel/ticketing-system"; |
| 29 | const FORGE_FEED_REL_REPOSITORY: &str = "http://forge-feed.org/rel/repository"; |
| 30 | // const FORGE_FEED_REL_REPOSITORY_URI: &str = "http://forge-feed.org/rel/repository-uri"; |
| 31 | const FORGE_FEED_REL_MAILING_LIST: &str = "http://forge-feed.org/rel/mailing-list"; |
| 32 | const FORGE_FEED_REL_PROJECT: &str = "http://forge-feed.org/rel/project"; |
| 33 | const FORGE_FEED_REL_HOMEPAGE: &str = "http://forge-feed.org/rel/homepage"; |
| 34 | const FORGE_FEED_REL_DESCRIPTION: &str = "http://forge-feed.org/rel/description"; |
| 35 | - // const FORGE_FEED_REL_LICENSE: &str = "http://forge-feed.org/rel/license"; |
| 36 | + const FORGE_FEED_REL_LICENSE: &str = "http://forge-feed.org/rel/license"; |
| 37 | const FORGE_FEED_REL_VCS_CLONE_LINK: &str = "http://forge-feed.org/rel/clone"; |
| 38 | - // const FORGE_FEED_REL_LABEL: &str = "http://forge-feed.org/rel/label"; |
| 39 | - // const FORGE_FEED_NS_LABEL: &str = "http://forge-feed.org/ns/label"; |
| 40 | + // const FORGE_FEED_REL_CHAT_LINK: &str = "http://forge-feed.org/rel/chatroom"; |
| 41 | + const FORGE_FEED_REL_LABEL: &str = "http://forge-feed.org/rel/label"; |
| 42 | + const FORGE_FEED_NS_LABEL: &str = "http://forge-feed.org/ns/label"; |
| 43 | const FORGE_FEED_NS_VCS_TYPE: &str = "http://forge-feed.org/ns/vcs-type"; |
| 44 | + const FORGE_FEED_NS_SPDX_IDENTIFIER: &str = "http://feed-forge.org/ns/spdx-identifier"; |
| 45 | |
| 46 | const SCHEME_ACCT: &str = "acct"; |
| 47 | const SCHEME_PROJECT: &str = "project"; |
| 48 | @@ -78,7 +81,6 @@ impl Resolver { |
| 49 | let collection_name = resource.host().ok_or(Error::Message(format!( |
| 50 | "Missing collection name: {resource:?}" |
| 51 | )))?; |
| 52 | - println!("Path: {}", resource.path()); |
| 53 | if resource.path() != "/" { |
| 54 | return Err(Error::Message(format!( |
| 55 | "Collection format is project://<name>, got: {resource}" |
| 56 | @@ -108,7 +110,6 @@ impl Resolver { |
| 57 | let collection_name = resource.host().ok_or(Error::Message(format!( |
| 58 | "Missing collection name: {resource:?}" |
| 59 | )))?; |
| 60 | - |
| 61 | let repository_name = resource.path().trim_start_matches("/"); |
| 62 | if repository_name.is_empty() { |
| 63 | return Err(Error::Message(format!( |
| 64 | @@ -204,6 +205,7 @@ impl Resolver { |
| 65 | Resource::Repository((collection, repo_path)) => { |
| 66 | let collection_link = self.origin.join(&collection.name).unwrap(); |
| 67 | let repository = ayllu_git::Wrapper::new(repo_path.as_path())?; |
| 68 | + let spdx_identifier = license::detect(&repository, None)?; |
| 69 | let config = repository.config()?; |
| 70 | let mut http_clone_link = self.origin.clone(); |
| 71 | http_clone_link.set_path(&repository.name()); |
| 72 | @@ -225,6 +227,22 @@ impl Resolver { |
| 73 | )])) |
| 74 | .build(), |
| 75 | ]; |
| 76 | + if let Some(spdx_identifier) = spdx_identifier { |
| 77 | + links.push( |
| 78 | + Link::builder(FORGE_FEED_REL_LICENSE) |
| 79 | + .properties(HashMap::from_iter(vec![( |
| 80 | + FORGE_FEED_NS_SPDX_IDENTIFIER.to_string(), |
| 81 | + Some(spdx_identifier), |
| 82 | + )])) |
| 83 | + .build(), |
| 84 | + ) |
| 85 | + } |
| 86 | + if let Some(ticketing_systems) = config.ticketing_systems { |
| 87 | + ticketing_systems.iter().for_each(|system| { |
| 88 | + let item = Link::builder(FORGE_FEED_REL_TICKETING).href(system).build(); |
| 89 | + links.push(item); |
| 90 | + }); |
| 91 | + } |
| 92 | if let Some(mailing_lists) = config.mail { |
| 93 | mailing_lists.iter().for_each(|mailing_list| { |
| 94 | links.push( |
| 95 | @@ -234,6 +252,15 @@ impl Resolver { |
| 96 | ); |
| 97 | }); |
| 98 | } |
| 99 | + if let Some(labels) = config.labels { |
| 100 | + labels.iter().for_each(|label| { |
| 101 | + links.push( |
| 102 | + Link::builder(FORGE_FEED_REL_LABEL) |
| 103 | + .property(FORGE_FEED_NS_LABEL, label.to_string()) |
| 104 | + .build(), |
| 105 | + ); |
| 106 | + }); |
| 107 | + } |
| 108 | Ok(WebFingerResponse { |
| 109 | subject: resource.to_string(), |
| 110 | aliases: None, |
| 111 | diff --git a/crates/git/src/config.rs b/crates/git/src/config.rs |
| 112 | index 6376734..d2a5fdd 100644 |
| 113 | --- a/crates/git/src/config.rs |
| 114 | +++ b/crates/git/src/config.rs |
| 115 | @@ -60,6 +60,8 @@ impl Remotes { |
| 116 | pub struct Config { |
| 117 | pub description: Option<String>, |
| 118 | pub homepage: Option<String>, |
| 119 | + pub labels: Option<Vec<String>>, |
| 120 | + pub ticketing_systems: Option<Vec<String>>, |
| 121 | pub chat: Option<Vec<ChatLink>>, |
| 122 | pub mail: Option<Vec<Email>>, |
| 123 | pub hidden: Option<bool>, |
| 124 | @@ -169,6 +171,8 @@ pub(crate) fn read(git_config: &GitConfig) -> Result<Config, Error> { |
| 125 | let is_mirror = remotes.has_mirror(); |
| 126 | Ok(Config { |
| 127 | description: string(git_config, "ayllu.description"), |
| 128 | + ticketing_systems: strings(git_config, "ayllu.tickets").unwrap(), |
| 129 | + labels: strings(git_config, "ayllu.label").unwrap(), |
| 130 | homepage: string(git_config, "ayllu.homepage"), |
| 131 | default_branch: string(git_config, "ayllu.default-branch"), |
| 132 | chat, |