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
//! **THIS IS NOT A LIBRARY. NONE OF THE APIS ARE PUBLIC. THEY DON'T
//! ADHERE TO SEMVER. DON'T EVEN USE AT YOUR OWN RISK. DON'T USE IT
//! AT ALL.**

use cargo_metadata::{CargoOpt, MetadataCommand, Target};
use log::{debug, info};
use std::{borrow::Cow, env, fs::File, io::Read as _, path::PathBuf};

#[doc(hidden)]
pub mod depinfo;
#[doc(hidden)]
pub mod diagnostic;
#[doc(hidden)]
pub mod input;
#[doc(hidden)]
pub mod links;
#[doc(hidden)]
pub mod output;
#[doc(hidden)]
pub mod preproc;
#[doc(hidden)]
pub mod verify;

use diagnostic::Diagnostic;
use input::{CrateCode, InputFile, TargetType};

#[doc(hidden)]
/// Read input. The manifest path, if present, will be passed to `cargo metadata`. If you set
/// expand_macros to true, the input will be passed to the rust compiler to expand macros. This
/// will only work on a nightly compiler. The template doesn't have to exist, a default will
/// be used if it does not exist.
pub fn read_input(
	manifest_path: Option<PathBuf>,
	prefer_bin: bool,
	expand_macros: bool,
	template: PathBuf
) -> (InputFile, Cow<'static, str>, Diagnostic) {
	// get the cargo manifest path
	let manifest_path = match manifest_path {
		Some(path) if path.is_relative() => Some(env::current_dir().unwrap().join(path)),
		Some(path) => Some(path),
		None => None
	};

	// parse the cargo metadata
	let mut cmd = MetadataCommand::new();
	cmd.features(CargoOpt::AllFeatures);
	if let Some(path) = &manifest_path {
		cmd.manifest_path(path);
	}
	let metadata = cmd.exec().expect("Failed to get cargo metadata");
	let pkg = metadata
		.root_package()
		.expect("Missing root package; did you call this command on a workspace root?");

	// find the target whose rustdoc comment we'll use.
	// this uses a library target if exists, otherwise a binary target with the same name as the
	// package, or otherwise the first binary target
	let is_lib = |target: &&Target| target.is_lib();
	let is_default_bin =
		|target: &&Target| target.is_bin() && target.name == pkg.name.as_str();
	let target_and_type = if prefer_bin {
		pkg.targets
			.iter()
			.find(is_default_bin)
			.map(|target| (target, TargetType::Bin))
			.or_else(|| {
				pkg.targets
					.iter()
					.find(is_lib)
					.map(|target| (target, TargetType::Lib))
			})
	} else {
		pkg.targets
			.iter()
			.find(is_lib)
			.map(|target| (target, TargetType::Lib))
			.or_else(|| {
				pkg.targets
					.iter()
					.find(is_default_bin)
					.map(|target| (target, TargetType::Bin))
			})
	};
	let (target, target_type) = target_and_type
		.or_else(|| {
			pkg.targets
				.iter()
				.find(|target| target.is_bin())
				.map(|target| (target, TargetType::Bin))
		})
		.expect("Failed to find a library or binary target");

	// read crate code
	let file = target.src_path.as_std_path();
	let filename = file
		.file_name()
		.expect("File has no filename")
		.to_string_lossy()
		.into_owned();
	let code = if expand_macros {
		CrateCode::read_expansion(manifest_path.as_ref(), target)
			.expect("Failed to read crate code")
	} else {
		CrateCode::read_from_disk(file).expect("Failed to read crate code")
	};
	let mut diagnostics = Diagnostic::new(filename, code.0.clone());

	// resolve the template
	let template: Cow<'static, str> = if template.exists() {
		let mut buf = String::new();
		File::open(template)
			.expect("Failed to open template")
			.read_to_string(&mut buf)
			.expect("Failed to read template");
		buf.into()
	} else {
		include_str!("README.j2").into()
	};

	// process the target
	info!("Reading {}", file.display());
	let input_file =
		input::read_code(&metadata, pkg, code, target_type, &mut diagnostics);
	debug!("Processing {input_file:#?}");

	(input_file, template, diagnostics)
}