1extern 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";
27thread_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
50pub fn get_lit_dir() -> String {
52 LIT_DIR.with(|lit_dir| lit_dir.borrow().clone())
53}
54
55pub 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 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 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 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
254fn 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 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 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 fn target(&self) -> Self::Target;
392
393 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 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 return Ok(TransformNeed::Unneeded);
562 }
563
564 if same_age_at_low_precision {
567 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 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 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 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 }
618 }
619
620 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 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 println!("warning: non-existant source: {:?}", m);
695 Ok(())
696 }
697 }
698
699 }
700
701 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 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 for ent in WalkDir::new(lit_path).into_iter() {
743 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 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 self.push_lit(t)
766 }
767 Ok(TransformNeed::Unneeded) => {
768 }
770 Err(e) => {
771 println!("gather_inputs err: {}", e);
772 return Err(Error::CheckInputError {
773 error: e,
774 })
775 }
776 }
777 }
778
779 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 #[cfg(not_possible_right_now)] assert_eq!(src_time, tgt_time);
815 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::USERINFO_ENCODE_SET).collect();
891 format!("https://play.rust-lang.org/?code={}&version=nightly", new_code)
892}
893
894#[cfg(test)]
895mod testing;