mirror of https://github.com/rust-lang/book
parent
f9d5ba4b8d
commit
b9d3dad120
|
@ -0,0 +1,218 @@
|
|||
<?xml version="1.0"?>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml">
|
||||
<xsl:output method="text" />
|
||||
<xsl:template match="/">
|
||||
<xsl:apply-templates select="/w:document/w:body/*" />
|
||||
</xsl:template>
|
||||
|
||||
<!-- Ignore these -->
|
||||
<xsl:template match="w:p[starts-with(w:pPr/w:pStyle/@w:val, 'TOC')]" />
|
||||
<xsl:template match="w:p[starts-with(w:pPr/w:pStyle/@w:val, 'Contents1')]" />
|
||||
<xsl:template match="w:p[starts-with(w:pPr/w:pStyle/@w:val, 'Contents2')]" />
|
||||
<xsl:template match="w:p[starts-with(w:pPr/w:pStyle/@w:val, 'Contents3')]" />
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'ChapterStart']" />
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'Normal']" />
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'Standard']" />
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'AuthorQuery']" />
|
||||
|
||||
<!-- Paragraph styles -->
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'ChapterTitle']">
|
||||
<xsl:text> [TOC] </xsl:text>
|
||||
<xsl:text># </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'HeadA']">
|
||||
<xsl:text>## </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'HeadB']">
|
||||
<xsl:text>### </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'HeadC']">
|
||||
<xsl:text>#### </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'HeadBox']">
|
||||
<xsl:text>### </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'NumListA' or @w:val = 'NumListB']]">
|
||||
<xsl:text>1. </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'NumListC']]">
|
||||
<xsl:text>1. </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'BulletA' or @w:val = 'BulletB' or @w:val = 'ListPlainA' or @w:val = 'ListPlainB']]">
|
||||
<xsl:text>* </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'BulletC' or @w:val = 'ListPlainC']]">
|
||||
<xsl:text>* </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'SubBullet']]">
|
||||
<xsl:text> * </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'BodyFirst' or @w:val = 'Body' or @w:val = 'BodyFirstBox' or @w:val = 'BodyBox' or @w:val = '1stPara']]">
|
||||
<xsl:if test=".//w:t">
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'CodeA' or @w:val = 'CodeAWingding']]">
|
||||
<xsl:text>``` </xsl:text>
|
||||
<!-- Don't apply Emphasis/etc templates in code blocks -->
|
||||
<xsl:for-each select="w:r">
|
||||
<xsl:value-of select="w:t" />
|
||||
</xsl:for-each>
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'CodeB' or @w:val = 'CodeBWingding']]">
|
||||
<!-- Don't apply Emphasis/etc templates in code blocks -->
|
||||
<xsl:for-each select="w:r">
|
||||
<xsl:value-of select="w:t" />
|
||||
</xsl:for-each>
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'CodeC' or @w:val = 'CodeCWingding']]">
|
||||
<!-- Don't apply Emphasis/etc templates in code blocks -->
|
||||
<xsl:for-each select="w:r">
|
||||
<xsl:value-of select="w:t" />
|
||||
</xsl:for-each>
|
||||
<xsl:text> ``` </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'CodeSingle']">
|
||||
<xsl:text>``` </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> ``` </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'ProductionDirective']">
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'Caption' or @w:val = 'TableTitle' or @w:val = 'Caption1' or @w:val = 'Listing']]">
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'BlockQuote']]">
|
||||
<xsl:text>> </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle[@w:val = 'BlockText']]">
|
||||
<xsl:text> </xsl:text>
|
||||
<xsl:text>> </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p[w:pPr/w:pStyle/@w:val = 'Note']">
|
||||
<xsl:text>> </xsl:text>
|
||||
<xsl:apply-templates select="*" />
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:p">
|
||||
Unmatched: <xsl:value-of select="w:pPr/w:pStyle/@w:val" />
|
||||
<xsl:text>
|
||||
</xsl:text>
|
||||
|
||||
|
||||
</xsl:template>
|
||||
|
||||
<!-- Character styles -->
|
||||
|
||||
<xsl:template match="w:r[w:rPr/w:rStyle[@w:val = 'Literal' or @w:val = 'LiteralBold' or @w:val = 'LiteralCaption' or @w:val = 'LiteralBox']]">
|
||||
<xsl:choose>
|
||||
<xsl:when test="normalize-space(w:t) != ''">
|
||||
<xsl:if test="starts-with(w:t, ' ')">
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:text>`</xsl:text>
|
||||
<xsl:value-of select="normalize-space(w:t)" />
|
||||
<xsl:text>`</xsl:text>
|
||||
<xsl:if test="substring(w:t, string-length(w:t)) = ' '">
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:when>
|
||||
<xsl:when test="normalize-space(w:t) != w:t and w:t != ''">
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:when>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:r[w:rPr/w:rStyle[@w:val = 'EmphasisBold']]">
|
||||
<xsl:choose>
|
||||
<xsl:when test="normalize-space(w:t) != ''">
|
||||
<xsl:if test="starts-with(w:t, ' ')">
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:text>**</xsl:text>
|
||||
<xsl:value-of select="normalize-space(w:t)" />
|
||||
<xsl:text>**</xsl:text>
|
||||
<xsl:if test="substring(w:t, string-length(w:t)) = ' '">
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:when>
|
||||
<xsl:when test="normalize-space(w:t) != w:t and w:t != ''">
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:when>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:r[w:rPr/w:rStyle[@w:val = 'EmphasisItalic' or @w:val = 'EmphasisItalicBox' or @w:val = 'EmphasisNote' or @w:val = 'EmphasisRevCaption' or @w:val = 'EmphasisRevItal']]">
|
||||
<xsl:choose>
|
||||
<xsl:when test="normalize-space(w:t) != ''">
|
||||
<xsl:if test="starts-with(w:t, ' ')">
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:if>
|
||||
<xsl:text>*</xsl:text>
|
||||
<xsl:value-of select="normalize-space(w:t)" />
|
||||
<xsl:text>*</xsl:text>
|
||||
<xsl:if test="substring(w:t, string-length(w:t)) = ' '">
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="w:r">
|
||||
<xsl:value-of select="w:t" />
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#[macro_use] extern crate lazy_static;
|
||||
extern crate regex;
|
||||
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::process::exit;
|
||||
use std::fs::{create_dir, read_dir, File};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
static PATTERNS: &'static [(&'static str, &'static str)] = &[
|
||||
(r"ch(\d\d)-\d\d-.*\.md", "chapter$1.md"),
|
||||
(r"appendix-(\d\d).*\.md", "appendix.md"),
|
||||
];
|
||||
|
||||
lazy_static! {
|
||||
static ref MATCHERS: Vec<(Regex, &'static str)> = {
|
||||
PATTERNS.iter()
|
||||
.map(|&(expr, repl)| (Regex::new(expr).unwrap(), repl))
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 3 {
|
||||
println!("Usage: {} <src-dir> <target-dir>", args[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let source_dir = ensure_dir_exists(&args[1]).unwrap();
|
||||
let target_dir = ensure_dir_exists(&args[2]).unwrap();
|
||||
|
||||
let mut matched_files = match_files(source_dir, target_dir);
|
||||
matched_files.sort();
|
||||
|
||||
for (target_path, source_paths) in group_by_target(matched_files) {
|
||||
concat_files(source_paths, target_path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn match_files(source_dir: &Path, target_dir: &Path) -> Vec<(PathBuf, PathBuf)> {
|
||||
read_dir(source_dir)
|
||||
.expect("Unable to read source directory")
|
||||
.filter_map(|maybe_entry| maybe_entry.ok())
|
||||
.filter_map(|entry| {
|
||||
let source_filename = entry.file_name();
|
||||
let source_filename = &source_filename.to_string_lossy().into_owned();
|
||||
for &(ref regex, replacement) in MATCHERS.iter() {
|
||||
if regex.is_match(source_filename) {
|
||||
let target_filename = regex.replace_all(source_filename, replacement);
|
||||
let source_path = entry.path();
|
||||
let mut target_path = PathBuf::from(&target_dir);
|
||||
target_path.push(target_filename);
|
||||
return Some((source_path, target_path));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn group_by_target(matched_files: Vec<(PathBuf, PathBuf)>) -> BTreeMap<PathBuf, Vec<PathBuf>> {
|
||||
let mut grouped: BTreeMap<PathBuf, Vec<PathBuf>> = BTreeMap::new();
|
||||
for (source, target) in matched_files {
|
||||
if let Some(source_paths) = grouped.get_mut(&target) {
|
||||
source_paths.push(source);
|
||||
continue;
|
||||
}
|
||||
let source_paths = vec![source];
|
||||
grouped.insert(target.clone(), source_paths);
|
||||
}
|
||||
grouped
|
||||
}
|
||||
|
||||
fn concat_files(source_paths: Vec<PathBuf>, target_path: PathBuf) -> io::Result<()> {
|
||||
println!("Concatenating into {}:", target_path.to_string_lossy());
|
||||
let mut target = try!(File::create(target_path));
|
||||
try!(target.write_all(b"\n[TOC]\n"));
|
||||
|
||||
for path in source_paths {
|
||||
println!(" {}", path.to_string_lossy());
|
||||
let mut source = try!(File::open(path));
|
||||
let mut contents: Vec<u8> = Vec::new();
|
||||
try!(source.read_to_end(&mut contents));
|
||||
|
||||
try!(target.write_all(b"\n"));
|
||||
try!(target.write_all(&contents));
|
||||
try!(target.write_all(b"\n"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_dir_exists(dir_string: &str) -> io::Result<&Path> {
|
||||
let path = Path::new(dir_string);
|
||||
if !path.exists() {
|
||||
try!(create_dir(path));
|
||||
}
|
||||
Ok(&path)
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
fn main() {
|
||||
let mut is_in_code_block = false;
|
||||
let mut is_in_inline_code = false;
|
||||
let mut is_in_html_tag = false;
|
||||
|
||||
let mut buffer = String::new();
|
||||
if let Err(e) = io::stdin().read_to_string(&mut buffer) {
|
||||
panic!(e);
|
||||
}
|
||||
|
||||
for line in buffer.lines() {
|
||||
|
||||
if line.is_empty() {
|
||||
is_in_inline_code = false;
|
||||
}
|
||||
if line.starts_with("```") {
|
||||
is_in_code_block = !is_in_code_block;
|
||||
}
|
||||
if is_in_code_block {
|
||||
is_in_inline_code = false;
|
||||
is_in_html_tag = false;
|
||||
write!(io::stdout(), "{}\n", line).unwrap();
|
||||
} else {
|
||||
let mut modified_line = &mut String::new();
|
||||
let mut previous_char = std::char::REPLACEMENT_CHARACTER;
|
||||
let mut chars_in_line = line.chars();
|
||||
|
||||
while let Some(possible_match) = chars_in_line.next() {
|
||||
// check if inside inline code
|
||||
if possible_match == '`' {
|
||||
is_in_inline_code = !is_in_inline_code;
|
||||
}
|
||||
// check if inside html tag
|
||||
if possible_match == '<' && !is_in_inline_code {
|
||||
is_in_html_tag = true;
|
||||
}
|
||||
if possible_match == '>' && !is_in_inline_code {
|
||||
is_in_html_tag = false;
|
||||
}
|
||||
|
||||
// replace with right/left apostrophe/quote
|
||||
let char_to_push =
|
||||
if possible_match == '\'' && !is_in_inline_code && !is_in_html_tag {
|
||||
if (previous_char != std::char::REPLACEMENT_CHARACTER &&
|
||||
!previous_char.is_whitespace()) ||
|
||||
previous_char == '‘'
|
||||
{
|
||||
'’'
|
||||
} else {
|
||||
'‘'
|
||||
}
|
||||
} else if possible_match == '"' && !is_in_inline_code && !is_in_html_tag {
|
||||
if (previous_char != std::char::REPLACEMENT_CHARACTER &&
|
||||
!previous_char.is_whitespace()) ||
|
||||
previous_char == '“'
|
||||
{
|
||||
'”'
|
||||
} else {
|
||||
'“'
|
||||
}
|
||||
} else {
|
||||
// leave untouched
|
||||
possible_match
|
||||
};
|
||||
modified_line.push(char_to_push);
|
||||
previous_char = char_to_push;
|
||||
}
|
||||
write!(io::stdout(), "{}\n", modified_line).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// We have some long regex literals, so:
|
||||
// ignore-tidy-linelength
|
||||
|
||||
extern crate rustc_serialize;
|
||||
extern crate docopt;
|
||||
use docopt::Docopt;
|
||||
extern crate walkdir;
|
||||
use std::{path, fs, io};
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
macro_rules! println_stderr(
|
||||
($($arg:tt)*) => (
|
||||
match writeln!(&mut ::std::io::stderr(), $($arg)* ) {
|
||||
Ok(_) => {},
|
||||
Err(x) => panic!("Unable to write to stderr: {}", x),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
fn main () {
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| d.decode())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
|
||||
let src_dir = &path::Path::new(&args.arg_src_dir);
|
||||
let found_errs = walkdir::WalkDir::new(src_dir)
|
||||
.min_depth(1)
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => {
|
||||
println_stderr!("{:?}", err);
|
||||
std::process::exit(911)
|
||||
},
|
||||
}
|
||||
})
|
||||
.map(|entry| {
|
||||
let path = entry.path();
|
||||
if is_file_of_interest(path) {
|
||||
let err_vec = lint_file(path);
|
||||
for err in &err_vec {
|
||||
match *err {
|
||||
LintingError::LineOfInterest(line_num, ref line) =>
|
||||
println_stderr!("{}:{}\t{}", path.display(), line_num, line),
|
||||
LintingError::UnableToOpenFile =>
|
||||
println_stderr!("Unable to open {}.", path.display()),
|
||||
}
|
||||
}
|
||||
!err_vec.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.iter()
|
||||
.any(|result| *result);
|
||||
|
||||
if found_errs {
|
||||
std::process::exit(1)
|
||||
} else {
|
||||
std::process::exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
const USAGE: &'static str = "
|
||||
counter
|
||||
Usage:
|
||||
lfp <src-dir>
|
||||
lfp (-h | --help)
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
";
|
||||
|
||||
#[derive(Debug, RustcDecodable)]
|
||||
struct Args {
|
||||
arg_src_dir: String,
|
||||
}
|
||||
|
||||
fn lint_file(path: &path::Path) -> Vec<LintingError> {
|
||||
match fs::File::open(path) {
|
||||
Ok(file) => lint_lines(io::BufReader::new(&file).lines()),
|
||||
Err(_) => vec![LintingError::UnableToOpenFile],
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_lines<I>(lines: I) -> Vec<LintingError>
|
||||
where I: Iterator<Item=io::Result<String>> {
|
||||
lines
|
||||
.enumerate()
|
||||
.map(|(line_num, line)| {
|
||||
let raw_line = line.unwrap();
|
||||
if is_line_of_interest(&raw_line) {
|
||||
Err(LintingError::LineOfInterest(line_num, raw_line))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.filter(|result| result.is_err())
|
||||
.map(|result| result.unwrap_err())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn is_file_of_interest(path: &path::Path) -> bool {
|
||||
path.extension()
|
||||
.map_or(false, |ext| ext == "md")
|
||||
}
|
||||
|
||||
fn is_line_of_interest(line: &str) -> bool {
|
||||
!line.split_whitespace()
|
||||
.filter(|sub_string|
|
||||
sub_string.contains("file://") &&
|
||||
!sub_string.contains("file:///projects/")
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
.is_empty()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LintingError {
|
||||
UnableToOpenFile,
|
||||
LineOfInterest(usize, String)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::path;
|
||||
|
||||
#[test]
|
||||
fn lint_file_returns_a_vec_with_errs_when_lines_of_interest_are_found() {
|
||||
let string = r#"
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
|
||||
Running `target/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 61
|
||||
Please input your guess.
|
||||
10
|
||||
You guessed: 10
|
||||
Too small!
|
||||
Please input your guess.
|
||||
99
|
||||
You guessed: 99
|
||||
Too big!
|
||||
Please input your guess.
|
||||
foo
|
||||
Please input your guess.
|
||||
61
|
||||
You guessed: 61
|
||||
You win!
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
|
||||
Running `target/debug/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 7
|
||||
Please input your guess.
|
||||
4
|
||||
You guessed: 4
|
||||
$ cargo run
|
||||
Running `target/debug/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 83
|
||||
Please input your guess.
|
||||
5
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
|
||||
Running `target/debug/guessing_game`
|
||||
Hello, world!
|
||||
"#;
|
||||
|
||||
let raw_lines = string.to_string();
|
||||
let lines = raw_lines.lines().map(|line| {
|
||||
Ok(line.to_string())
|
||||
});
|
||||
|
||||
let result_vec = super::lint_lines(lines);
|
||||
|
||||
assert!(!result_vec.is_empty());
|
||||
assert_eq!(3, result_vec.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lint_file_returns_an_empty_vec_when_no_lines_of_interest_are_found() {
|
||||
let string = r#"
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Running `target/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 61
|
||||
Please input your guess.
|
||||
10
|
||||
You guessed: 10
|
||||
Too small!
|
||||
Please input your guess.
|
||||
99
|
||||
You guessed: 99
|
||||
Too big!
|
||||
Please input your guess.
|
||||
foo
|
||||
Please input your guess.
|
||||
61
|
||||
You guessed: 61
|
||||
You win!
|
||||
"#;
|
||||
|
||||
let raw_lines = string.to_string();
|
||||
let lines = raw_lines.lines().map(|line| {
|
||||
Ok(line.to_string())
|
||||
});
|
||||
|
||||
let result_vec = super::lint_lines(lines);
|
||||
|
||||
assert!(result_vec.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_file_of_interest_returns_false_when_the_path_is_a_directory() {
|
||||
let uninteresting_fn = "src/img";
|
||||
|
||||
assert!(!super::is_file_of_interest(path::Path::new(uninteresting_fn)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_file_of_interest_returns_false_when_the_filename_does_not_have_the_md_extension() {
|
||||
let uninteresting_fn = "src/img/foo1.png";
|
||||
|
||||
assert!(!super::is_file_of_interest(path::Path::new(uninteresting_fn)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_file_of_interest_returns_true_when_the_filename_has_the_md_extension() {
|
||||
let interesting_fn = "src/ch01-00-introduction.md";
|
||||
|
||||
assert!(super::is_file_of_interest(path::Path::new(interesting_fn)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_line_of_interest_does_not_report_a_line_if_the_line_contains_a_file_url_which_is_directly_followed_by_the_project_path() {
|
||||
let sample_line = "Compiling guessing_game v0.1.0 (file:///projects/guessing_game)";
|
||||
|
||||
assert!(!super::is_line_of_interest(sample_line));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_line_of_interest_reports_a_line_if_the_line_contains_a_file_url_which_is_not_directly_followed_by_the_project_path() {
|
||||
let sample_line = "Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)";
|
||||
|
||||
assert!(super::is_line_of_interest(sample_line));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,421 @@
|
|||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
|
||||
// FIXME: We have some long lines that could be refactored, but it's not a big deal.
|
||||
// ignore-tidy-linelength
|
||||
|
||||
extern crate regex;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use regex::{Regex, Captures};
|
||||
|
||||
fn main() {
|
||||
|
||||
write_md(parse_links(parse_references(read_md())));
|
||||
}
|
||||
|
||||
fn read_md() -> String {
|
||||
let mut buffer = String::new();
|
||||
match io::stdin().read_to_string(&mut buffer) {
|
||||
Ok(_) => buffer,
|
||||
Err(error) => panic!(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_md(output: String) {
|
||||
write!(io::stdout(), "{}", output).unwrap();
|
||||
}
|
||||
|
||||
fn parse_references(buffer: String) -> (String, HashMap<String, String>) {
|
||||
let mut ref_map = HashMap::new();
|
||||
// FIXME: Currently doesn't handle "title" in following line
|
||||
let re = Regex::new(r###"(?m)\n?^ {0,3}\[([^]]+)\]:[[:blank:]]*(.*)$"###).unwrap();
|
||||
let output = re.replace_all(&buffer, |caps: &Captures| {
|
||||
let key = caps.at(1).unwrap().to_owned().to_uppercase();
|
||||
let val = caps.at(2).unwrap().to_owned();
|
||||
if ref_map.insert(key, val).is_some() {
|
||||
panic!("Did not expect markdown page to have duplicate reference");
|
||||
}
|
||||
"".to_string()
|
||||
});
|
||||
(output, ref_map)
|
||||
}
|
||||
|
||||
fn parse_links((buffer, ref_map): (String, HashMap<String, String>)) -> String {
|
||||
// FIXME: check which punctuation is allowed by spec
|
||||
let re = Regex::new(r###"(?:(?P<pre>(?:```(?:[^`]|`[^`])*`?\n```\n)|(?:[^[]`[^`\n]+[\n]?[^`\n]*`))|(?:\[(?P<name>[^]]+)\](?:(?:\([[:blank:]]*(?P<val>[^")]*[^ ])(?:[[:blank:]]*"[^"]*")?\))|(?:\[(?P<key>[^]]*)\]))?))"###).expect("could not create regex");
|
||||
let error_code = Regex::new(r###"^E\d{4}$"###).expect("could not create regex");
|
||||
let output = re.replace_all(&buffer, |caps: &Captures| {
|
||||
match caps.name("pre") {
|
||||
Some(pre_section) => format!("{}", pre_section.to_owned()),
|
||||
None => {
|
||||
let name = caps.name("name").expect("could not get name").to_owned();
|
||||
// Really we should ignore text inside code blocks,
|
||||
// this is a hack to not try to treat `#[derive()]`,
|
||||
// `[profile]`, `[test]`, or `[E\d\d\d\d]` like a link
|
||||
if name.starts_with("derive(") ||
|
||||
name.starts_with("profile") ||
|
||||
name.starts_with("test") ||
|
||||
error_code.is_match(&name) {
|
||||
return name
|
||||
}
|
||||
|
||||
let val = match caps.name("val") {
|
||||
// [name](link)
|
||||
Some(value) => value.to_owned(),
|
||||
None => {
|
||||
match caps.name("key") {
|
||||
Some(key) => {
|
||||
match key {
|
||||
// [name][]
|
||||
"" => format!("{}", ref_map.get(&name.to_uppercase()).expect(&format!("could not find url for the link text `{}`", name))),
|
||||
// [name][reference]
|
||||
_ => format!("{}", ref_map.get(&key.to_uppercase()).expect(&format!("could not find url for the link text `{}`", key))),
|
||||
}
|
||||
}
|
||||
// [name] as reference
|
||||
None => format!("{}", ref_map.get(&name.to_uppercase()).expect(&format!("could not find url for the link text `{}`", name))),
|
||||
}
|
||||
}
|
||||
};
|
||||
format!("{} at *{}*", name, val)
|
||||
}
|
||||
}
|
||||
});
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn parse(source: String) -> String {
|
||||
super::parse_links(super::parse_references(source))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_inline_link() {
|
||||
let source = r"This is a [link](http://google.com) that should be expanded".to_string();
|
||||
let target = r"This is a link at *http://google.com* that should be expanded".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_multiline_links() {
|
||||
let source = r"This is a [link](http://google.com) that
|
||||
should appear expanded. Another [location](/here/) and [another](http://gogogo)"
|
||||
.to_string();
|
||||
let target = r"This is a link at *http://google.com* that
|
||||
should appear expanded. Another location at */here/* and another at *http://gogogo*"
|
||||
.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_reference() {
|
||||
let source = r"This is a [link][theref].
|
||||
[theref]: http://example.com/foo
|
||||
more text"
|
||||
.to_string();
|
||||
let target = r"This is a link at *http://example.com/foo*.
|
||||
more text"
|
||||
.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_implicit_link() {
|
||||
let source = r"This is an [implicit][] link.
|
||||
[implicit]: /The Link/"
|
||||
.to_string();
|
||||
let target = r"This is an implicit at */The Link/* link.".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn parses_refs_with_one_space_indentation() {
|
||||
let source = r"This is a [link][ref]
|
||||
[ref]: The link"
|
||||
.to_string();
|
||||
let target = r"This is a link at *The link*".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_refs_with_two_space_indentation() {
|
||||
let source = r"This is a [link][ref]
|
||||
[ref]: The link"
|
||||
.to_string();
|
||||
let target = r"This is a link at *The link*".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_refs_with_three_space_indentation() {
|
||||
let source = r"This is a [link][ref]
|
||||
[ref]: The link"
|
||||
.to_string();
|
||||
let target = r"This is a link at *The link*".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn rejects_refs_with_four_space_indentation() {
|
||||
let source = r"This is a [link][ref]
|
||||
[ref]: The link"
|
||||
.to_string();
|
||||
let target = r"This is a link at *The link*".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_optional_inline_title() {
|
||||
let source = r###"This is a titled [link](http://example.com "My title")."###.to_string();
|
||||
let target = r"This is a titled link at *http://example.com*.".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_title_with_puctuation() {
|
||||
let source = r###"[link](http://example.com "It's Title")"###.to_string();
|
||||
let target = r"link at *http://example.com*".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_name_with_punctuation() {
|
||||
let source = r###"[I'm here](there)"###.to_string();
|
||||
let target = r###"I'm here at *there*"###.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn parses_name_with_utf8() {
|
||||
let source = r###"[user’s forum](the user’s forum)"###.to_string();
|
||||
let target = r###"user’s forum at *the user’s forum*"###.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn parses_reference_with_punctuation() {
|
||||
let source = r###"[link][the ref-ref]
|
||||
[the ref-ref]:http://example.com/ref-ref"###
|
||||
.to_string();
|
||||
let target = r###"link at *http://example.com/ref-ref*"###.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_reference_case_insensitively() {
|
||||
let source = r"[link][Ref]
|
||||
[ref]: The reference"
|
||||
.to_string();
|
||||
let target = r"link at *The reference*".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn parses_link_as_reference_when_reference_is_empty() {
|
||||
let source = r"[link as reference][]
|
||||
[link as reference]: the actual reference"
|
||||
.to_string();
|
||||
let target = r"link as reference at *the actual reference*".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_link_without_reference_as_reference() {
|
||||
let source = r"[link] is alone
|
||||
[link]: The contents"
|
||||
.to_string();
|
||||
let target = r"link at *The contents* is alone".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn parses_link_without_reference_as_reference_with_asterisks() {
|
||||
let source = r"*[link]* is alone
|
||||
[link]: The contents"
|
||||
.to_string();
|
||||
let target = r"*link* at *The contents* is alone".to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn ignores_links_in_pre_sections() {
|
||||
let source = r###"```toml
|
||||
[package]
|
||||
name = "hello_cargo"
|
||||
version = "0.1.0"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[dependencies]
|
||||
```
|
||||
"###
|
||||
.to_string();
|
||||
let target = source.clone();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_links_in_quoted_sections() {
|
||||
let source = r###"do not change `[package]`."###.to_string();
|
||||
let target = source.clone();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn ignores_links_in_quoted_sections_containing_newlines() {
|
||||
let source = r"do not change `this [package]
|
||||
is still here` [link](ref)"
|
||||
.to_string();
|
||||
let target = r"do not change `this [package]
|
||||
is still here` link at *ref*"
|
||||
.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_links_in_pre_sections_while_still_handling_links() {
|
||||
let source = r###"```toml
|
||||
[package]
|
||||
name = "hello_cargo"
|
||||
version = "0.1.0"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[dependencies]
|
||||
```
|
||||
Another [link]
|
||||
more text
|
||||
[link]: http://gohere
|
||||
"###
|
||||
.to_string();
|
||||
let target = r###"```toml
|
||||
[package]
|
||||
name = "hello_cargo"
|
||||
version = "0.1.0"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[dependencies]
|
||||
```
|
||||
Another link at *http://gohere*
|
||||
more text
|
||||
"###
|
||||
.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn ignores_quotes_in_pre_sections() {
|
||||
let source = r###"```bash
|
||||
$ cargo build
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
src/main.rs:23:21: 23:35 error: mismatched types [E0308]
|
||||
src/main.rs:23 match guess.cmp(&secret_number) {
|
||||
^~~~~~~~~~~~~~
|
||||
src/main.rs:23:21: 23:35 help: run `rustc --explain E0308` to see a detailed explanation
|
||||
src/main.rs:23:21: 23:35 note: expected type `&std::string::String`
|
||||
src/main.rs:23:21: 23:35 note: found type `&_`
|
||||
error: aborting due to previous error
|
||||
Could not compile `guessing_game`.
|
||||
```
|
||||
"###
|
||||
.to_string();
|
||||
let target = source.clone();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn ignores_short_quotes() {
|
||||
let source = r"to `1` at index `[0]` i".to_string();
|
||||
let target = source.clone();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn ignores_pre_sections_with_final_quote() {
|
||||
let source = r###"```bash
|
||||
$ cargo run
|
||||
Compiling points v0.1.0 (file:///projects/points)
|
||||
error: the trait bound `Point: std::fmt::Display` is not satisfied [--explain E0277]
|
||||
--> src/main.rs:8:29
|
||||
8 |> println!("Point 1: {}", p1);
|
||||
|> ^^
|
||||
<std macros>:2:27: 2:58: note: in this expansion of format_args!
|
||||
<std macros>:3:1: 3:54: note: in this expansion of print! (defined in <std macros>)
|
||||
src/main.rs:8:5: 8:33: note: in this expansion of println! (defined in <std macros>)
|
||||
note: `Point` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
|
||||
note: required by `std::fmt::Display::fmt`
|
||||
```
|
||||
`here` is another [link](the ref)
|
||||
"###.to_string();
|
||||
let target = r###"```bash
|
||||
$ cargo run
|
||||
Compiling points v0.1.0 (file:///projects/points)
|
||||
error: the trait bound `Point: std::fmt::Display` is not satisfied [--explain E0277]
|
||||
--> src/main.rs:8:29
|
||||
8 |> println!("Point 1: {}", p1);
|
||||
|> ^^
|
||||
<std macros>:2:27: 2:58: note: in this expansion of format_args!
|
||||
<std macros>:3:1: 3:54: note: in this expansion of print! (defined in <std macros>)
|
||||
src/main.rs:8:5: 8:33: note: in this expansion of println! (defined in <std macros>)
|
||||
note: `Point` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
|
||||
note: required by `std::fmt::Display::fmt`
|
||||
```
|
||||
`here` is another link at *the ref*
|
||||
"###.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
#[test]
|
||||
fn parses_adam_p_cheatsheet() {
|
||||
let source = r###"[I'm an inline-style link](https://www.google.com)
|
||||
|
||||
[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
|
||||
|
||||
[I'm a reference-style link][Arbitrary case-insensitive reference text]
|
||||
|
||||
[I'm a relative reference to a repository file](../blob/master/LICENSE)
|
||||
|
||||
[You can use numbers for reference-style link definitions][1]
|
||||
|
||||
Or leave it empty and use the [link text itself][].
|
||||
|
||||
URLs and URLs in angle brackets will automatically get turned into links.
|
||||
http://www.example.com or <http://www.example.com> and sometimes
|
||||
example.com (but not on Github, for example).
|
||||
|
||||
Some text to show that the reference links can follow later.
|
||||
|
||||
[arbitrary case-insensitive reference text]: https://www.mozilla.org
|
||||
[1]: http://slashdot.org
|
||||
[link text itself]: http://www.reddit.com"###
|
||||
.to_string();
|
||||
|
||||
let target = r###"I'm an inline-style link at *https://www.google.com*
|
||||
|
||||
I'm an inline-style link with title at *https://www.google.com*
|
||||
|
||||
I'm a reference-style link at *https://www.mozilla.org*
|
||||
|
||||
I'm a relative reference to a repository file at *../blob/master/LICENSE*
|
||||
|
||||
You can use numbers for reference-style link definitions at *http://slashdot.org*
|
||||
|
||||
Or leave it empty and use the link text itself at *http://www.reddit.com*.
|
||||
|
||||
URLs and URLs in angle brackets will automatically get turned into links.
|
||||
http://www.example.com or <http://www.example.com> and sometimes
|
||||
example.com (but not on Github, for example).
|
||||
|
||||
Some text to show that the reference links can follow later.
|
||||
"###
|
||||
.to_string();
|
||||
assert_eq!(parse(source), target);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
extern crate regex;
|
||||
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use regex::{Regex, Captures};
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn main () {
|
||||
let mut buffer = String::new();
|
||||
if let Err(e) = io::stdin().read_to_string(&mut buffer) {
|
||||
panic!(e);
|
||||
}
|
||||
|
||||
let mut refs = HashSet::new();
|
||||
|
||||
// capture all links and link references
|
||||
let regex = r"\[([^\]]+)\](?:(?:\[([^\]]+)\])|(?:\([^\)]+\)))(?i)<!-- ignore -->";
|
||||
let link_regex = Regex::new(regex).unwrap();
|
||||
let first_pass = link_regex.replace_all(&buffer, |caps: &Captures| {
|
||||
|
||||
// save the link reference we want to delete
|
||||
if let Some(reference) = caps.at(2) {
|
||||
refs.insert(reference.to_owned());
|
||||
}
|
||||
|
||||
// put the link title back
|
||||
caps.at(1).unwrap().to_owned()
|
||||
});
|
||||
|
||||
// search for the references we need to delete
|
||||
let ref_regex = Regex::new(r"\n\[([^\]]+)\]:\s.*\n").unwrap();
|
||||
let out = ref_regex.replace_all(&first_pass, |caps: &Captures| {
|
||||
let capture = caps.at(1).unwrap().to_owned();
|
||||
|
||||
// check if we've marked this reference for deletion...
|
||||
if refs.contains(capture.as_str()) {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
//... else we put back everything we captured
|
||||
caps.at(0).unwrap().to_owned()
|
||||
});
|
||||
|
||||
write!(io::stdout(), "{}", out).unwrap();
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
extern crate regex;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use regex::{Regex, Captures};
|
||||
|
||||
fn main() {
|
||||
write_md(remove_markup(read_md()));
|
||||
}
|
||||
|
||||
fn read_md() -> String {
|
||||
let mut buffer = String::new();
|
||||
match io::stdin().read_to_string(&mut buffer) {
|
||||
Ok(_) => buffer,
|
||||
Err(error) => panic!(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_md(output: String) {
|
||||
write!(io::stdout(), "{}", output).unwrap();
|
||||
}
|
||||
|
||||
fn remove_markup(input: String) -> String {
|
||||
let filename_regex = Regex::new(r#"\A<span class="filename">(.*)</span>\z"#).unwrap();
|
||||
// Captions sometimes take up multiple lines
|
||||
let caption_start_regex = Regex::new(r#"\A<span class="caption">(.*)\z"#).unwrap();
|
||||
let caption_end_regex = Regex::new(r#"(.*)</span>\z"#).unwrap();
|
||||
let regexen = vec![filename_regex, caption_start_regex, caption_end_regex];
|
||||
|
||||
let lines: Vec<_> = input.lines().flat_map(|line| {
|
||||
// Remove our figure and caption markup
|
||||
if line == "<figure>" ||
|
||||
line == "<figcaption>" ||
|
||||
line == "</figcaption>" ||
|
||||
line == "</figure>"
|
||||
{
|
||||
None
|
||||
// Remove our syntax highlighting and rustdoc markers
|
||||
} else if line.starts_with("```") {
|
||||
Some(String::from("```"))
|
||||
// Remove the span around filenames and captions
|
||||
} else {
|
||||
let result = regexen.iter().fold(line.to_string(), |result, regex| {
|
||||
regex.replace_all(&result, |caps: &Captures| {
|
||||
caps.at(1).unwrap().to_owned()
|
||||
})
|
||||
});
|
||||
Some(result)
|
||||
}
|
||||
}).collect();
|
||||
lines.join("\n")
|
||||
}
|
|
@ -41,6 +41,8 @@ mdbook build
|
|||
echo 'Linting second edition for local file paths...'
|
||||
cargo run --bin lfp src
|
||||
|
||||
cd ..
|
||||
|
||||
# tests for the 2018 edition
|
||||
cd 2018-edition
|
||||
echo 'Spellchecking 2018 edition...'
|
||||
|
|
Loading…
Reference in New Issue