Skip to main content

tango/
lib.rs

1// #[macro_use]
2// extern crate log;
3// extern crate env_logger;
4
5extern crate filetime;
6extern crate url;
7extern crate walkdir;
8
9use filetime::set_file_times;
10use walkdir::{WalkDir};
11
12use std::convert;
13use std::error::Error as ErrorTrait;
14use std::ffi::OsStr;
15use std::fmt;
16use std::fs::{self, File};
17use std::io::{self, Read, Write};
18use std::ops;
19use std::path::{Path, PathBuf};
20use std::cell::RefCell;
21
22use self::timestamp::{Timestamp, Timestamped};
23
24pub mod timestamp;
25
26pub const STAMP: &'static str = "tango.stamp";
27//pub const SRC_DIR: &'static str = "src";
28// pnkfelix wanted the `LIT_DIR` to be `lit/`, but `cargo build`
29// currently assumes that *all* build sources live in `src/`. So it
30// is easier for now to just have the two directories be the same.
31//pub const LIT_DIR: &'static str = "src/lit";
32
33thread_local! {
34    pub static SRC_DIR: RefCell<String> = RefCell::new("src".to_string());
35    pub static LIT_DIR: RefCell<String> = RefCell::new("src".to_string());
36}
37
38fn set_lit_dir(directory: String) {
39    LIT_DIR.with(|lit_dir| {
40        *lit_dir.borrow_mut() = directory
41    });
42}
43
44fn set_src_dir(directory: String) {
45    SRC_DIR.with(|src_dir| {
46        *src_dir.borrow_mut() = directory
47    });
48}
49
50/// Returns the current directory for storing the literate .md files
51pub fn get_lit_dir() -> String {
52    LIT_DIR.with(|lit_dir| lit_dir.borrow().clone())
53}
54
55/// Returns the current directory for storing the "source" .rs files
56pub fn get_src_dir() -> String {
57    SRC_DIR.with(|src_dir| src_dir.borrow().clone())
58}
59
60pub struct Config {
61    src_dir: String,
62    lit_dir: String,
63    rerun_if: bool,
64}
65
66impl Config {
67    pub fn new() -> Config {
68        Config {
69            src_dir: String::from("src"),
70            lit_dir: String::from("src"),
71            rerun_if: false,
72        }
73    }
74    pub fn set_src_dir(&mut self, new_src_dir: String) -> &mut Config {
75        self.src_dir = new_src_dir;
76        self
77    }
78    pub fn set_lit_dir(&mut self, new_lit_dir: String) -> &mut Config {
79        self.lit_dir = new_lit_dir;
80        self
81    }
82    pub fn emit_rerun_if(&mut self) -> &mut Config {
83        self.rerun_if = true;
84        self
85    }
86
87}
88
89
90#[derive(Debug)]
91pub enum Error {
92    IoError(io::Error),
93    CheckInputError { error: check::Error },
94    MtimeError(PathBuf),
95    ConcurrentUpdate { path_buf: PathBuf, old_time: mtime, new_time: mtime },
96    Warnings(Vec<Warning>),
97}
98
99#[derive(Debug)]
100pub enum Warning {
101    EncodedUrlMismatch { actual: String, expect: String }
102}
103
104impl fmt::Display for Warning {
105    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
106        match *self {
107            Warning::EncodedUrlMismatch { ref actual, ref expect } => {
108                write!(w, "mismatch between encoded url, expect: {} actual: {}",
109                       expect, actual)
110            }
111        }
112    }
113}
114
115impl From<md2rs::Exception> for Error {
116    fn from(e: md2rs::Exception) -> Self {
117        match e {
118            md2rs::Exception::IoError(e) => Error::IoError(e),
119            md2rs::Exception::Warnings(w) => Error::Warnings(w),
120        }
121    }
122}
123
124impl fmt::Display for Error {
125    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
126        match *self {
127            Error::IoError(_) =>
128                write!(w, "IO error running `tango`"),
129            Error::CheckInputError { .. } =>
130                write!(w, "input check errors running `tango`"),
131            Error::MtimeError(ref p) =>
132                write!(w, "modification time error from `tango` checking {}",
133                       p.to_string_lossy()),
134            Error::ConcurrentUpdate { ref path_buf, .. } =>
135                write!(w, "concurrent update during `tango` to source file {}",
136                       path_buf.to_string_lossy()),
137            Error::Warnings(ref warnings) => {
138                for warn in warnings {
139                    (write!(w, "WARNING: {}", warn))?;
140                }
141                Ok(())
142            }
143        }
144    }
145}
146
147impl ErrorTrait for Error {
148    fn source(&self) -> Option<&(dyn ErrorTrait + 'static)> {
149        match *self {
150            Error::IoError(ref e) => Some(e),
151            Error::CheckInputError { ref error, .. } => {
152                Some(error)
153            }
154            Error::Warnings(_) |
155            Error::MtimeError(_) |
156            Error::ConcurrentUpdate { .. } => None,
157        }
158    }
159}
160
161impl convert::From<io::Error> for Error {
162    fn from(e: io::Error) -> Self {
163        Error::IoError(e)
164    }
165}
166
167impl convert::From<walkdir::Error> for Error {
168    fn from(e: walkdir::Error) -> Self {
169        Error::IoError(From::from(e))
170    }
171}
172
173pub type Result<X> = std::result::Result<X, Error>;
174
175#[allow(non_camel_case_types)]
176pub type mtime = Timestamp;
177
178#[derive(Copy, Clone, PartialEq, Eq, Debug)]
179enum MtimeResult {
180    NonExistant,
181    Modified(mtime),
182}
183
184trait Mtime { fn modified(&self) -> Result<MtimeResult>; }
185impl Mtime for File {
186    fn modified(&self) -> Result<MtimeResult> {
187        // #![allow(deprecated)]
188        // if let Some(p) = self.path() {
189        //     if !p.exists() {
190        //         return Err(Error::MtimeError(p.to_path_buf()));
191        //     }
192        // }
193        let m = (self.metadata())?;
194        Ok(MtimeResult::Modified(m.timestamp()))
195    }
196}
197impl Mtime for fs::DirEntry {
198    fn modified(&self) -> Result<MtimeResult> {
199        let m = (self.metadata())?;
200        Ok(MtimeResult::Modified(m.timestamp()))
201    }
202}
203impl Mtime for RsPath {
204    fn modified(&self) -> Result<MtimeResult> {
205        if self.0.exists() {
206            let f = (File::open(&self.0))?;
207            f.modified()
208        } else {
209            Ok(MtimeResult::NonExistant)
210        }
211    }
212}
213impl Mtime for MdPath {
214    fn modified(&self) -> Result<MtimeResult> {
215        if self.0.exists() {
216            let f = (File::open(&self.0))?;
217            f.modified()
218        } else {
219            Ok(MtimeResult::NonExistant)
220        }
221    }
222}
223
224pub fn process_root_with_config(config: Config) -> Result<()> {
225    //let _root = (std::env::current_dir())?;
226    //println!("Tango is running from: {:?}", root);
227    //std::env::set_current_dir(_root).unwrap();
228    set_lit_dir(config.lit_dir);
229    set_src_dir(config.src_dir);
230    let emit_rerun_if = config.rerun_if;
231
232    let stamp_path = Path::new(STAMP);
233    if stamp_path.exists() {
234        process_with_stamp((File::open(stamp_path))?, emit_rerun_if)
235    } else {
236        process_without_stamp(emit_rerun_if)
237    }
238}
239
240
241pub fn process_root() -> Result<()> {
242    //let _root = (std::env::current_dir())?;
243    // println!("Tango is running from: {:?}", _root);
244
245    let emit_rerun_if = false;
246    let stamp_path = Path::new(STAMP);
247    if stamp_path.exists() {
248        process_with_stamp((File::open(stamp_path))?, emit_rerun_if)
249    } else {
250        process_without_stamp(emit_rerun_if)
251    }
252}
253
254// Both of the functions below have the same basic outline:
255//
256// 1. gather_inputs(): Build up a list of potential transforms based
257//    on existing files.
258//
259// 2. generate_content(): Apply each transform in turn, *iff* the
260//    source is newer than target.
261//
262// 3. check_input_timestamps(): Ensure no input was concurrently
263//    modified while tango ran.
264//
265// 4. adjust_stamp_timestamp(): Update the `tango.stamp` file to the
266//    youngest timestamp we saw, creating the file if necessary.
267//
268// The reason there are two functions is that in one case we have a
269// pre-existing `tango.stamp` that we want to compare against during
270// `generate_content()` (to guard against diverging {source, target}
271// paths; *at most* one of {source, target} is meant to be updated in
272// between tango runs.
273//
274// (It probably wouldn't be hard to unify the two functions into a
275//  single method on the `Context`, though.)
276
277fn process_with_stamp(stamp: File, emit_rerun_if: bool) -> Result<()> {
278    println!("\n\nemit rerun if: {:?}\n\n", emit_rerun_if);
279    if let Ok(MtimeResult::Modified(ts)) = stamp.modified() {
280        println!("Rerunning tango; last recorded run was stamped: {}",
281                 ts.date_fulltime_badly());
282    } else {
283        panic!("why are we trying to process_with_stamp when given: {:?}", stamp);
284    }
285    let mut c = (Context::new(Some(stamp)))?;
286    c.emit_rerun_if = emit_rerun_if;
287    (c.gather_inputs())?;
288    (c.generate_content())?;
289    (c.check_input_timestamps())?;
290    (c.adjust_stamp_timestamp())?;
291    // (c.report_dir(Path::new(".")))?;
292    Ok(())
293}
294
295fn process_without_stamp(emit_rerun_if: bool) -> Result<()> {
296    println!("Running tango; no previously recorded run");
297    println!("\n\nemit rerun if: {:?}\n\n", emit_rerun_if);
298    let mut c = (Context::new(None))?;
299    c.emit_rerun_if = emit_rerun_if;
300    (c.gather_inputs())?;
301    (c.generate_content())?;
302    (c.check_input_timestamps())?;
303    (c.create_stamp())?;
304    (c.adjust_stamp_timestamp())?;
305    // (c.report_dir(Path::new(".")))?;
306    Ok(())
307}
308
309#[derive(Debug)]
310struct RsPath(PathBuf);
311#[derive(Debug)]
312struct MdPath(PathBuf);
313
314
315struct Context {
316    orig_stamp: Option<(File, mtime)>,
317    src_inputs: Vec<Transform<RsPath, MdPath>>,
318    lit_inputs: Vec<Transform<MdPath, RsPath>>,
319    newest_stamp: Option<mtime>,
320    emit_rerun_if: bool,
321}
322
323trait Extensions {
324    fn extension(&self) -> Option<&str>;
325    fn rs_extension(&self) -> bool {
326        self.extension() == Some("rs")
327    }
328    fn md_extension(&self) -> bool {
329        self.extension() == Some("md")
330    }
331}
332
333impl Extensions for Path {
334    fn extension(&self) -> Option<&str> {
335        Path::extension(self).and_then(|s|s.to_str())
336    }
337}
338
339impl ops::Deref for RsPath {
340    type Target = Path; fn deref(&self) -> &Path { &self.0 }
341}
342
343impl ops::Deref for MdPath {
344    type Target = Path; fn deref(&self) -> &Path { &self.0 }
345}
346
347fn check_path(typename: &str, p: &Path, ext: &str, root: &str) {
348    println!("\n in check_path, the root is: {r:?} , path is: {p:?}, ext is {e:?}", r=root, p=p, e=ext);
349    if Extensions::extension(p) != Some(ext) { panic!("{t} requires `.{ext}` extension; path: {p:?}", t=typename, ext=ext, p=p); }
350    if !p.starts_with(root) { panic!("{t} must be rooted at `{root}/`; path: {p:?}", t=typename, root=root, p=p); }
351}
352
353impl RsPath {
354    fn new(p: PathBuf) -> RsPath {
355        check_path("RsPath", &p, "rs", &get_src_dir());
356        RsPath(p)
357    }
358    fn to_md(&self) -> MdPath {
359        let mut p = PathBuf::new();
360        p.push(get_lit_dir());
361        for c in self.0.components().skip(1) {
362            let c: &OsStr = c.as_ref();
363            p.push(c.to_str().expect("how else can I replace root?"));
364        }
365        p.set_extension("md");
366        MdPath::new(p)
367    }
368}
369
370impl MdPath {
371    fn new(p: PathBuf) -> MdPath {
372        check_path("MdPath", &p, "md", &get_lit_dir());
373        MdPath(p)
374    }
375    fn to_rs(&self) -> RsPath {
376        let mut p = PathBuf::new();
377        p.push(get_src_dir());
378        for c in self.0.components().skip(1) {
379            let c: &OsStr = c.as_ref();
380            p.push(c.to_str().expect("how else can I replace root?"));
381        }
382        p.set_extension("rs");
383        RsPath::new(p)
384    }
385}
386
387trait Transforms: Sized + Mtime + fmt::Debug {
388    type Target: Mtime + fmt::Debug;
389
390    // Computes path to desired target based on self's (source) path.
391    fn target(&self) -> Self::Target;
392
393    // Constructs a transform for generating the target from self
394    // (which is a path to the source), gathering the current
395    // timestamps on both the source and the target.
396    fn transform(self) -> Result<Transform<Self, Self::Target>> {
397        let source_time = match self.modified() {
398            Ok(MtimeResult::Modified(t)) => t,
399            Ok(MtimeResult::NonExistant) => panic!("impossible for {:?} to be NonExistant", self),
400            Err(e) => {
401                println!("failure to extract mtime on source {:?}", self);
402                return Err(e);
403            }
404        };
405
406        let target = self.target();
407        let target_time = match target.modified() {
408            Ok(t) => t,
409            Err(e) => {
410                println!("failure to extract mtime on target {:?}", target);
411                return Err(e);
412            }
413        };
414        Ok(Transform { source_time: source_time,
415                       target_time: target_time,
416                       original: self,
417                       generate: target,
418        })
419    }
420}
421
422impl Transforms for RsPath {
423    type Target = MdPath;
424    fn target(&self) -> MdPath { self.to_md() }
425}
426
427impl Transforms for MdPath {
428    type Target = RsPath;
429    fn target(&self) -> RsPath { self.to_rs() }
430}
431
432#[derive(Debug)]
433pub struct Transform<X, Y> {
434    source_time: mtime,
435    target_time: MtimeResult,
436    original: X,
437    generate: Y,
438}
439
440pub mod check {
441    use std::error::Error as ErrorTrait;
442    use std::fmt;
443    use std::ops;
444    use std::path::{Path, PathBuf};
445    use std::result;
446    use super::Transform;
447    pub type PathTransform = Transform<PathBuf, PathBuf>;
448    #[derive(Debug)]
449    pub enum ErrorKind {
450        TargetYoungerThanOriginal { tgt: String, src: String },
451        NoTangoStampExists { tgt: String, src: String },
452        TangoStampOlderThanTarget { tgt: String },
453    }
454    #[derive(Debug)]
455    pub struct Error(ErrorKind, PathTransform);
456
457    impl fmt::Display for Error {
458        fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
459            match self.0 {
460                ErrorKind::TargetYoungerThanOriginal { ref tgt, ref src } => {
461                    write!(w, "target `{}` is younger than source `{}`; \
462                               therefore we assume target has modifications that need to be preserved.",
463                           tgt, src)
464                }
465                ErrorKind::NoTangoStampExists { ref src, ref tgt } => {
466                    write!(w, "both source `{}` and target `{}` exist but no `tango.stamp` is present",
467                           src, tgt)
468                }
469                ErrorKind::TangoStampOlderThanTarget { ref tgt } => {
470                    write!(w, "`tango.stamp` is older than target `{}`; \
471                               therefore we assume source and target have diverged since last tango run.",
472                           tgt)
473                }
474            }
475        }
476    }
477
478    impl ErrorTrait for Error {
479        fn description(&self) -> &str {
480            match self.0 {
481                ErrorKind::TargetYoungerThanOriginal { .. }=> {
482                    "target is younger than source; \
483                     therefore we assume target has modifications that need to be preserved."
484                }
485                ErrorKind::NoTangoStampExists { .. } => {
486                    "both source and target exist but no `tango.stamp` is present"
487                }
488                ErrorKind::TangoStampOlderThanTarget { .. } => {
489                    "`tango.stamp` is older than target; \
490                     therefore we assume source and target have diverged since last tango run."
491                }
492            }
493        }
494    }
495
496    pub type Result<X> = result::Result<X, Error>;
497
498    impl<X,Y> Transform<X, Y>
499        where X: ops::Deref<Target=Path>, Y: ops::Deref<Target=Path>
500    {
501        pub fn error(&self, kind: ErrorKind) -> Error {
502            let t = Transform { original: self.original.to_path_buf(),
503                                generate: self.generate.to_path_buf(),
504                                source_time: self.source_time,
505                                target_time: self.target_time,
506            };
507            Error(kind, t)
508        }
509    }
510}
511
512enum TransformNeed { Needed, Unneeded, }
513
514impl Context {
515    fn new(opt_stamp: Option<File>) -> Result<Context> {
516        let stamp_modified = match opt_stamp {
517            None => None,
518            Some(stamp) => {
519                let mtime = (stamp.modified())?;
520                let mtime = match mtime {
521                    MtimeResult::NonExistant => panic!("impossible"),
522                    MtimeResult::Modified(t) => t,
523                };
524                Some((stamp, mtime))
525            }
526        };
527        let c = Context {
528            orig_stamp: stamp_modified,
529            src_inputs: Vec::new(),
530            lit_inputs: Vec::new(),
531            newest_stamp: None,
532            emit_rerun_if: true,
533        };
534        Ok(c)
535    }
536
537    fn check_transform<X, Y>(&self, t: &Transform<X, Y>) -> check::Result<TransformNeed>
538        where X: ops::Deref<Target=Path> + Mtime,
539              Y: ops::Deref<Target=Path> + Mtime,
540    {
541
542        use self::check::ErrorKind::*;
543
544
545        let t_mod = match t.target_time {
546            MtimeResult::Modified(t) => t,
547            MtimeResult::NonExistant => {
548                assert!(!t.generate.exists());
549                return Ok(TransformNeed::Needed);
550            }
551        };
552        // let src = t.original.display().to_string();
553        // let tgt = t.generate.display().to_string();
554        let s_mod = t.source_time;
555
556        let same_age_at_low_precision = s_mod.to_ms() == t_mod.to_ms();
557
558        if t_mod > s_mod {
559            // Target is newer than source: therefore we do not want to
560            // overwrite the target via this transform.
561            return Ok(TransformNeed::Unneeded);
562        }
563
564        // Now know:  t_mod <= s_mod
565
566        if same_age_at_low_precision {
567            //        00000000011111111112222222222333333333344444444445555555555666666666677777777778
568            //        12345678901234567890123456789012345678901234567890123456789012345678901234567890
569            println!("Warning: source and target have timestamps that differ only at nanosecond level\n    \
570                          precision. Tango currently treats such timestamps as matching, and therefore\n    \
571                          will not rebuild the target file.\n\
572                          \n    \
573                          source: {SRC:?} timestamp: {SRC_TS} \n    \
574                          target: {TGT:?} timestamp: {TGT_TS}\n",
575                     SRC=t.original.display(), SRC_TS=s_mod.date_fulltime_badly(),
576                     TGT=t.generate.display(), TGT_TS=t_mod.date_fulltime_badly());
577            return Ok(TransformNeed::Unneeded);
578        }
579
580        // Now know: t_mod is older than source even after truncating
581        // to millisecond precision.
582
583        match self.orig_stamp {
584            None => return Err(t.error(NoTangoStampExists {
585                src: t.original.display().to_string(),
586                tgt: t.generate.display().to_string(),
587            })),
588            Some((_, stamp_time)) => {
589                let older_at_high_precision = stamp_time < t_mod;
590                let older_at_low_precision = stamp_time.to_ms() < t_mod.to_ms();
591                if older_at_low_precision {
592                    // The target file was updated more recently than
593                    // the tango.stamp file, even after truncation to
594                    // millisecond precision.
595                    //
596                    // Therefore, we assume that user has updated both
597                    // the source and the target independently since
598                    // the last tango run.  This is a scenario that
599                    // tango cannot currently recover from, so we
600                    // issue an error and tell the user to fix the
601                    // problem.
602                    return Err(t.error(TangoStampOlderThanTarget {
603                        tgt: t.generate.display().to_string(),
604                    }));
605                }
606                if older_at_high_precision && !older_at_low_precision {
607                    //        00000000011111111112222222222333333333344444444445555555555666666666677777777778
608                    //        12345678901234567890123456789012345678901234567890123456789012345678901234567890
609                    println!("Warning: `tango.stamp` and target `{}` have timestamps that differ only at \n\
610                                  nanosecond level precision. Tango currently treats such timestamps as,\n\
611                                  matching and will rebuild the target file rather than error",
612                             t.generate.display());
613                }
614
615                // got here: tango.stamp is not older than the target
616                // file.  So we fall through to the base case.
617            }
618        }
619
620        // Invariant:
621        // Target `t` exists, but,
622        // s_mod >= t_mod (and t_mod <= stamp_time if stamp exists).
623        //
624        // Thus it is safe to overwrite `t` based on source content.
625        Ok(TransformNeed::Needed)
626    }
627
628    #[cfg(not_now)]
629    fn report_dir(&self, p: &Path) -> Result<()> {
630        let src_dir = get_src_dir();
631        let lit_dir = get_lit_dir();
632        let src_path = Path::new(&src_dir);
633        let lit_path = Path::new(&lit_dir);
634
635        for (i, ent) in (WalkDir::new(p))?.enumerate() {
636            let ent = (ent)?;
637            let modified = (ent.modified())?;
638            println!("entry[{}]: {:?} {:?}", i, ent.path(), modified);
639        }
640        Ok(())
641    }
642
643    fn update_newest_time(&mut self, new_time: mtime) {
644        if let Some(ref mut stamp) = self.newest_stamp {
645            if new_time > *stamp {
646                *stamp = new_time;
647            }
648        } else {
649            self.newest_stamp = Some(new_time);
650        }
651    }
652
653    fn push_src(&mut self, t: Transform<RsPath, MdPath>) {
654        self.update_newest_time(t.source_time);
655        self.src_inputs.push(t);
656    }
657    fn push_lit(&mut self, t: Transform<MdPath, RsPath>) {
658        self.update_newest_time(t.source_time);
659        self.lit_inputs.push(t);
660    }
661
662    fn gather_inputs(&mut self) -> Result<()> {
663        // println!("gather_inputs");
664        let src_dir = get_src_dir();
665        let lit_dir = get_lit_dir();
666        let src_path = Path::new(&src_dir);
667        let lit_path = Path::new(&lit_dir);
668
669        fn keep_file_name(p: &Path) -> std::result::Result<(), &'static str> {
670            match p.file_name().and_then(|x|x.to_str()) {
671                None =>
672                    Err("file name is not valid unicode"),
673                Some(s) if s.starts_with('.') =>
674                    Err("file name has leading period"),
675                Some(..) =>
676                    Ok(()),
677            }
678        }
679
680        fn warn_if_nonexistant<M:Mtime+fmt::Debug>(m: &M) -> Result<()> {
681            match m.modified() {
682                Err(e) => Err(e),
683                Ok(MtimeResult::Modified(..)) => Ok(()),
684                Ok(MtimeResult::NonExistant) => {
685                    // This can arise; namely some tools are
686                    // generating symlinks in `src` of the form
687                    //
688                    // `src/.#lib.md -> fklock@fklock-Oenone.local.96195`
689                    //
690                    // where the target is non-existant (presumably as
691                    // a way to locally mark a file as being open by
692                    // the tool?), and then this script interprets it
693                    // as being open.
694                    println!("warning: non-existant source: {:?}", m);
695                    Ok(())
696                }
697            }
698
699        }
700
701        // This loop gathers all of the .rs files that currently
702        // exist, and schedules transforms that would turn them into
703        // corresponding target .md files.
704
705        // println!("gather-rs");
706        for ent in WalkDir::new(src_path).into_iter() {
707            let ent = (ent)?;
708            let p = ent.path();
709            if let Err(why) = keep_file_name(p) {
710                println!("skipping {}; {}", p.display(), why);
711                continue;
712            }
713            if !p.rs_extension() {
714                // println!("gather-rs skip {} due to non .rs", p.display());
715                continue;
716            }
717            let rs = RsPath::new(p.to_path_buf());
718            (warn_if_nonexistant(&rs))?;
719
720            if self.emit_rerun_if {
721                println!("cargo:rerun-if-changed={}", &rs.display());
722            }
723
724            let t = (rs.transform())?;
725            match self.check_transform(&t) {
726                Ok(TransformNeed::Needed) => self.push_src(t),
727                Ok(TransformNeed::Unneeded) => {}
728                Err(e) => {
729                    println!("gather_inputs err: {}", e);
730                    return Err(Error::CheckInputError {
731                        error: e,
732                    })
733                }
734            }
735        }
736
737        // This loop gathers all of the .md files that currently
738        // exist, and schedules transforms that would turn them into
739        // corresponding target .rs files.
740
741        //println!("gather-md, lit_path is: {:?}", lit_path);
742        for ent in WalkDir::new(lit_path).into_iter() {
743            //println!("ent is {:?}", ent);
744            let ent = (ent)?;
745            let p = ent.path();
746            if let Err(why) = keep_file_name(p) {
747                println!("skipping {}; {}", p.display(), why);
748                continue;
749            }
750            if !p.md_extension() {
751                // println!("gather-md skip {} due to non .md", p.display());
752                continue;
753            }
754            let md = MdPath::new(p.to_path_buf());
755            (warn_if_nonexistant(&md))?;
756
757            if self.emit_rerun_if {
758                println!("cargo:rerun-if-changed={}", &md.display());
759            }
760
761            let t = (md.transform())?;
762            match self.check_transform(&t) {
763                Ok(TransformNeed::Needed) => {
764                    // println!("gather-md add {:?}", t);;
765                    self.push_lit(t)
766                }
767                Ok(TransformNeed::Unneeded) => {
768                    // println!("gather-md discard unneeded {:?}", t);;
769                }
770                Err(e) => {
771                    println!("gather_inputs err: {}", e);
772                    return Err(Error::CheckInputError {
773                        error: e,
774                    })
775                }
776            }
777        }
778
779        // At this point we've scheduled all the transforms we want to
780        // run; they will be applied unconditionally, even if both
781        // source and target exist. (The intent is that a target
782        // younger than source would have been filtered during the
783        // .check_transform calls above.)
784
785        Ok(())
786    }
787    fn generate_content(&mut self) -> Result<()> {
788        for &Transform { ref original, ref generate, source_time, .. } in &self.src_inputs {
789            let source = (File::open(&original.0))?;
790            let target = (File::create(&generate.0))?;
791            assert!(source_time > 0);
792            println!("generating lit {:?}", &generate.0);
793            (rs2md(source, target))?;
794            let timestamp = source_time.to_filetime();
795            println!("backdating lit {:?} to {}", &generate.0, source_time.date_fulltime_badly());
796            (set_file_times(&generate.0, timestamp, timestamp))?;
797        }
798        for &mut Transform { ref original, ref generate, ref mut source_time, .. } in &mut self.lit_inputs {
799            let source = (File::open(&original.0))?;
800            let target = (File::create(&generate.0))?;
801            assert!(*source_time > 0);
802            println!("generating src {:?}", &generate.0);
803            (md2rs(source, target))?;
804            println!("backdating src {:?} to {}", &generate.0, source_time.date_fulltime_badly());
805            (set_file_times(&generate.0,
806                                source_time.to_filetime(),
807                                source_time.to_filetime()))?;
808            let source = (File::open(&original.0))?;
809            let target = (File::open(&generate.0))?;
810            match (source.modified(), target.modified()) {
811                (Ok(MtimeResult::Modified(src_time)),
812                 Ok(MtimeResult::Modified(tgt_time))) => {
813                    // At this point, we would *like* to assert this:
814                    #[cfg(not_possible_right_now)] assert_eq!(src_time, tgt_time);
815                    // but it does not work, due to this bug:
816                    // https://github.com/alexcrichton/filetime/issues/9
817
818                    assert_eq!(src_time.to_ms(), tgt_time.to_ms());
819                }
820                (Ok(MtimeResult::NonExistant), _) => panic!("how could source not exist"),
821                (_, Ok(MtimeResult::NonExistant)) => panic!("how could target not exist"),
822                (Err(_), Err(_)) => panic!("errored looking up both source and target times"),
823                (Err(_), _) => panic!("errored looking up source time"),
824                (_, Err(_)) => panic!("errored looking up target time"),
825            }
826        }
827        Ok(())
828    }
829    fn check_input_timestamps(&mut self) -> Result<()> {
830        for &Transform { ref original, source_time, .. } in &self.src_inputs {
831            if let MtimeResult::Modified(new_time) = (original.modified())? {
832                if new_time != source_time {
833                    return Err(Error::ConcurrentUpdate {
834                        path_buf: original.to_path_buf(),
835                        old_time: source_time,
836                        new_time: new_time,
837                    })
838                }
839            }
840        }
841        for &Transform { ref original, source_time, .. } in &self.lit_inputs {
842            if let MtimeResult::Modified(new_time) = (original.modified())? {
843                if new_time != source_time {
844                    return Err(Error::ConcurrentUpdate {
845                        path_buf: original.to_path_buf(),
846                        old_time: source_time,
847                        new_time: new_time,
848                    })
849                }
850            }
851        }
852        Ok(())
853    }
854    fn create_stamp(&mut self) -> Result<()> {
855        let _f = (File::create(STAMP))?;
856        Ok(())
857    }
858    fn adjust_stamp_timestamp(&mut self) -> Result<()> {
859        if let Some(stamp) = self.newest_stamp {
860            assert!(stamp > 0);
861            println!("re-stamping tango.stamp to {}", stamp.date_fulltime_badly());
862
863            match set_file_times(STAMP, stamp.to_filetime(), stamp.to_filetime()) {
864                Ok(()) => Ok(()),
865                Err(e) => Err(Error::IoError(e)),
866            }
867        } else {
868            Ok(())
869        }
870    }
871}
872
873fn rs2md<R:Read, W:Write>(source: R, target: W) -> Result<()> {
874    let mut converter = rs2md::Converter::new();
875    converter.convert(source, target).map_err(Error::IoError)
876}
877
878fn md2rs<R:Read, W:Write>(source: R, target: W) -> Result<()> {
879    let converter = md2rs::Converter::new();
880    converter.convert(source, target).map_err(From::from)
881}
882
883mod md2rs;
884
885mod rs2md;
886
887fn encode_to_url(code: &str) -> String {
888    use url::percent_encoding as enc;
889    // let new_code: String = enc::utf8_percent_encode(code.trim(), enc::QUERY_ENCODE_SET);
890    let new_code: String = enc::utf8_percent_encode(code.trim(), enc::USERINFO_ENCODE_SET).collect();
891    format!("https://play.rust-lang.org/?code={}&version=nightly", new_code)
892}
893
894#[cfg(test)]
895mod testing;