[
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  push:\n    branches:\n      - main\n    tags:\n      - 'v*'\n  pull_request:\n    branches:\n      - main\n\nenv:\n  CARGO_TERM_VERBOSE: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Cache dependencies\n      uses: actions/cache@v4\n      with:\n        path: |\n          ~/.cargo/registry\n          ~/.cargo/git\n          target\n        key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n\n    - name: Run test\n      run: cargo test\n"
  },
  {
    "path": ".gitignore",
    "content": "target/\n*.tar.gz\ndot-*/\ncompletions/\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"dot\"\nversion = \"0.2.0-dev\"\npublish = false\ndescription = \"Alternative of dotfile management frameworks\"\nedition = \"2018\"\n\n[dependencies]\nansi_term = \"0.9\"\nclap = \"3\"\nclap_complete = \"3\"\nerror-chain = \"0.12.1\"\nregex = \"1.11\"\nshellexpand = \"1\"\ntoml = \"0.4\"\nurl = \"2.5\"\ndirs = \"2.0.2\"\n\n[target.'cfg(windows)'.dependencies]\nwinapi = \"0.2.8\"\nadvapi32-sys = \"0.2.0\"\nkernel32-sys = \"0.2.2\"\nrunas = \"0.1.1\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016 Yusuke Sasaki\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# `dot`\n\n![GitHub Actions](https://github.com/ubnt-intrepid/dot/workflows/Workflow/badge.svg)\n\n`dot` is a command-line tool for managing dotfiles, written in Rust.\n\n## Overview\n`dot` provides a way to organize configuration files in your home directory.\n\n## Installation\nPrecompiled binaries are on our [GitHub releases page](https://github.com/ubnt-intrepid/dot/releases/latest).\nIf you want to use the development version, try `cargo install` to build from source:\n\n```shell-session\n$ cargo install --git https://github.com/ubnt-intrepid/dot.git\n```\n\n## Example Usage\nClone your dotfiles repository from github and then create home directory symlinks:  \n```sh\n$ dot init ubnt-intrepid/dotfiles\n```\n\nCheck if all of the links exist and are correct:\n```sh\n$ dot check\n```\n\n`<pattern>` determines the remote repository's URL of dotfiles.\n\nPattern types:\n\n* `(http|https|ssh|git)://[username@]github.com[:port]/path-to-repo.git` – URL of dotfiles repository\n* `git@github.com:path-to-repo.git` – SCP-like path\n* `username/dotfiles` – GitHub user and repository\n* `username` – GitHub user only (repository `dotfiles`, e.g.: `https://github.com/myuser/dotfiles`)\n\nBy default, the repository will be cloned locally to `$HOME/.dotfiles`. This can be overridden with `$DOT_DIR`.\n\nFor more information, run `dot help`.\n\n## Configuration\n`$DOT_DIR/.mappings` where the symlinks are defined in [TOML](https://github.com/toml-lang/toml). For example:\n\n```toml\n[general]\ngitconfig   = \"~/.gitconfig\"\n\"vim/vimrc\" = [\"~/.vimrc\", \"~/.config/nvim/init.vim\"]\n#...\n\n[windows]\nvscode = \"$APPDATA/Code/User\"\npowershell = \"$HOME/Documents/WindowsPowerShell\"\n#...\n\n[linux]\nxinitrc = \"~/.xinitrc\"\n```\n\nUse `[general]` for symlinks on all platforms. `[windows]`, `[linux]`, `[macos]` for symlinks on specific platforms.\n\nSee [my dotfiles](https://github.com/ubnt-intrepid/dotfiles) for a real example.\n\n## License\n`dot` is distributed under the MIT license.\nSee [LICENSE](LICENSE) for details.\n\n## Similar Projects\n- [ssh0/dot](https://github.com/ssh0/dot)  \n  written in shell script\n- [rhysd/dotfiles](https://github.com/rhysd/dotfiles)  \n  written in Golang\n"
  },
  {
    "path": "ci/before_deploy.ps1",
    "content": "# This script takes care of packaging the build artifacts that will go in the\n# release zipfile\n\n$SRC_DIR = $PWD.Path\n$STAGE = [System.Guid]::NewGuid().ToString()\n\nSet-Location $ENV:Temp\nNew-Item -Type Directory -Name $STAGE\nSet-Location $STAGE\n\n$ZIP = \"$SRC_DIR\\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip\"\n\n# TODO Update this to package the right artifacts\nCopy-Item \"$SRC_DIR\\target\\$($Env:TARGET)\\release\\dot.exe\" '.\\'\n\n7z a \"$ZIP\" *\n\nPush-AppveyorArtifact \"$ZIP\"\n\nRemove-Item *.* -Force\nSet-Location ..\nRemove-Item $STAGE\nSet-Location $SRC_DIR\n"
  },
  {
    "path": "ci/before_deploy.sh",
    "content": "#!/bin/bash\n\n# This script takes care of building your crate and packaging it for release\n\nset -ex\n\nmain() {\n    local src=$(pwd) \\\n          stage=\n\n    case $TRAVIS_OS_NAME in\n        linux)\n            stage=$(mktemp -d)\n            ;;\n        osx)\n            stage=$(mktemp -d -t tmp)\n            ;;\n    esac\n\n    test -f Cargo.lock || cargo generate-lockfile\n\n    # TODO Update this to build the artifacts that matter to you\n    cross rustc --bin dot --target $TARGET --release -- -C lto\n\n    # TODO Update this to package the right artifacts\n    cp target/$TARGET/release/dot $stage/\n\n    cd $stage\n    tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *\n    cd $src\n\n    rm -rf $stage\n}\n\nmain\n"
  },
  {
    "path": "ci/install.sh",
    "content": "#!/bin/bash\n\n# copied from trust\n\nset -ex\n\nmain() {\n    local target=\n    if [ $TRAVIS_OS_NAME = linux ]; then\n        target=x86_64-unknown-linux-musl\n        sort=sort\n    else\n        target=x86_64-apple-darwin\n        sort=gsort  # for `sort --sort-version`, from brew's coreutils.\n    fi\n\n    # This fetches latest stable release\n    local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \\\n                       | cut -d/ -f3 \\\n                       | grep -E '^v[0.1.0-9.]+$' \\\n                       | $sort --version-sort \\\n                       | tail -n1)\n    curl -LSfs https://japaric.github.io/trust/install.sh | \\\n        sh -s -- \\\n           --force \\\n           --git japaric/cross \\\n           --tag $tag \\\n           --target $target\n}\n\nmain\n"
  },
  {
    "path": "ci/script.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n# TODO This is the \"test phase\", tweak it as you see fit\nmain() {\n    cross build --target $TARGET --release\n\n    if [ ! -z $DISABLE_TESTS ]; then\n        return\n    fi\n\n    cross test --target $TARGET --release\n}\n\n# we don't run the \"test phase\" when doing deploys\nif [ -z $TRAVIS_TAG ]; then\n    main\nfi\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"stable\"\nprofile = \"minimal\"\ncomponents = [ \"rustfmt\", \"clippy\" ]\n\n"
  },
  {
    "path": "scripts/bootstrap.sh",
    "content": "#!/bin/bash -e\n\n# Usage:\n# DOTURL=https://github.com/ubnt-intrepid/.dotfiles.git [PREFIX=$HOME/.local] ./bootstrap.sh\n\n# Repository URL of your dotfiles.\nDOT_URL=${DOT_URL:-\"https://github.com/ubnt-intrepid/.dotfiles.git\"}\n\n#\nDOT_DIR=${DOT_DIR:-\"$HOME/.dotfiles\"}\n\n# installation directory of `dot`\nPREFIX=${PREFIX:-\"$HOME/.local\"}\n\n\n# --- export as environment variables\nexport DOT_DIR\n\n\n# --- download `dot.rs` from GitHub Releases and install\ncase `uname -s | tr '[A-Z]' '[a-z]'` in\n  *mingw* | *msys*)\n    DOTRS_SUFFIX=\"`uname -m`-windows-msvc\"\n    ;;\n  *darwin*)\n    DOTRS_SUFFIX=\"`uname -m`-apple-darwin\"\n    ;;\n  *linux*)\n    DOTRS_SUFFIX=\"`uname -m`-unknown-linux-musl\"\n    ;;\n  *android*)\n    # TODO: support for other architectures\n    DOTRS_SUFFIX=\"arm-linux-androideabi\"\n    ;;\n  *)\n    echo \"[fatal] cannot recognize the platform.\"\n    exit 1\nesac\n\nDOTRS_URL=\"`curl -s https://api.github.com/repos/ubnt-intrepid/dot.rs/releases | grep browser_download_url | cut -d '\"' -f 4 | grep \"$DOTRS_SUFFIX\" | head -n 1`\"\necho \"$DOTRS_URL\"\n\nmkdir -p \"${PREFIX}/bin\"\ncurl -sL \"${DOTRS_URL}\" | tar xz -C \"$PREFIX/bin/\" --strip=1 './dot'\n\nexport PATH=\"$PREFIX/bin:$PATH\"\n\n# --- clone your dotfiles into home directory, and make links.\n[[ -d \"$DOT_DIR\" ]] || git clone \"$DOT_URL\" \"$DOT_DIR\"\ndot link --verbose\n"
  },
  {
    "path": "src/app.rs",
    "content": "use crate::dotfiles::Dotfiles;\nuse crate::errors::Result;\nuse crate::util;\nuse dirs;\nuse regex::Regex;\nuse std::borrow::Borrow;\nuse std::env;\nuse std::path::Path;\nuse url::Url;\n\n#[cfg(windows)]\nuse crate::windows;\n\npub struct App {\n    dotfiles: Dotfiles,\n    dry_run: bool,\n    verbose: bool,\n}\n\nimpl App {\n    pub fn new(dry_run: bool, verbose: bool) -> Result<App> {\n        let dotdir = init_envs()?;\n        let dotfiles = Dotfiles::new(Path::new(&dotdir).to_path_buf());\n        Ok(App {\n            dotfiles: dotfiles,\n            dry_run: dry_run,\n            verbose: verbose,\n        })\n    }\n\n    pub fn command_clone(&self, query: &str) -> Result<i32> {\n        let url = resolve_url(query)?;\n        let dotdir = self.dotfiles.root_dir().to_string_lossy();\n        util::wait_exec(\n            \"git\",\n            &[\"clone\", url.as_str(), dotdir.borrow()],\n            None,\n            self.dry_run,\n        )\n        .map_err(Into::into)\n    }\n\n    pub fn command_root(&self) -> Result<i32> {\n        println!(\"{}\", self.dotfiles.root_dir().display());\n        Ok(0)\n    }\n\n    pub fn command_check(&mut self) -> Result<i32> {\n        self.dotfiles.read_entries();\n\n        let mut num_unhealth = 0;\n        for entry in self.dotfiles.entries() {\n            if entry.check(self.verbose).unwrap() == false {\n                num_unhealth += 1;\n            }\n        }\n        Ok(num_unhealth)\n    }\n\n    pub fn command_link(&mut self) -> Result<i32> {\n        self.dotfiles.read_entries();\n\n        if !self.dry_run {\n            check_symlink_privilege();\n        }\n\n        for entry in self.dotfiles.entries() {\n            entry.mklink(self.dry_run, self.verbose).unwrap();\n        }\n\n        Ok(0)\n    }\n\n    pub fn command_clean(&mut self) -> Result<i32> {\n        self.dotfiles.read_entries();\n\n        for entry in self.dotfiles.entries() {\n            entry.unlink(self.dry_run, self.verbose).unwrap();\n        }\n\n        Ok(0)\n    }\n}\n\n#[cfg(windows)]\nfn check_symlink_privilege() {\n    use windows::ElevationType;\n\n    match windows::get_elevation_type().unwrap() {\n        ElevationType::Default => {\n            match windows::enable_privilege(\"SeCreateSymbolicLinkPrivilege\") {\n                Ok(_) => (),\n                Err(err) => panic!(\"failed to enable SeCreateSymbolicLinkPrivilege: {}\", err),\n            }\n        }\n        ElevationType::Limited => {\n            panic!(\"should be elevate as an Administrator.\");\n        }\n        ElevationType::Full => (),\n    }\n}\n\n#[cfg(not(windows))]\n#[inline]\npub fn check_symlink_privilege() {}\n\nfn init_envs() -> Result<String> {\n    if env::var(\"HOME\").is_err() {\n        env::set_var(\"HOME\", dirs::home_dir().unwrap());\n    }\n\n    let dotdir = env::var(\"DOT_DIR\")\n        .or(util::expand_full(\"$HOME/.dotfiles\"))\n        .map_err(|_| \"failed to determine dotdir\".to_string())?;\n    env::set_var(\"DOT_DIR\", dotdir.as_str());\n    env::set_var(\"dotdir\", dotdir.as_str());\n\n    Ok(dotdir)\n}\n\nfn resolve_url(s: &str) -> Result<Url> {\n    let re_scheme = Regex::new(r\"^([^:]+)://\").unwrap();\n    let re_scplike = Regex::new(r\"^((?:[^@]+@)?)([^:]+):/?(.+)$\").unwrap();\n\n    if let Some(cap) = re_scheme.captures(s) {\n        match cap.get(1).unwrap().as_str() {\n            \"http\" | \"https\" | \"ssh\" | \"git\" | \"file\" => Url::parse(s).map_err(Into::into),\n            scheme => Err(format!(\"'{}' is invalid scheme\", scheme).into()),\n        }\n    } else if let Some(cap) = re_scplike.captures(s) {\n        let username = cap\n            .get(1)\n            .and_then(|s| {\n                if s.as_str() != \"\" {\n                    Some(s.as_str())\n                } else {\n                    None\n                }\n            })\n            .unwrap_or(\"git@\");\n        let host = cap.get(2).unwrap().as_str();\n        let path = cap.get(3).unwrap().as_str();\n\n        Url::parse(&format!(\"ssh://{}{}/{}.git\", username, host, path)).map_err(Into::into)\n    } else {\n        let username = s\n            .splitn(2, \"/\")\n            .next()\n            .ok_or(\"'username' is unknown\".to_owned())?;\n        let reponame = s.splitn(2, \"/\").skip(1).next().unwrap_or(\"dotfiles\");\n        Url::parse(&format!(\"https://github.com/{}/{}.git\", username, reponame)).map_err(Into::into)\n    }\n}\n"
  },
  {
    "path": "src/dotfiles.rs",
    "content": "use crate::entry::Entry;\nuse crate::util;\nuse std::path::{Path, PathBuf};\nuse toml;\n\npub struct Dotfiles {\n    _root_dir: PathBuf,\n    _entries: Vec<Entry>,\n}\n\nimpl Dotfiles {\n    pub fn new(root_dir: PathBuf) -> Dotfiles {\n        Dotfiles {\n            _root_dir: root_dir,\n            _entries: Vec::new(),\n        }\n    }\n\n    pub fn read_entries(&mut self) {\n        self._entries = read_entries(self._root_dir.as_path());\n    }\n\n    pub fn root_dir(&self) -> &Path {\n        self._root_dir.as_path()\n    }\n\n    pub fn entries(&self) -> &[Entry] {\n        self._entries.as_slice()\n    }\n}\n\nfn read_entries(root_dir: &Path) -> Vec<Entry> {\n    let ref entries = util::read_toml(root_dir.join(\".mappings\")).unwrap();\n\n    let mut buf = Vec::new();\n    read_entries_from_key(&mut buf, entries, root_dir, \"general\");\n    read_entries_from_key(&mut buf, entries, root_dir, util::OS_NAME);\n\n    buf\n}\n\nfn new_entry(root_dir: &Path, key: &str, val: &str) -> Entry {\n    let src = util::expand_full(&format!(\"{}/{}\", root_dir.display(), key)).unwrap();\n\n    let mut dst = util::expand_full(val).unwrap();\n    if Path::new(&dst).is_relative() {\n        dst = util::expand_full(&format!(\"$HOME/{}\", val)).unwrap();\n    }\n\n    Entry::new(&src, &dst)\n}\n\nfn read_entries_from_key(\n    buf: &mut Vec<Entry>,\n    entries: &toml::value::Table,\n    root_dir: &Path,\n    key: &str,\n) {\n    if let Some(entries_table) = entries.get(key).and_then(|value| value.as_table()) {\n        for (ref key, ref val) in entries_table.iter() {\n            if let Some(val) = val.as_str() {\n                buf.push(new_entry(root_dir, key, val));\n            }\n            if let Some(val) = val.as_array() {\n                for v in val {\n                    if let Some(v) = v.as_str() {\n                        buf.push(new_entry(root_dir, key, v));\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::{read_entries_from_key, Dotfiles};\n    use crate::util;\n    use std::path::Path;\n\n    #[test]\n    fn smoke_test() {\n        let root_dir = Path::new(\"tests/dotfiles\").to_path_buf();\n        let mut dotfiles = Dotfiles::new(root_dir);\n        assert_eq!(Path::new(\"tests/dotfiles\"), dotfiles.root_dir());\n        dotfiles.read_entries();\n    }\n\n    #[test]\n    fn do_nothing_if_given_key_is_not_exist() {\n        let root_dir = Path::new(\"tests/dotfiles\").to_path_buf();\n        let entries = util::read_toml(root_dir.join(\".mappings\")).unwrap();\n\n        let mut buf = Vec::new();\n        read_entries_from_key(&mut buf, &entries, &root_dir, \"hogehoge\");\n        assert_eq!(buf.len(), 0);\n    }\n}\n"
  },
  {
    "path": "src/entry.rs",
    "content": "use crate::util;\nuse ansi_term;\nuse std::fs;\nuse std::io;\nuse std::path::{Path, PathBuf};\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum EntryStatus {\n    Healthy,\n    LinkNotCreated,\n    NotSymLink,\n    WrongLinkPath,\n}\n\n#[derive(Debug, Clone)]\npub struct Entry {\n    src: PathBuf,\n    dst: PathBuf,\n}\n\nimpl Entry {\n    pub fn new(src: &str, dst: &str) -> Entry {\n        Entry {\n            src: util::make_pathbuf(src),\n            dst: util::make_pathbuf(dst),\n        }\n    }\n\n    pub fn status(&self) -> Result<EntryStatus, io::Error> {\n        let status = if !self.dst.exists() {\n            EntryStatus::LinkNotCreated\n        } else if !util::is_symlink(&self.dst)? {\n            EntryStatus::NotSymLink\n        } else if self.src != self.dst.read_link()? {\n            EntryStatus::WrongLinkPath\n        } else {\n            EntryStatus::Healthy\n        };\n\n        Ok(status)\n    }\n\n    pub fn check(&self, verbose: bool) -> Result<bool, io::Error> {\n        let status = self.status()?;\n        if status != EntryStatus::Healthy {\n            println!(\n                \"{} {} ({:?})\",\n                ansi_term::Style::new()\n                    .bold()\n                    .fg(ansi_term::Colour::Red)\n                    .paint(\"✘\"),\n                self.dst.display(),\n                status\n            );\n            return Ok(false);\n        }\n        if verbose {\n            println!(\n                \"{} {}\\n  => {}\",\n                ansi_term::Style::new()\n                    .bold()\n                    .fg(ansi_term::Colour::Green)\n                    .paint(\"✓\"),\n                self.dst.display(),\n                self.src.display()\n            );\n        }\n        Ok(true)\n    }\n\n    pub fn mklink(&self, dry_run: bool, verbose: bool) -> Result<(), io::Error> {\n        if !self.src.exists() || self.status()? == EntryStatus::Healthy {\n            return Ok(()); // Do nothing.\n        }\n\n        if self.dst.exists() && !util::is_symlink(&self.dst)? {\n            let origpath = orig_path(&self.dst);\n            println!(\n                \"file {} has already existed. It will be renamed to {}\",\n                self.dst.display(),\n                origpath.display()\n            );\n            fs::rename(&self.dst, origpath)?;\n        }\n\n        if verbose {\n            println!(\"{}\\n  => {}\", self.dst.display(), self.src.display());\n        }\n        util::make_link(&self.src, &self.dst, dry_run)\n    }\n\n    pub fn unlink(&self, dry_run: bool, verbose: bool) -> Result<(), io::Error> {\n        if !self.dst.exists() || !util::is_symlink(&self.dst)? {\n            return Ok(()); // do nothing\n        }\n\n        if verbose {\n            println!(\"unlink {}\", self.dst.display());\n        }\n        util::remove_link(&self.dst, dry_run)?;\n\n        let origpath = orig_path(&self.dst);\n        if origpath.exists() {\n            fs::rename(origpath, &self.dst)?;\n        }\n\n        Ok(())\n    }\n}\n\nfn orig_path<P: AsRef<Path>>(path: P) -> PathBuf {\n    let origpath = format!(\"{}.bk\", path.as_ref().to_str().unwrap());\n    Path::new(&origpath).to_path_buf()\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "extern crate ansi_term;\nextern crate shellexpand;\nextern crate toml;\n#[macro_use]\nextern crate error_chain;\nextern crate regex;\nextern crate url;\n\n#[cfg(windows)]\nextern crate advapi32;\n#[cfg(windows)]\nextern crate kernel32;\n#[cfg(windows)]\nextern crate winapi;\n\npub mod app;\nmod dotfiles;\nmod entry;\npub mod util;\n#[cfg(windows)]\nmod windows;\n\nmod errors {\n    error_chain! {\n      foreign_links {\n        Io(::std::io::Error);\n        UrlParse(::url::ParseError);\n      }\n    }\n}\npub use crate::errors::*;\n\npub use crate::app::App;\n"
  },
  {
    "path": "src/main.rs",
    "content": "extern crate clap;\nextern crate dot;\n\nuse clap::{AppSettings, Arg, SubCommand};\nuse dot::App;\n\npub fn main() {\n    match run() {\n        Ok(retcode) => std::process::exit(retcode),\n        Err(err) => panic!(\"unknown error: {}\", err),\n    }\n}\n\npub fn run() -> dot::Result<i32> {\n    let matches = cli().get_matches();\n    let dry_run = matches.is_present(\"dry-run\");\n    let verbose = matches.is_present(\"verbose\");\n\n    let mut app = App::new(dry_run, verbose)?;\n\n    match matches.subcommand() {\n        Some((\"check\", _)) => app.command_check(),\n        Some((\"link\", _)) => app.command_link(),\n        Some((\"clean\", _)) => app.command_clean(),\n        Some((\"root\", _)) => app.command_root(),\n\n        Some((\"clone\", args)) => {\n            let url = args.value_of(\"url\").unwrap();\n            app.command_clone(url)\n        }\n\n        Some((\"init\", args)) => {\n            let url = args.value_of(\"url\").unwrap();\n            let ret = app.command_clone(url)?;\n            if ret != 0 {\n                return Ok(ret);\n            }\n            app.command_link()\n        }\n\n        Some((\"completion\", args)) => {\n            let shell: clap_complete::Shell = args.value_of_t_or_exit(\"shell\");\n            clap_complete::generate(\n                shell,\n                &mut cli(),\n                env!(\"CARGO_PKG_NAME\"),\n                &mut std::io::stdout(),\n            );\n            Ok(0)\n        }\n\n        Some(..) | None => unreachable!(),\n    }\n}\n\nfn cli() -> clap::Command<'static> {\n    clap::Command::new(env!(\"CARGO_PKG_NAME\"))\n        .about(env!(\"CARGO_PKG_DESCRIPTION\"))\n        .version(env!(\"CARGO_PKG_VERSION\"))\n        .author(env!(\"CARGO_PKG_AUTHORS\"))\n        .setting(AppSettings::SubcommandRequiredElseHelp)\n        .arg(\n            Arg::with_name(\"verbose\")\n                .help(\"Use verbose output\")\n                .long(\"verbose\")\n                .short('v'),\n        )\n        .arg(\n            Arg::with_name(\"dry-run\")\n                .help(\"do not actually perform I/O operations\")\n                .long(\"dry-run\")\n                .short('n'),\n        )\n        .subcommand(\n            SubCommand::with_name(\"check\")\n                .about(\"Check the files are correctly linked to the right places\"),\n        )\n        .subcommand(\n            SubCommand::with_name(\"link\")\n                .about(\"Create all of the symbolic links into home directory\"),\n        )\n        .subcommand(\n            SubCommand::with_name(\"clean\")\n                .about(\"Remote all of registered links from home directory\"),\n        )\n        .subcommand(\n            SubCommand::with_name(\"root\")\n                .about(\"Show the location of dotfiles repository and exit\"),\n        )\n        .subcommand(\n            SubCommand::with_name(\"clone\")\n                .about(\"Clone dotfiles repository from remote\")\n                .arg(\n                    Arg::with_name(\"url\")\n                        .help(\"URL of remote repository\")\n                        .required(true)\n                        .takes_value(true),\n                ),\n        )\n        .subcommand(\n            SubCommand::with_name(\"init\")\n                .about(\"Clone dotfiles repository from remote & make links\")\n                .arg(\n                    Arg::with_name(\"url\")\n                        .help(\"URL of remote repository\")\n                        .required(true)\n                        .takes_value(true),\n                ),\n        )\n        .subcommand(\n            SubCommand::with_name(\"completion\")\n                .about(\"Generate completion scripts\")\n                .setting(AppSettings::ArgRequiredElseHelp)\n                .arg(\n                    Arg::with_name(\"shell\")\n                        .help(\"target shell\")\n                        .required(true)\n                        .possible_values(&[\"bash\", \"fish\", \"zsh\", \"powershell\"]),\n                ),\n        )\n}\n"
  },
  {
    "path": "src/util.rs",
    "content": "use shellexpand::{self, LookupError};\nuse std::env;\nuse std::fs::{self, File};\nuse std::io::{self, Read};\nuse std::path::{Path, PathBuf, MAIN_SEPARATOR};\nuse std::process::{Command, Stdio};\nuse toml;\n\n#[allow(dead_code)]\npub fn wait_exec(\n    cmd: &str,\n    args: &[&str],\n    curr_dir: Option<&Path>,\n    dry_run: bool,\n) -> Result<i32, io::Error> {\n    if dry_run {\n        println!(\"{} {:?} (@ {:?})\", cmd, args, curr_dir);\n        return Ok(0);\n    }\n\n    let mut command = Command::new(cmd);\n    command\n        .args(args)\n        .stdin(Stdio::inherit())\n        .stdout(Stdio::inherit())\n        .stderr(Stdio::inherit());\n    if let Some(curr_dir) = curr_dir {\n        command.current_dir(curr_dir);\n    }\n\n    let mut child = command.spawn()?;\n    child\n        .wait()\n        .and_then(|st| st.code().ok_or(io::Error::new(io::ErrorKind::Other, \"\")))\n}\n\npub fn expand_full(s: &str) -> Result<String, LookupError<env::VarError>> {\n    shellexpand::full(s).map(|s| s.into_owned())\n}\n\n#[cfg(windows)]\nfn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<(), io::Error> {\n    use std::os::windows::fs;\n    if src.as_ref().is_dir() {\n        fs::symlink_dir(src, dst)\n    } else {\n        fs::symlink_file(src, dst)\n    }\n}\n\n#[cfg(not(windows))]\nfn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<(), io::Error> {\n    use std::os::unix::fs::symlink;\n    symlink(src, dst)\n}\n\npub fn make_link<P, Q>(src: P, dst: Q, dry_run: bool) -> Result<(), io::Error>\nwhere\n    P: AsRef<Path>,\n    Q: AsRef<Path>,\n{\n    if dry_run {\n        println!(\n            \"make_link({}, {})\",\n            src.as_ref().display(),\n            dst.as_ref().display()\n        );\n        Ok(())\n    } else {\n        fs::create_dir_all(dst.as_ref().parent().unwrap())?;\n        symlink(src, dst)\n    }\n}\n\n#[cfg(windows)]\nfn unlink<P: AsRef<Path>>(dst: P) -> Result<(), io::Error> {\n    if dst.as_ref().is_dir() {\n        fs::remove_dir(dst)\n    } else {\n        fs::remove_file(dst)\n    }\n}\n\n#[cfg(not(windows))]\nfn unlink<P: AsRef<Path>>(dst: P) -> Result<(), io::Error> {\n    fs::remove_file(dst)\n}\n\npub fn remove_link<P: AsRef<Path>>(dst: P, dry_run: bool) -> Result<(), io::Error> {\n    if dry_run {\n        println!(\"fs::remove_file {}\", dst.as_ref().display());\n        Ok(())\n    } else {\n        unlink(dst)\n    }\n}\n\npub fn read_toml<P: AsRef<Path>>(path: P) -> Result<toml::value::Table, io::Error> {\n    let mut file = File::open(path)?;\n\n    let mut buf = Vec::new();\n    file.read_to_end(&mut buf)?;\n\n    let content = String::from_utf8_lossy(&buf[..]).into_owned();\n    toml::de::from_str(&content).map_err(|_| {\n        io::Error::new(\n            io::ErrorKind::Other,\n            \"failed to parse configuration file as TOML\",\n        )\n    })\n}\n\n#[cfg(target_os = \"windows\")]\npub static OS_NAME: &'static str = \"windows\";\n\n#[cfg(target_os = \"macos\")]\npub static OS_NAME: &'static str = \"darwin\";\n\n#[cfg(target_os = \"linux\")]\npub static OS_NAME: &'static str = \"linux\";\n\n#[cfg(target_os = \"android\")]\npub static OS_NAME: &'static str = \"linux\";\n\n#[cfg(target_os = \"freebsd\")]\npub static OS_NAME: &'static str = \"freebsd\";\n\n#[cfg(target_os = \"openbsd\")]\npub static OS_NAME: &'static str = \"openbsd\";\n\n// create an instance of PathBuf from string.\npub fn make_pathbuf(path: &str) -> PathBuf {\n    let path = path.replace(\"/\", &format!(\"{}\", MAIN_SEPARATOR));\n    Path::new(&path).to_path_buf()\n}\n\npub fn is_symlink<P: AsRef<Path>>(path: P) -> Result<bool, io::Error> {\n    let meta = path.as_ref().symlink_metadata()?;\n    Ok(meta.file_type().is_symlink())\n}\n"
  },
  {
    "path": "src/windows.rs",
    "content": "//\n\n#![allow(non_camel_case_types)]\n#![allow(non_snake_case)]\n#![allow(dead_code)]\n#![allow(improper_ctypes)]\n\nuse advapi32;\nuse kernel32;\nuse std::ffi::CString;\nuse std::mem;\nuse std::os::raw::c_void;\nuse std::ptr::{null, null_mut};\nuse winapi::winerror::ERROR_SUCCESS;\nuse winapi::winnt;\nuse winapi::LUID;\n\ntype BYTE = u8;\ntype BOOL = i32;\ntype DWORD = u32;\n\n#[repr(C)]\nstruct TOKEN_ELEVATION {\n    TokenIsElevated: DWORD,\n}\n\ntype TOKEN_ELEVATION_TYPE = u32;\n\n#[repr(C)]\nstruct TOKEN_GROUPS {\n    GroupCount: DWORD,\n    Groups: [SID_AND_ATTRIBUTES; 0],\n}\n\n#[repr(C)]\nstruct SID_AND_ATTRIBUTES {\n    Sid: PSID,\n    Attributes: DWORD,\n}\n\n#[repr(C)]\nstruct SID_IDENTIFIER_AUTHORITY {\n    Value: [BYTE; 6],\n}\n\n#[repr(C)]\n#[allow(improper_ctypes)]\nstruct SID;\n\ntype PSID = *mut SID;\ntype PSID_IDENTIFIER_AUTHORITY = *mut SID_IDENTIFIER_AUTHORITY;\n\n#[allow(dead_code)]\nenum TOKEN_INFORMATION_CLASS {\n    TokenUser = 1,\n    TokenGroups,\n    TokenPrivileges,\n    TokenOwner,\n    TokenPrimaryGroup,\n    TokenDefaultDacl,\n    TokenSource,\n    TokenType,\n    TokenImpersonationLevel,\n    TokenStatistics,\n    TokenRestrictedSids,\n    TokenSessionId,\n    TokenGroupsAndPrivileges,\n    TokenSessionReference,\n    TokenSandBoxInert,\n    TokenAuditPolicy,\n    TokenOrigin,\n    TokenElevationType,\n    TokenLinkedToken,\n    TokenElevation,\n    TokenHasRestrictions,\n    TokenAccessInformation,\n    TokenVirtualizationAllowed,\n    TokenVirtualizationEnabled,\n    TokenIntegrityLevel,\n    TokenUIAccess,\n    TokenMandatoryPolicy,\n    TokenLogonSid,\n    TokenIsAppContainer,\n    TokenCapabilities,\n    TokenAppContainerSid,\n    TokenAppContainerNumber,\n    TokenUserClaimAttributes,\n    TokenDeviceClaimAttributes,\n    TokenRestrictedUserClaimAttributes,\n    TokenRestrictedDeviceClaimAttributes,\n    TokenDeviceGroups,\n    TokenRestrictedDeviceGroups,\n    TokenSecurityAttributes,\n    TokenIsRestricted,\n    MaxTokenInfoClass,\n}\n\nextern \"system\" {\n    fn GetTokenInformation(\n        TokenHandle: winnt::HANDLE,\n        TokenInformationClass: DWORD,\n        TokenInformation: *mut c_void,\n        TokenInformationLength: DWORD,\n        ReturnLength: *mut DWORD,\n    ) -> BOOL;\n\n    fn IsUserAnAdmin() -> BOOL;\n\n    fn AllocateAndInitializeSid(\n        pIdentifierAuthority: PSID_IDENTIFIER_AUTHORITY,\n        nSubAuthorityCount: BYTE,\n        dwSubAuthority0: DWORD,\n        dwSubAuthority1: DWORD,\n        dwSubAuthority2: DWORD,\n        dwSubAuthority3: DWORD,\n        dwSubAuthority4: DWORD,\n        dwSubAuthority5: DWORD,\n        dwSubAuthority6: DWORD,\n        dwSubAuthority7: DWORD,\n        pSid: *mut PSID,\n    ) -> BOOL;\n    fn FreeSid(pSid: PSID) -> *mut c_void;\n\n    fn CheckTokenMembership(\n        TokenHandle: winnt::HANDLE,\n        SidToCheck: PSID,\n        IsMember: *mut BOOL,\n    ) -> BOOL;\n}\n\nstruct Handle(winnt::HANDLE);\n\nimpl Handle {\n    fn new(h: winnt::HANDLE) -> Handle {\n        Handle(h)\n    }\n\n    fn as_raw(&self) -> winnt::HANDLE {\n        self.0\n    }\n}\n\nimpl Drop for Handle {\n    fn drop(&mut self) {\n        unsafe { kernel32::CloseHandle(self.0) };\n        self.0 = null_mut();\n    }\n}\n\nstruct Sid(PSID);\n\nimpl Sid {\n    fn as_raw(&self) -> PSID {\n        self.0\n    }\n}\n\nimpl Drop for Sid {\n    fn drop(&mut self) {\n        unsafe { FreeSid(self.0) };\n        self.0 = null_mut();\n    }\n}\n\npub fn enable_privilege(name: &str) -> Result<(), &'static str> {\n    // 1. retrieve the process token of current process.\n    let token = open_process_token(winnt::TOKEN_ADJUST_PRIVILEGES | winnt::TOKEN_QUERY)?;\n\n    // 2. retrieve a LUID for given priviledge\n    let luid = lookup_privilege_value(name)?;\n\n    let len = mem::size_of::<winnt::TOKEN_PRIVILEGES>()\n        + 1 * mem::size_of::<winnt::LUID_AND_ATTRIBUTES>();\n    let token_privileges = vec![0u8; len];\n    unsafe {\n        let mut p = token_privileges.as_ptr() as *mut winnt::TOKEN_PRIVILEGES;\n        let mut la = (*p).Privileges.as_ptr() as *mut winnt::LUID_AND_ATTRIBUTES;\n        (*p).PrivilegeCount = 1;\n        (*la).Luid = luid;\n        (*la).Attributes = winnt::SE_PRIVILEGE_ENABLED;\n    }\n\n    unsafe {\n        advapi32::AdjustTokenPrivileges(\n            token.as_raw(),\n            0,\n            token_privileges.as_ptr() as *mut winnt::TOKEN_PRIVILEGES,\n            0,\n            null_mut(),\n            null_mut(),\n        );\n    }\n\n    match unsafe { kernel32::GetLastError() } {\n        ERROR_SUCCESS => Ok(()),\n        _ => Err(\"failed to adjust token privilege\"),\n    }\n}\n\npub fn is_elevated() -> Result<bool, &'static str> {\n    let token = open_process_token(winnt::TOKEN_QUERY)?;\n\n    let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 };\n    let mut cb_size: u32 = mem::size_of_val(&elevation) as u32;\n    let ret = unsafe {\n        GetTokenInformation(\n            token.as_raw(),\n            mem::transmute::<_, u8>(TOKEN_INFORMATION_CLASS::TokenElevation) as u32,\n            mem::transmute(&mut elevation),\n            mem::size_of_val(&elevation) as u32,\n            &mut cb_size,\n        )\n    };\n    if ret == 0 {\n        return Err(\"failed to get token information\");\n    }\n\n    Ok(elevation.TokenIsElevated != 0)\n}\n\n#[derive(Debug, PartialEq)]\npub enum ElevationType {\n    Default = 1,\n    Full,\n    Limited,\n}\n\npub fn get_elevation_type() -> Result<ElevationType, &'static str> {\n    let token = open_process_token(winnt::TOKEN_QUERY)?;\n\n    let mut elev_type = 0;\n    let mut cb_size = mem::size_of_val(&elev_type) as u32;\n    let ret = unsafe {\n        GetTokenInformation(\n            token.as_raw(),\n            mem::transmute::<_, u8>(TOKEN_INFORMATION_CLASS::TokenElevationType) as u32,\n            mem::transmute(&mut elev_type),\n            mem::size_of_val(&elev_type) as u32,\n            &mut cb_size,\n        )\n    };\n    if ret == 0 {\n        return Err(\"failed to get token information\");\n    }\n\n    match elev_type {\n        1 => Ok(ElevationType::Default), // default (standard user/ administrator without UAC)\n        2 => Ok(ElevationType::Full),    // full access (administrator, not elevated)\n        3 => Ok(ElevationType::Limited), // limited access (administrator, not elevated)\n        _ => Err(\"unknown elevation type\"),\n    }\n}\n\nfn open_process_token(token_type: u32) -> Result<Handle, &'static str> {\n    let mut h_token = null_mut();\n    let ret = unsafe {\n        advapi32::OpenProcessToken(kernel32::GetCurrentProcess(), token_type, &mut h_token)\n    };\n    match ret {\n        0 => Err(\"failed to get process token\"),\n        _ => Ok(Handle::new(h_token)),\n    }\n}\n\nfn lookup_privilege_value(name: &str) -> Result<LUID, &'static str> {\n    let mut luid = LUID {\n        LowPart: 0,\n        HighPart: 0,\n    };\n    let ret = unsafe {\n        let name = CString::new(name).unwrap();\n        advapi32::LookupPrivilegeValueA(null(), name.as_ptr(), &mut luid)\n    };\n    match ret {\n        0 => Err(\"failed to get the privilege value\"),\n        _ => Ok(luid),\n    }\n}\n"
  },
  {
    "path": "templates/mappings-example.toml",
    "content": "# vim: set ft=toml ts=2 sw=2 et :\n\n[general]\n\"tmux.conf\"     = \"~/.tmux.conf\"\ngitconfig       = \"~/.gitconfig\"\ntigrc           = \"~/.tigrc\"\nzsh             = \"~/.config/zsh\"\n\"zsh/zshenv\"    = \"~/.zshenv\"\n\"zsh/zshrc\"     = \"~/.zshrc\"\nvim             = \"~/.config/vim\"\n\"vim/vimrc\"     = \"~/.vimrc\"\n\"vim/gvimrc\"    = \"~/.gvimrc\"\n\n[windows]\natom                   = \"~/.config/atom\"\nmintty                 = \"~/.config/mintty\"\n\"vscode/settings.json\" = \"$APPDATA/Code/User/settings.json\"\n\"vscode/locale.json\"   = \"$APPDATA/Code/User/locale.json\"\npowershell             = \"~/Documents/WindowsPowerShell\"\nconsolez               = \"$APPDATA/Console\"\n\"ConEmu.xml\"           = \"$APPDATA/ConEmu.xml\"\n\n[linux]\nneovim  = \"~/.config/nvim\"\nxinitrc = \"~/.xinitrc\"\ntermux  = \"~/.termux\"\n\"virtualenvwrapper/postactivate\" = \"~/.virtualenvs/postactivate\"\n\"virtualenvwrapper/postdeactivate\" = \"~/.virtualenvs/postdeactivate\"\n"
  },
  {
    "path": "tests/dotfiles/.mappings",
    "content": "# vim: set ft=toml ts=2 sw=2 et :\n\n[general]\n\"tmux.conf\"     = \"~/.tmux.conf\"\ngitconfig       = [\"~/.gitconfig\", \"~/.config/git/config\"]\ntigrc           = \"~/.tigrc\"\nzsh             = \"~/.config/zsh\"\n\"zsh/zshenv\"    = \"~/.zshenv\"\n\"zsh/zshrc\"     = \"~/.zshrc\"\nvim             = [\"~/.vim\", \"~/.config/nvim\"]\n\"vim/vimrc\"     = \"~/.vimrc\"\n\"vim/gvimrc\"    = \"~/.gvimrc\"\n\n[windows]\natom                   = \"~/.config/atom\"\nmintty                 = \"~/.config/mintty\"\n\"vscode/settings.json\" = \"$APPDATA/Code/User/settings.json\"\n\"vscode/locale.json\"   = \"$APPDATA/Code/User/locale.json\"\npowershell             = \"~/Documents/WindowsPowerShell\"\nconsolez               = \"$APPDATA/Console\"\n\"ConEmu.xml\"           = \"$APPDATA/ConEmu.xml\"\n\n[linux]\nneovim  = \"~/.config/nvim\"\nxinitrc = \"~/.xinitrc\"\ntermux  = \"~/.termux\"\n\"virtualenvwrapper/postactivate\" = \"~/.virtualenvs/postactivate\"\n\"virtualenvwrapper/postdeactivate\" = \"~/.virtualenvs/postdeactivate\"\n"
  }
]