1use std::path::Path;
2use std::path::PathBuf;
3
4use std::str::Lines;
5
6use std::fs;
7use std::io;
8use std::env;
9
10#[derive(Debug)]
11struct TangleError {
12 report: String,
13}
14
15impl TangleError {
16 fn new (report: &str) -> Self {
17 TangleError {
18 report: report.to_string (),
19 }
20 }
21}
22
23fn property_line_p (line: &str) -> bool {
24 line .trim_start () .starts_with ("#+property:")
25}
26
27fn find_destination_in_property_line (
28 line: &str,
29) -> Option <String> {
30 let mut words = line.split_whitespace ();
31 while let Some (word) = words.next () {
32 if word == "tangle" || word == ":tangle" {
33 if let Some (destination) = words.next () {
34 return Some (destination.to_string ())
35 }
36 }
37 }
38 None
39}
40
41fn find_destination (string: &str) -> Option <String> {
42 for line in string.lines () {
43 if property_line_p (line) {
44 let destination =
45 find_destination_in_property_line (line);
46 if destination. is_some () {
47 return destination;
48 }
49 }
50 }
51 None
52}
53
54#[test]
55fn test_find_destination () {
56 let example = "#+property: tangle lib.rs";
57 let destination = find_destination (example) .unwrap ();
58 assert_eq! (destination, "lib.rs");
59
60 let example = "#+property: header-args :tangle lib.rs";
61 let destination = find_destination (example) .unwrap ();
62 assert_eq! (destination, "lib.rs");
63}
64
65fn block_begin_line_p (line: &str) -> bool {
66 line .trim_start () .starts_with ("#+begin_src")
67}
68
69fn block_end_line_p (line: &str) -> bool {
70 line .trim_start () .starts_with ("#+end_src")
71}
72
73fn block_indentation (line: &str) -> usize {
74 let mut indentation = 0;
75 for ch in line.chars () {
76 if ch == ' ' {
77 indentation += 1;
78 } else {
79 return indentation;
80 }
81 }
82 0
83}
84
85 fn line_trim_indentation <'a> (
86 mut line: &'a str,
87 indentation: usize,
88 ) -> &'a str {
89 let mut counter = 0;
90 while counter < indentation {
91 if line.starts_with (' ') {
92 counter += 1;
93 line = &line[1..];
94 } else {
95 return line;
96 }
97 }
98 line
99 }
100
101fn tangle_collect (
102 result: &mut String,
103 lines: &mut Lines,
104 indentation: usize,
105) -> Result <(), TangleError> {
106 for line in lines {
107 if block_end_line_p (line) {
108 result.push ('\n');
109 return Ok (());
110 } else {
111 let line = line_trim_indentation (
112 line, indentation);
113 result.push_str (line);
114 result.push ('\n');
115 }
116 }
117 let error = TangleError::new ("block_end mismatch");
118 Err (error)
119}
120
121fn tangle (string: &str) -> Result <String, TangleError> {
122 let mut result = String::new ();
123 let mut lines = string.lines ();
124 while let Some (line) = lines.next () {
125 if block_begin_line_p (line) {
126 tangle_collect (
127 &mut result,
128 &mut lines,
129 block_indentation (line))?;
130 }
131 }
132 result.pop ();
133 Ok (result)
134}
135
136#[test]
137fn test_tangle () {
138 let example = format! (
139 "{}\n{}\n{}\n{}\n",
140 "#+begin_src rust",
141 "hi",
142 "hi",
143 "#+end_src",
144 );
145 let expect = format! (
146 "{}\n{}\n",
147 "hi",
148 "hi",
149 );
150 let result = tangle (&example) .unwrap ();
151 assert_eq! (expect, result);
152
153 let example = format! (
154 "{}\n{}\n{}\n{}\n",
155 " #+begin_src rust",
156 " hi",
157 " hi",
158 " #+end_src",
159 );
160 let expect = format! (
161 "{}\n{}\n",
162 "hi",
163 "hi",
164 );
165 let result = tangle (&example) .unwrap ();
166 assert_eq! (expect, result);
167
168 let example = format! (
169 "{}\n{}\n{}\n{}\n",
170 "#+begin_src rust",
171 " hi",
172 " hi",
173 "#+end_src",
174 );
175 let expect = format! (
176 "{}\n{}\n",
177 " hi",
178 " hi",
179 );
180 let result = tangle (&example) .unwrap ();
181 assert_eq! (expect, result);
182}
183
184fn good_path_p (path: &Path) -> bool {
185 for component in path.iter () {
186 if let Some (string) = component.to_str () {
187 if string.starts_with ('.') {
188 if ! string .chars () .all (|x| x == '.') {
189 return false;
190 }
191 }
192 } else {
193 return false;
194 }
195 }
196 true
197}
198
199pub fn org_file_p (file: &Path) -> bool {
200 if let Some (os_string) = file.extension () {
201 if let Some (string) = os_string.to_str () {
202 string == "org"
203 } else {
204 false
205 }
206 } else {
207 false
208 }
209}
210
211pub fn file_tangle (file: &Path) -> io::Result <()> {
212 if ! org_file_p (file) {
213 return Ok (());
214 }
215 let string = fs::read_to_string (file)?;
216 if let Some (destination) = find_destination (&string) {
217 let result = tangle (&string) .unwrap ();
218 let mut destination_path = PathBuf::new ();
219 destination_path.push (file);
220 destination_path.pop ();
221 destination_path.push (destination);
222 fs::write (&destination_path, result)?;
223 println! (
224 "- tangle : {:?} => {:?}",
225 file.canonicalize ()?,
226 destination_path.canonicalize ()?);
227 Ok (())
228 } else {
229 Ok (())
230 }
231}
232
233pub fn dir_tangle (dir: &Path) -> io::Result <()> {
234 for entry in dir.read_dir ()? {
235 if let Ok (entry) = entry {
236 if good_path_p (&entry.path ()) {
237 if entry.file_type ()? .is_file () {
238 file_tangle (&entry.path ())?
239 }
240 }
241 }
242 }
243 Ok (())
244}
245
246pub fn dir_tangle_rec (dir: &Path) -> io::Result <()> {
247 for entry in dir.read_dir ()? {
248 if let Ok (entry) = entry {
249 if good_path_p (&entry.path ()) {
250 if entry.file_type ()? .is_file () {
251 file_tangle (&entry.path ())?
252 } else if entry.file_type ()? .is_dir () {
253 dir_tangle_rec (&entry.path ())?
254 }
255 }
256 }
257 }
258 Ok (())
259}
260
261pub fn absolute_lize (path: &Path) -> PathBuf {
262 if path.is_relative () {
263 let mut absolute_path = env::current_dir () .unwrap ();
264 absolute_path.push (path);
265 absolute_path
266 } else {
267 path.to_path_buf ()
268 }
269}
270
271pub fn tangle_all_before_build () -> io::Result <()> {
272 let path = Path::new (".");
273 let current_dir = env::current_dir () .unwrap ();
274 println! ("- org_tangle_engine");
275 println! (" tangle_all_before_build");
276 println! (" current_dir : {:?}", current_dir);
277 let path = absolute_lize (&path);
278 dir_tangle_rec (&path)
279}