@@ -28,8 +28,8 @@ pub struct FrontMatter {
2828 #[ serde( default ) ]
2929 pub authors : Vec < String > ,
3030 pub description : Option < String > ,
31- /// Used to generate redirects from the old URL scheme to preserve
32- /// permalinks.
31+ /// Used for `releases/X.XX.X` redirects and ones from the old URL scheme to
32+ /// preserve permalinks (e.g. slug.html => slug/) .
3333 #[ serde( default ) ]
3434 pub aliases : Vec < String > ,
3535 /// Moved to the `extra` table.
@@ -113,12 +113,26 @@ pub fn normalize(
113113 bail ! ( "extra.team and extra.team_url must always come in a pair" ) ;
114114 }
115115
116+ if front_matter. extra . release && !front_matter. aliases . iter ( ) . any ( |a| a. contains ( "releases" ) ) {
117+ // Make sure release posts have a matching `releases/X.XX.X` alias.
118+ let version = guess_version_from_path ( & front_matter. path ) . unwrap_or ( "?.??.?" . into ( ) ) ;
119+ front_matter. aliases . push ( format ! ( "releases/{version}" ) ) ;
120+ }
121+
116122 let serialized = toml:: to_string_pretty ( & front_matter) ?;
117123 let deserialized = toml:: from_str ( & serialized) ?;
118124
119125 Ok ( deserialized)
120126}
121127
128+ fn guess_version_from_path ( path : & str ) -> Option < String > {
129+ let mut iter = path. split ( [ '-' , '.' ] ) . filter_map ( |s| s. parse :: < u32 > ( ) . ok ( ) ) ;
130+ let major = iter. next ( ) ?;
131+ let minor = iter. next ( ) ?;
132+ let patch = iter. next ( ) . unwrap_or ( 0 ) ; // some posts omit the patch version
133+ Some ( format ! ( "{major}.{minor}.{patch}" ) )
134+ }
135+
122136#[ cfg( test) ]
123137mod tests {
124138 use std:: { env, fs, path:: PathBuf } ;
0 commit comments