1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/// Define a private namespace for all its items.
#[ allow( clippy ::std_instead_of_alloc, clippy ::std_instead_of_core ) ]
mod private
{
use crate :: *;
use std ::
{
io :: { self, Read },
fs,
};
use error ::
{
typed ::Error,
untyped :: { format_err },
};
/// Represents errors related to manifest data processing.
#[ derive( Debug, Error ) ]
pub enum ManifestError
{
/// Manifest data not loaded.
#[ error( "Manifest data not loaded." ) ]
EmptyManifestData,
/// Cannot find the specified tag in the TOML file.
#[ error( "Cannot find tag {0} in toml file." ) ]
CannotFindValue( String ),
/// Try to read or write
#[ error( "Io operation with manifest failed. Details: {0}" ) ]
Io( #[ from ] io ::Error ),
/// It was expected to be a package, but it wasn't
#[ error( "Is not a package" ) ]
NotAPackage,
/// It was expected to be a package, but it wasn't
#[ error( "Invalid value `{0}` in manifest file." ) ]
InvalidValue( String ),
}
///
/// Hold manifest data.
///
#[ derive( Debug, Clone ) ]
pub struct Manifest
{
/// Path to `Cargo.toml`
// pub manifest_file: AbsolutePath,
pub manifest_file: ManifestFile,
// aaa: for Bohdan: for Petro: why not ManifestFile?
/// Strict type of `Cargo.toml` manifest.
pub data: toml_edit ::Document,
// pub data: Option< toml_edit ::Document >,
}
impl TryFrom< ManifestFile > for Manifest
{
type Error = ManifestError;
fn try_from( manifest_file: ManifestFile ) -> Result< Self, Self ::Error >
{
let read = fs ::read_to_string( &manifest_file )?;
let data = read.parse :: < toml_edit ::Document >()
.map_err( | e | io ::Error ::new( io ::ErrorKind ::InvalidData, e ) )?;
Result ::Ok
(
Manifest
{
manifest_file,
data,
}
)
}
}
impl TryFrom< CrateDir > for Manifest
{
type Error = ManifestError;
fn try_from( src: CrateDir ) -> Result< Self, Self ::Error >
{
Self ::try_from( src.manifest_file() )
}
}
impl Manifest
{
/// Returns a mutable reference to the TOML document.
///
/// If the TOML document has not been loaded yet, this function will load it
/// by calling the `load` method. If loading fails, this function will panic.
///
/// # Returns
///
/// A mutable reference to the TOML document.
pub fn data( &mut self ) -> &mut toml_edit ::Document
{
// if self.data.is_none() { self.load().unwrap() }
// self.data.as_mut().unwrap()
&mut self.data
}
/// Returns path to `Cargo.toml`.
#[ must_use ]
pub fn manifest_file( &self ) -> &AbsolutePath
{
&self.manifest_file
}
/// Path to directory where `Cargo.toml` located.
/// # Panics
/// qqq: doc
#[ must_use ]
pub fn crate_dir( &self ) -> CrateDir
{
self.manifest_file.parent().unwrap().try_into().unwrap()
// CrateDir( self.manifest_file.parent().unwrap() )
}
/// Store manifest.
/// # Errors
/// qqq: doc
pub fn store( &self ) -> io ::Result< () >
{
fs ::write( &self.manifest_file, self.data.to_string() )?;
std ::io ::Result ::Ok( () )
}
/// Check that the current manifest is the manifest of the package (can also be a virtual workspace).
#[ must_use ]
pub fn package_is( &self ) -> bool
{
// let data = self.data.as_ref().ok_or_else( || ManifestError ::EmptyManifestData )?;
let data = &self.data;
data.get( "package" ).is_some() && data[ "package" ].get( "name" ).is_some()
}
/// Check that module is local.
/// The package is defined as local if the `publish` field is set to `false` or the registers are specified.
#[ must_use ]
pub fn local_is( &self ) -> bool
{
// let data = self.data.as_ref().ok_or_else( || ManifestError ::EmptyManifestData )?;
let data = &self.data;
if data.get( "package" ).is_some() && data[ "package" ].get( "name" ).is_some()
{
let remote = data[ "package" ].get( "publish" ).is_none()
|| data[ "package" ][ "publish" ].as_bool().unwrap_or( true );
return !remote;
}
true
}
/// Resolves workspace-inherited field value.
///
/// When a package uses workspace inheritance (e.g., `version.workspace = true`),
/// this function locates the workspace root `Cargo.toml` and extracts the value
/// from `[workspace.package]` section.
///
/// # Arguments
///
/// * `field_name` - Name of the field to resolve (e.g., "version", "edition", "license")
///
/// # Returns
///
/// Returns the resolved field value as a string if found.
///
/// # Errors
///
/// Returns error if:
/// - Cannot locate workspace root manifest
/// - Cannot read workspace manifest file
/// - Field not found in workspace.package section
/// - Field value is not a string
///
/// # Panics
///
/// Panics if parent directory traversal encounters an invalid state (should not occur in practice).
pub fn resolve_workspace_field( &self, field_name: &str ) -> Result< String, ManifestError >
{
// Find workspace root by traversing up from current manifest
let mut current_dir = self.manifest_file.parent()
.ok_or_else( || ManifestError ::Io( io ::Error ::new( io ::ErrorKind ::NotFound, "Cannot find parent directory" ) ) )?;
// Search for workspace Cargo.toml by going up directories
loop
{
let workspace_manifest = current_dir.join( "Cargo.toml" )?;
if workspace_manifest.exists()
{
let workspace_content = fs ::read_to_string( workspace_manifest )?;
let workspace_doc = workspace_content.parse :: < toml_edit ::Document >()
.map_err( | e | io ::Error ::new( io ::ErrorKind ::InvalidData, e ) )?;
// Check if this is a workspace manifest
if let Some( workspace ) = workspace_doc.get( "workspace" )
{
// Try to get field from [workspace.package]
if let Some( workspace_package ) = workspace.get( "package" )
{
if let Some( field_value ) = workspace_package.get( field_name )
{
if let Some( value_str ) = field_value.as_str()
{
return Result ::Ok( value_str.to_string() );
}
}
}
}
}
// Move up to parent directory
let parent = current_dir.parent();
if parent.is_none() || parent == Some( current_dir )
{
// Reached filesystem root without finding workspace
break;
}
current_dir = parent.unwrap();
}
Err( ManifestError ::CannotFindValue( format!( "workspace.package.{field_name}" ) ) )
}
/// Gets package version, handling both direct and workspace-inherited values.
///
/// This function properly resolves the version field whether it's:
/// - A direct string value: `version = "0.2.0"`
/// - Workspace-inherited: `version.workspace = true`
///
/// # Returns
///
/// Returns the version string if found.
///
/// # Errors
///
/// Returns error if:
/// - Version field is missing
/// - Version field has invalid format
/// - Workspace inheritance is declared but cannot be resolved
pub fn version( &self ) -> Result< String, ManifestError >
{
let data = &self.data;
// First, try to get version as a direct string
if let Some( version_str ) = data.get( "package" )
.and_then( | p | p.get( "version" ) )
.and_then( | v | v.as_str() )
{
return Result ::Ok( version_str.to_string() );
}
// Check if version uses workspace inheritance
if let Some( version_value ) = data.get( "package" ).and_then( | p | p.get( "version" ) )
{
if let Some( version_table ) = version_value.as_table()
{
if version_table.get( "workspace" ).and_then( toml_edit::Item::as_bool ) == Some( true )
{
// Resolve from workspace
return self.resolve_workspace_field( "version" );
}
}
}
// Version field is missing or has invalid format
Err( ManifestError ::CannotFindValue( "package.version".to_string() ) )
}
}
/// Retrieves the repository URL of a package from its `Cargo.toml` file.
/// # Errors
/// qqq: doc
// qqq: use typed error
pub fn repo_url( crate_dir: &CrateDir ) -> error ::untyped ::Result< String >
{
let path = crate_dir.clone().manifest_file().inner().inner();
if path.exists()
{
let mut contents = String ::new();
// qqq: zzz: for Petro: redundant read and parse
fs ::File ::open( path )?.read_to_string( &mut contents )?;
let doc = contents.parse :: < toml_edit ::Document >()?;
let repo_url = doc
.get( "package" )
.and_then( | package | package.get( "repository" ) )
.and_then( | i | i.as_str() );
if let Some( repo_url ) = repo_url
{
url ::repo_url_extract( repo_url ).ok_or_else( || format_err!( "Fail to extract repository url ") )
}
else
{
let report = tool ::git ::ls_remote_url( crate_dir.clone().absolute_path() )?;
url ::repo_url_extract( report.out.trim() ).ok_or_else( || format_err!( "Fail to extract repository url from git remote.") )
}
}
else
{
Err( format_err!( "No Cargo.toml found" ) )
}
}
}
//
crate ::mod_interface!
{
exposed use Manifest;
orphan use ManifestError;
own use repo_url;
}