<commit_before>use std::old_io::{fs, IoResult};
use std::old_io as io;
use std::ascii::AsciiExt;
use std::env::current_dir;

use ansi_term::{ANSIString, ANSIStrings, Colour, Style};
use ansi_term::Style::Plain;
use ansi_term::Colour::{Red, Green, Yellow, Blue, Purple, Cyan, Fixed};

use users::Users;

use locale;
use output::details::UserLocale;

use number_prefix::{binary_prefix, decimal_prefix, Prefixed, Standalone, PrefixNames};

use datetime::local::{LocalDateTime, DatePiece};
use datetime::format::{DateFormat};

use column::{Column, Cell};
use column::Column::*;
use dir::Dir;
use filetype::HasType;
use options::{SizeFormat, TimeType};
use xattr;
use xattr::Attribute;

/// This grey value is directly in between white and black, so it's guaranteed
/// to show up on either backgrounded terminal.
pub static GREY: Colour = Fixed(244);

/// A **File** is a wrapper around one of Rust's Path objects, along with
/// associated data about the file.
///
/// Each file is definitely going to have its filename displayed at least
/// once, have its file extension extracted at least once, and have its stat
/// information queried at least once, so it makes sense to do all this at the
/// start and hold on to all the information.
pub struct File<'a> {
    pub name:  String,
    pub dir:   Option<&'a Dir>,
    pub ext:   Option<String>,
    pub path:  Path,
    pub stat:  io::FileStat,
    pub xattrs: Vec<Attribute>,
    pub this:  Option<Dir>,
}

impl<'a> File<'a> {
    /// Create a new File object from the given Path, inside the given Dir, if
    /// appropriate. Paths specified directly on the command-line have no Dirs.
    ///
    /// This uses lstat instead of stat, which doesn't follow symbolic links.
    pub fn from_path(path: &Path, parent: Option<&'a Dir>, recurse: bool) -> IoResult<File<'a>> {
        fs::lstat(path).map(|stat| File::with_stat(stat, path, parent, recurse))
    }

    /// Create a new File object from the given Stat result, and other data.
    pub fn with_stat(stat: io::FileStat, path: &Path, parent: Option<&'a Dir>, recurse: bool) -> File<'a> {
        let filename = path_filename(path);

        // If we are recursing, then the `this` field contains a Dir object
        // that represents the current File as a directory, if it is a
        // directory. This is used for the --tree option.
        let this = if recurse && stat.kind == io::FileType::Directory {
            Dir::readdir(path).ok()
        }
        else {
            None
        };

        File {
            path:   path.clone(),
            dir:    parent,
            stat:   stat,
            ext:    ext(&filename),
            xattrs: xattr::llist(path).unwrap_or(Vec::new()),
            name:   filename.to_string(),
            this:   this,
        }
    }

    /// Whether this file is a dotfile or not.
    pub fn is_dotfile(&self) -> bool {
        self.name.starts_with(".")
    }

    /// Whether this file is a temporary file or not.
    pub fn is_tmpfile(&self) -> bool {
        let name = &self.name;
        name.ends_with("~") || (name.starts_with("#") && name.ends_with("#"))
    }

    /// Get the data for a column, formatted as a coloured string.
    pub fn display<U: Users>(&self, column: &Column, users_cache: &mut U, locale: &UserLocale) -> Cell {
        match *column {
            Permissions     => self.permissions_string(),
            FileSize(f)     => self.file_size(f, &locale.numeric),
            Timestamp(t, y) => self.timestamp(t, y, &locale.time),
            HardLinks       => self.hard_links(&locale.numeric),
            Inode           => self.inode(),
            Blocks          => self.blocks(&locale.numeric),
            User            => self.user(users_cache),
            Group           => self.group(users_cache),
            GitStatus       => self.git_status(),
        }
    }

    /// The "file name view" is what's displayed in the column and lines
    /// views, but *not* in the grid view.
    ///
    /// It consists of the file name coloured in the appropriate style,
    /// with special formatting for a symlink.
    pub fn file_name_view(&self) -> String {
        if self.stat.kind == io::FileType::Symlink {
            self.symlink_file_name_view()
        }
        else {
            self.file_colour().paint(&*self.name).to_string()
        }
    }

    /// If this file is a symlink, returns a string displaying its name,
    /// and an arrow pointing to the file it links to, which is also
    /// coloured in the appropriate style.
    ///
    /// If the symlink target doesn't exist, then instead of displaying
    /// an error, highlight the target and arrow in red. The error would
    /// be shown out of context, and it's almost always because the
    /// target doesn't exist.
    fn symlink_file_name_view(&self) -> String {
        let name = &*self.name;
        let style = self.file_colour();

        if let Ok(path) = fs::readlink(&self.path) {
            let target_path = match self.dir {
                Some(dir) => dir.join(path),
                None => path,
            };

            match self.target_file(&target_path) {
                Ok(file) => {

                    // Generate a preview for the path this symlink links to.
                    // The preview should consist of the directory of the file
                    // (if present) in cyan, an extra slash if necessary, then
                    // the target file, colourised in the appropriate style.
                    let mut path_prefix = String::new();

                    // The root directory has the name "/", which has to be
                    // catered for separately, otherwise there'll be two
                    // slashes in the resulting output.
                    if file.path.is_absolute() && file.name != "/" {
                        path_prefix.push_str("/");
                    }

                    let path_bytes: Vec<&[u8]> = file.path.components().collect();
                    if !path_bytes.is_empty() {
                        // Use init() to add all but the last component of the
                        // path to the prefix. init() panics when given an
                        // empty list, hence the check.
                        for component in path_bytes.init().iter() {
                            let string = String::from_utf8_lossy(component).to_string();
                            path_prefix.push_str(&string);
                        }
                    }

                    // Only add a slash when there's something in the path
                    // prefix so far.
                    if path_bytes.len() > 1 {
                        path_prefix.push_str("/");
                    }

                    format!("{} {} {}",
                            style.paint(name),
                            GREY.paint("=>"),
                            ANSIStrings(&[ Cyan.paint(&path_prefix),
                                           file.file_colour().paint(&file.name) ]))
                },
                Err(filename) => format!("{} {} {}",
                                         style.paint(name),
                                         Red.paint("=>"),
                                         Red.underline().paint(&filename)),
            }
        }<commit_msg>Fix a missing '/' in symlink targets<commit_after>use std::old_io::{fs, IoResult};
use std::old_io as io;
use std::ascii::AsciiExt;
use std::env::current_dir;

use ansi_term::{ANSIString, ANSIStrings, Colour, Style};
use ansi_term::Style::Plain;
use ansi_term::Colour::{Red, Green, Yellow, Blue, Purple, Cyan, Fixed};

use users::Users;

use locale;
use output::details::UserLocale;

use number_prefix::{binary_prefix, decimal_prefix, Prefixed, Standalone, PrefixNames};

use datetime::local::{LocalDateTime, DatePiece};
use datetime::format::{DateFormat};

use column::{Column, Cell};
use column::Column::*;
use dir::Dir;
use filetype::HasType;
use options::{SizeFormat, TimeType};
use xattr;
use xattr::Attribute;

/// This grey value is directly in between white and black, so it's guaranteed
/// to show up on either backgrounded terminal.
pub static GREY: Colour = Fixed(244);

/// A **File** is a wrapper around one of Rust's Path objects, along with
/// associated data about the file.
///
/// Each file is definitely going to have its filename displayed at least
/// once, have its file extension extracted at least once, and have its stat
/// information queried at least once, so it makes sense to do all this at the
/// start and hold on to all the information.
pub struct File<'a> {
    pub name:  String,
    pub dir:   Option<&'a Dir>,
    pub ext:   Option<String>,
    pub path:  Path,
    pub stat:  io::FileStat,
    pub xattrs: Vec<Attribute>,
    pub this:  Option<Dir>,
}

impl<'a> File<'a> {
    /// Create a new File object from the given Path, inside the given Dir, if
    /// appropriate. Paths specified directly on the command-line have no Dirs.
    ///
    /// This uses lstat instead of stat, which doesn't follow symbolic links.
    pub fn from_path(path: &Path, parent: Option<&'a Dir>, recurse: bool) -> IoResult<File<'a>> {
        fs::lstat(path).map(|stat| File::with_stat(stat, path, parent, recurse))
    }

    /// Create a new File object from the given Stat result, and other data.
    pub fn with_stat(stat: io::FileStat, path: &Path, parent: Option<&'a Dir>, recurse: bool) -> File<'a> {
        let filename = path_filename(path);

        // If we are recursing, then the `this` field contains a Dir object
        // that represents the current File as a directory, if it is a
        // directory. This is used for the --tree option.
        let this = if recurse && stat.kind == io::FileType::Directory {
            Dir::readdir(path).ok()
        }
        else {
            None
        };

        File {
            path:   path.clone(),
            dir:    parent,
            stat:   stat,
            ext:    ext(&filename),
            xattrs: xattr::llist(path).unwrap_or(Vec::new()),
            name:   filename.to_string(),
            this:   this,
        }
    }

    /// Whether this file is a dotfile or not.
    pub fn is_dotfile(&self) -> bool {
        self.name.starts_with(".")
    }

    /// Whether this file is a temporary file or not.
    pub fn is_tmpfile(&self) -> bool {
        let name = &self.name;
        name.ends_with("~") || (name.starts_with("#") && name.ends_with("#"))
    }

    /// Get the data for a column, formatted as a coloured string.
    pub fn display<U: Users>(&self, column: &Column, users_cache: &mut U, locale: &UserLocale) -> Cell {
        match *column {
            Permissions     => self.permissions_string(),
            FileSize(f)     => self.file_size(f, &locale.numeric),
            Timestamp(t, y) => self.timestamp(t, y, &locale.time),
            HardLinks       => self.hard_links(&locale.numeric),
            Inode           => self.inode(),
            Blocks          => self.blocks(&locale.numeric),
            User            => self.user(users_cache),
            Group           => self.group(users_cache),
            GitStatus       => self.git_status(),
        }
    }

    /// The "file name view" is what's displayed in the column and lines
    /// views, but *not* in the grid view.
    ///
    /// It consists of the file name coloured in the appropriate style,
    /// with special formatting for a symlink.
    pub fn file_name_view(&self) -> String {
        if self.stat.kind == io::FileType::Symlink {
            self.symlink_file_name_view()
        }
        else {
            self.file_colour().paint(&*self.name).to_string()
        }
    }

    /// If this file is a symlink, returns a string displaying its name,
    /// and an arrow pointing to the file it links to, which is also
    /// coloured in the appropriate style.
    ///
    /// If the symlink target doesn't exist, then instead of displaying
    /// an error, highlight the target and arrow in red. The error would
    /// be shown out of context, and it's almost always because the
    /// target doesn't exist.
    fn symlink_file_name_view(&self) -> String {
        let name = &*self.name;
        let style = self.file_colour();

        if let Ok(path) = fs::readlink(&self.path) {
            let target_path = match self.dir {
                Some(dir) => dir.join(path),
                None => path,
            };

            match self.target_file(&target_path) {
                Ok(file) => {

                    // Generate a preview for the path this symlink links to.
                    // The preview should consist of the directory of the file
                    // (if present) in cyan, an extra slash if necessary, then
                    // the target file, colourised in the appropriate style.
                    let mut path_prefix = String::new();

                    // The root directory has the name "/", which has to be
                    // catered for separately, otherwise there'll be two
                    // slashes in the resulting output.
                    if file.path.is_absolute() && file.name != "/" {
                        path_prefix.push_str("/");
                    }

                    let path_bytes: Vec<&[u8]> = file.path.components().collect();
                    if !path_bytes.is_empty() {
                        // Use init() to add all but the last component of the
                        // path to the prefix. init() panics when given an
                        // empty list, hence the check.
                        for component in path_bytes.init().iter() {
                            let string = String::from_utf8_lossy(component).to_string();
                            path_prefix.push_str(&string);
                            path_prefix.push_str("/");
                        }
                    }

                    format!("{} {} {}",
                            style.paint(name),
                            GREY.paint("=>"),
                            ANSIStrings(&[ Cyan.paint(&path_prefix),
                                           file.file_colour().paint(&file.name) ]))
                },
                Err(filename) => format!("{} {} {}",
                                         style.paint(name),
                                         Red.paint("=>"),
                                         Red.underline().paint(&filename)),
            }
        }
{'fraction_non_alphanumeric': 0.11906239227852465, 'fraction_numerical': 0.0007583591864874182, 'mean_word_length': 1.75674648422653, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 76, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6088163', 'n_tokens_mistral': 3969, 'n_tokens_neox': 3845, 'n_words': 1311}