crates_tools/
lib.rs

1#![doc(html_logo_url = "https: //raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png")]
2#![doc(
3  html_favicon_url = "https: //raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico"
4)]
5#![doc(html_root_url = "https: //docs.rs/crates_tools/latest/crates_tools/")]
6#![ cfg_attr( doc, doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "readme.md" ) ) ) ]
7#![ cfg_attr( not( doc ), doc = "Crate management utilities" ) ]
8
9/// Define a private namespace for all its items.
10#[ cfg(feature = "enabled") ]
11mod private 
12{
13  use std ::collections ::HashMap;
14  use core ::fmt ::Formatter;
15  use std ::io ::Read;
16  use std ::path :: { Path, PathBuf };
17  use core ::time ::Duration;
18  use ureq ::AgentBuilder;
19
20  /// Represents a `.crate` archive, which is a collection of files and their contents.
21  #[ derive(Default, Clone, PartialEq) ]
22  pub struct CrateArchive(HashMap< PathBuf, Vec<u8 >>);
23
24  impl core ::fmt ::Debug for CrateArchive 
25  {
26  #[ allow(clippy ::implicit_return, clippy ::min_ident_chars) ]
27  #[ inline ]
28  fn fmt(&self, f: &mut Formatter< '_ >) -> core ::fmt ::Result 
29  {
30   f.debug_struct("CrateArchive").field("files", &self.0.keys()).finish()
31 }
32 }
33
34  impl CrateArchive 
35  {
36  /// Reads and decode a `.crate` archive from a given path.
37  ///
38  /// # Errors
39  ///
40  /// Returns `std::io::Error` if:
41  /// - The file at the specified path does not exist
42  /// - Insufficient permissions to read the file
43  /// - The file is not a valid gzip-compressed tar archive
44  /// - The archive contains malformed tar entries
45  #[ allow(clippy ::question_mark_used, clippy ::implicit_return) ]
46  #[ inline ]
47  pub fn read< P >(path: P) -> std ::io ::Result< Self >
48  where
49   P: AsRef< Path >,
50  {
51   let mut file = std ::fs ::File ::open(path)?;
52   let mut buf = vec![];
53   #[ allow(clippy ::verbose_file_reads) ]
54   file.read_to_end(&mut buf)?;
55
56   Self ::decode(buf)
57 }
58
59  #[ cfg(feature = "network") ]
60  #[ allow(clippy ::question_mark_used, clippy ::implicit_return, clippy ::result_large_err) ]
61  /// Downloads and decodes a `.crate` archive from a given url.
62  ///
63  /// # Errors
64  ///
65  /// Returns `ureq::Error` if:
66  /// - The HTTP request fails (network connectivity issues)
67  /// - The URL is invalid or unreachable
68  /// - The HTTP request times out (5 second read/write timeout)
69  /// - The downloaded content is not a valid gzip-compressed tar archive
70  /// - An I/O error occurs during download or decompression
71  #[ inline ]
72  pub fn download< Url >(url: Url) -> Result< Self, ureq ::Error >
73  where
74   Url: AsRef< str >,
75  {
76   let agent = AgentBuilder ::new()
77  .timeout_read(Duration ::from_secs(5))
78  .timeout_write(Duration ::from_secs(5))
79  .build();
80
81   let resp = agent.get(url.as_ref()).call()?;
82
83   let mut buf = vec![];
84   resp.into_reader().read_to_end(&mut buf)?;
85
86   Ok(Self ::decode(buf)?)
87 }
88
89  /// Downloads and decodes a `.crate` archive from `crates.io` repository by given name and version of the package.
90  /// Requires the full version of the package, in the format of `"x.y.z"`
91  ///
92  /// Returns error if the package with specified name and version - not exists.
93  /// # Errors
94  ///
95  /// Returns `ureq::Error` if:
96  /// - The HTTP request to crates.io fails
97  /// - The crate with the specified name and version does not exist
98  /// - Network connectivity issues occur
99  /// - The downloaded file is not a valid crate archive
100  #[ cfg(feature = "network") ]
101  #[ allow(clippy ::implicit_return, clippy ::result_large_err) ]
102  #[ inline ]
103  pub fn download_crates_io< N, V >(name: N, version: V) -> Result< Self, ureq ::Error >
104  where
105   N: core ::fmt ::Display,
106   V: core ::fmt ::Display,
107  {
108   Self ::download(format!("https://static.crates.io/crates/{name}/{name}-{version}.crate"))
109 }
110
111  /// Decodes a bytes that represents a `.crate` file.
112  ///
113  /// # Errors
114  ///
115  /// Returns `std::io::Error` if:
116  /// - The input bytes are not a valid gzip-compressed archive
117  /// - The decompressed data is not a valid tar archive
118  /// - Any tar entry has an invalid path or cannot be read
119  /// - An I/O error occurs during decompression or extraction
120  #[ allow(clippy ::question_mark_used, unknown_lints, clippy ::implicit_return) ]
121  #[ inline ]
122  pub fn decode< B >(bytes: B) -> std ::io ::Result< Self >
123  where
124   B: AsRef< [u8] >,
125  {
126   use std ::io ::prelude :: *;
127   use flate2 ::bufread ::GzDecoder;
128   use tar ::Archive;
129
130   let bytes_slice = bytes.as_ref();
131   if bytes_slice.is_empty() 
132   {
133  return Ok(Self ::default());
134 }
135
136   let gz = GzDecoder ::new(bytes_slice);
137   let mut archive = Archive ::new(gz);
138
139   let mut output = HashMap ::new();
140
141   for file in archive.entries()? 
142   {
143  let mut archive_file = file?;
144
145  let mut contents = vec![];
146  archive_file.read_to_end(&mut contents)?;
147
148  output.insert(archive_file.path()?.to_path_buf(), contents);
149 }
150
151   Ok(Self(output))
152 }
153
154  /// Returns a list of files from the `.crate` file.
155  #[ allow(clippy ::implicit_return) ]
156  #[ inline ]
157  pub fn list( &self ) -> Vec< &Path >
158  {
159   self.0.keys().map(PathBuf ::as_path).collect()
160 }
161
162  /// Returns content of file by specified path from the `.crate` file in bytes representation.
163  #[ allow(clippy ::implicit_return) ]
164  #[ inline ]
165  pub fn content_bytes< P >(&self, path: P) -> Option< &[u8] >
166  where
167   P: AsRef< Path >,
168  {
169   self.0.get(path.as_ref()).map(Vec ::as_ref)
170 }
171 }
172}
173
174#[ cfg(feature = "enabled") ]
175#[ doc(inline) ]
176#[ allow(unused_imports, clippy ::pub_use) ]
177pub use own :: *;
178
179/// Own namespace of the module.
180#[ cfg(feature = "enabled") ]
181#[ allow(unused_imports) ]
182pub mod own 
183{
184  use super ::orphan;
185  #[ doc(inline) ]
186  #[ allow(unused_imports, clippy ::pub_use) ]
187  pub use orphan :: *;
188}
189
190/// Orphan namespace of the module.
191#[ cfg(feature = "enabled") ]
192#[ allow(unused_imports) ]
193pub mod orphan 
194{
195  use super ::exposed;
196  #[ doc(inline) ]
197  #[ allow(unused_imports, clippy ::pub_use) ]
198  pub use exposed :: *;
199}
200
201/// Exposed namespace of the module.
202#[ cfg(feature = "enabled") ]
203#[ allow(unused_imports) ]
204pub mod exposed 
205{
206  use super ::prelude;
207  #[ doc(inline) ]
208  #[ allow(unused_imports, clippy ::pub_use) ]
209  pub use prelude :: *;
210}
211
212/// Prelude to use essentials: `use my_module ::prelude :: *`.
213#[ cfg(feature = "enabled") ]
214#[ allow(unused_imports) ]
215pub mod prelude 
216{
217  use super ::private;
218  #[ doc(inline) ]
219  #[ allow(unused_imports, clippy ::pub_use) ]
220  pub use private ::CrateArchive;
221}