<commit_before>use crate::utils::schemas;
use defaults::Defaults;
use eyre::Result;
use once_cell::sync::Lazy;
use schemars::JsonSchema;
use serde::Serialize;
use serde_with::skip_serializing_none;
use std::{collections::HashMap, path::Path};

/// Information about a document format
///
/// Used to determine various application behaviors
/// e.g. not reading binary formats into memory unnecessarily
#[skip_serializing_none]
#[derive(Clone, Debug, Defaults, JsonSchema, Serialize)]
#[schemars(deny_unknown_fields)]
pub struct Format {
    /// The lowercase name of the format e.g. `md`, `docx`, `dockerfile`
    pub name: String,

    /// Whether or not the format should be considered binary
    /// e.g. not to be displayed in a text / code editor
    pub binary: bool,

    /// Whether or not previews should be generated for files of
    /// this format. e.g. a `.py` is not binary, but should not
    /// necessarily have a preview opened for it.
    pub preview: bool,

    /// Any additional extensions (other than it's name) that this format
    /// should match against.
    pub extensions: Vec<String>,
}

impl Format {
    /// Create a new file format
    pub fn new(name: &str, binary: bool, preview: bool) -> Format {
        Format {
            name: name.into(),
            binary,
            preview,
            ..Default::default()
        }
    }

    /// Create a new file format with mutliple matching extensions
    pub fn new_extensions(name: &str, binary: bool, preview: bool, extensions: &[&str]) -> Format {
        let mut format = Format::new(name, binary, preview);
        format.extensions = extensions.iter().map(|s| s.to_string()).collect();
        format
    }

    /// Create the special `directory` format used on `File` objects
    /// that are directories
    pub fn directory() -> Format {
        Format {
            name: "dir".into(),
            binary: true,
            preview: false,
            ..Default::default()
        }
    }

    /// Create the special `unregistered` file format where all we
    /// have is the name e.g. from a file extension
    pub fn unregistered(name: &str) -> Format {
        Format {
            name: name.into(),
            binary: true,
            preview: false,
            ..Default::default()
        }
    }

    /// Create the special `unknown` file format where we do not
    /// even know the name.
    pub fn unknown() -> Format {
        Format::unregistered("unknown")
    }
}

/// List of known document formats
#[derive(Serialize)]
#[serde(transparent)]
pub struct Formats {
    /// Document formats keyed by their name
    formats: HashMap<String, Format>,
}

impl Default for Formats {
    fn default() -> Formats {
        let formats = vec![
            // Data serialization formats. These may be used
            // to store documents so `preview: true`.
            Format::new("json", false, true),
            Format::new("json5", false, true),
            Format::new("toml", false, true),
            Format::new("xml", false, true),
            Format::new("yaml", false, true),
            // Code formats
            Format::new("dockerfile", false, false),
            Format::new("js", false, false),
            Format::new("makefile", false, false),
            Format::new("py", false, false),
            Format::new("r", false, false),
            Format::new("sh", false, false),
            Format::new("ts", false, false),
            // Article formats
            Format::new("docx", true, true),
            Format::new("html", false, true),
            Format::new("ipynb", false, true),
            Format::new("md", false, true),
            Format::new("odt", true, true),
            Format::new("rmd", false, true),
            Format::new_extensions("latex", false, true, &["tex"]),
            // Audio formats
            Format::new("flac", true, true),
            Format::new("mp3", true, true),
            Format::new("ogg", true, true),
            // Image formats
            Format::new("gif", true, true),
            Format::new_extensions("jpg", true, true, &["jpeg"]),
            Format::new("png", true, true),
            // Video formats
            Format::new("3gp", true, true),
            Format::new("mp4", true, true),
            Format::new("ogv", true, true),
            Format::new("webm", true, true),
            // Other
            Format::new("txt", false, false),
            // Specials
            Format::directory(),
            Format::unknown(),
        ];

        let formats = formats
            .into_iter()
            .map(|format| (format.name.clone(), format))
            .collect();

        Formats { formats }
    }
}

impl Formats {
    /// Match a format name to a `Format`
    pub fn match_name(&self, name: &str) -> Format {
        match self.formats.get(&name.to_lowercase()) {
            Some(format) => format.clone(),
            None => Format::unregistered(name),
        }
    }

    /// Match a file path to a `Format`
    pub fn match_path<P: AsRef<Path>>(&self, path: &P) -> Format {
        let path = path.as_ref();

        // Get name from file extension, or filename if no extension
        let name = match path.extension() {
            Some(ext) => ext,
            None => match path.file_name() {
                Some(name) => name,
                // Fallback to the provided "path"
                None => path.as_os_str(),
            },
        };
        let name = name.to_string_lossy().to_string();
        if let Some(format) = self.formats.get(&name.to_lowercase()) {
            return format.clone();
        }

        // Try matching against "extra" extensions
        if let Some(ext) = path.extension() {
            let ext = ext.to_string_lossy().to_string();
            for format in self.formats.values() {
                if format.extensions.contains(&ext) {
                    return format.clone();
                }
            }
        }

        Format::unregistered(&name)
    }
}

pub static FORMATS: Lazy<Formats> = Lazy::new(Formats::default);

/// Get JSON Schemas for this modules
pub fn schemas() -> Result<serde_json::Value> {
    let schemas = serde_json::Value::Array(vec![schemas::generate::<Format>()?]);
    Ok(schemas)
}
<commit_msg>fix(Formats): Unregistered formats have binary false<commit_after>use crate::utils::schemas;
use defaults::Defaults;
use eyre::Result;
use once_cell::sync::Lazy;
use schemars::JsonSchema;
use serde::Serialize;
use serde_with::skip_serializing_none;
use std::{collections::HashMap, path::Path};

/// Information about a document format
///
/// Used to determine various application behaviors
/// e.g. not reading binary formats into memory unnecessarily
#[skip_serializing_none]
#[derive(Clone, Debug, Defaults, JsonSchema, Serialize)]
#[schemars(deny_unknown_fields)]
pub struct Format {
    /// The lowercase name of the format e.g. `md`, `docx`, `dockerfile`
    pub name: String,

    /// Whether or not the format should be considered binary
    /// e.g. not to be displayed in a text / code editor
    pub binary: bool,

    /// Whether or not previews should be generated for files of
    /// this format. e.g. a `.py` is not binary, but should not
    /// necessarily have a preview opened for it.
    pub preview: bool,

    /// Any additional extensions (other than it's name) that this format
    /// should match against.
    pub extensions: Vec<String>,
}

impl Format {
    /// Create a new file format
    pub fn new(name: &str, binary: bool, preview: bool) -> Format {
        Format {
            name: name.into(),
            binary,
            preview,
            ..Default::default()
        }
    }

    /// Create a new file format with mutliple matching extensions
    pub fn new_extensions(name: &str, binary: bool, preview: bool, extensions: &[&str]) -> Format {
        let mut format = Format::new(name, binary, preview);
        format.extensions = extensions.iter().map(|s| s.to_string()).collect();
        format
    }

    /// Create the special `directory` format used on `File` objects
    /// that are directories
    pub fn directory() -> Format {
        Format {
            name: "dir".into(),
            binary: true,
            preview: false,
            ..Default::default()
        }
    }

    /// Create the special `unregistered` file format where all we
    /// have is the name e.g. from a file extension.
    pub fn unregistered(name: &str) -> Format {
        Format {
            name: name.into(),
            // Set binary to false so that any unregistered format
            // will be at least shown in editor...
            binary: false,
            // ..but not have a preview
            preview: false,
            ..Default::default()
        }
    }

    /// Create the special `unknown` file format where we do not
    /// even know the name.
    pub fn unknown() -> Format {
        Format::unregistered("unknown")
    }
}

/// List of known document formats
#[derive(Serialize)]
#[serde(transparent)]
pub struct Formats {
    /// Document formats keyed by their name
    formats: HashMap<String, Format>,
}

impl Default for Formats {
    fn default() -> Formats {
        let formats = vec![
            // Data serialization formats. These may be used
            // to store documents so `preview: true`.
            Format::new("json", false, true),
            Format::new("json5", false, true),
            Format::new("toml", false, true),
            Format::new("xml", false, true),
            Format::new("yaml", false, true),
            // Code formats
            Format::new("dockerfile", false, false),
            Format::new("js", false, false),
            Format::new("makefile", false, false),
            Format::new("py", false, false),
            Format::new("r", false, false),
            Format::new("sh", false, false),
            Format::new("ts", false, false),
            // Article formats
            Format::new("docx", true, true),
            Format::new("html", false, true),
            Format::new("ipynb", false, true),
            Format::new("md", false, true),
            Format::new("odt", true, true),
            Format::new("rmd", false, true),
            Format::new_extensions("latex", false, true, &["tex"]),
            // Audio formats
            Format::new("flac", true, true),
            Format::new("mp3", true, true),
            Format::new("ogg", true, true),
            // Image formats
            Format::new("gif", true, true),
            Format::new_extensions("jpg", true, true, &["jpeg"]),
            Format::new("png", true, true),
            // Video formats
            Format::new("3gp", true, true),
            Format::new("mp4", true, true),
            Format::new("ogv", true, true),
            Format::new("webm", true, true),
            // Other
            Format::new("txt", false, false),
            // Specials
            Format::directory(),
            Format::unknown(),
        ];

        let formats = formats
            .into_iter()
            .map(|format| (format.name.clone(), format))
            .collect();

        Formats { formats }
    }
}

impl Formats {
    /// Match a format name to a `Format`
    pub fn match_name(&self, name: &str) -> Format {
        match self.formats.get(&name.to_lowercase()) {
            Some(format) => format.clone(),
            None => Format::unregistered(name),
        }
    }

    /// Match a file path to a `Format`
    pub fn match_path<P: AsRef<Path>>(&self, path: &P) -> Format {
        let path = path.as_ref();

        // Get name from file extension, or filename if no extension
        let name = match path.extension() {
            Some(ext) => ext,
            None => match path.file_name() {
                Some(name) => name,
                // Fallback to the provided "path"
                None => path.as_os_str(),
            },
        };
        let name = name.to_string_lossy().to_string();
        if let Some(format) = self.formats.get(&name.to_lowercase()) {
            return format.clone();
        }

        // Try matching against "extra" extensions
        if let Some(ext) = path.extension() {
            let ext = ext.to_string_lossy().to_string();
            for format in self.formats.values() {
                if format.extensions.contains(&ext) {
                    return format.clone();
                }
            }
        }

        Format::unregistered(&name)
    }
}

pub static FORMATS: Lazy<Formats> = Lazy::new(Formats::default);

/// Get JSON Schemas for this modules
pub fn schemas() -> Result<serde_json::Value> {
    let schemas = serde_json::Value::Array(vec![schemas::generate::<Format>()?]);
    Ok(schemas)
}
<|endoftext|>


<commit_before>// Zinc, the bare metal stack for rust.
// Copyright 2014 Vladimir "farcaller" Pouzanov <farcaller@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![crate_id="platformtree_macro"]
#![crate_type="dylib"]

#![feature(macro_registrar, quote, managed_boxes)]

extern crate syntax;

use syntax::ast::{Name, TokenTree};
use syntax::ast;
use syntax::codemap::{Span};
use syntax::ext::base::{SyntaxExtension, BasicMacroExpander, NormalTT, ExtCtxt};
use syntax::ext::base::{MacResult, MacExpr};
use syntax::ext::base;
use syntax::parse::{token, ParseSess, lexer};
use syntax::print::pprust::expr_to_str;
use syntax::ext::build::AstBuilder;
use syntax::ext::quote::rt::{ToTokens, ExtParseUtils};

use std::collections::hashmap;

mod pt;

#[macro_registrar]
pub fn macro_registrar(register: |Name, SyntaxExtension|) {
  register(token::intern("platformtree_parse"), NormalTT(
    box BasicMacroExpander {
      expander: platformtree_parse,
      span: None,
    },
    None)
  );
}

/// Parse paltformtree to pt::Node
pub fn platformtree_parse(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree])
    -> Box<MacResult> {
  let mut parser = Parser::new(cx, tts);

  let node = parser.parse_node();

  println!("PT parsed: {}", node);

  base::MacExpr::new(quote_expr!(&*cx, $node))
}

impl pt::Node {
  pub fn to_source(&self) -> String {
    let mappath: Vec<String> = self.path.path.iter().map(|x| format!("\"{}\".to_string()", x)).collect();
    let node_struct = format!("pt::Node \\{ \
        name: {}, \
        path: pt::Path \\{ absolute: {}, path: vec!({}) \\}, \
        attributes: hashmap::HashMap::new(), \
        subnodes: Vec::new() \
      \\}",
      match self.name {
        Some(ref s) => format!("Some(\"{}\".to_string())", s),
        None => "None".to_string(),
      },
      self.path.absolute,
      mappath.connect(", "));

    let mut init_chunks = "".to_string();
    for (k, v) in self.attributes.iter() {
      let strinified_val = match v {
        &pt::UIntValue(ref u) => format!("pt::UIntValue({})", u),
        &pt::StrValue(ref s)  => format!("pt::StrValue(\"{}\".to_string())", s),
        &pt::RefValue(ref r)  => format!("pt::RefValue(\"{}\".to_string())", r),
      };

      init_chunks = init_chunks.append(format!("attrs.insert(\"{}\".to_string(), {});",
          k, strinified_val).as_slice());
    }

    for sn in self.subnodes.iter() {
      init_chunks = init_chunks.append(format!(
          "nodes.push(box {});", sn.to_source()).as_slice());
    }

    // TODO(farcaller): this triggers unused_mut for some reason.
    let init_struct = format!("
      \\{
        let mut node = {};
        let mut attrs = hashmap::HashMap::new();
        let mut nodes = Vec::new();
        {}
        node.attributes = attrs;
        node.subnodes = nodes;
        node
      \\}", node_struct, init_chunks);

    init_struct
  }
}

impl ToTokens for pt::Node {
  fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
    (cx as &ExtParseUtils).parse_tts(self.to_source())
  }
}

struct Parser<'a> {
  pub sess: &'a ParseSess,
  reader: Box<lexer::Reader:>,

  token: token::Token,
  span: Span,

  last_token: Option<Box<token::Token>>,
  last_span: Span,

  backlog: Vec<lexer::TokenAndSpan>,
}

impl<'a> Parser<'a> {
  pub fn new<'a>(cx: &'a ExtCtxt, tts: &[TokenTree]) -> Parser<'a> {
    let sess = cx.parse_sess();
    let ttsvec = tts.iter().map(|x| (*x).clone()).collect();
    let mut reader = box lexer::new_tt_reader(&sess.span_diagnostic, None, ttsvec)
        as Box<lexer::Reader>;

    let tok0 = reader.next_token();
    let token = tok0.tok;
    let span = tok0.sp;

    Parser {
      sess: sess,
      reader: reader,

      token: token,
      span: span,

      last_token: None,
      last_span: span,

      backlog: Vec::new(),
    }
  }

  fn fatal(&self, m: &str) -> ! {
    self.sess.span_diagnostic.span_fatal(self.span, m);
  }

  fn bump(&mut self) -> token::Token {
    let tok = self.token.clone();
    self.last_span = self.span;
    self.last_token = Some(box tok.clone());

    let next = match self.backlog.pop() {
      Some(ts) => ts,
      None => self.reader.next_token(),
    };

    self.span = next.sp;
    self.token = next.tok;

    tok
  }

  fn unbump(&mut self) {
    let span = self.last_span;
    let boxtok: &Box<token::Token> = match self.last_token {
      Some(ref t) => t,
      None => fail!(),
    };
    let tok = *boxtok.clone();

    self.backlog.push(lexer::TokenAndSpan {
      tok: self.token.clone(),
      sp: self.span,
    });

    self.token = tok;
    self.span = span;
  }

  fn expect(&mut self, t: &token::Token) {
    if self.token == *t {
      self.bump();
    } else {
      let token_str = token::to_str(t);
      let this_token_str = token::to_str(&self.token);
      self.fatal(format!("expected `{}` but found `{}`", token_str, this_token_str).as_slice())
    }
  }

  fn expect_ident(&mut self) -> token::Token {
    match self.token {
      token::IDENT(_, _) => {
        self.bump()
      },
      _ => {
        let this_token_str = token::to_str(&self.token);
        self.fatal(format!("expected identifier but found `{}`", this_token_str).as_slice())
      },
    }
  }

  pub fn parse_node(&mut self) -> pt::Node {
    let mut node = pt::Node::new();
    // NODE_ID @ NODE_PATH { CONTENTS }
    // or
    //         @ NODE_PATH { CONTENTS }

    node.name = match self.token {
      token::IDENT(_, _) => {
        let ret = Some(token::to_str(&self.token));
        self.bump();
        ret
      },
      token::AT => {
        None
      },
      _ => {
        let this_token_str = token::to_str(&self.token);
        self.fatal(format!("expected identifier or `@` but found `{}`", this_token_str).as_slice())
      },
    };

    self.expect(&token::AT);

    node.path = self.parse_path();

    self.expect(&token::LBRACE);
    self.parse_node_contents(&mut node);
    self.expect(&token::RBRACE);

    node
  }

  pub fn parse_node_contents(&mut self, node: &mut pt::Node) {
    let mut attrs = hashmap::HashMap::new();
    let mut nodes = Vec::new();

    loop {
      match self.token {
        token::RBRACE => break,
        token::IDENT(_, _) => {
          let name = token::to_str(&self.token);
          self.bump();

          match self.token {
            token::EQ => {
              self.bump();
              let val = self.parse_attribute_value();
              self.expect(&token::SEMI);

              attrs.insert(name, val);
            },
            token::AT => {
              self.unbump();
              let node = self.parse_node();
              nodes.push(box node);
            },
            _ => {
              let this_token_str = token::to_str(&self.token);
              self.fatal(format!("expected `@` or `=` but found `{}`", this_token_str).as_slice())
            }
          };
        }
        _ => {
          let this_token_str = token::to_str(&self.token);
          self.fatal(format!("expected identifier or `\\}` but found `{}`", this_token_str).as_slice())
        }
      }
    }

    node.attributes = attrs;
    node.subnodes = nodes;
  }

  pub fn parse_attribute_value(&mut self) -> pt::AttributeValue {
    let val = match self.token {
      token::LIT_STR(sv) => {
        let val = pt::StrValue(token::get_ident(sv).get().to_string());
        self.bump();
        val
      },
      token::LIT_INT_UNSUFFIXED(intval) => {
        let val = pt::UIntValue(intval as uint);
        self.bump();
        val
      },
      token::BINOP(token::AND) => {
        self.bump();
        pt::RefValue(token::to_str(&self.expect_ident()).to_string())
      },
      _ => {
        let this_token_str = token::to_str(&self.token);
        self.fatal(format!("expected integer or string but found {} `{}`", self.token, this_token_str).as_slice())
      }
    };

    val
  }

  pub fn parse_path(&mut self) -> pt::Path {
    let mut v = Vec::new();
    let mut expect_more = false;
    let mut absolute = false;

    if self.token == token::MOD_SEP {
      self.bump();
      expect_more = true;
      absolute = true;
    }

    loop {
      match self.token {
        token::MOD_SEP => {
          if expect_more {
            let this_token_str = token::to_str(&self.token);
            self.fatal(format!("expected identifier but found `{}`", this_token_str).as_slice())
          }
          self.bump();
          expect_more = true;
        },
        token::IDENT(_, _) => {
          v.push(token::to_str(&self.token));
          self.bump();
          expect_more = false;
        },
        token::LIT_INT_UNSUFFIXED(u) => {
          v.push(u.to_str());
          self.bump();
          expect_more = false;
        }
        _ => {
          if !expect_more {
            break
          } else {
            self.fatal(format!("unfinished path, found {} `{}`", self.token,
                token::to_str(&self.token)).as_slice());
          }
        }
      }
    }
    pt::Path {
      absolute: absolute,
      path: v,
    }
  }
}
<commit_msg>Fail if PT path is empty.<commit_after>// Zinc, the bare metal stack for rust.
// Copyright 2014 Vladimir "farcaller" Pouzanov <farcaller@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![crate_id="platformtree_macro"]
#![crate_type="dylib"]

#![feature(macro_registrar, quote, managed_boxes)]

extern crate syntax;

use syntax::ast::{Name, TokenTree};
use syntax::ast;
use syntax::codemap::{Span};
use syntax::ext::base::{SyntaxExtension, BasicMacroExpander, NormalTT, ExtCtxt};
use syntax::ext::base::{MacResult, MacExpr};
use syntax::ext::base;
use syntax::parse::{token, ParseSess, lexer};
use syntax::print::pprust::expr_to_str;
use syntax::ext::build::AstBuilder;
use syntax::ext::quote::rt::{ToTokens, ExtParseUtils};

use std::collections::hashmap;

mod pt;

#[macro_registrar]
pub fn macro_registrar(register: |Name, SyntaxExtension|) {
  register(token::intern("platformtree_parse"), NormalTT(
    box BasicMacroExpander {
      expander: platformtree_parse,
      span: None,
    },
    None)
  );
}

/// Parse paltformtree to pt::Node
pub fn platformtree_parse(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree])
    -> Box<MacResult> {
  let mut parser = Parser::new(cx, tts);

  let node = parser.parse_node();

  println!("PT parsed: {}", node);

  base::MacExpr::new(quote_expr!(&*cx, $node))
}

impl pt::Node {
  pub fn to_source(&self) -> String {
    let mappath: Vec<String> = self.path.path.iter().map(|x| format!("\"{}\".to_string()", x)).collect();
    let node_struct = format!("pt::Node \\{ \
        name: {}, \
        path: pt::Path \\{ absolute: {}, path: vec!({}) \\}, \
        attributes: hashmap::HashMap::new(), \
        subnodes: Vec::new() \
      \\}",
      match self.name {
        Some(ref s) => format!("Some(\"{}\".to_string())", s),
        None => "None".to_string(),
      },
      self.path.absolute,
      mappath.connect(", "));

    let mut init_chunks = "".to_string();
    for (k, v) in self.attributes.iter() {
      let strinified_val = match v {
        &pt::UIntValue(ref u) => format!("pt::UIntValue({})", u),
        &pt::StrValue(ref s)  => format!("pt::StrValue(\"{}\".to_string())", s),
        &pt::RefValue(ref r)  => format!("pt::RefValue(\"{}\".to_string())", r),
      };

      init_chunks = init_chunks.append(format!("attrs.insert(\"{}\".to_string(), {});",
          k, strinified_val).as_slice());
    }

    for sn in self.subnodes.iter() {
      init_chunks = init_chunks.append(format!(
          "nodes.push(box {});", sn.to_source()).as_slice());
    }

    // TODO(farcaller): this triggers unused_mut for some reason.
    let init_struct = format!("
      \\{
        let mut node = {};
        let mut attrs = hashmap::HashMap::new();
        let mut nodes = Vec::new();
        {}
        node.attributes = attrs;
        node.subnodes = nodes;
        node
      \\}", node_struct, init_chunks);

    init_struct
  }
}

impl ToTokens for pt::Node {
  fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
    (cx as &ExtParseUtils).parse_tts(self.to_source())
  }
}

struct Parser<'a> {
  pub sess: &'a ParseSess,
  reader: Box<lexer::Reader:>,

  token: token::Token,
  span: Span,

  last_token: Option<Box<token::Token>>,
  last_span: Span,

  backlog: Vec<lexer::TokenAndSpan>,
}

impl<'a> Parser<'a> {
  pub fn new<'a>(cx: &'a ExtCtxt, tts: &[TokenTree]) -> Parser<'a> {
    let sess = cx.parse_sess();
    let ttsvec = tts.iter().map(|x| (*x).clone()).collect();
    let mut reader = box lexer::new_tt_reader(&sess.span_diagnostic, None, ttsvec)
        as Box<lexer::Reader>;

    let tok0 = reader.next_token();
    let token = tok0.tok;
    let span = tok0.sp;

    Parser {
      sess: sess,
      reader: reader,

      token: token,
      span: span,

      last_token: None,
      last_span: span,

      backlog: Vec::new(),
    }
  }

  fn fatal(&self, m: &str) -> ! {
    self.sess.span_diagnostic.span_fatal(self.span, m);
  }

  fn bump(&mut self) -> token::Token {
    let tok = self.token.clone();
    self.last_span = self.span;
    self.last_token = Some(box tok.clone());

    let next = match self.backlog.pop() {
      Some(ts) => ts,
      None => self.reader.next_token(),
    };

    self.span = next.sp;
    self.token = next.tok;

    tok
  }

  fn unbump(&mut self) {
    let span = self.last_span;
    let boxtok: &Box<token::Token> = match self.last_token {
      Some(ref t) => t,
      None => fail!(),
    };
    let tok = *boxtok.clone();

    self.backlog.push(lexer::TokenAndSpan {
      tok: self.token.clone(),
      sp: self.span,
    });

    self.token = tok;
    self.span = span;
  }

  fn expect(&mut self, t: &token::Token) {
    if self.token == *t {
      self.bump();
    } else {
      let token_str = token::to_str(t);
      let this_token_str = token::to_str(&self.token);
      self.fatal(format!("expected `{}` but found `{}`", token_str, this_token_str).as_slice())
    }
  }

  fn expect_ident(&mut self) -> token::Token {
    match self.token {
      token::IDENT(_, _) => {
        self.bump()
      },
      _ => {
        let this_token_str = token::to_str(&self.token);
        self.fatal(format!("expected identifier but found `{}`", this_token_str).as_slice())
      },
    }
  }

  pub fn parse_node(&mut self) -> pt::Node {
    let mut node = pt::Node::new();
    // NODE_ID @ NODE_PATH { CONTENTS }
    // or
    //         @ NODE_PATH { CONTENTS }

    node.name = match self.token {
      token::IDENT(_, _) => {
        let ret = Some(token::to_str(&self.token));
        self.bump();
        ret
      },
      token::AT => {
        None
      },
      _ => {
        let this_token_str = token::to_str(&self.token);
        self.fatal(format!("expected identifier or `@` but found `{}`", this_token_str).as_slice())
      },
    };

    self.expect(&token::AT);

    node.path = self.parse_path();

    self.expect(&token::LBRACE);
    self.parse_node_contents(&mut node);
    self.expect(&token::RBRACE);

    node
  }

  pub fn parse_node_contents(&mut self, node: &mut pt::Node) {
    let mut attrs = hashmap::HashMap::new();
    let mut nodes = Vec::new();

    loop {
      match self.token {
        token::RBRACE => break,
        token::IDENT(_, _) => {
          let name = token::to_str(&self.token);
          self.bump();

          match self.token {
            token::EQ => {
              self.bump();
              let val = self.parse_attribute_value();
              self.expect(&token::SEMI);

              attrs.insert(name, val);
            },
            token::AT => {
              self.unbump();
              let node = self.parse_node();
              nodes.push(box node);
            },
            _ => {
              let this_token_str = token::to_str(&self.token);
              self.fatal(format!("expected `@` or `=` but found `{}`", this_token_str).as_slice())
            }
          };
        }
        _ => {
          let this_token_str = token::to_str(&self.token);
          self.fatal(format!("expected identifier or `\\}` but found `{}`", this_token_str).as_slice())
        }
      }
    }

    node.attributes = attrs;
    node.subnodes = nodes;
  }

  pub fn parse_attribute_value(&mut self) -> pt::AttributeValue {
    let val = match self.token {
      token::LIT_STR(sv) => {
        let val = pt::StrValue(token::get_ident(sv).get().to_string());
        self.bump();
        val
      },
      token::LIT_INT_UNSUFFIXED(intval) => {
        let val = pt::UIntValue(intval as uint);
        self.bump();
        val
      },
      token::BINOP(token::AND) => {
        self.bump();
        pt::RefValue(token::to_str(&self.expect_ident()).to_string())
      },
      _ => {
        let this_token_str = token::to_str(&self.token);
        self.fatal(format!("expected integer or string but found {} `{}`", self.token, this_token_str).as_slice())
      }
    };

    val
  }

  pub fn parse_path(&mut self) -> pt::Path {
    let mut v = Vec::new();
    let mut expect_more = false;
    let mut absolute = false;

    if self.token == token::MOD_SEP {
      self.bump();
      expect_more = true;
      absolute = true;
    }

    loop {
      match self.token {
        token::MOD_SEP => {
          if expect_more {
            let this_token_str = token::to_str(&self.token);
            self.fatal(format!("expected identifier but found `{}`", this_token_str).as_slice())
          }
          self.bump();
          expect_more = true;
        },
        token::IDENT(_, _) => {
          v.push(token::to_str(&self.token));
          self.bump();
          expect_more = false;
        },
        token::LIT_INT_UNSUFFIXED(u) => {
          v.push(u.to_str());
          self.bump();
          expect_more = false;
        }
        _ => {
          if !expect_more {
            if v.len() == 0 {
              self.fatal(format!("unfinished path, found {} `{}`", self.token,
                  token::to_str(&self.token)).as_slice());
            }
            break
          } else {
            self.fatal(format!("unfinished path, found {} `{}`", self.token,
                token::to_str(&self.token)).as_slice());
          }
        }
      }
    }
    pt::Path {
      absolute: absolute,
      path: v,
    }
  }
}
<|endoftext|>


<commit_before>//! The Rust AST Visitor. Extracts useful information and massages it into a form
//! usable for `clean`.

use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::Node;
use rustc_middle::middle::privacy::AccessLevel;
use rustc_middle::ty::TyCtxt;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::{self, Span};

use std::mem;

use crate::clean::{self, AttributesExt, NestedAttributesExt};
use crate::core;
use crate::doctree::*;

// FIXME: Should this be replaced with tcx.def_path_str?
fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
    let crate_name = tcx.crate_name(did.krate).to_string();
    let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| {
        // extern blocks have an empty name
        let s = elem.data.to_string();
        if !s.is_empty() { Some(s) } else { None }
    });
    std::iter::once(crate_name).chain(relative).collect()
}

crate fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool {
    while let Some(id) = tcx.hir().get_enclosing_scope(node) {
        node = id;
        if tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
            return true;
        }
    }
    false
}

// Also, is there some reason that this doesn't use the 'visit'
// framework from syntax?.

crate struct RustdocVisitor<'a, 'tcx> {
    cx: &'a mut core::DocContext<'tcx>,
    view_item_stack: FxHashSet<hir::HirId>,
    inlining: bool,
    /// Are the current module and all of its parents public?
    inside_public_path: bool,
    exact_paths: FxHashMap<DefId, Vec<String>>,
}

impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
    crate fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> {
        // If the root is re-exported, terminate all recursion.
        let mut stack = FxHashSet::default();
        stack.insert(hir::CRATE_HIR_ID);
        RustdocVisitor {
            cx,
            view_item_stack: stack,
            inlining: false,
            inside_public_path: true,
            exact_paths: FxHashMap::default(),
        }
    }

    fn store_path(&mut self, did: DefId) {
        let tcx = self.cx.tcx;
        self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
    }

    crate fn visit(mut self, krate: &'tcx hir::Crate<'_>) -> Module<'tcx> {
        let span = krate.item.inner;
        let mut top_level_module = self.visit_mod_contents(
            span,
            &Spanned { span, node: hir::VisibilityKind::Public },
            hir::CRATE_HIR_ID,
            &krate.item,
            self.cx.tcx.crate_name,
        );
        // Attach the crate's exported macros to the top-level module.
        // In the case of macros 2.0 (`pub macro`), and for built-in `derive`s or attributes as
        // well (_e.g._, `Copy`), these are wrongly bundled in there too, so we need to fix that by
        // moving them back to their correct locations.
        'exported_macros: for def in krate.exported_macros {
            // The `def` of a macro in `exported_macros` should correspond to either:
            //  - a `#[macro_export] macro_rules!` macro,
            //  - a built-in `derive` (or attribute) macro such as the ones in `::core`,
            //  - a `pub macro`.
            // Only the last two need to be fixed, thus:
            if def.ast.macro_rules {
                top_level_module.macros.push((def, None));
                continue 'exported_macros;
            }
            let tcx = self.cx.tcx;
            // Note: this is not the same as `.parent_module()`. Indeed, the latter looks
            // for the closest module _ancestor_, which is not necessarily a direct parent
            // (since a direct parent isn't necessarily a module, c.f. #77828).
            let macro_parent_def_id = {
                use rustc_middle::ty::DefIdTree;
                tcx.parent(def.def_id.to_def_id()).unwrap()
            };
            let macro_parent_path = tcx.def_path(macro_parent_def_id);
            // HACK: rustdoc has no way to lookup `doctree::Module`s by their HirId. Instead,
            // lookup the module by its name, by looking at each path segment one at a time.
            let mut cur_mod = &mut top_level_module;
            for path_segment in macro_parent_path.data {
                // Path segments may refer to a module (in which case they belong to the type
                // namespace), which is _necessary_ for the macro to be accessible outside it
                // (no "associated macros" as of yet). Else we bail with an outer `continue`.
                let path_segment_ty_ns = match path_segment.data {
                    rustc_hir::definitions::DefPathData::TypeNs(symbol) => symbol,
                    _ => continue 'exported_macros,
                };
                // Descend into the child module that matches this path segment (if any).
                match cur_mod.mods.iter_mut().find(|child| child.name == path_segment_ty_ns) {
                    Some(child_mod) => cur_mod = &mut *child_mod,
                    None => continue 'exported_macros,
                }
            }
            let cur_mod_def_id = tcx.hir().local_def_id(cur_mod.id).to_def_id();
            assert_eq!(cur_mod_def_id, macro_parent_def_id);
            cur_mod.macros.push((def, None));
        }
        self.cx.cache.exact_paths = self.exact_paths;
        top_level_module
    }

    fn visit_mod_contents(
        &mut self,
        span: Span,
        vis: &hir::Visibility<'_>,
        id: hir::HirId,
        m: &'tcx hir::Mod<'tcx>,
        name: Symbol,
    ) -> Module<'tcx> {
        let mut om = Module::new(name);
        om.where_outer = span;
        om.where_inner = m.inner;
        om.id = id;
        // Keep track of if there were any private modules in the path.
        let orig_inside_public_path = self.inside_public_path;
        self.inside_public_path &= vis.node.is_pub();
        for &i in m.item_ids {
            let item = self.cx.tcx.hir().item(i);
            self.visit_item(item, None, &mut om);
        }
        self.inside_public_path = orig_inside_public_path;
        om
    }

    /// Tries to resolve the target of a `crate use` statement and inlines the
    /// target if it is defined locally and would not be documented otherwise,
    /// or when it is specifically requested with `please_inline`.
    /// (the latter is the case when the import is marked `doc(inline)`)
    ///
    /// Cross-crate inlining occurs later on during crate cleaning
    /// and follows different rules.
    ///
    /// Returns `true` if the target has been inlined.
    fn maybe_inline_local(
        &mut self,
        id: hir::HirId,
        res: Res,
        renamed: Option<Symbol>,
        glob: bool,
        om: &mut Module<'tcx>,
        please_inline: bool,
    ) -> bool {
        debug!("maybe_inline_local res: {:?}", res);

        let tcx = self.cx.tcx;
        let res_did = if let Some(did) = res.opt_def_id() {
            did
        } else {
            return false;
        };

        let use_attrs = tcx.hir().attrs(id);
        // Don't inline `doc(hidden)` imports so they can be stripped at a later stage.
        let is_no_inline = use_attrs.lists(sym::doc).has_word(sym::no_inline)
            || use_attrs.lists(sym::doc).has_word(sym::hidden);

        // For cross-crate impl inlining we need to know whether items are
        // reachable in documentation -- a previously nonreachable item can be
        // made reachable by cross-crate inlining which we're checking here.
        // (this is done here because we need to know this upfront).
        if !res_did.is_local() && !is_no_inline {
            let attrs = clean::inline::load_attrs(self.cx, res_did);
            let self_is_hidden = attrs.lists(sym::doc).has_word(sym::hidden);
            if !self_is_hidden {
                if let Res::Def(kind, did) = res {
                    if kind == DefKind::Mod {
                        crate::visit_lib::LibEmbargoVisitor::new(self.cx).visit_mod(did)
                    } else {
                        // All items need to be handled here in case someone wishes to link
                        // to them with intra-doc links
                        self.cx.cache.access_levels.map.insert(did, AccessLevel::Public);
                    }
                }
            }
            return false;
        }

        let res_hir_id = match res_did.as_local() {
            Some(n) => tcx.hir().local_def_id_to_hir_id(n),
            None => return false,
        };

        let is_private = !self.cx.cache.access_levels.is_public(res_did);
        let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id);

        // Only inline if requested or if the item would otherwise be stripped.
        if (!please_inline && !is_private && !is_hidden) || is_no_inline {
            return false;
        }

        if !self.view_item_stack.insert(res_hir_id) {
            return false;
        }

        let ret = match tcx.hir().get(res_hir_id) {
            Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => {
                let prev = mem::replace(&mut self.inlining, true);
                for &i in m.item_ids {
                    let i = self.cx.tcx.hir().item(i);
                    self.visit_item(i, None, om);
                }
                self.inlining = prev;
                true
            }
            Node::Item(it) if !glob => {
                let prev = mem::replace(&mut self.inlining, true);
                self.visit_item(it, renamed, om);
                self.inlining = prev;
                true
            }
            Node::ForeignItem(it) if !glob => {
                let prev = mem::replace(&mut self.inlining, true);
                self.visit_foreign_item(it, renamed, om);
                self.inlining = prev;
                true
            }
            Node::MacroDef(def) if !glob => {
                om.macros.push((def, renamed));
                true
            }
            _ => false,
        };
        self.view_item_stack.remove(&res_hir_id);
        ret
    }

    fn visit_item(
        &mut self,
        item: &'tcx hir::Item<'_>,
        renamed: Option<Symbol>,
        om: &mut Module<'tcx>,
    ) {
        debug!("visiting item {:?}", item);
        let name = renamed.unwrap_or(item.ident.name);

        if item.vis.node.is_pub() {
            self.store_path(item.def_id.to_def_id());
        }

        match item.kind {
            hir::ItemKind::ForeignMod { items, .. } => {
                for item in items {
                    let item = self.cx.tcx.hir().foreign_item(item.id);
                    self.visit_foreign_item(item, None, om);
                }
            }
            // If we're inlining, skip private items.
            _ if self.inlining && !item.vis.node.is_pub() => {}
            hir::ItemKind::GlobalAsm(..) => {}
            hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
            hir::ItemKind::Use(ref path, kind) => {
                let is_glob = kind == hir::UseKind::Glob;

                // Struct and variant constructors and proc macro stubs always show up alongside
                // their definitions, we've already processed them so just discard these.
                if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = path.res {
                    return;
                }

                let attrs = self.cx.tcx.hir().attrs(item.hir_id());

                // If there was a private module in the current path then don't bother inlining
                // anything as it will probably be stripped anyway.
                if item.vis.node.is_pub() && self.inside_public_path {
                    let please_inline = attrs.iter().any(|item| match item.meta_item_list() {
                        Some(ref list) if item.has_name(sym::doc) => {
                            list.iter().any(|i| i.has_name(sym::inline))
                        }
                        _ => false,
                    });
                    let ident = if is_glob { None } else { Some(name) };
                    if self.maybe_inline_local(
                        item.hir_id(),
                        path.res,
                        ident,
                        is_glob,
                        om,
                        please_inline,
                    ) {
                        return;
                    }
                }

                om.items.push((item, renamed))
            }
            hir::ItemKind::Mod(ref m) => {
                om.mods.push(self.visit_mod_contents(item.span, &item.vis, item.hir_id(), m, name));
            }
            hir::ItemKind::Fn(..)
            | hir::ItemKind::ExternCrate(..)
            | hir::ItemKind::Enum(..)
            | hir::ItemKind::Struct(..)
            | hir::ItemKind::Union(..)
            | hir::ItemKind::TyAlias(..)
            | hir::ItemKind::OpaqueTy(..)
            | hir::ItemKind::Static(..)
            | hir::ItemKind::Trait(..)
            | hir::ItemKind::TraitAlias(..) => om.items.push((item, renamed)),
            hir::ItemKind::Const(..) => {
                // Underscore constants do not correspond to a nameable item and
                // so are never useful in documentation.
                if name != kw::Underscore {
                    om.items.push((item, renamed));
                }
            }
            hir::ItemKind::Impl(ref impl_) => {
                // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick
                // them up regardless of where they're located.
                if !self.inlining && impl_.of_trait.is_none() {
                    om.items.push((item, None));
                }
            }
        }
    }

    fn visit_foreign_item(
        &mut self,
        item: &'tcx hir::ForeignItem<'_>,
        renamed: Option<Symbol>,
        om: &mut Module<'tcx>,
    ) {
        // If inlining we only want to include public functions.
        if !self.inlining || item.vis.node.is_pub() {
            om.foreigns.push((item, renamed));
        }
    }
}
<commit_msg>rustdoc: Fix typo for maybe_inline_local fn<commit_after>//! The Rust AST Visitor. Extracts useful information and massages it into a form
//! usable for `clean`.

use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::Node;
use rustc_middle::middle::privacy::AccessLevel;
use rustc_middle::ty::TyCtxt;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::{self, Span};

use std::mem;

use crate::clean::{self, AttributesExt, NestedAttributesExt};
use crate::core;
use crate::doctree::*;

// FIXME: Should this be replaced with tcx.def_path_str?
fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
    let crate_name = tcx.crate_name(did.krate).to_string();
    let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| {
        // extern blocks have an empty name
        let s = elem.data.to_string();
        if !s.is_empty() { Some(s) } else { None }
    });
    std::iter::once(crate_name).chain(relative).collect()
}

crate fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool {
    while let Some(id) = tcx.hir().get_enclosing_scope(node) {
        node = id;
        if tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
            return true;
        }
    }
    false
}

// Also, is there some reason that this doesn't use the 'visit'
// framework from syntax?.

crate struct RustdocVisitor<'a, 'tcx> {
    cx: &'a mut core::DocContext<'tcx>,
    view_item_stack: FxHashSet<hir::HirId>,
    inlining: bool,
    /// Are the current module and all of its parents public?
    inside_public_path: bool,
    exact_paths: FxHashMap<DefId, Vec<String>>,
}

impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
    crate fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> {
        // If the root is re-exported, terminate all recursion.
        let mut stack = FxHashSet::default();
        stack.insert(hir::CRATE_HIR_ID);
        RustdocVisitor {
            cx,
            view_item_stack: stack,
            inlining: false,
            inside_public_path: true,
            exact_paths: FxHashMap::default(),
        }
    }

    fn store_path(&mut self, did: DefId) {
        let tcx = self.cx.tcx;
        self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
    }

    crate fn visit(mut self, krate: &'tcx hir::Crate<'_>) -> Module<'tcx> {
        let span = krate.item.inner;
        let mut top_level_module = self.visit_mod_contents(
            span,
            &Spanned { span, node: hir::VisibilityKind::Public },
            hir::CRATE_HIR_ID,
            &krate.item,
            self.cx.tcx.crate_name,
        );
        // Attach the crate's exported macros to the top-level module.
        // In the case of macros 2.0 (`pub macro`), and for built-in `derive`s or attributes as
        // well (_e.g._, `Copy`), these are wrongly bundled in there too, so we need to fix that by
        // moving them back to their correct locations.
        'exported_macros: for def in krate.exported_macros {
            // The `def` of a macro in `exported_macros` should correspond to either:
            //  - a `#[macro_export] macro_rules!` macro,
            //  - a built-in `derive` (or attribute) macro such as the ones in `::core`,
            //  - a `pub macro`.
            // Only the last two need to be fixed, thus:
            if def.ast.macro_rules {
                top_level_module.macros.push((def, None));
                continue 'exported_macros;
            }
            let tcx = self.cx.tcx;
            // Note: this is not the same as `.parent_module()`. Indeed, the latter looks
            // for the closest module _ancestor_, which is not necessarily a direct parent
            // (since a direct parent isn't necessarily a module, c.f. #77828).
            let macro_parent_def_id = {
                use rustc_middle::ty::DefIdTree;
                tcx.parent(def.def_id.to_def_id()).unwrap()
            };
            let macro_parent_path = tcx.def_path(macro_parent_def_id);
            // HACK: rustdoc has no way to lookup `doctree::Module`s by their HirId. Instead,
            // lookup the module by its name, by looking at each path segment one at a time.
            let mut cur_mod = &mut top_level_module;
            for path_segment in macro_parent_path.data {
                // Path segments may refer to a module (in which case they belong to the type
                // namespace), which is _necessary_ for the macro to be accessible outside it
                // (no "associated macros" as of yet). Else we bail with an outer `continue`.
                let path_segment_ty_ns = match path_segment.data {
                    rustc_hir::definitions::DefPathData::TypeNs(symbol) => symbol,
                    _ => continue 'exported_macros,
                };
                // Descend into the child module that matches this path segment (if any).
                match cur_mod.mods.iter_mut().find(|child| child.name == path_segment_ty_ns) {
                    Some(child_mod) => cur_mod = &mut *child_mod,
                    None => continue 'exported_macros,
                }
            }
            let cur_mod_def_id = tcx.hir().local_def_id(cur_mod.id).to_def_id();
            assert_eq!(cur_mod_def_id, macro_parent_def_id);
            cur_mod.macros.push((def, None));
        }
        self.cx.cache.exact_paths = self.exact_paths;
        top_level_module
    }

    fn visit_mod_contents(
        &mut self,
        span: Span,
        vis: &hir::Visibility<'_>,
        id: hir::HirId,
        m: &'tcx hir::Mod<'tcx>,
        name: Symbol,
    ) -> Module<'tcx> {
        let mut om = Module::new(name);
        om.where_outer = span;
        om.where_inner = m.inner;
        om.id = id;
        // Keep track of if there were any private modules in the path.
        let orig_inside_public_path = self.inside_public_path;
        self.inside_public_path &= vis.node.is_pub();
        for &i in m.item_ids {
            let item = self.cx.tcx.hir().item(i);
            self.visit_item(item, None, &mut om);
        }
        self.inside_public_path = orig_inside_public_path;
        om
    }

    /// Tries to resolve the target of a `pub use` statement and inlines the
    /// target if it is defined locally and would not be documented otherwise,
    /// or when it is specifically requested with `please_inline`.
    /// (the latter is the case when the import is marked `doc(inline)`)
    ///
    /// Cross-crate inlining occurs later on during crate cleaning
    /// and follows different rules.
    ///
    /// Returns `true` if the target has been inlined.
    fn maybe_inline_local(
        &mut self,
        id: hir::HirId,
        res: Res,
        renamed: Option<Symbol>,
        glob: bool,
        om: &mut Module<'tcx>,
        please_inline: bool,
    ) -> bool {
        debug!("maybe_inline_local res: {:?}", res);

        let tcx = self.cx.tcx;
        let res_did = if let Some(did) = res.opt_def_id() {
            did
        } else {
            return false;
        };

        let use_attrs = tcx.hir().attrs(id);
        // Don't inline `doc(hidden)` imports so they can be stripped at a later stage.
        let is_no_inline = use_attrs.lists(sym::doc).has_word(sym::no_inline)
            || use_attrs.lists(sym::doc).has_word(sym::hidden);

        // For cross-crate impl inlining we need to know whether items are
        // reachable in documentation -- a previously unreachable item can be
        // made reachable by cross-crate inlining which we're checking here.
        // (this is done here because we need to know this upfront).
        if !res_did.is_local() && !is_no_inline {
            let attrs = clean::inline::load_attrs(self.cx, res_did);
            let self_is_hidden = attrs.lists(sym::doc).has_word(sym::hidden);
            if !self_is_hidden {
                if let Res::Def(kind, did) = res {
                    if kind == DefKind::Mod {
                        crate::visit_lib::LibEmbargoVisitor::new(self.cx).visit_mod(did)
                    } else {
                        // All items need to be handled here in case someone wishes to link
                        // to them with intra-doc links
                        self.cx.cache.access_levels.map.insert(did, AccessLevel::Public);
                    }
                }
            }
            return false;
        }

        let res_hir_id = match res_did.as_local() {
            Some(n) => tcx.hir().local_def_id_to_hir_id(n),
            None => return false,
        };

        let is_private = !self.cx.cache.access_levels.is_public(res_did);
        let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id);

        // Only inline if requested or if the item would otherwise be stripped.
        if (!please_inline && !is_private && !is_hidden) || is_no_inline {
            return false;
        }

        if !self.view_item_stack.insert(res_hir_id) {
            return false;
        }

        let ret = match tcx.hir().get(res_hir_id) {
            Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => {
                let prev = mem::replace(&mut self.inlining, true);
                for &i in m.item_ids {
                    let i = self.cx.tcx.hir().item(i);
                    self.visit_item(i, None, om);
                }
                self.inlining = prev;
                true
            }
            Node::Item(it) if !glob => {
                let prev = mem::replace(&mut self.inlining, true);
                self.visit_item(it, renamed, om);
                self.inlining = prev;
                true
            }
            Node::ForeignItem(it) if !glob => {
                let prev = mem::replace(&mut self.inlining, true);
                self.visit_foreign_item(it, renamed, om);
                self.inlining = prev;
                true
            }
            Node::MacroDef(def) if !glob => {
                om.macros.push((def, renamed));
                true
            }
            _ => false,
        };
        self.view_item_stack.remove(&res_hir_id);
        ret
    }

    fn visit_item(
        &mut self,
        item: &'tcx hir::Item<'_>,
        renamed: Option<Symbol>,
        om: &mut Module<'tcx>,
    ) {
        debug!("visiting item {:?}", item);
        let name = renamed.unwrap_or(item.ident.name);

        if item.vis.node.is_pub() {
            self.store_path(item.def_id.to_def_id());
        }

        match item.kind {
            hir::ItemKind::ForeignMod { items, .. } => {
                for item in items {
                    let item = self.cx.tcx.hir().foreign_item(item.id);
                    self.visit_foreign_item(item, None, om);
                }
            }
            // If we're inlining, skip private items.
            _ if self.inlining && !item.vis.node.is_pub() => {}
            hir::ItemKind::GlobalAsm(..) => {}
            hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
            hir::ItemKind::Use(ref path, kind) => {
                let is_glob = kind == hir::UseKind::Glob;

                // Struct and variant constructors and proc macro stubs always show up alongside
                // their definitions, we've already processed them so just discard these.
                if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = path.res {
                    return;
                }

                let attrs = self.cx.tcx.hir().attrs(item.hir_id());

                // If there was a private module in the current path then don't bother inlining
                // anything as it will probably be stripped anyway.
                if item.vis.node.is_pub() && self.inside_public_path {
                    let please_inline = attrs.iter().any(|item| match item.meta_item_list() {
                        Some(ref list) if item.has_name(sym::doc) => {
                            list.iter().any(|i| i.has_name(sym::inline))
                        }
                        _ => false,
                    });
                    let ident = if is_glob { None } else { Some(name) };
                    if self.maybe_inline_local(
                        item.hir_id(),
                        path.res,
                        ident,
                        is_glob,
                        om,
                        please_inline,
                    ) {
                        return;
                    }
                }

                om.items.push((item, renamed))
            }
            hir::ItemKind::Mod(ref m) => {
                om.mods.push(self.visit_mod_contents(item.span, &item.vis, item.hir_id(), m, name));
            }
            hir::ItemKind::Fn(..)
            | hir::ItemKind::ExternCrate(..)
            | hir::ItemKind::Enum(..)
            | hir::ItemKind::Struct(..)
            | hir::ItemKind::Union(..)
            | hir::ItemKind::TyAlias(..)
            | hir::ItemKind::OpaqueTy(..)
            | hir::ItemKind::Static(..)
            | hir::ItemKind::Trait(..)
            | hir::ItemKind::TraitAlias(..) => om.items.push((item, renamed)),
            hir::ItemKind::Const(..) => {
                // Underscore constants do not correspond to a nameable item and
                // so are never useful in documentation.
                if name != kw::Underscore {
                    om.items.push((item, renamed));
                }
            }
            hir::ItemKind::Impl(ref impl_) => {
                // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick
                // them up regardless of where they're located.
                if !self.inlining && impl_.of_trait.is_none() {
                    om.items.push((item, None));
                }
            }
        }
    }

    fn visit_foreign_item(
        &mut self,
        item: &'tcx hir::ForeignItem<'_>,
        renamed: Option<Symbol>,
        om: &mut Module<'tcx>,
    ) {
        // If inlining we only want to include public functions.
        if !self.inlining || item.vis.node.is_pub() {
            om.foreigns.push((item, renamed));
        }
    }
}
<|endoftext|>


<commit_before>extern crate "zaldinar-core" as zaldinar;

use zaldinar::client::PluginRegister;
use zaldinar::events::CommandEvent;
use zaldinar::client::ExecutingState;

fn action(event: &CommandEvent) {
    if event.args[0].starts_with("#") {
        event.client.send_ctcp(&*event.args[0], "ACTION", event.args[1..].connect(" "));
    } else {
        event.client.send_ctcp(event.channel(), "ACTION", event.args.connect(" "));
    }
}

fn say(event: &CommandEvent) {
    if !event.client.is_admin(event) {
        return;
    }
    let (channel, message) = if event.args[0].starts_with("#") {
        (&*event.args[0], event.args[1..].connect(" "))
    } else {
        (event.channel(), event.args.connect(" "))
    };
    event.client.send_message(channel, message);
    event.client.reply_notice(event, format!("Sent message to {}.", channel));
}

fn quit(event: &CommandEvent) {
    if event.args.len() != 0 {
        event.client.quit(Some(event.args.connect(" ")), ExecutingState::Done);
    } else {
        event.client.quit::<&str>(None, ExecutingState::Done);
    }
}

fn raw(event: &CommandEvent) {
    event.client.send_raw(event.args.connect(" "));
    event.client.reply_notice(event, "Sent raw message.");
}

fn join(event: &CommandEvent) {
    if event.args.is_empty() {
        event.client.reply_notice(event, "Please specify a channel to join.");
        return;
    }
    event.client.join(&*event.args[0]);
    event.client.reply_notice(event, format!("Joined {}.", event.args[0]));
}

fn part(event: &CommandEvent) {
    let (channel, reason) = if event.args.is_empty() {
        (event.channel(), None)
    } else if event.args[0].starts_with("#") {
        if event.args.len() > 1 {
            (&*event.args[0], Some(event.args[1..].connect(" ")))
        } else {
            (&*event.args[0], None)
        }
    } else {
        (event.channel(), Some(event.args.connect(" ")))
    };
    event.client.part(channel, reason);
    event.client.reply_notice(event, format!("Parted {}.", channel));
}

fn message(event: &CommandEvent) {
    if event.args.len() < 2 {
        event.client.reply_notice(event, "Please specify both a channel and a message to send");
        return;
    }
    event.client.send_message(&*event.args[0], event.args[1..].connect(" "));
    event.client.reply_notice(event, format!("Sent message to {}.", event.args[0]));
}

pub fn register(register: &mut PluginRegister) {
    register.register_admin_command("say", say);
    register.register_admin_command("message", message);
    register.register_admin_command("action", action);
    register.register_admin_command("raw", raw);
    register.register_admin_command("join", join);
    register.register_admin_command("part", part);
    register.register_admin_command("leave", part);
    register.register_admin_command("quit", quit);
    // register.register_command("nick", nick);
}
<commit_msg>Remove leave alias for part command.<commit_after>extern crate "zaldinar-core" as zaldinar;

use zaldinar::client::PluginRegister;
use zaldinar::events::CommandEvent;
use zaldinar::client::ExecutingState;

fn action(event: &CommandEvent) {
    if event.args[0].starts_with("#") {
        event.client.send_ctcp(&*event.args[0], "ACTION", event.args[1..].connect(" "));
    } else {
        event.client.send_ctcp(event.channel(), "ACTION", event.args.connect(" "));
    }
}

fn say(event: &CommandEvent) {
    if !event.client.is_admin(event) {
        return;
    }
    let (channel, message) = if event.args[0].starts_with("#") {
        (&*event.args[0], event.args[1..].connect(" "))
    } else {
        (event.channel(), event.args.connect(" "))
    };
    event.client.send_message(channel, message);
    event.client.reply_notice(event, format!("Sent message to {}.", channel));
}

fn quit(event: &CommandEvent) {
    if event.args.len() != 0 {
        event.client.quit(Some(event.args.connect(" ")), ExecutingState::Done);
    } else {
        event.client.quit::<&str>(None, ExecutingState::Done);
    }
}

fn raw(event: &CommandEvent) {
    event.client.send_raw(event.args.connect(" "));
    event.client.reply_notice(event, "Sent raw message.");
}

fn join(event: &CommandEvent) {
    if event.args.is_empty() {
        event.client.reply_notice(event, "Please specify a channel to join.");
        return;
    }
    event.client.join(&*event.args[0]);
    event.client.reply_notice(event, format!("Joined {}.", event.args[0]));
}

fn part(event: &CommandEvent) {
    let (channel, reason) = if event.args.is_empty() {
        (event.channel(), None)
    } else if event.args[0].starts_with("#") {
        if event.args.len() > 1 {
            (&*event.args[0], Some(event.args[1..].connect(" ")))
        } else {
            (&*event.args[0], None)
        }
    } else {
        (event.channel(), Some(event.args.connect(" ")))
    };
    event.client.part(channel, reason);
    event.client.reply_notice(event, format!("Parted {}.", channel));
}

fn message(event: &CommandEvent) {
    if event.args.len() < 2 {
        event.client.reply_notice(event, "Please specify both a channel and a message to send");
        return;
    }
    event.client.send_message(&*event.args[0], event.args[1..].connect(" "));
    event.client.reply_notice(event, format!("Sent message to {}.", event.args[0]));
}

pub fn register(register: &mut PluginRegister) {
    register.register_admin_command("say", say);
    register.register_admin_command("message", message);
    register.register_admin_command("action", action);
    register.register_admin_command("raw", raw);
    register.register_admin_command("join", join);
    register.register_admin_command("part", part);
    register.register_admin_command("quit", quit);
    // register.register_command("nick", nick);
}
<|endoftext|>


<commit_before>//! Source code generator.
//!
//! The `srcgen` module contains generic helper routines and classes for
//! generating source code.

#![macro_use]

use std::cmp;
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::io::Write;
use std::path;

use crate::error;

static SHIFTWIDTH: usize = 4;

/// A macro that simplifies the usage of the Formatter by allowing format
/// strings.
macro_rules! fmtln {
    ($fmt:ident, $fmtstring:expr, $($fmtargs:expr),*) => {
        $fmt.line(format!($fmtstring, $($fmtargs),*));
    };

    ($fmt:ident, $arg:expr) => {
        $fmt.line($arg);
    };

    ($_:tt, $($args:expr),+) => {
        compile_error!("This macro requires at least two arguments: the Formatter instance and a format string.");
    };

    ($_:tt) => {
        compile_error!("This macro requires at least two arguments: the Formatter instance and a format string.");
    };
}

pub struct Formatter {
    indent: usize,
    lines: Vec<String>,
}

impl Formatter {
    /// Source code formatter class. Used to collect source code to be written
    /// to a file, and keep track of indentation.
    pub fn new() -> Self {
        Self {
            indent: 0,
            lines: Vec::new(),
        }
    }

    /// Increase current indentation level by one.
    pub fn indent_push(&mut self) {
        self.indent += 1;
    }

    /// Decrease indentation by one level.
    pub fn indent_pop(&mut self) {
        assert!(self.indent > 0, "Already at top level indentation");
        self.indent -= 1;
    }

    pub fn indent<T, F: FnOnce(&mut Formatter) -> T>(&mut self, f: F) -> T {
        self.indent_push();
        let ret = f(self);
        self.indent_pop();
        ret
    }

    /// Get the current whitespace indentation in the form of a String.
    fn get_indent(&self) -> String {
        if self.indent == 0 {
            String::new()
        } else {
            format!("{:-1$}", " ", self.indent * SHIFTWIDTH)
        }
    }

    /// Get a string containing whitespace outdented one level. Used for
    /// lines of code that are inside a single indented block.
    fn _get_outdent(&mut self) -> String {
        self.indent_push();
        let s = self.get_indent();
        self.indent_pop();
        s
    }

    /// Add an indented line.
    pub fn line(&mut self, contents: impl AsRef<str>) {
        let indented_line = format!("{}{}\n", self.get_indent(), contents.as_ref());
        self.lines.push(indented_line);
    }

    /// Pushes an empty line.
    pub fn empty_line(&mut self) {
        self.lines.push("\n".to_string());
    }

    /// Emit a line outdented one level.
    pub fn _outdented_line(&mut self, s: &str) {
        let new_line = format!("{}{}", self._get_outdent(), s);
        self.lines.push(new_line);
    }

    /// Write `self.lines` to a file.
    pub fn update_file(
        &self,
        filename: impl AsRef<str>,
        directory: &str,
    ) -> Result<(), error::Error> {
        #[cfg(target_family = "windows")]
        let path_str = format!("{}\\{}", directory, filename.as_ref());
        #[cfg(not(target_family = "windows"))]
        let path_str = format!("{}/{}", directory, filename.as_ref());

        let path = path::Path::new(&path_str);
        let mut f = fs::File::create(path)?;

        for l in self.lines.iter().map(|l| l.as_bytes()) {
            f.write_all(l)?;
        }

        Ok(())
    }

    /// Add one or more lines after stripping common indentation.
    pub fn multi_line(&mut self, s: &str) {
        parse_multiline(s).into_iter().for_each(|l| self.line(&l));
    }

    /// Add a comment line.
    pub fn comment(&mut self, s: impl AsRef<str>) {
        fmtln!(self, "// {}", s.as_ref());
    }

    /// Add a (multi-line) documentation comment.
    pub fn doc_comment(&mut self, contents: impl AsRef<str>) {
        parse_multiline(contents.as_ref())
            .iter()
            .map(|l| {
                if l.len() == 0 {
                    "///".into()
                } else {
                    format!("/// {}", l)
                }
            })
            .for_each(|s| self.line(s.as_str()));
    }

    /// Add a match expression.
    pub fn add_match(&mut self, m: Match) {
        fmtln!(self, "match {} {{", m.expr);
        self.indent(|fmt| {
            for (&(ref fields, ref body), ref names) in m.arms.iter() {
                // name { fields } | name { fields } => { body }
                let conditions = names
                    .iter()
                    .map(|name| {
                        if fields.len() > 0 {
                            format!("{} {{ {} }}", name, fields.join(", "))
                        } else {
                            name.clone()
                        }
                    })
                    .collect::<Vec<_>>()
                    .join(" |\n")
                    + " => {";

                fmt.multi_line(&conditions);
                fmt.indent(|fmt| {
                    fmt.line(body);
                });
                fmt.line("}");
            }

            // Make sure to include the catch all clause last.
            if let Some(body) = m.catch_all {
                fmt.line("_ => {");
                fmt.indent(|fmt| {
                    fmt.line(body);
                });
                fmt.line("}");
            }
        });
        self.line("}");
    }
}

/// Compute the indentation of s, or None of an empty line.
fn _indent(s: &str) -> Option<usize> {
    if s.is_empty() {
        None
    } else {
        let t = s.trim_start();
        Some(s.len() - t.len())
    }
}

/// Given a multi-line string, split it into a sequence of lines after
/// stripping a common indentation. This is useful for strings defined with
/// doc strings.
fn parse_multiline(s: &str) -> Vec<String> {
    // Convert tabs into spaces.
    let expanded_tab = format!("{:-1$}", " ", SHIFTWIDTH);
    let lines: Vec<String> = s.lines().map(|l| l.replace("\t", &expanded_tab)).collect();

    // Determine minimum indentation, ignoring the first line and empty lines.
    let indent = lines
        .iter()
        .skip(1)
        .filter(|l| !l.trim().is_empty())
        .map(|l| l.len() - l.trim_start().len())
        .min();

    // Strip off leading blank lines.
    let mut lines_iter = lines.iter().skip_while(|l| l.is_empty());
    let mut trimmed = Vec::with_capacity(lines.len());

    // Remove indentation (first line is special)
    if let Some(s) = lines_iter.next().map(|l| l.trim()).map(|l| l.to_string()) {
        trimmed.push(s);
    }

    // Remove trailing whitespace from other lines.
    let mut other_lines = if let Some(indent) = indent {
        // Note that empty lines may have fewer than `indent` chars.
        lines_iter
            .map(|l| &l[cmp::min(indent, l.len())..])
            .map(|l| l.trim_end())
            .map(|l| l.to_string())
            .collect::<Vec<_>>()
    } else {
        lines_iter
            .map(|l| l.trim_end())
            .map(|l| l.to_string())
            .collect::<Vec<_>>()
    };

    trimmed.append(&mut other_lines);

    // Strip off trailing blank lines.
    while let Some(s) = trimmed.pop() {
        if s.is_empty() {
            continue;
        } else {
            trimmed.push(s);
            break;
        }
    }

    trimmed
}

/// Match formatting class.
///
/// Match objects collect all the information needed to emit a Rust `match`
/// expression, automatically deduplicating overlapping identical arms.
///
/// Note that this class is ignorant of Rust types, and considers two fields
/// with the same name to be equivalent. BTreeMap/BTreeSet are used to
/// represent the arms in order to make the order deterministic.
pub struct Match {
    expr: String,
    arms: BTreeMap<(Vec<String>, String), BTreeSet<String>>,
    /// The clause for the placeholder pattern _.
    catch_all: Option<String>,
}

impl Match {
    /// Create a new match statement on `expr`.
    pub fn new(expr: impl Into<String>) -> Self {
        Self {
            expr: expr.into(),
            arms: BTreeMap::new(),
            catch_all: None,
        }
    }

    fn set_catch_all(&mut self, clause: String) {
        assert!(self.catch_all.is_none());
        self.catch_all = Some(clause);
    }

    /// Add an arm that reads fields to the Match statement.
    pub fn arm<T: Into<String>, S: Into<String>>(&mut self, name: T, fields: Vec<S>, body: T) {
        let name = name.into();
        assert!(
            name != "_",
            "catch all clause can't extract fields, use arm_no_fields instead."
        );

        let body = body.into();
        let fields = fields.into_iter().map(|x| x.into()).collect();
        let match_arm = self
            .arms
            .entry((fields, body))
            .or_insert_with(BTreeSet::new);
        match_arm.insert(name);
    }

    /// Adds an arm that doesn't read anythings from the fields to the Match statement.
    pub fn arm_no_fields(&mut self, name: impl Into<String>, body: impl Into<String>) {
        let body = body.into();

        let name = name.into();
        if name == "_" {
            self.set_catch_all(body);
            return;
        }

        let match_arm = self
            .arms
            .entry((Vec::new(), body))
            .or_insert_with(BTreeSet::new);
        match_arm.insert(name);
    }
}

#[cfg(test)]
mod srcgen_tests {
    use super::parse_multiline;
    use super::Formatter;
    use super::Match;

    fn from_raw_string<S: Into<String>>(s: S) -> Vec<String> {
        s.into()
            .trim()
            .split("\n")
            .into_iter()
            .map(|x| format!("{}\n", x))
            .collect()
    }

    #[test]
    fn adding_arms_works() {
        let mut m = Match::new("x");
        m.arm("Orange", vec!["a", "b"], "some body");
        m.arm("Yellow", vec!["a", "b"], "some body");
        m.arm("Green", vec!["a", "b"], "different body");
        m.arm("Blue", vec!["x", "y"], "some body");
        assert_eq!(m.arms.len(), 3);

        let mut fmt = Formatter::new();
        fmt.add_match(m);

        let expected_lines = from_raw_string(
            r#"
match x {
    Green { a, b } => {
        different body
    }
    Orange { a, b } |
    Yellow { a, b } => {
        some body
    }
    Blue { x, y } => {
        some body
    }
}
        "#,
        );
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn match_with_catchall_order() {
        // The catchall placeholder must be placed after other clauses.
        let mut m = Match::new("x");
        m.arm("Orange", vec!["a", "b"], "some body");
        m.arm("Green", vec!["a", "b"], "different body");
        m.arm_no_fields("_", "unreachable!()");
        assert_eq!(m.arms.len(), 2); // catchall is not counted

        let mut fmt = Formatter::new();
        fmt.add_match(m);

        let expected_lines = from_raw_string(
            r#"
match x {
    Green { a, b } => {
        different body
    }
    Orange { a, b } => {
        some body
    }
    _ => {
        unreachable!()
    }
}
        "#,
        );
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn parse_multiline_works() {
        let input = "\n    hello\n    world\n";
        let expected = vec!["hello", "world"];
        let output = parse_multiline(input);
        assert_eq!(output, expected);
    }

    #[test]
    fn formatter_basic_example_works() {
        let mut fmt = Formatter::new();
        fmt.line("Hello line 1");
        fmt.indent_push();
        fmt.comment("Nested comment");
        fmt.indent_pop();
        fmt.line("Back home again");
        let expected_lines = vec![
            "Hello line 1\n",
            "    // Nested comment\n",
            "Back home again\n",
        ];
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn get_indent_works() {
        let mut fmt = Formatter::new();
        let expected_results = vec!["", "    ", "        ", ""];

        let actual_results = Vec::with_capacity(4);
        (0..3).for_each(|_| {
            fmt.get_indent();
            fmt.indent_push();
        });
        (0..3).for_each(|_| fmt.indent_pop());
        fmt.get_indent();

        actual_results
            .into_iter()
            .zip(expected_results.into_iter())
            .for_each(|(actual, expected): (String, &str)| assert_eq!(&actual, expected));
    }

    #[test]
    fn fmt_can_add_type_to_lines() {
        let mut fmt = Formatter::new();
        fmt.line(format!("pub const {}: Type = Type({:#x});", "example", 0,));
        let expected_lines = vec!["pub const example: Type = Type(0x0);\n"];
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn fmt_can_add_indented_line() {
        let mut fmt = Formatter::new();
        fmt.line("hello");
        fmt.indent_push();
        fmt.line("world");
        let expected_lines = vec!["hello\n", "    world\n"];
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn fmt_can_add_doc_comments() {
        let mut fmt = Formatter::new();
        fmt.doc_comment("documentation\nis\ngood");
        let expected_lines = vec!["/// documentation\n", "/// is\n", "/// good\n"];
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn fmt_can_add_doc_comments_with_empty_lines() {
        let mut fmt = Formatter::new();
        fmt.doc_comment(
            r#"documentation
        can be really good.

        If you stick to writing it.
"#,
        );
        let expected_lines = from_raw_string(
            r#"
/// documentation
/// can be really good.
///
/// If you stick to writing it."#,
        );
        assert_eq!(fmt.lines, expected_lines);
    }
}
<commit_msg>[meta] Fix outdented_line in srcgen;<commit_after>//! Source code generator.
//!
//! The `srcgen` module contains generic helper routines and classes for
//! generating source code.

#![macro_use]

use std::cmp;
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::io::Write;
use std::path;

use crate::error;

static SHIFTWIDTH: usize = 4;

/// A macro that simplifies the usage of the Formatter by allowing format
/// strings.
macro_rules! fmtln {
    ($fmt:ident, $fmtstring:expr, $($fmtargs:expr),*) => {
        $fmt.line(format!($fmtstring, $($fmtargs),*));
    };

    ($fmt:ident, $arg:expr) => {
        $fmt.line($arg);
    };

    ($_:tt, $($args:expr),+) => {
        compile_error!("This macro requires at least two arguments: the Formatter instance and a format string.");
    };

    ($_:tt) => {
        compile_error!("This macro requires at least two arguments: the Formatter instance and a format string.");
    };
}

pub struct Formatter {
    indent: usize,
    lines: Vec<String>,
}

impl Formatter {
    /// Source code formatter class. Used to collect source code to be written
    /// to a file, and keep track of indentation.
    pub fn new() -> Self {
        Self {
            indent: 0,
            lines: Vec::new(),
        }
    }

    /// Increase current indentation level by one.
    pub fn indent_push(&mut self) {
        self.indent += 1;
    }

    /// Decrease indentation by one level.
    pub fn indent_pop(&mut self) {
        assert!(self.indent > 0, "Already at top level indentation");
        self.indent -= 1;
    }

    pub fn indent<T, F: FnOnce(&mut Formatter) -> T>(&mut self, f: F) -> T {
        self.indent_push();
        let ret = f(self);
        self.indent_pop();
        ret
    }

    /// Get the current whitespace indentation in the form of a String.
    fn get_indent(&self) -> String {
        if self.indent == 0 {
            String::new()
        } else {
            format!("{:-1$}", " ", self.indent * SHIFTWIDTH)
        }
    }

    /// Get a string containing whitespace outdented one level. Used for
    /// lines of code that are inside a single indented block.
    fn _get_outdent(&mut self) -> String {
        self.indent_pop();
        let s = self.get_indent();
        self.indent_push();
        s
    }

    /// Add an indented line.
    pub fn line(&mut self, contents: impl AsRef<str>) {
        let indented_line = format!("{}{}\n", self.get_indent(), contents.as_ref());
        self.lines.push(indented_line);
    }

    /// Pushes an empty line.
    pub fn empty_line(&mut self) {
        self.lines.push("\n".to_string());
    }

    /// Emit a line outdented one level.
    pub fn _outdented_line(&mut self, s: &str) {
        let new_line = format!("{}{}\n", self._get_outdent(), s);
        self.lines.push(new_line);
    }

    /// Write `self.lines` to a file.
    pub fn update_file(
        &self,
        filename: impl AsRef<str>,
        directory: &str,
    ) -> Result<(), error::Error> {
        #[cfg(target_family = "windows")]
        let path_str = format!("{}\\{}", directory, filename.as_ref());
        #[cfg(not(target_family = "windows"))]
        let path_str = format!("{}/{}", directory, filename.as_ref());

        let path = path::Path::new(&path_str);
        let mut f = fs::File::create(path)?;

        for l in self.lines.iter().map(|l| l.as_bytes()) {
            f.write_all(l)?;
        }

        Ok(())
    }

    /// Add one or more lines after stripping common indentation.
    pub fn multi_line(&mut self, s: &str) {
        parse_multiline(s).into_iter().for_each(|l| self.line(&l));
    }

    /// Add a comment line.
    pub fn comment(&mut self, s: impl AsRef<str>) {
        fmtln!(self, "// {}", s.as_ref());
    }

    /// Add a (multi-line) documentation comment.
    pub fn doc_comment(&mut self, contents: impl AsRef<str>) {
        parse_multiline(contents.as_ref())
            .iter()
            .map(|l| {
                if l.len() == 0 {
                    "///".into()
                } else {
                    format!("/// {}", l)
                }
            })
            .for_each(|s| self.line(s.as_str()));
    }

    /// Add a match expression.
    pub fn add_match(&mut self, m: Match) {
        fmtln!(self, "match {} {{", m.expr);
        self.indent(|fmt| {
            for (&(ref fields, ref body), ref names) in m.arms.iter() {
                // name { fields } | name { fields } => { body }
                let conditions = names
                    .iter()
                    .map(|name| {
                        if fields.len() > 0 {
                            format!("{} {{ {} }}", name, fields.join(", "))
                        } else {
                            name.clone()
                        }
                    })
                    .collect::<Vec<_>>()
                    .join(" |\n")
                    + " => {";

                fmt.multi_line(&conditions);
                fmt.indent(|fmt| {
                    fmt.line(body);
                });
                fmt.line("}");
            }

            // Make sure to include the catch all clause last.
            if let Some(body) = m.catch_all {
                fmt.line("_ => {");
                fmt.indent(|fmt| {
                    fmt.line(body);
                });
                fmt.line("}");
            }
        });
        self.line("}");
    }
}

/// Compute the indentation of s, or None of an empty line.
fn _indent(s: &str) -> Option<usize> {
    if s.is_empty() {
        None
    } else {
        let t = s.trim_start();
        Some(s.len() - t.len())
    }
}

/// Given a multi-line string, split it into a sequence of lines after
/// stripping a common indentation. This is useful for strings defined with
/// doc strings.
fn parse_multiline(s: &str) -> Vec<String> {
    // Convert tabs into spaces.
    let expanded_tab = format!("{:-1$}", " ", SHIFTWIDTH);
    let lines: Vec<String> = s.lines().map(|l| l.replace("\t", &expanded_tab)).collect();

    // Determine minimum indentation, ignoring the first line and empty lines.
    let indent = lines
        .iter()
        .skip(1)
        .filter(|l| !l.trim().is_empty())
        .map(|l| l.len() - l.trim_start().len())
        .min();

    // Strip off leading blank lines.
    let mut lines_iter = lines.iter().skip_while(|l| l.is_empty());
    let mut trimmed = Vec::with_capacity(lines.len());

    // Remove indentation (first line is special)
    if let Some(s) = lines_iter.next().map(|l| l.trim()).map(|l| l.to_string()) {
        trimmed.push(s);
    }

    // Remove trailing whitespace from other lines.
    let mut other_lines = if let Some(indent) = indent {
        // Note that empty lines may have fewer than `indent` chars.
        lines_iter
            .map(|l| &l[cmp::min(indent, l.len())..])
            .map(|l| l.trim_end())
            .map(|l| l.to_string())
            .collect::<Vec<_>>()
    } else {
        lines_iter
            .map(|l| l.trim_end())
            .map(|l| l.to_string())
            .collect::<Vec<_>>()
    };

    trimmed.append(&mut other_lines);

    // Strip off trailing blank lines.
    while let Some(s) = trimmed.pop() {
        if s.is_empty() {
            continue;
        } else {
            trimmed.push(s);
            break;
        }
    }

    trimmed
}

/// Match formatting class.
///
/// Match objects collect all the information needed to emit a Rust `match`
/// expression, automatically deduplicating overlapping identical arms.
///
/// Note that this class is ignorant of Rust types, and considers two fields
/// with the same name to be equivalent. BTreeMap/BTreeSet are used to
/// represent the arms in order to make the order deterministic.
pub struct Match {
    expr: String,
    arms: BTreeMap<(Vec<String>, String), BTreeSet<String>>,
    /// The clause for the placeholder pattern _.
    catch_all: Option<String>,
}

impl Match {
    /// Create a new match statement on `expr`.
    pub fn new(expr: impl Into<String>) -> Self {
        Self {
            expr: expr.into(),
            arms: BTreeMap::new(),
            catch_all: None,
        }
    }

    fn set_catch_all(&mut self, clause: String) {
        assert!(self.catch_all.is_none());
        self.catch_all = Some(clause);
    }

    /// Add an arm that reads fields to the Match statement.
    pub fn arm<T: Into<String>, S: Into<String>>(&mut self, name: T, fields: Vec<S>, body: T) {
        let name = name.into();
        assert!(
            name != "_",
            "catch all clause can't extract fields, use arm_no_fields instead."
        );

        let body = body.into();
        let fields = fields.into_iter().map(|x| x.into()).collect();
        let match_arm = self
            .arms
            .entry((fields, body))
            .or_insert_with(BTreeSet::new);
        match_arm.insert(name);
    }

    /// Adds an arm that doesn't read anythings from the fields to the Match statement.
    pub fn arm_no_fields(&mut self, name: impl Into<String>, body: impl Into<String>) {
        let body = body.into();

        let name = name.into();
        if name == "_" {
            self.set_catch_all(body);
            return;
        }

        let match_arm = self
            .arms
            .entry((Vec::new(), body))
            .or_insert_with(BTreeSet::new);
        match_arm.insert(name);
    }
}

#[cfg(test)]
mod srcgen_tests {
    use super::parse_multiline;
    use super::Formatter;
    use super::Match;

    fn from_raw_string<S: Into<String>>(s: S) -> Vec<String> {
        s.into()
            .trim()
            .split("\n")
            .into_iter()
            .map(|x| format!("{}\n", x))
            .collect()
    }

    #[test]
    fn adding_arms_works() {
        let mut m = Match::new("x");
        m.arm("Orange", vec!["a", "b"], "some body");
        m.arm("Yellow", vec!["a", "b"], "some body");
        m.arm("Green", vec!["a", "b"], "different body");
        m.arm("Blue", vec!["x", "y"], "some body");
        assert_eq!(m.arms.len(), 3);

        let mut fmt = Formatter::new();
        fmt.add_match(m);

        let expected_lines = from_raw_string(
            r#"
match x {
    Green { a, b } => {
        different body
    }
    Orange { a, b } |
    Yellow { a, b } => {
        some body
    }
    Blue { x, y } => {
        some body
    }
}
        "#,
        );
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn match_with_catchall_order() {
        // The catchall placeholder must be placed after other clauses.
        let mut m = Match::new("x");
        m.arm("Orange", vec!["a", "b"], "some body");
        m.arm("Green", vec!["a", "b"], "different body");
        m.arm_no_fields("_", "unreachable!()");
        assert_eq!(m.arms.len(), 2); // catchall is not counted

        let mut fmt = Formatter::new();
        fmt.add_match(m);

        let expected_lines = from_raw_string(
            r#"
match x {
    Green { a, b } => {
        different body
    }
    Orange { a, b } => {
        some body
    }
    _ => {
        unreachable!()
    }
}
        "#,
        );
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn parse_multiline_works() {
        let input = "\n    hello\n    world\n";
        let expected = vec!["hello", "world"];
        let output = parse_multiline(input);
        assert_eq!(output, expected);
    }

    #[test]
    fn formatter_basic_example_works() {
        let mut fmt = Formatter::new();
        fmt.line("Hello line 1");
        fmt.indent_push();
        fmt.comment("Nested comment");
        fmt.indent_pop();
        fmt.line("Back home again");
        let expected_lines = vec![
            "Hello line 1\n",
            "    // Nested comment\n",
            "Back home again\n",
        ];
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn get_indent_works() {
        let mut fmt = Formatter::new();
        let expected_results = vec!["", "    ", "        ", ""];

        let actual_results = Vec::with_capacity(4);
        (0..3).for_each(|_| {
            fmt.get_indent();
            fmt.indent_push();
        });
        (0..3).for_each(|_| fmt.indent_pop());
        fmt.get_indent();

        actual_results
            .into_iter()
            .zip(expected_results.into_iter())
            .for_each(|(actual, expected): (String, &str)| assert_eq!(&actual, expected));
    }

    #[test]
    fn fmt_can_add_type_to_lines() {
        let mut fmt = Formatter::new();
        fmt.line(format!("pub const {}: Type = Type({:#x});", "example", 0,));
        let expected_lines = vec!["pub const example: Type = Type(0x0);\n"];
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn fmt_can_add_indented_line() {
        let mut fmt = Formatter::new();
        fmt.line("hello");
        fmt.indent_push();
        fmt.line("world");
        let expected_lines = vec!["hello\n", "    world\n"];
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn fmt_can_add_doc_comments() {
        let mut fmt = Formatter::new();
        fmt.doc_comment("documentation\nis\ngood");
        let expected_lines = vec!["/// documentation\n", "/// is\n", "/// good\n"];
        assert_eq!(fmt.lines, expected_lines);
    }

    #[test]
    fn fmt_can_add_doc_comments_with_empty_lines() {
        let mut fmt = Formatter::new();
        fmt.doc_comment(
            r#"documentation
        can be really good.

        If you stick to writing it.
"#,
        );
        let expected_lines = from_raw_string(
            r#"
/// documentation
/// can be really good.
///
/// If you stick to writing it."#,
        );
        assert_eq!(fmt.lines, expected_lines);
    }
}
<|endoftext|>