use std::env::temp_dir;
use std::ffi::OsStr;
use std::fs::{self, write};
use std::io::{BufReader, Read, Seek, Write};
use std::path::{Path, PathBuf};
use std::str;
use phf::{phf_map, Map};
use rand::{distributions::Alphanumeric, Rng};
use gistit_project::var::GISTIT_MAX_SIZE;
use crate::Result;
pub const EXTENSION_TO_LANG_MAPPING: Map<&'static str, &'static str> = phf_map! {
"abap" => "abap",
"as" => "actionscript",
"ada" => "ada",
"adb" => "ada",
"ads" => "ada",
"agda" => "agda",
"als" => "al",
"g4" => "antlr4",
"apacheconf" => "apacheconf",
"vhost" => "apacheconf",
"apl" => "apl",
"dyalog" => "apl",
"applescript" => "applescript",
"scpt" => "applescript",
"ino" => "arduino",
"asciidoc" => "asciidoc",
"adoc" => "asciidoc",
"asc" => "asciidoc",
"asm" => "asm6502",
"a51" => "asm6502",
"nasm" => "asm6502",
"asp" => "aspnet",
"asax" => "aspnet",
"ascx" => "aspnet",
"ashx" => "aspnet",
"asmx" => "aspnet",
"aspx" => "aspnet",
"axd" => "aspnet",
"md" => "markdown",
"ts" => "typescript",
"rs" => "rust",
"toml" => "toml",
"ahk" => "autohotkey",
"ahkl" => "autohotkey",
"au3" => "autoit",
"sh" => "bash",
"bash" => "bash",
"bats" => "bash",
"cgi" => "bash",
"command" => "bash",
"fcgi" => "bash",
"ksh" => "bash",
"sh.in" => "bash",
"tmux" => "bash",
"tool" => "bash",
"zsh" => "bash",
"vb" => "basic",
"bas" => "basic",
"cls" => "basic",
"frm" => "basic",
"frx" => "basic",
"vba" => "basic",
"vbhtml" => "basic",
"vbs" => "basic",
"bat" => "batch",
"cmd" => "batch",
"bb" => "bbcode",
"bison" => "bison",
"b" => "brainfuck",
"bf" => "brainfuck",
"brs" => "brightscript",
"bro" => "bro",
"bsl" => "bsl",
"c" => "c",
"cats" => "c",
"idc" => "c",
"w" => "c",
"cil" => "cil",
"clike" => "clike",
"clj" => "clojure",
"boot" => "clojure",
"cl2" => "clojure",
"cljc" => "clojure",
"cljs" => "clojure",
"cljs.hl" => "clojure",
"cljscm" => "clojure",
"cljx" => "clojure",
"hic" => "clojure",
"cmake" => "cmake",
"cmake.in" => "cmake",
"coffee" => "coffeescript",
"_coffee" => "coffeescript",
"cjsx" => "coffeescript",
"cson" => "coffeescript",
"iced" => "coffeescript",
"conc" => "concurnas",
"cpp" => "cpp",
"c++" => "cpp",
"cc" => "cpp",
"cp" => "cpp",
"cxx" => "cpp",
"h" => "cpp",
"h++" => "cpp",
"hh" => "cpp",
"hpp" => "cpp",
"hxx" => "cpp",
"inc" => "cpp",
"inl" => "cpp",
"ipp" => "cpp",
"tcc" => "cpp",
"tpp" => "cpp",
"cr" => "crystal",
"csx" => "csharp",
"cshtml" => "csharp",
"csp" => "csp",
"css" => "css",
"cypher" => "cypher",
"d" => "d",
"di" => "d",
"dart" => "dart",
"dax" => "dax",
"dhall" => "dhall",
"diff" => "diff",
"patch" => "diff",
"mustache" => "django",
"jinja" => "django",
"zone" => "dnsZoneFile",
"arpa" => "dnsZoneFile",
"dockerfile" => "docker",
"ebnf" => "ebnf",
"editorconfig" => "editorconfig",
"e" => "eiffel",
"ejs" => "ejs",
"ex" => "elixir",
"exs" => "elixir",
"elm" => "elm",
"erb" => "erb",
"erb.deface" => "erb",
"erl" => "erlang",
"es" => "erlang",
"escript" => "erlang",
"hrl" => "erlang",
"xrl" => "erlang",
"yrl" => "erlang",
"etlua" => "etlua",
"xls" => "excelFormula",
"xlsx" => "excelFormula",
"factor" => "factor",
"rules" => "firestoreSecurityRules",
"flow" => "flow",
"f90" => "fortran",
"f" => "fortran",
"f03" => "fortran",
"f08" => "fortran",
"f77" => "fortran",
"f95" => "fortran",
"for" => "fortran",
"fpp" => "fortran",
"fs" => "fsharp",
"fsi" => "fsharp",
"fsx" => "fsharp",
"ftl" => "ftl",
"g" => "gcode",
"gco" => "gcode",
"gcode" => "gcode",
"gd" => "gdscript",
"gedcom" => "gedcom",
"feature" => "gherkin",
"git" => "git",
"glsl" => "glsl",
"fp" => "glsl",
"frag" => "glsl",
"frg" => "glsl",
"fsh" => "glsl",
"fshader" => "glsl",
"geo" => "glsl",
"geom" => "glsl",
"glslv" => "glsl",
"gshader" => "glsl",
"shader" => "glsl",
"vert" => "glsl",
"vrx" => "glsl",
"vsh" => "glsl",
"vshader" => "glsl",
"gml" => "gml",
"go" => "go",
"graphql" => "graphql",
"groovy" => "groovy",
"grt" => "groovy",
"gtpl" => "groovy",
"gvy" => "groovy",
"haml" => "haml",
"haml.deface" => "haml",
"handlebars" => "handlebars",
"hbs" => "handlebars",
"hs" => "haskell",
"hsc" => "haskell",
"hx" => "haxe",
"hxsl" => "haxe",
"hcl" => "hcl",
"tf" => "hcl",
"hlsl" => "hlsl",
"fx" => "hlsl",
"fxh" => "hlsl",
"hlsli" => "hlsl",
"hpkp" => "hpkp",
"hsts" => "hsts",
"http" => "http",
"ico" => "icon",
"ni" => "inform7",
"i7x" => "inform7",
"ini" => "ini",
"cfg" => "ini",
"prefs" => "ini",
"pro" => "ini",
"io" => "io",
"j" => "j",
"java" => "java",
"js" => "javascript",
"_js" => "javascript",
"bones" => "javascript",
"es6" => "javascript",
"gs" => "javascript",
"jake" => "javascript",
"jsb" => "javascript",
"jscad" => "javascript",
"jsfl" => "javascript",
"jsm" => "javascript",
"jss" => "javascript",
"njs" => "javascript",
"pac" => "javascript",
"sjs" => "javascript",
"ssjs" => "javascript",
"sublime-build" => "javascript",
"sublime-commands" => "javascript",
"sublime-completions" => "javascript",
"sublime-keymap" => "javascript",
"sublime-macro" => "javascript",
"sublime-menu" => "javascript",
"sublime-mousemap" => "javascript",
"sublime-project" => "javascript",
"sublime-settings" => "javascript",
"sublime-theme" => "javascript",
"sublime-workspace" => "javascript",
"sublime_metrics" => "javascript",
"sublime_session" => "javascript",
"xsjs" => "javascript",
"xsjslib" => "javascript",
"jolie" => "jolie",
"jq" => "jq",
"json" => "json",
"geojson" => "json",
"lock" => "json",
"topojson" => "json",
"json5" => "json5",
"jsonp" => "jsonp",
"jsx" => "jsx",
"jl" => "julia",
"keyman" => "keyman",
"kt" => "kotlin",
"ktm" => "kotlin",
"kts" => "kotlin",
"latex" => "latex",
"latte" => "latte",
"less" => "less",
"ly" => "lilypond",
"ily" => "lilypond",
"liquid" => "liquid",
"nl" => "lisp",
"lisp" => "lisp",
"lsp" => "lisp",
"ls" => "livescript",
"_ls" => "livescript",
"ll" => "llvm",
"lol" => "lolcode",
"lua" => "lua",
"nse" => "lua",
"pd_lua" => "lua",
"rbxs" => "lua",
"wlua" => "lua",
"mak" => "makefile",
"mk" => "makefile",
"mkfile" => "makefile",
"matlap" => "matlab",
"m" => "matlab",
"mel" => "mel",
"mizar" => "mizar",
"monkey" => "monkey",
"moon" => "moonscript",
"n1ql" => "n1ql",
"n4js" => "n4js",
"nand2tetrisHdl" => "nand2tetrisHdl",
"naniscript" => "naniscript",
"neon" => "neon",
"nginxconf" => "nginx",
"nim" => "nim",
"nimrod" => "nim",
"nix" => "nix",
"nsi" => "nsis",
"nsh" => "nsis",
"mm" => "objectivec",
"ml" => "ocaml",
"eliom" => "ocaml",
"eliomi" => "ocaml",
"ml4" => "ocaml",
"mli" => "ocaml",
"mll" => "ocaml",
"mly" => "ocaml",
"opencl" => "opencl",
"cl" => "opencl",
"oz" => "oz",
"parigp" => "parigp",
"parser" => "parser",
"pas" => "pascal",
"dfm" => "pascal",
"dpr" => "pascal",
"ipr" => "pascal",
"pcaxis" => "pcaxis",
"peoplecode" => "peoplecode",
"pl" => "perl",
"al" => "perl",
"perl" => "perl",
"ph" => "perl",
"plx" => "perl",
"pm" => "perl",
"pod" => "perl",
"psgi" => "perl",
"6pl" => "perl",
"6pm" => "perl",
"nqd" => "perl",
"p6" => "perl",
"p6l" => "perl",
"p6m" => "perl",
"pm6" => "perl",
"php" => "php",
"pls" => "plsql",
"pck" => "plsql",
"pkb" => "plsql",
"pks" => "plsql",
"plb" => "plsql",
"plsql" => "plsql",
"powerquery" => "powerquery",
"ps1" => "powershell",
"psd1" => "powershell",
"psm1" => "powershell",
"pde" => "processing",
"prolog" => "prolog",
"yap" => "prolog",
"properties" => "properties",
"proto" => "protobuf",
"pug" => "pug",
"pp" => "puppet",
"pd" => "pure",
"pb" => "purebasic",
"pbi" => "purebasic",
"purs" => "purescript",
"py" => "python",
"bzl" => "python",
"gyp" => "python",
"lmi" => "python",
"pyde" => "python",
"pyp" => "python",
"pyt" => "python",
"pyw" => "python",
"rpy" => "python",
"tac" => "python",
"wsgi" => "python",
"xpy" => "python",
"q" => "q",
"qml" => "qml",
"qbs" => "qml",
"qore" => "qore",
"r" => "r",
"rd" => "r",
"rsx" => "r",
"rkt" => "racket",
"rktd" => "racket",
"rktl" => "racket",
"scrbl" => "racket",
"re" => "reason",
"regex" => "regex",
"renpy" => "renpy",
"rst" => "rest",
"rest" => "rest",
"rest.txt" => "rest",
"rst.txt" => "rest",
"rip" => "rip",
"roboconf" => "roboconf",
"robotframework" => "robotframework",
"rb" => "ruby",
"builder" => "ruby",
"gemspec" => "ruby",
"god" => "ruby",
"irbrc" => "ruby",
"jbuilder" => "ruby",
"mspec" => "ruby",
"pluginspec" => "ruby",
"podspec" => "ruby",
"rabl" => "ruby",
"rake" => "ruby",
"rbuild" => "ruby",
"rbw" => "ruby",
"rbx" => "ruby",
"ru" => "ruby",
"ruby" => "ruby",
"thor" => "ruby",
"watchr" => "ruby",
"sas" => "sas",
"sass" => "sass",
"sbt" => "scala",
"scala" => "scala",
"sc" => "scala",
"scm" => "scheme",
"sld" => "scheme",
"sls" => "scheme",
"sps" => "scheme",
"ss" => "scheme",
"scss" => "scss",
"sh-session" => "shellSession",
"smali" => "smali",
"st" => "smalltalk",
"cs" => "smalltalk",
"tpl" => "smarty",
"sml" => "sml",
"sol" => "solidity",
"soy" => "soy",
"sparql" => "sparql",
"rq" => "sparql",
"splunk" => "splunkSpl",
"sqf" => "sqf",
"hqf" => "sqf",
"sql" => "sql",
"cql" => "sql",
"ddl" => "sql",
"prc" => "sql",
"tab" => "sql",
"udf" => "sql",
"viw" => "sql",
"stan" => "stan",
"styl" => "stylus",
"swift" => "swift",
"t4cs" => "t4Cs",
"t4" => "t4Vb",
"tap" => "tap",
"tcl" => "tcl",
"adp" => "tcl",
"tm" => "tcl",
"textile" => "textile",
"tsx" => "tsx",
"tt2" => "tt2",
"ttl" => "turtle",
"twig" => "twig",
"typoscript" => "typoscript",
"uc" => "unrealscript",
"vala" => "vala",
"vapi" => "vala",
"vbnet" => "vbnet",
"velocity" => "velocity",
"v" => "verilog",
"veo" => "verilog",
"vhdl" => "vhdl",
"vhd" => "vhdl",
"vhf" => "vhdl",
"vhi" => "vhdl",
"vho" => "vhdl",
"vhs" => "vhdl",
"vht" => "vhdl",
"vhw" => "vhdl",
"vim" => "vim",
"warpscript" => "warpscript",
"wasm" => "wasm",
"wiki" => "wiki",
"xeora" => "xeora",
"xml" => "xmlDoc",
"ant" => "xmlDoc",
"axml" => "xmlDoc",
"ccxml" => "xmlDoc",
"clixml" => "xmlDoc",
"cproject" => "xmlDoc",
"csl" => "xmlDoc",
"csproj" => "xmlDoc",
"ct" => "xmlDoc",
"dita" => "xmlDoc",
"ditamap" => "xmlDoc",
"ditaval" => "xmlDoc",
"dll.config" => "xmlDoc",
"dotsettings" => "xmlDoc",
"filters" => "xmlDoc",
"fsproj" => "xmlDoc",
"fxml" => "xmlDoc",
"glade" => "xmlDoc",
"grxml" => "xmlDoc",
"iml" => "xmlDoc",
"ivy" => "xmlDoc",
"jelly" => "xmlDoc",
"jsproj" => "xmlDoc",
"kml" => "xmlDoc",
"launch" => "xmlDoc",
"mdpolicy" => "xmlDoc",
"mod" => "xmlDoc",
"mxml" => "xmlDoc",
"nproj" => "xmlDoc",
"nuspec" => "xmlDoc",
"odd" => "xmlDoc",
"osm" => "xmlDoc",
"plist" => "xmlDoc",
"props" => "xmlDoc",
"ps1xml" => "xmlDoc",
"psc1" => "xmlDoc",
"pt" => "xmlDoc",
"rdf" => "xmlDoc",
"rss" => "xmlDoc",
"scxml" => "xmlDoc",
"srdf" => "xmlDoc",
"storyboard" => "xmlDoc",
"stTheme" => "xmlDoc",
"sublime-snippet" => "xmlDoc",
"targets" => "xmlDoc",
"tmCommand" => "xmlDoc",
"tml" => "xmlDoc",
"tmLanguage" => "xmlDoc",
"tmPreferences" => "xmlDoc",
"tmSnippet" => "xmlDoc",
"tmTheme" => "xmlDoc",
"ui" => "xmlDoc",
"urdf" => "xmlDoc",
"ux" => "xmlDoc",
"vbproj" => "xmlDoc",
"vcxproj" => "xmlDoc",
"vssettings" => "xmlDoc",
"vxml" => "xmlDoc",
"wsdl" => "xmlDoc",
"wsf" => "xmlDoc",
"wxi" => "xmlDoc",
"wxl" => "xmlDoc",
"wxs" => "xmlDoc",
"x3d" => "xmlDoc",
"xacro" => "xmlDoc",
"xaml" => "xmlDoc",
"xib" => "xmlDoc",
"xlf" => "xmlDoc",
"xliff" => "xmlDoc",
"xmi" => "xmlDoc",
"xml.dist" => "xmlDoc",
"xproj" => "xmlDoc",
"xsd" => "xmlDoc",
"xul" => "xmlDoc",
"zcml" => "xmlDoc",
"xojo_code" => "xojo",
"xojo_menu" => "xojo",
"xojo_report" => "xojo",
"xojo_script" => "xojo",
"xojo_toolbar" => "xojo",
"xojo_window" => "xojo",
"xquery" => "xquery",
"xq" => "xquery",
"xql" => "xquery",
"xqm" => "xquery",
"xqy" => "xquery",
"yaml" => "yaml",
"yml" => "yaml",
"reek" => "yaml",
"rviz" => "yaml",
"yaml-tmlanguage" => "yaml",
"sublime-syntax" => "yaml",
"syntax" => "yaml",
"yang" => "yang",
"zig" => "zig",
"txt" => "text",
"" => "text",
};
#[derive(Debug)]
pub struct File {
handler: fs::File,
path: PathBuf,
size: usize,
}
impl std::ops::Deref for File {
type Target = fs::File;
fn deref(&self) -> &Self::Target {
&self.handler
}
}
impl std::ops::DerefMut for File {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.handler
}
}
#[must_use]
pub fn name_from_path(path: &Path) -> String {
path.file_name()
.unwrap_or_else(|| OsStr::new("unknown"))
.to_string_lossy()
.to_string()
}
impl File {
#[allow(clippy::cast_possible_truncation)]
pub fn from_path(path: &Path) -> Result<Self> {
let handler = fs::File::open(path)?;
let size = fs::metadata(path)?.len() as usize;
Ok(Self {
handler,
path: path.to_path_buf(),
size,
})
}
pub fn from_data(data: impl AsRef<str>, name: &str) -> Result<Self> {
let data = data.as_ref();
let (handler, path) = {
let rng_string: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(8)
.map(char::from)
.collect();
let mut rng_name = "gistit-".to_owned();
rng_name.push_str(&rng_string);
rng_name.push_str(name);
let path = temp_dir().join(&rng_name);
let mut handler = fs::OpenOptions::new()
.write(true)
.read(true)
.create(true)
.truncate(true)
.open(&path)?;
handler.write_all(data.as_bytes())?;
handler.rewind()?;
(handler, path)
};
Ok(Self {
handler,
path,
size: data.len(),
})
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn name(&self) -> String {
name_from_path(&self.path)
}
#[must_use]
pub fn lang(&self) -> &str {
self.path.extension().map_or("text", |ext| {
let ext_str = OsStr::to_str(ext).expect("file to contain valid utf8 extension");
EXTENSION_TO_LANG_MAPPING.get(ext_str).unwrap_or(&"text")
})
}
#[must_use]
pub const fn size(&self) -> usize {
self.size
}
pub fn read(&self) -> Result<String> {
let mut buf = String::with_capacity(GISTIT_MAX_SIZE);
let mut reader = BufReader::new(&**self);
reader.read_to_string(&mut buf)?;
Ok(buf)
}
pub fn save_as(&mut self, path: &Path) -> Result<()> {
let content = self.read()?;
Ok(write(path, content)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
use assert_fs::prelude::*;
use predicates::prelude::*;
#[test]
fn file_name_from_path_edge_cases() {
let n1 = name_from_path(Path::new("foo.txt"));
let n2 = name_from_path(Path::new("~/foo.txt"));
let n3 = name_from_path(Path::new("/foo.txt"));
let n4 = name_from_path(Path::new("/😁.txt"));
let n5 = name_from_path(Path::new("/lol/what/foo/😁.txt"));
let n6 = name_from_path(Path::new("😁"));
assert_eq!(n1, "foo.txt");
assert_eq!(n2, "foo.txt");
assert_eq!(n3, "foo.txt");
assert_eq!(n4, "😁.txt");
assert_eq!(n5, "😁.txt");
assert_eq!(n6, "😁");
}
#[test]
fn file_spawn_random_and_write() {
let data: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(512)
.map(char::from)
.collect();
let mut file = File::from_data(&data, "foo.txt").unwrap();
let read = file.read().unwrap();
assert_eq!(data, read);
}
#[test]
fn file_structure_new_from_path() {
let data: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(512)
.map(char::from)
.collect();
let tmp = assert_fs::TempDir::new().unwrap();
let input_file = tmp.child("foo.txt");
input_file.touch().unwrap();
input_file.write_binary(data.as_bytes()).unwrap();
let mut file = File::from_path(&input_file).unwrap();
assert_eq!(file.size(), 512);
let mut content = file.read().unwrap();
assert_eq!(content, data);
assert_eq!(file.name(), "foo.txt".to_owned());
}
#[test]
fn file_structure_new_from_bytes() {
let data: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(512)
.map(char::from)
.collect();
let mut file = File::from_data(data.clone(), "nameless").unwrap();
let content = file.read().unwrap();
assert_eq!(content, data);
}
#[test]
fn file_structure_save_as() {
let data: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(512)
.map(char::from)
.collect();
let tmp = assert_fs::TempDir::new().unwrap();
let tmp_file = tmp.child("foo.txt");
tmp_file.touch().unwrap();
tmp_file.write_binary(data.as_bytes()).unwrap();
let mut file = File::from_path(&tmp_file).unwrap();
file.save_as(&tmp.join("bar.txt")).unwrap();
tmp.assert(predicates::path::exists());
let other = tmp.child("bar.txt");
let other = fs::read(other).unwrap();
assert_eq!(data.as_bytes(), other);
}
#[test]
fn file_structure_extension_to_lang_mapping() {
let tmp = assert_fs::TempDir::new().unwrap();
let rust = tmp.child("foo.rs");
rust.touch().unwrap();
let js = tmp.child("bar.js");
js.touch().unwrap();
let cpp = tmp.child("lol.cpp");
cpp.touch().unwrap();
let brainfuck = tmp.child("rly.bf");
brainfuck.touch().unwrap();
assert_eq!(File::from_path(&rust).unwrap().lang(), "rust");
assert_eq!(File::from_path(&js).unwrap().lang(), "javascript");
assert_eq!(File::from_path(&cpp).unwrap().lang(), "cpp");
assert_eq!(File::from_path(&brainfuck).unwrap().lang(), "brainfuck");
}
#[test]
fn file_structure_support_methods() {
let data: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(512)
.map(char::from)
.collect();
let tmp = assert_fs::TempDir::new().unwrap();
let tmp_file = tmp.child("foo");
tmp_file.touch().unwrap();
tmp_file.write_binary(data.as_bytes()).unwrap();
let file = File::from_path(&tmp_file).unwrap();
assert_eq!(file.name(), "foo");
assert_eq!(file.size(), 512);
}
}