diff --git a/cli/js/deno.ts b/cli/js/deno.ts index 27a7bb3bd0..11f4675808 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -1,7 +1,27 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. // Public deno module. -export { env, exit, isTTY, execPath, homeDir, hostname } from "./os.ts"; +export { + env, + exit, + isTTY, + execPath, + homeDir, + cacheDir, + configDir, + dataDir, + dataLocalDir, + audioDir, + desktopDir, + documentDir, + downloadDir, + fontDir, + pictureDir, + publicDir, + templateDir, + videoDir, + hostname +} from "./os.ts"; export { chdir, cwd } from "./dir.ts"; export { File, diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index ed6f570520..609f83c699 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -13,7 +13,7 @@ export let OP_EXEC_PATH: number; export let OP_UTIME: number; export let OP_SET_ENV: number; export let OP_GET_ENV: number; -export let OP_HOME_DIR: number; +export let OP_GET_DIR: number; export let OP_START: number; export let OP_APPLY_SOURCE_MAP: number; export let OP_FORMAT_ERROR: number; @@ -85,6 +85,7 @@ export function asyncMsgFromRust(opId: number, ui8: Uint8Array): void { case OP_READ: minimal.asyncMsgFromRust(opId, ui8); break; + case OP_GET_DIR: case OP_EXIT: case OP_IS_TTY: case OP_ENV: diff --git a/cli/js/lib.deno_runtime.d.ts b/cli/js/lib.deno_runtime.d.ts index 98e93fc368..53cf100f4d 100644 --- a/cli/js/lib.deno_runtime.d.ts +++ b/cli/js/lib.deno_runtime.d.ts @@ -56,9 +56,153 @@ declare namespace Deno { export function env(key: string): string | undefined; /** * Returns the current user's home directory. + * If the directory does not exist, an exception is thrown * Requires the `--allow-env` flag. */ export function homeDir(): string; + /** + * Returns the current user's cache directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ----------------------------------- | ---------------------------- | + * | Linux | `$XDG_CACHE_HOME` or `$HOME`/.cache | /home/alice/.cache | + * | macOS | `$HOME`/Library/Caches | /Users/Alice/Library/Caches | + * | Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Local | + */ + export function cacheDir(): string; + /** + * Returns the current user's config directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ------------------------------------- | -------------------------------- | + * | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | + * | macOS | `$HOME`/Library/Preferences | /Users/Alice/Library/Preferences | + * | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | + */ + export function configDir(): string; + /** + * Returns the current user's data directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------------------------- | ---------------------------------------- | + * | Linux | `$XDG_DATA_HOME` or `$HOME`/.local/share | /home/alice/.local/share | + * | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | + * | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | + */ + export function dataDir(): string; + /** + * Returns the current user's local data directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------------------------- | ---------------------------------------- | + * | Linux | `$XDG_DATA_HOME` or `$HOME`/.local/share | /home/alice/.local/share | + * | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | + * | Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Local | + */ + export function dataLocalDir(): string; + /** + * Returns the current user's audio directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ------------------ | -------------------- | + * | Linux | `XDG_MUSIC_DIR` | /home/alice/Music | + * | macOS | `$HOME`/Music | /Users/Alice/Music | + * | Windows | `{FOLDERID_Music}` | C:\Users\Alice\Music | + */ + export function audioDir(): string; + /** + * Returns the current user's desktop directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | -------------------- | ---------------------- | + * | Linux | `XDG_DESKTOP_DIR` | /home/alice/Desktop | + * | macOS | `$HOME`/Desktop | /Users/Alice/Desktop | + * | Windows | `{FOLDERID_Desktop}` | C:\Users\Alice\Desktop | + */ + export function desktopDir(): string; + /** + * Returns the current user's document directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------- | ------------------------ | + * | Linux | `XDG_DOCUMENTS_DIR` | /home/alice/Documents | + * | macOS | `$HOME`/Documents | /Users/Alice/Documents | + * | Windows | `{FOLDERID_Documents}` | C:\Users\Alice\Documents | + */ + export function documentDir(): string; + /** + * Returns the current user's download directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------- | ------------------------ | + * | Linux | `XDG_DOWNLOAD_DIR` | /home/alice/Downloads | + * | macOS | `$HOME`/Downloads | /Users/Alice/Downloads | + * | Windows | `{FOLDERID_Downloads}` | C:\Users\Alice\Downloads | + */ + export function downloadDir(): string; + /** + * Returns the current user's font directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------------------------------------- | ------------------------------ | + * | Linux | `$XDG_DATA_HOME`/fonts or `$HOME`/.local/share/fonts | /home/alice/.local/share/fonts | + * | macOS | `$HOME/Library/Fonts` | /Users/Alice/Library/Fonts | + * | Windows | – | – | + */ + export function fontDir(): string; + /** + * Returns the current user's picture directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | --------------------- | ----------------------- | + * | Linux | `XDG_PICTURES_DIR` | /home/alice/Pictures | + * | macOS | `$HOME`/Pictures | /Users/Alice/Pictures | + * | Windows | `{FOLDERID_Pictures}` | C:\Users\Alice\Pictures | + */ + export function pictureDir(): string; + /** + * Returns the current user's public directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | --------------------- | ------------------- | + * | Linux | `XDG_PUBLICSHARE_DIR` | /home/alice/Public | + * | macOS | `$HOME`/Public | /Users/Alice/Public | + * | Windows | `{FOLDERID_Public}` | C:\Users\Public | + */ + export function publicDir(): string; + /** + * Returns the current user's template directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------- | ---------------------------------------------------------- | + * | Linux | `XDG_TEMPLATES_DIR` | /home/alice/Templates | + * | macOS | – | – | + * | Windows | `{FOLDERID_Templates}` | C:\Users\Alice\AppData\Roaming\Microsoft\Windows\Templates | + */ + export function templateDir(): string; + /** + * Returns the current user's video directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ------------------- | --------------------- | + * | Linux | `XDG_VIDEOS_DIR` | /home/alice/Videos | + * | macOS | `$HOME`/Movies | /Users/Alice/Movies | + * | Windows | `{FOLDERID_Videos}` | C:\Users\Alice\Videos | + */ + export function videoDir(): string; /** * Returns the path to the current deno executable. * Requires the `--allow-env` flag. diff --git a/cli/js/os.ts b/cli/js/os.ts index 4e17e2030b..ed46268d9a 100644 --- a/cli/js/os.ts +++ b/cli/js/os.ts @@ -133,11 +133,189 @@ export function start(preserveDenoNamespace = true, source?: string): Start { * Requires the `--allow-env` flag. */ export function homeDir(): string { - const path = sendSync(dispatch.OP_HOME_DIR); - if (!path) { - throw new Error("Could not get home directory."); - } - return path; + return sendSync(dispatch.OP_GET_DIR, { name: "home" }); +} + +/** + * Returns the current user's cache directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ----------------------------------- | ---------------------------- | + * | Linux | `$XDG_CACHE_HOME` or `$HOME`/.cache | /home/alice/.cache | + * | macOS | `$HOME`/Library/Caches | /Users/Alice/Library/Caches | + * | Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Local | + */ +export function cacheDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "cache" }); +} + +/** + * Returns the current user's config directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ------------------------------------- | -------------------------------- | + * | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | + * | macOS | `$HOME`/Library/Preferences | /Users/Alice/Library/Preferences | + * | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | + */ +export function configDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "config" }); +} + +/** + * Returns the current user's data directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------------------------- | ---------------------------------------- | + * | Linux | `$XDG_DATA_HOME` or `$HOME`/.local/share | /home/alice/.local/share | + * | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | + * | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | + */ +export function dataDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "data" }); +} + +/** + * Returns the current user's local data directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------------------------- | ---------------------------------------- | + * | Linux | `$XDG_DATA_HOME` or `$HOME`/.local/share | /home/alice/.local/share | + * | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | + * | Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Local | + */ +export function dataLocalDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "data_local" }); +} + +/** + * Returns the current user's audio directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ------------------ | -------------------- | + * | Linux | `XDG_MUSIC_DIR` | /home/alice/Music | + * | macOS | `$HOME`/Music | /Users/Alice/Music | + * | Windows | `{FOLDERID_Music}` | C:\Users\Alice\Music | + */ +export function audioDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "audio" }); +} + +/** + * Returns the current user's desktop directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | -------------------- | ---------------------- | + * | Linux | `XDG_DESKTOP_DIR` | /home/alice/Desktop | + * | macOS | `$HOME`/Desktop | /Users/Alice/Desktop | + * | Windows | `{FOLDERID_Desktop}` | C:\Users\Alice\Desktop | + */ +export function desktopDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "desktop" }); +} + +/** + * Returns the current user's document directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------- | ------------------------ | + * | Linux | `XDG_DOCUMENTS_DIR` | /home/alice/Documents | + * | macOS | `$HOME`/Documents | /Users/Alice/Documents | + * | Windows | `{FOLDERID_Documents}` | C:\Users\Alice\Documents | + */ +export function documentDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "document" }); +} + +/** + * Returns the current user's download directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------- | ------------------------ | + * | Linux | `XDG_DOWNLOAD_DIR` | /home/alice/Downloads | + * | macOS | `$HOME`/Downloads | /Users/Alice/Downloads | + * | Windows | `{FOLDERID_Downloads}` | C:\Users\Alice\Downloads | + */ +export function downloadDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "download" }); +} + +/** + * Returns the current user's font directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------------------------------------- | ------------------------------ | + * | Linux | `$XDG_DATA_HOME`/fonts or `$HOME`/.local/share/fonts | /home/alice/.local/share/fonts | + * | macOS | `$HOME/Library/Fonts` | /Users/Alice/Library/Fonts | + * | Windows | – | – | + */ +export function fontDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "font" }); +} + +/** + * Returns the current user's picture directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | --------------------- | ----------------------- | + * | Linux | `XDG_PICTURES_DIR` | /home/alice/Pictures | + * | macOS | `$HOME`/Pictures | /Users/Alice/Pictures | + * | Windows | `{FOLDERID_Pictures}` | C:\Users\Alice\Pictures | + */ +export function pictureDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "picture" }); +} + +/** + * Returns the current user's public directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | --------------------- | ------------------- | + * | Linux | `XDG_PUBLICSHARE_DIR` | /home/alice/Public | + * | macOS | `$HOME`/Public | /Users/Alice/Public | + * | Windows | `{FOLDERID_Public}` | C:\Users\Public | + */ +export function publicDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "public" }); +} + +/** + * Returns the current user's template directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ---------------------- | ---------------------------------------------------------- | + * | Linux | `XDG_TEMPLATES_DIR` | /home/alice/Templates | + * | macOS | – | – | + * | Windows | `{FOLDERID_Templates}` | C:\Users\Alice\AppData\Roaming\Microsoft\Windows\Templates | + */ +export function templateDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "template" }); +} + +/** + * Returns the current user's video directory. + * If the directory does not exist, an exception is thrown + * Requires the `--allow-env` flag. + * |Platform | Value | Example | + * | ------- | ------------------- | --------------------- | + * | Linux | `XDG_VIDEOS_DIR` | /home/alice/Videos | + * | macOS | `$HOME`/Movies | /Users/Alice/Movies | + * | Windows | `{FOLDERID_Videos}` | C:\Users\Alice\Videos | + */ +export function videoDir(): string { + return sendSync(dispatch.OP_GET_DIR, { name: "video" }); } /** diff --git a/cli/js/os_test.ts b/cli/js/os_test.ts index 4faee1166c..0b859836b9 100644 --- a/cli/js/os_test.ts +++ b/cli/js/os_test.ts @@ -4,7 +4,8 @@ import { testPerm, assert, assertEquals, - assertNotEquals + assertNotEquals, + assertThrows } from "./test_util.ts"; testPerm({ env: true }, function envSuccess(): void { @@ -131,6 +132,189 @@ testPerm({ env: false }, function homeDirPerm(): void { assert(caughtError); }); +testPerm({ env: true }, function getUserDir(): void { + type supportOS = "mac" | "win" | "linux"; + + interface Runtime { + os: supportOS; + shouldHaveValue: boolean; + } + + interface Scenes { + name: string; + fn: string; + runtime: Runtime[]; + } + + const scenes: Scenes[] = [ + { + name: "config", + fn: "configDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + name: "cache", + fn: "cacheDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + name: "data", + fn: "dataDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + name: "data local", + fn: "dataLocalDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + name: "audio", + fn: "audioDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + name: "desktop", + fn: "desktopDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + name: "document", + fn: "documentDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + name: "download", + fn: "downloadDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + name: "font", + fn: "fontDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: false }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + name: "picture", + fn: "pictureDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + name: "public", + fn: "publicDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + name: "template", + fn: "templateDir", + runtime: [ + { os: "mac", shouldHaveValue: false }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + name: "video", + fn: "videoDir", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + } + ]; + + for (const s of scenes) { + console.log(`test Deno.${s.fn}()`); + const fn = Deno[s.fn]; + + for (const r of s.runtime) { + if (Deno.build.os !== r.os) continue; + if (r.shouldHaveValue) { + assertNotEquals(fn(), ""); + } else { + // if not support your platform. it should throw an error + assertThrows( + () => fn(), + Deno.DenoError, + `Could not get user ${s.name} directory.` + ); + } + } + } +}); + +testPerm({}, function getUserDirWithoutPermission(): void { + const funcs: string[] = [ + "configDir", + "cacheDir", + "dataDir", + "dataLocalDir", + "audioDir", + "desktopDir", + "documentDir", + "downloadDir", + "fontDir", + "pictureDir", + "publicDir", + "templateDir", + "videoDir" + ]; + + for (const fnName of funcs) { + console.log(`test Deno.${fnName}()`); + const fn = Deno[fnName]; + + assertThrows( + () => fn(), + Deno.DenoError, + `run again with the --allow-env flag` + ); + } +}); + testPerm({ env: true }, function execPath(): void { assertNotEquals(Deno.execPath(), ""); }); diff --git a/cli/ops/os.rs b/cli/ops/os.rs index 70e22cea60..13fadbf6d5 100644 --- a/cli/ops/os.rs +++ b/cli/ops/os.rs @@ -9,6 +9,7 @@ use atty; use deno::*; use std::collections::HashMap; use std::env; +use std::io::{Error, ErrorKind}; use sys_info; use url::Url; @@ -29,7 +30,7 @@ pub fn init(i: &mut Isolate, s: &ThreadSafeState) { i.register_op("exec_path", s.core_op(json_op(s.stateful_op(op_exec_path)))); i.register_op("set_env", s.core_op(json_op(s.stateful_op(op_set_env)))); i.register_op("get_env", s.core_op(json_op(s.stateful_op(op_get_env)))); - i.register_op("home_dir", s.core_op(json_op(s.stateful_op(op_home_dir)))); + i.register_op("get_dir", s.core_op(json_op(s.stateful_op(op_get_dir)))); i.register_op("hostname", s.core_op(json_op(s.stateful_op(op_hostname)))); i.register_op("start", s.core_op(json_op(s.stateful_op(op_start)))); } @@ -57,18 +58,54 @@ fn op_start( }))) } -fn op_home_dir( +#[derive(Deserialize)] +struct GetDirArgs { + name: std::string::String, +} + +fn op_get_dir( state: &ThreadSafeState, - _args: Value, + args: Value, _zero_copy: Option, ) -> Result { state.check_env()?; - let path = dirs::home_dir() - .unwrap_or_default() - .into_os_string() - .into_string() - .unwrap_or_default(); - Ok(JsonOp::Sync(json!(path))) + let args: GetDirArgs = serde_json::from_value(args)?; + + let path = match args.name.as_str() { + "home" => dirs::home_dir(), + "config" => dirs::config_dir(), + "cache" => dirs::cache_dir(), + "data" => dirs::data_dir(), + "data_local" => dirs::data_local_dir(), + "audio" => dirs::audio_dir(), + "desktop" => dirs::desktop_dir(), + "document" => dirs::document_dir(), + "download" => dirs::download_dir(), + "font" => dirs::font_dir(), + "picture" => dirs::picture_dir(), + "public" => dirs::public_dir(), + "template" => dirs::template_dir(), + "video" => dirs::video_dir(), + _ => { + return Err(ErrBox::from(Error::new( + ErrorKind::InvalidInput, + format!("Invalid dir type `{}`", args.name.as_str()), + ))) + } + }; + + if path == None { + Err(ErrBox::from(Error::new( + ErrorKind::NotFound, + format!("Could not get user {} directory.", args.name.as_str()), + ))) + } else { + Ok(JsonOp::Sync(json!(path + .unwrap_or_default() + .into_os_string() + .into_string() + .unwrap_or_default()))) + } } fn op_exec_path(