]>
Commit | Line | Data |
---|---|---|
74b04a01 XL |
1 | #[macro_use] |
2 | extern crate lazy_static; | |
3 | ||
4 | use regex::Regex; | |
5 | use std::error::Error; | |
6 | use std::fs; | |
7 | use std::fs::File; | |
8 | use std::io::prelude::*; | |
9 | use std::io::{BufReader, BufWriter}; | |
10 | use std::path::{Path, PathBuf}; | |
11 | ||
12 | fn main() -> Result<(), Box<dyn Error>> { | |
13 | // Get all listings from the `listings` directory | |
14 | let listings_dir = Path::new("listings"); | |
15 | ||
16 | // Put the results in the `tmp/listings` directory | |
17 | let out_dir = Path::new("tmp/listings"); | |
18 | ||
19 | // Clear out any existing content in `tmp/listings` | |
20 | if out_dir.is_dir() { | |
21 | fs::remove_dir_all(out_dir)?; | |
22 | } | |
23 | ||
24 | // Create a new, empty `tmp/listings` directory | |
25 | fs::create_dir(out_dir)?; | |
26 | ||
27 | // For each chapter in the `listings` directory, | |
28 | for chapter in fs::read_dir(listings_dir)? { | |
29 | let chapter = chapter?; | |
30 | let chapter_path = chapter.path(); | |
31 | ||
32 | let chapter_name = chapter_path | |
33 | .file_name() | |
34 | .expect("Chapter should've had a name"); | |
35 | ||
36 | // Create a corresponding chapter dir in `tmp/listings` | |
37 | let output_chapter_path = out_dir.join(chapter_name); | |
38 | fs::create_dir(&output_chapter_path)?; | |
39 | ||
40 | // For each listing in the chapter directory, | |
41 | for listing in fs::read_dir(chapter_path)? { | |
42 | let listing = listing?; | |
43 | let listing_path = listing.path(); | |
44 | ||
45 | let listing_name = listing_path | |
46 | .file_name() | |
47 | .expect("Listing should've had a name"); | |
48 | ||
49 | // Create a corresponding listing dir in the tmp chapter dir | |
50 | let output_listing_dir = output_chapter_path.join(listing_name); | |
51 | fs::create_dir(&output_listing_dir)?; | |
52 | ||
53 | // Copy all the cleaned files in the listing to the tmp directory | |
54 | copy_cleaned_listing_files(listing_path, output_listing_dir)?; | |
55 | } | |
56 | } | |
57 | ||
58 | // Create a compressed archive of all the listings | |
59 | let tarfile = File::create("tmp/listings.tar.gz")?; | |
60 | let encoder = | |
61 | flate2::write::GzEncoder::new(tarfile, flate2::Compression::default()); | |
62 | let mut archive = tar::Builder::new(encoder); | |
63 | archive.append_dir_all("listings", "tmp/listings")?; | |
64 | ||
65 | // Assure whoever is running this that the script exiting successfully, and remind them | |
66 | // where the generated file ends up | |
67 | println!("Release tarball of listings in tmp/listings.tar.gz"); | |
68 | ||
69 | Ok(()) | |
70 | } | |
71 | ||
72 | // Cleaned listings will not contain: | |
73 | // | |
74 | // - `target` directories | |
75 | // - `output.txt` files used to display output in the book | |
76 | // - `rustfmt-ignore` files used to signal to update-rustc.sh the listing shouldn't be formatted | |
77 | // - anchor comments or snip comments | |
78 | // - empty `main` functions in `lib.rs` files used to trick rustdoc | |
79 | fn copy_cleaned_listing_files( | |
80 | from: PathBuf, | |
81 | to: PathBuf, | |
82 | ) -> Result<(), Box<dyn Error>> { | |
83 | for item in fs::read_dir(from)? { | |
84 | let item = item?; | |
85 | let item_path = item.path(); | |
86 | ||
87 | let item_name = | |
88 | item_path.file_name().expect("Item should've had a name"); | |
89 | let output_item = to.join(item_name); | |
90 | ||
91 | if item_path.is_dir() { | |
92 | // Don't copy `target` directories | |
93 | if item_name != "target" { | |
94 | fs::create_dir(&output_item)?; | |
95 | copy_cleaned_listing_files(item_path, output_item)?; | |
96 | } | |
97 | } else { | |
98 | // Don't copy output files or files that tell update-rustc.sh not to format | |
99 | if item_name != "output.txt" && item_name != "rustfmt-ignore" { | |
100 | let item_extension = item_path.extension(); | |
101 | if item_extension.is_some() && item_extension.unwrap() == "rs" { | |
102 | copy_cleaned_rust_file( | |
103 | item_name, | |
104 | &item_path, | |
105 | &output_item, | |
106 | )?; | |
107 | } else { | |
108 | // Copy any non-Rust files without modification | |
109 | fs::copy(item_path, output_item)?; | |
110 | } | |
111 | } | |
112 | } | |
113 | } | |
114 | ||
115 | Ok(()) | |
116 | } | |
117 | ||
118 | lazy_static! { | |
119 | static ref ANCHOR_OR_SNIP_COMMENTS: Regex = Regex::new( | |
120 | r"(?x) | |
121 | //\s*ANCHOR:\s*[\w_-]+ # Remove all anchor comments | |
122 | | | |
123 | //\s*ANCHOR_END:\s*[\w_-]+ # Remove all anchor ending comments | |
124 | | | |
125 | //\s*--snip-- # Remove all snip comments | |
126 | " | |
127 | ) | |
128 | .unwrap(); | |
129 | } | |
130 | ||
131 | lazy_static! { | |
132 | static ref EMPTY_MAIN: Regex = Regex::new(r"fn main\(\) \{}").unwrap(); | |
133 | } | |
134 | ||
135 | // Cleaned Rust files will not contain: | |
136 | // | |
137 | // - anchor comments or snip comments | |
138 | // - empty `main` functions in `lib.rs` files used to trick rustdoc | |
139 | fn copy_cleaned_rust_file( | |
140 | item_name: &std::ffi::OsStr, | |
141 | from: &PathBuf, | |
142 | to: &PathBuf, | |
143 | ) -> Result<(), Box<dyn Error>> { | |
144 | let from_buf = BufReader::new(File::open(from)?); | |
145 | let mut to_buf = BufWriter::new(File::create(to)?); | |
146 | ||
147 | for line in from_buf.lines() { | |
148 | let line = line?; | |
923072b8 FG |
149 | if !ANCHOR_OR_SNIP_COMMENTS.is_match(&line) |
150 | && (item_name != "lib.rs" || !EMPTY_MAIN.is_match(&line)) | |
151 | { | |
152 | writeln!(&mut to_buf, "{}", line)?; | |
74b04a01 XL |
153 | } |
154 | } | |
155 | ||
156 | to_buf.flush()?; | |
157 | ||
158 | Ok(()) | |
159 | } |