dotnet_lens/
lib.rs

1//! dotnet-lens is a library for listing dependencies between .NET projects and packages.
2//!
3//! This library provides functionality to parse .NET project files (`.csproj`, `.fsproj`, `.vbproj`)
4//! and extract information about project dependencies, including project references and package references.
5//!
6//! ## Overview
7//!
8//! The main components of this library include:
9//! - `Project`: A struct representing a .NET project, including its language, name, path, target framework,
10//!   project references, and package references.
11//! - `ProjectLanguage`: An enum representing the language of the project based on the file extension.
12//! - `ProjectReference`: A struct representing a reference to another project.
13//! - `PackageReference`: A struct representing a reference to a NuGet package.
14//!
15//! ## Modules
16//!
17//! - `parser`: A module for parsing .NET project files and extracting dependency information.
18//! - `search`: A module for searching .NET project files in a directory.
19//!
20//! ## Features
21//! - `serde`: Adds support for serde serialization and deserialization for the Project struct and
22//!    adjacent types
23//!
24//! ## Examples
25//!
26//! Here is a brief example demonstrating how to use the `Project` struct and its methods:
27//!
28//! ```no_run
29//! use dotnet_lens::{Project, search};
30//!
31//! let projects_paths = search::search_projects(&"path/to/repository")?;
32//!
33//! for path in projects_paths {
34//!     let project = Project::new(path)?;
35//!
36//!     for package_reference in project.package_references() {
37//!         println!("{}: {}", package_reference.name(), package_reference.version());
38//!     }
39//! }
40//!
41//! # Ok::<(), Box<dyn std::error::Error>>(())
42//! ```
43
44use std::{
45    ffi::OsStr,
46    fs::File,
47    path::{Path, PathBuf},
48};
49
50use parser::ParseError;
51
52pub mod parser;
53pub mod search;
54
55/// List of valid extensions: "csproj", "fsproj", "vbproj".
56pub const VALID_EXTENSIONS: [&str; 3] = ["csproj", "fsproj", "vbproj"];
57
58/// Represents a .NET project.
59#[derive(Debug, Clone, PartialEq)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61pub struct Project {
62    name: String,
63    language: ProjectLanguage,
64    path: PathBuf,
65    target_framework: Option<String>,
66    project_references: Vec<ProjectReference>,
67    package_references: Vec<PackageReference>,
68}
69
70impl Project {
71    /// Creates a new `Project` instance by parsing a .NET project file.
72    ///
73    /// This function opens the file specified by the `path`, reads its contents, and parses it
74    /// into a `Project` instance using the `parser::parse` function.
75    ///
76    /// # Arguments
77    ///
78    /// * `path` - A path to the .NET project file that implements the `AsRef<Path>` trait.
79    ///
80    /// # Returns
81    ///
82    /// A `Result<Self, ParseError>` where `Ok(Self)` contains the parsed `Project` instance, and
83    /// `Err(ParseError)` contains the error if the file could not be read or parsed.
84    ///
85    /// # Errors
86    ///
87    /// This function will return an error if:
88    /// - The file could not be opened.
89    /// - The file could not be parsed as a .NET project file.
90    ///
91    /// # Examples
92    ///
93    /// ```rust
94    /// use dotnet_lens::{Project, parser::ParseError};
95    /// use std::path::Path;
96    ///
97    /// match Project::new(Path::new("path/to/MyProject.csproj")) {
98    ///     Ok(project) => {
99    ///         println!("Project parsed successfully: {:?}", project);
100    ///     },
101    ///     Err(e) => {
102    ///         eprintln!("Failed to parse project: {:?}", e);
103    ///     }
104    /// }
105    /// ```
106    pub fn new<P>(path: P) -> Result<Self, ParseError>
107    where
108        P: AsRef<Path>,
109    {
110        let file_reader = File::open(path.as_ref())?;
111
112        parser::parse(file_reader, path)
113    }
114
115    /// Returns the name of the project based on the file name of the provided path.
116    ///
117    /// # Arguments
118    ///
119    /// * `path` - A reference to a path that implements the `AsRef<Path>` trait.
120    ///
121    /// # Returns
122    ///
123    /// An `Option<String>` containing the project name if it could be extracted, otherwise `None`.
124    pub fn get_project_name<P>(path: P) -> Option<String>
125    where
126        P: AsRef<Path>,
127    {
128        path.as_ref()
129            .file_stem()
130            .and_then(|name| name.to_str())
131            .map(|name| name.to_string())
132    }
133
134    /// Returns the name of the project.
135    pub fn name(&self) -> &String {
136        &self.name
137    }
138
139    /// Returns the language of the project.
140    pub fn language(&self) -> ProjectLanguage {
141        self.language
142    }
143
144    /// Returns the path of the project.
145    pub fn path(&self) -> &PathBuf {
146        &self.path
147    }
148
149    /// Returns the target framework of the project, if any.
150    pub fn target_framework(&self) -> Option<&String> {
151        self.target_framework.as_ref()
152    }
153
154    /// Returns a reference to the list of project references.
155    pub fn project_references(&self) -> &Vec<ProjectReference> {
156        &self.project_references
157    }
158
159    /// Adds a new project reference to the list of project references.
160    pub fn add_project_reference(&mut self, value: ProjectReference) {
161        self.project_references.push(value);
162    }
163
164    /// Returns a reference to the list of package references.
165    pub fn package_references(&self) -> &Vec<PackageReference> {
166        &self.package_references
167    }
168
169    /// Adds a new package reference to the list of package references.
170    pub fn add_package_reference(&mut self, value: PackageReference) {
171        self.package_references.push(value);
172    }
173}
174
175/// Represents the language of a .NET project based on the file extension.
176#[derive(Debug, Copy, Clone, PartialEq)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178pub enum ProjectLanguage {
179    CSharp,
180    FSharp,
181    VB,
182}
183
184impl ProjectLanguage {
185    /// Determines the project language from the file extension.
186    ///
187    /// The extension must be provided without a "." (ex: "csproj", "fsproj", "vbproj").
188    ///
189    /// # Arguments
190    ///
191    /// * `extension` - A reference to an `OsStr` representing the file extension.
192    ///
193    /// # Returns
194    ///
195    /// An `Option<ProjectLanguage>` containing the project language if it could be determined, otherwise `None`.
196    pub fn from_extension(extension: &OsStr) -> Option<Self> {
197        match extension.to_str() {
198            Some(val) => match val {
199                "csproj" => Some(Self::CSharp),
200                "fsproj" => Some(Self::FSharp),
201                "vbproj" => Some(Self::VB),
202                _ => None,
203            }
204            _ => None,
205        }
206    }
207}
208
209/// Represents a reference to another .NET project.
210#[derive(Debug, Clone, PartialEq, Default)]
211#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
212pub struct ProjectReference {
213    name: String,
214    path: PathBuf,
215}
216
217impl ProjectReference {
218    /// Creates a new `ProjectReference` instance with the specified name and path.
219    ///
220    /// # Arguments
221    ///
222    /// * `name` - The name of the referenced project.
223    /// * `path` - The path to the referenced project file.
224    ///
225    /// # Examples
226    ///
227    /// ```rust
228    /// use dotnet_lens::ProjectReference;
229    /// use std::path::PathBuf;
230    ///
231    /// let project_ref = ProjectReference::new("OtherProject".to_string(), PathBuf::from("path/to/OtherProject.csproj"));
232    /// println!("Project Name: {}", project_ref.name());
233    /// println!("Project Path: {:?}", project_ref.path());
234    /// ```
235    pub fn new(name: String, path: PathBuf) -> Self {
236        Self { name, path }
237    }
238
239    /// Returns the name of the referenced project.
240    ///
241    /// # Examples
242    ///
243    /// ```rust
244    /// use dotnet_lens::ProjectReference;
245    /// use std::path::PathBuf;
246    ///
247    /// let project_ref = ProjectReference::new("OtherProject".to_string(), PathBuf::from("path/to/OtherProject.csproj"));
248    /// println!("Project Name: {}", project_ref.name());
249    /// ```
250    pub fn name(&self) -> &String {
251        &self.name
252    }
253
254    /// Returns the path to the referenced project file.
255    ///
256    /// # Examples
257    ///
258    /// ```rust
259    /// use dotnet_lens::ProjectReference;
260    /// use std::path::PathBuf;
261    ///
262    /// let project_ref = ProjectReference::new("OtherProject".to_string(), PathBuf::from("path/to/OtherProject.csproj"));
263    /// println!("Project Path: {:?}", project_ref.path());
264    /// ```
265    pub fn path(&self) -> &Path {
266        &self.path
267    }
268}
269
270/// Represents a reference to a NuGet package.
271#[derive(Debug, Clone, PartialEq, Default)]
272#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
273pub struct PackageReference {
274    name: String,
275    version: String,
276}
277
278impl PackageReference {
279    /// Creates a new `PackageReference` instance with the specified name and version.
280    ///
281    /// # Arguments
282    ///
283    /// * `name` - The name of the package.
284    /// * `version` - The version of the package.
285    ///
286    /// # Examples
287    ///
288    /// ```rust
289    /// use dotnet_lens::PackageReference;
290    ///
291    /// let package_ref = PackageReference::new("MyPackage".to_string(), "1.0.0".to_string());
292    /// println!("Package Name: {}", package_ref.name());
293    /// println!("Package Version: {}", package_ref.version());
294    /// ```
295    pub fn new(name: String, version: String) -> Self {
296        Self { name, version }
297    }
298
299    /// Returns the name of the package.
300    ///
301    /// # Examples
302    ///
303    /// ```rust
304    /// use dotnet_lens::PackageReference;
305    ///
306    /// let package_ref = PackageReference::new("MyPackage".to_string(), "1.0.0".to_string());
307    /// println!("Package Name: {}", package_ref.name());
308    /// ```
309    pub fn name(&self) -> &String {
310        &self.name
311    }
312
313    /// Returns the version of the package.
314    ///
315    /// # Examples
316    ///
317    /// ```rust
318    /// use dotnet_lens::PackageReference;
319    ///
320    /// let package_ref = PackageReference::new("MyPackage".to_string(), "1.0.0".to_string());
321    /// println!("Package Version: {}", package_ref.version());
322    /// ```
323    pub fn version(&self) -> &String {
324        &self.version
325    }
326}