#[ allow( clippy ::std_instead_of_alloc, clippy ::std_instead_of_core ) ]
mod private
{
use crate :: *;
use std ::
{
borrow ::Cow,
fs ::OpenOptions,
fmt,
io ::
{
Read,
Seek,
Write,
SeekFrom,
}
};
use collection_tools ::collection ::BTreeSet;
// use pth ::AbsolutePath;
use action ::readme_health_table_renew :: { Stability, stability_generate, find_example_file };
use crate ::entity ::package ::Package;
use error ::
{
// err,
untyped ::
{
// Result,
// Error as wError,
Context,
},
};
// aaa: for Petro: group properly, don't repeat std ::
// aaa: done
use std ::path ::PathBuf;
use convert_case :: { Case, Casing };
// use rayon ::scope_fifo;
use regex ::Regex;
use entity :: { WorkspaceInitError, PathError };
use crate ::entity ::package ::PackageError;
use error ::typed ::Error;
use workspace_md_extension ::WorkspaceMdExtension;
// use error ::ErrWith;
static TAGS_TEMPLATE: std ::sync ::OnceLock< Regex > = std ::sync ::OnceLock ::new();
fn regexes_initialize()
{
TAGS_TEMPLATE.set
(
Regex ::new
(
r"<!--\{ generate\.module_header\.start(\(\)|\{\}|\(.*?\)|\{.*?\}) \}-->(.|\n|\r\n)+<!--\{ generate\.module_header\.end \}-->"
).unwrap()
).ok();
}
/// Report.
#[ derive( Debug, Default, Clone ) ]
pub struct ModulesHeadersRenewReport
{
found_files: BTreeSet< PathBuf >,
touched_files: BTreeSet< PathBuf >,
}
impl fmt ::Display for ModulesHeadersRenewReport
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> fmt ::Result
{
if self.touched_files.len() < self.found_files.len()
{
writeln!
(
f,
"Something went wrong.\n{}/{} was touched.",
self.found_files.len(),
self.touched_files.len()
)?;
return std ::fmt ::Result ::Ok(())
}
writeln!( f, "Touched files: " )?;
let mut count = self.found_files.len();
for path in &self.touched_files
{
if let Some( file_path ) = path.to_str()
{
writeln!( f, "{file_path}" )?;
count -= 1;
}
}
if count != 0
{
writeln!( f, "Other {count} files contains non-UTF-8 characters." )?;
}
std ::fmt ::Result ::Ok( () )
}
}
/// The `ModulesHeadersRenewError` enum represents the various errors that can occur during
/// the renewal of module headers.
#[ derive( Debug, Error ) ]
pub enum ModulesHeadersRenewError
{
/// Represents a common error.
#[ error( "Common error: {0}" ) ]
Common(#[ from ] error ::untyped ::Error ), // qqq: rid of
/// Represents an I/O error.
#[ error( "I/O error: {0}" ) ]
IO( #[ from ] std ::io ::Error ),
/// Represents an error related to workspace initialization.
#[ error( "Workspace error: {0}" ) ]
Workspace( #[ from ] WorkspaceInitError ),
/// Represents an error related to a package.
#[ error( "Package error: {0}" ) ]
Package( #[ from ] PackageError ),
/// Represents an error related to directory paths.
#[ error( "Directory error: {0}" ) ]
Directory( #[ from ] PathError ),
}
/// The `ModuleHeader` structure represents a set of parameters, used for creating url for header.
struct ModuleHeader
{
module_path: PathBuf,
stability: Stability,
module_name: String,
repository_url: String,
discord_url: Option< String >,
}
impl ModuleHeader
{
/// Create `ModuleHeader` instance from the folder where Cargo.toml is stored.
#[ allow( clippy ::needless_pass_by_value ) ]
fn from_cargo_toml
(
package: Package< '_ >,
// fix clippy
default_discord_url: Option< &String >,
)
-> Result< Self, ModulesHeadersRenewError >
{
let stability = package.stability()?;
let module_name = package.name()?;
let repository_url = package.repository()?
.ok_or_else :: < error ::untyped ::Error, _ >
(
|| error ::untyped ::format_err!( "Fail to find repository_url in module`s Cargo.toml" )
)?;
let discord_url = package
.discord_url()?
.or_else( || default_discord_url.cloned() );
Result ::Ok
(
Self
{
module_path: package.manifest_file().parent().unwrap().as_ref().to_path_buf(),
stability,
module_name: module_name.to_string(),
repository_url,
discord_url,
}
)
}
/// Convert `ModuleHeader`to header.
#[ allow( clippy ::uninlined_format_args, clippy ::wrong_self_convention ) ]
fn to_header( self, workspace_path: &str ) -> Result< String, ModulesHeadersRenewError >
{
let discord = self.discord_url.map( | discord_url |
format!
(
" []({})",
discord_url
)
)
.unwrap_or_default();
let repo_url = url ::repo_url_extract( &self.repository_url )
.and_then( | r | url ::git_info_extract( &r ).ok() )
.ok_or_else :: < error ::untyped ::Error, _ >( || error ::untyped ::format_err!( "Fail to parse repository url" ) )?;
let example= if let Some( name ) = find_example_file
(
self.module_path.as_path(),
&self.module_name
)
{
let relative_path = pth ::path ::path_relative
(
workspace_path.into(),
name
)
.to_string_lossy()
.to_string();
// fix clippy
#[ cfg( target_os = "windows" ) ]
let relative_path = relative_path.replace( '\\', "/" );
// aaa: for Petro: use path_toools
// aaa: used
let p = relative_path.replace( '/',"%2F" );
format!
(
" [](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE={},RUN_POSTFIX=--example%20{}/https://github.com/{})",
p,
p,
repo_url
)
}
else
{
String ::new()
};
Result ::Ok( format!
(
"{} \
[](https://github.com/{}/actions/workflows/module_{}_push.yml) \
[](https://docs.rs/{}){}{}",
stability_generate( &self.stability ),
repo_url, self.module_name.to_case( Case ::Snake ), repo_url, self.module_name.to_case( Case ::Snake ),
self.module_name, self.module_name,
example,
discord,
) )
}
}
/// Generate header in modules readme.md.
/// The location of header is defined by a tag :
/// ``` md
/// <!--{ generate.module_header.start() }-->
/// <!--{ generate.module_header.end }-->
/// ```
/// To use it you need to add these fields to Cargo.toml each module workspace :
/// ``` toml
/// [package]
/// name = "test_module"
/// repository = "https://github.com/Wandalen/wTools/tree/master/module/move/test_module"
/// ...
/// [package.metadata]
/// stability = "stable" (Optional)
/// discord_url = "https://discord.gg/m3YfbXpUUY" (Optional)
/// ```
/// Result example :
/// ``` md
/// <!--{ generate.module_header.start() }-->
/// [](https://github.com/emersion/stability-badges#experimental) | [](https://github.com/Username/test/actions/workflows/ModuleChainOfPackagesAPush.yml)[](https://docs.rs/_chain_of_packages_a)[](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2F_chain_of_packages_a_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20_chain_of_packages_a_trivial/https://github.com/Username/test)
/// <!--{ generate.module_header.end }-->
/// ```
///
/// # Errors
/// qqq: doc
///
/// # Panics
/// qqq: doc
pub fn readme_modules_headers_renew( crate_dir: CrateDir )
-> ResultWithReport< ModulesHeadersRenewReport, ModulesHeadersRenewError >
// -> Result< ModulesHeadersRenewReport, ( ModulesHeadersRenewReport, ModulesHeadersRenewError ) >
{
let mut report = ModulesHeadersRenewReport ::default();
regexes_initialize();
let workspace = Workspace ::try_from( crate_dir )
.err_with_report( &report )?;
let discord_url = workspace.discord_url();
// qqq: inspect each collect in willbe and rid of most of them
let paths: Vec< AbsolutePath > = workspace
.packages()
.filter_map( | p | p.manifest_file().ok().map( crate ::entity ::files ::ManifestFile ::inner ) )
.collect();
report.found_files = paths
.iter()
.map( | ap | ap.as_ref().to_path_buf() )
.collect();
for path in paths
{
let read_me_path = path
.parent()
.unwrap()
.join
(
repository ::readme_path( path.parent().unwrap().as_ref() )
// .ok_or_else :: < error ::untyped ::Error, _ >( || error ::untyped ::format_err!( "Fail to find README.md at {}", &path ) )
.err_with_report( &report )?
)
.err_with_report( &report )?;
let pakage = Package ::try_from
(
CrateDir ::try_from
(
&path
.parent()
.unwrap()
)
.err_with_report( &report )?
)
.err_with_report( &report )?;
// fix clippy
let header = ModuleHeader ::from_cargo_toml( pakage, discord_url.as_ref() )
.err_with_report( &report )?;
let mut file = OpenOptions ::new()
.read( true )
.write( true )
.open( &read_me_path )
.err_with_report( &report )?;
let mut content = String ::new();
file.read_to_string( &mut content ).err_with_report( &report )?;
let raw_params = TAGS_TEMPLATE
.get()
.unwrap()
.captures( &content )
.and_then( | c | c.get( 1 ) )
.map( | m | m.as_str() )
.unwrap_or_default();
_ = query ::parse( raw_params ).context( "Fail to parse raw params." );
// qqq: for Petro: why ignored?
let content = header_content_generate
(
&content,
header,
raw_params,
workspace.workspace_root().to_str().unwrap()
).err_with_report( &report )?;
file.set_len( 0 ).err_with_report( &report )?;
file.seek( SeekFrom ::Start( 0 ) ).err_with_report( &report )?;
file.write_all( content.as_bytes() ).err_with_report( &report )?;
report.touched_files.insert( path.as_ref().to_path_buf() );
}
ResultWithReport ::Ok( report )
}
#[ allow( clippy ::uninlined_format_args ) ]
fn header_content_generate< 'a >
(
content: &'a str,
header: ModuleHeader,
raw_params: &str,
workspace_root: &str
)
// qqq: use typed error
-> error ::untyped ::Result< Cow< 'a, str > >
{
let header = header.to_header( workspace_root )?;
let result = TAGS_TEMPLATE
.get()
.unwrap()
.replace
(
content,
&format!
(
"<!--{{ generate.module_header.start{} }}-->\n{}\n<!--{{ generate.module_header.end }}-->",
raw_params,
header
)
);
error ::untyped ::Result ::Ok( result )
}
}
crate ::mod_interface!
{
/// Generate headers in modules
orphan use readme_modules_headers_renew;
/// report
orphan use ModulesHeadersRenewReport;
/// Error.
orphan use ModulesHeadersRenewError;
}