syncdoc_migrate/
discover.rs1use crate::config::DocsPathMode;
4use proc_macro2::TokenStream;
5use std::fs;
6use std::path::{Path, PathBuf};
7use std::str::FromStr;
8use syncdoc_core::parse::ModuleContent;
9use unsynn::*;
10
11#[derive(Debug)]
13pub struct ParsedFile {
14 pub path: PathBuf,
15 pub content: ModuleContent,
16 pub original_source: String,
17}
18
19#[derive(Debug)]
21pub enum ParseError {
22 IoError(std::io::Error),
23 ParseFailed(String),
24}
25
26impl From<std::io::Error> for ParseError {
27 fn from(err: std::io::Error) -> Self {
28 ParseError::IoError(err)
29 }
30}
31
32impl std::fmt::Display for ParseError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 ParseError::IoError(e) => write!(f, "IO error: {}", e),
36 ParseError::ParseFailed(msg) => write!(f, "Parse failed: {}", msg),
37 }
38 }
39}
40
41impl std::error::Error for ParseError {}
42
43#[derive(Debug)]
45pub enum ConfigError {
46 IoError(std::io::Error),
47 Other(String),
48}
49
50impl From<std::io::Error> for ConfigError {
51 fn from(err: std::io::Error) -> Self {
52 ConfigError::IoError(err)
53 }
54}
55
56impl std::fmt::Display for ConfigError {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 ConfigError::IoError(e) => write!(f, "IO error: {}", e),
60 ConfigError::Other(msg) => write!(f, "{}", msg),
61 }
62 }
63}
64
65impl std::error::Error for ConfigError {}
66
67pub fn discover_rust_files(source_dir: &Path) -> std::result::Result<Vec<PathBuf>, std::io::Error> {
71 let mut rust_files = Vec::new();
72 discover_rust_files_recursive(source_dir, &mut rust_files)?;
73 rust_files.sort();
74 Ok(rust_files)
75}
76
77fn discover_rust_files_recursive(
78 dir: &Path,
79 files: &mut Vec<PathBuf>,
80) -> std::result::Result<(), std::io::Error> {
81 for entry in fs::read_dir(dir)? {
82 let entry = entry?;
83 let path = entry.path();
84
85 if path.is_dir() {
86 discover_rust_files_recursive(&path, files)?;
87 } else if path.extension() == Some(std::ffi::OsStr::new("rs")) {
88 files.push(path.canonicalize()?);
89 }
90 }
91 Ok(())
92}
93
94pub fn parse_file(path: &Path) -> std::result::Result<ParsedFile, ParseError> {
99 let original_source = fs::read_to_string(path)?;
100
101 let token_stream = TokenStream::from_str(&original_source)
102 .map_err(|e| ParseError::ParseFailed(format!("Failed to tokenize: {}", e)))?;
103
104 let content = token_stream
105 .into_token_iter()
106 .parse::<ModuleContent>()
107 .map_err(|e| ParseError::ParseFailed(format!("Failed to parse module: {}", e)))?;
108
109 Ok(ParsedFile {
110 path: path.to_path_buf(),
111 content,
112 original_source,
113 })
114}
115
116pub fn get_or_create_docs_path(
124 source_file: &Path,
125 dry_run: bool,
126) -> std::result::Result<(String, DocsPathMode), ConfigError> {
127 match syncdoc_core::config::get_docs_path(source_file.to_str().unwrap()) {
129 Ok(path) => Ok((path, DocsPathMode::TomlConfig)),
130 Err(_) => {
131 if !dry_run {
133 let source_dir = source_file.parent().ok_or_else(|| {
134 ConfigError::Other("Source file has no parent directory".to_string())
135 })?;
136
137 let manifest_dir = syncdoc_core::path_utils::find_manifest_dir(source_dir)
138 .ok_or_else(|| ConfigError::Other("Could not find Cargo.toml".to_string()))?;
139
140 let cargo_toml_path = manifest_dir.join("Cargo.toml");
141
142 let mut content = fs::read_to_string(&cargo_toml_path)?;
144
145 if !content.contains("[package.metadata.syncdoc]") {
147 content.push_str("\n[package.metadata.syncdoc]\n");
149 content.push_str("docs-path = \"docs\"\n");
150
151 fs::write(&cargo_toml_path, content)?;
153 }
154 }
155
156 Ok(("docs".to_string(), DocsPathMode::TomlConfig))
158 }
159 }
160}
161
162#[cfg(test)]
163mod tests;