mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 19:08:15 +00:00
Rename //src/ to //cli/ (#1962)
To better distinguish the deno_core crate from the executable deno, which will now be called "the cli" internally.
This commit is contained in:
parent
c7d81fa9ff
commit
fa3c35301a
32 changed files with 21 additions and 17 deletions
70
cli/ansi.rs
Normal file
70
cli/ansi.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use ansi_term::Color::Fixed;
|
||||
use ansi_term::Color::Red;
|
||||
use ansi_term::Style;
|
||||
use regex::Regex;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
|
||||
lazy_static! {
|
||||
// STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate.
|
||||
// Copyright 2017 Armin Ronacher <armin.ronacher@active-4.com>. MIT License.
|
||||
static ref STRIP_ANSI_RE: Regex = Regex::new(
|
||||
r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]"
|
||||
).unwrap();
|
||||
static ref NO_COLOR: bool = {
|
||||
env::var_os("NO_COLOR").is_some()
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper function to strip ansi codes.
|
||||
#[cfg(test)]
|
||||
pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow<str> {
|
||||
STRIP_ANSI_RE.replace_all(s, "")
|
||||
}
|
||||
|
||||
pub fn use_color() -> bool {
|
||||
!(*NO_COLOR)
|
||||
}
|
||||
|
||||
pub fn red_bold(s: String) -> impl fmt::Display {
|
||||
let mut style = Style::new();
|
||||
if use_color() {
|
||||
style = style.bold().fg(Red);
|
||||
}
|
||||
style.paint(s)
|
||||
}
|
||||
|
||||
pub fn italic_bold(s: String) -> impl fmt::Display {
|
||||
let mut style = Style::new();
|
||||
if use_color() {
|
||||
style = style.italic().bold();
|
||||
}
|
||||
style.paint(s)
|
||||
}
|
||||
|
||||
pub fn yellow(s: String) -> impl fmt::Display {
|
||||
let mut style = Style::new();
|
||||
if use_color() {
|
||||
// matches TypeScript's ForegroundColorEscapeSequences.Yellow
|
||||
style = style.fg(Fixed(11));
|
||||
}
|
||||
style.paint(s)
|
||||
}
|
||||
|
||||
pub fn cyan(s: String) -> impl fmt::Display {
|
||||
let mut style = Style::new();
|
||||
if use_color() {
|
||||
// matches TypeScript's ForegroundColorEscapeSequences.Cyan
|
||||
style = style.fg(Fixed(14));
|
||||
}
|
||||
style.paint(s)
|
||||
}
|
||||
|
||||
pub fn bold(s: String) -> impl fmt::Display {
|
||||
let mut style = Style::new();
|
||||
if use_color() {
|
||||
style = style.bold();
|
||||
}
|
||||
style.paint(s)
|
||||
}
|
90
cli/cli.rs
Normal file
90
cli/cli.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::errors::DenoResult;
|
||||
use crate::isolate_state::IsolateState;
|
||||
use crate::ops;
|
||||
use crate::permissions::DenoPermissions;
|
||||
use deno_core::deno_buf;
|
||||
use deno_core::deno_mod;
|
||||
use deno_core::Behavior;
|
||||
use deno_core::Op;
|
||||
use deno_core::StartupData;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Buf represents a byte array returned from a "Op". The message might be empty
|
||||
// (which will be translated into a null object on the javascript side) or it is
|
||||
// a heap allocated opaque sequence of bytes. Usually a flatbuffer message.
|
||||
pub type Buf = Box<[u8]>;
|
||||
|
||||
/// Implements deno_core::Behavior for the main Deno command-line.
|
||||
pub struct Cli {
|
||||
startup_data: Option<StartupData>,
|
||||
pub state: Arc<IsolateState>,
|
||||
pub permissions: Arc<DenoPermissions>, // TODO(ry) move to IsolateState
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn new(
|
||||
startup_data: Option<StartupData>,
|
||||
state: Arc<IsolateState>,
|
||||
permissions: DenoPermissions,
|
||||
) -> Self {
|
||||
Self {
|
||||
startup_data,
|
||||
state,
|
||||
permissions: Arc::new(permissions),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_read(&self, filename: &str) -> DenoResult<()> {
|
||||
self.permissions.check_read(filename)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_write(&self, filename: &str) -> DenoResult<()> {
|
||||
self.permissions.check_write(filename)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_env(&self) -> DenoResult<()> {
|
||||
self.permissions.check_env()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_net(&self, filename: &str) -> DenoResult<()> {
|
||||
self.permissions.check_net(filename)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_run(&self) -> DenoResult<()> {
|
||||
self.permissions.check_run()
|
||||
}
|
||||
}
|
||||
|
||||
impl Behavior for Cli {
|
||||
fn startup_data(&mut self) -> Option<StartupData> {
|
||||
self.startup_data.take()
|
||||
}
|
||||
|
||||
fn resolve(&mut self, specifier: &str, referrer: deno_mod) -> deno_mod {
|
||||
self
|
||||
.state
|
||||
.metrics
|
||||
.resolve_count
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
let mut modules = self.state.modules.lock().unwrap();
|
||||
modules.resolve_cb(&self.state.dir, specifier, referrer)
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
&mut self,
|
||||
control: &[u8],
|
||||
zero_copy: deno_buf,
|
||||
) -> (bool, Box<Op>) {
|
||||
ops::dispatch(self, control, zero_copy)
|
||||
}
|
||||
}
|
151
cli/compiler.rs
Normal file
151
cli/compiler.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::cli::Buf;
|
||||
use crate::isolate_state::IsolateState;
|
||||
use crate::msg;
|
||||
use crate::permissions::{DenoPermissions, PermissionAccessor};
|
||||
use crate::resources;
|
||||
use crate::resources::Resource;
|
||||
use crate::resources::ResourceId;
|
||||
use crate::startup_data;
|
||||
use crate::workers;
|
||||
use futures::Future;
|
||||
use serde_json;
|
||||
use std::str;
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref C_RID: Mutex<Option<ResourceId>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
// This corresponds to JS ModuleMetaData.
|
||||
// TODO Rename one or the other so they correspond.
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleMetaData {
|
||||
pub module_name: String,
|
||||
pub filename: String,
|
||||
pub media_type: msg::MediaType,
|
||||
pub source_code: Vec<u8>,
|
||||
pub maybe_output_code_filename: Option<String>,
|
||||
pub maybe_output_code: Option<Vec<u8>>,
|
||||
pub maybe_source_map_filename: Option<String>,
|
||||
pub maybe_source_map: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ModuleMetaData {
|
||||
pub fn js_source(&self) -> String {
|
||||
if self.media_type == msg::MediaType::Json {
|
||||
return format!(
|
||||
"export default {};",
|
||||
str::from_utf8(&self.source_code).unwrap()
|
||||
);
|
||||
}
|
||||
match self.maybe_output_code {
|
||||
None => str::from_utf8(&self.source_code).unwrap().to_string(),
|
||||
Some(ref output_code) => str::from_utf8(output_code).unwrap().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lazy_start(parent_state: &IsolateState) -> Resource {
|
||||
let mut cell = C_RID.lock().unwrap();
|
||||
let startup_data = startup_data::compiler_isolate_init();
|
||||
let permissions = DenoPermissions {
|
||||
allow_read: PermissionAccessor::from(true),
|
||||
allow_write: PermissionAccessor::from(true),
|
||||
allow_net: PermissionAccessor::from(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let rid = cell.get_or_insert_with(|| {
|
||||
let resource = workers::spawn(
|
||||
Some(startup_data),
|
||||
parent_state,
|
||||
"compilerMain()".to_string(),
|
||||
permissions,
|
||||
);
|
||||
resource.rid
|
||||
});
|
||||
Resource { rid: *rid }
|
||||
}
|
||||
|
||||
fn req(specifier: &str, referrer: &str) -> Buf {
|
||||
json!({
|
||||
"specifier": specifier,
|
||||
"referrer": referrer,
|
||||
}).to_string()
|
||||
.into_boxed_str()
|
||||
.into_boxed_bytes()
|
||||
}
|
||||
|
||||
pub fn compile_sync(
|
||||
parent_state: &IsolateState,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
module_meta_data: &ModuleMetaData,
|
||||
) -> ModuleMetaData {
|
||||
let req_msg = req(specifier, referrer);
|
||||
|
||||
let compiler = lazy_start(parent_state);
|
||||
|
||||
let send_future = resources::worker_post_message(compiler.rid, req_msg);
|
||||
send_future.wait().unwrap();
|
||||
|
||||
let recv_future = resources::worker_recv_message(compiler.rid);
|
||||
let result = recv_future.wait().unwrap();
|
||||
assert!(result.is_some());
|
||||
let res_msg = result.unwrap();
|
||||
|
||||
let res_json = std::str::from_utf8(&res_msg).unwrap();
|
||||
match serde_json::from_str::<serde_json::Value>(res_json) {
|
||||
Ok(serde_json::Value::Object(map)) => ModuleMetaData {
|
||||
module_name: module_meta_data.module_name.clone(),
|
||||
filename: module_meta_data.filename.clone(),
|
||||
media_type: module_meta_data.media_type,
|
||||
source_code: module_meta_data.source_code.clone(),
|
||||
maybe_output_code: match map["outputCode"].as_str() {
|
||||
Some(str) => Some(str.as_bytes().to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
maybe_output_code_filename: None,
|
||||
maybe_source_map: match map["sourceMap"].as_str() {
|
||||
Some(str) => Some(str.as_bytes().to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
maybe_source_map_filename: None,
|
||||
},
|
||||
_ => panic!("error decoding compiler response"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_compile_sync() {
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let cwd_string = cwd.to_str().unwrap().to_owned();
|
||||
|
||||
let specifier = "./tests/002_hello.ts";
|
||||
let referrer = cwd_string + "/";
|
||||
|
||||
let mut out = ModuleMetaData {
|
||||
module_name: "xxx".to_owned(),
|
||||
filename: "/tests/002_hello.ts".to_owned(),
|
||||
media_type: msg::MediaType::TypeScript,
|
||||
source_code: "console.log(\"Hello World\");".as_bytes().to_owned(),
|
||||
maybe_output_code_filename: None,
|
||||
maybe_output_code: None,
|
||||
maybe_source_map_filename: None,
|
||||
maybe_source_map: None,
|
||||
};
|
||||
|
||||
out = compile_sync(&IsolateState::mock(), specifier, &referrer, &mut out);
|
||||
assert!(
|
||||
out
|
||||
.maybe_output_code
|
||||
.unwrap()
|
||||
.starts_with("console.log(\"Hello World\");".as_bytes())
|
||||
);
|
||||
}
|
||||
}
|
1369
cli/deno_dir.rs
Normal file
1369
cli/deno_dir.rs
Normal file
File diff suppressed because it is too large
Load diff
207
cli/errors.rs
Normal file
207
cli/errors.rs
Normal file
|
@ -0,0 +1,207 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::js_errors::JSErrorColor;
|
||||
pub use crate::msg::ErrorKind;
|
||||
use crate::resolve_addr::ResolveAddrError;
|
||||
use deno_core::JSError;
|
||||
use hyper;
|
||||
use std;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use url;
|
||||
|
||||
pub type DenoResult<T> = std::result::Result<T, DenoError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DenoError {
|
||||
repr: Repr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Repr {
|
||||
Simple(ErrorKind, String),
|
||||
IoErr(io::Error),
|
||||
UrlErr(url::ParseError),
|
||||
HyperErr(hyper::Error),
|
||||
}
|
||||
|
||||
pub fn new(kind: ErrorKind, msg: String) -> DenoError {
|
||||
DenoError {
|
||||
repr: Repr::Simple(kind, msg),
|
||||
}
|
||||
}
|
||||
|
||||
impl DenoError {
|
||||
pub fn kind(&self) -> ErrorKind {
|
||||
match self.repr {
|
||||
Repr::Simple(kind, ref _msg) => kind,
|
||||
// Repr::Simple(kind) => kind,
|
||||
Repr::IoErr(ref err) => {
|
||||
use std::io::ErrorKind::*;
|
||||
match err.kind() {
|
||||
NotFound => ErrorKind::NotFound,
|
||||
PermissionDenied => ErrorKind::PermissionDenied,
|
||||
ConnectionRefused => ErrorKind::ConnectionRefused,
|
||||
ConnectionReset => ErrorKind::ConnectionReset,
|
||||
ConnectionAborted => ErrorKind::ConnectionAborted,
|
||||
NotConnected => ErrorKind::NotConnected,
|
||||
AddrInUse => ErrorKind::AddrInUse,
|
||||
AddrNotAvailable => ErrorKind::AddrNotAvailable,
|
||||
BrokenPipe => ErrorKind::BrokenPipe,
|
||||
AlreadyExists => ErrorKind::AlreadyExists,
|
||||
WouldBlock => ErrorKind::WouldBlock,
|
||||
InvalidInput => ErrorKind::InvalidInput,
|
||||
InvalidData => ErrorKind::InvalidData,
|
||||
TimedOut => ErrorKind::TimedOut,
|
||||
Interrupted => ErrorKind::Interrupted,
|
||||
WriteZero => ErrorKind::WriteZero,
|
||||
Other => ErrorKind::Other,
|
||||
UnexpectedEof => ErrorKind::UnexpectedEof,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Repr::UrlErr(ref err) => {
|
||||
use url::ParseError::*;
|
||||
match err {
|
||||
EmptyHost => ErrorKind::EmptyHost,
|
||||
IdnaError => ErrorKind::IdnaError,
|
||||
InvalidPort => ErrorKind::InvalidPort,
|
||||
InvalidIpv4Address => ErrorKind::InvalidIpv4Address,
|
||||
InvalidIpv6Address => ErrorKind::InvalidIpv6Address,
|
||||
InvalidDomainCharacter => ErrorKind::InvalidDomainCharacter,
|
||||
RelativeUrlWithoutBase => ErrorKind::RelativeUrlWithoutBase,
|
||||
RelativeUrlWithCannotBeABaseBase => {
|
||||
ErrorKind::RelativeUrlWithCannotBeABaseBase
|
||||
}
|
||||
SetHostOnCannotBeABaseUrl => ErrorKind::SetHostOnCannotBeABaseUrl,
|
||||
Overflow => ErrorKind::Overflow,
|
||||
}
|
||||
}
|
||||
Repr::HyperErr(ref err) => {
|
||||
// For some reason hyper::errors::Kind is private.
|
||||
if err.is_parse() {
|
||||
ErrorKind::HttpParse
|
||||
} else if err.is_user() {
|
||||
ErrorKind::HttpUser
|
||||
} else if err.is_canceled() {
|
||||
ErrorKind::HttpCanceled
|
||||
} else if err.is_closed() {
|
||||
ErrorKind::HttpClosed
|
||||
} else {
|
||||
ErrorKind::HttpOther
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DenoError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.repr {
|
||||
Repr::Simple(_kind, ref err_str) => f.pad(err_str),
|
||||
Repr::IoErr(ref err) => err.fmt(f),
|
||||
Repr::UrlErr(ref err) => err.fmt(f),
|
||||
Repr::HyperErr(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for DenoError {
|
||||
fn description(&self) -> &str {
|
||||
match self.repr {
|
||||
Repr::Simple(_kind, ref msg) => msg.as_str(),
|
||||
Repr::IoErr(ref err) => err.description(),
|
||||
Repr::UrlErr(ref err) => err.description(),
|
||||
Repr::HyperErr(ref err) => err.description(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||
match self.repr {
|
||||
Repr::Simple(_kind, ref _msg) => None,
|
||||
Repr::IoErr(ref err) => Some(err),
|
||||
Repr::UrlErr(ref err) => Some(err),
|
||||
Repr::HyperErr(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for DenoError {
|
||||
#[inline]
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self {
|
||||
repr: Repr::IoErr(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for DenoError {
|
||||
#[inline]
|
||||
fn from(err: url::ParseError) -> Self {
|
||||
Self {
|
||||
repr: Repr::UrlErr(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hyper::Error> for DenoError {
|
||||
#[inline]
|
||||
fn from(err: hyper::Error) -> Self {
|
||||
Self {
|
||||
repr: Repr::HyperErr(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResolveAddrError> for DenoError {
|
||||
fn from(e: ResolveAddrError) -> Self {
|
||||
match e {
|
||||
ResolveAddrError::Syntax => Self {
|
||||
repr: Repr::Simple(
|
||||
ErrorKind::InvalidInput,
|
||||
"invalid address syntax".to_string(),
|
||||
),
|
||||
},
|
||||
ResolveAddrError::Resolution(io_err) => Self {
|
||||
repr: Repr::IoErr(io_err),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bad_resource() -> DenoError {
|
||||
new(ErrorKind::BadResource, String::from("bad resource id"))
|
||||
}
|
||||
|
||||
pub fn permission_denied() -> DenoError {
|
||||
new(
|
||||
ErrorKind::PermissionDenied,
|
||||
String::from("permission denied"),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RustOrJsError {
|
||||
Rust(DenoError),
|
||||
Js(JSError),
|
||||
}
|
||||
|
||||
impl From<DenoError> for RustOrJsError {
|
||||
fn from(e: DenoError) -> Self {
|
||||
RustOrJsError::Rust(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JSError> for RustOrJsError {
|
||||
fn from(e: JSError) -> Self {
|
||||
RustOrJsError::Js(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RustOrJsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RustOrJsError::Rust(e) => e.fmt(f),
|
||||
RustOrJsError::Js(e) => JSErrorColor(e).fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
291
cli/flags.rs
Normal file
291
cli/flags.rs
Normal file
|
@ -0,0 +1,291 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use deno_core::v8_set_flags;
|
||||
use getopts;
|
||||
use getopts::Options;
|
||||
|
||||
// Creates vector of strings, Vec<String>
|
||||
#[cfg(test)]
|
||||
macro_rules! svec {
|
||||
($($x:expr),*) => (vec![$($x.to_string()),*]);
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct DenoFlags {
|
||||
pub help: bool,
|
||||
pub log_debug: bool,
|
||||
pub version: bool,
|
||||
pub reload: bool,
|
||||
pub recompile: bool,
|
||||
pub allow_read: bool,
|
||||
pub allow_write: bool,
|
||||
pub allow_net: bool,
|
||||
pub allow_env: bool,
|
||||
pub allow_run: bool,
|
||||
pub no_prompts: bool,
|
||||
pub types: bool,
|
||||
pub prefetch: bool,
|
||||
pub info: bool,
|
||||
pub fmt: bool,
|
||||
}
|
||||
|
||||
pub fn get_usage(opts: &Options) -> String {
|
||||
format!(
|
||||
"Usage: deno script.ts {}
|
||||
Environment variables:
|
||||
DENO_DIR Set deno's base directory
|
||||
NO_COLOR Set to disable color",
|
||||
opts.usage("")
|
||||
)
|
||||
}
|
||||
|
||||
/// Checks provided arguments for known options and sets appropriate Deno flags
|
||||
/// for them. Unknown options are returned for further use.
|
||||
/// Note:
|
||||
///
|
||||
/// 1. This assumes that privileged flags do not accept parameters deno --foo bar.
|
||||
/// This assumption is currently valid. But if it were to change in the future,
|
||||
/// this parsing technique would need to be modified. I think we want to keep the
|
||||
/// privileged flags minimal - so having this restriction is maybe a good thing.
|
||||
///
|
||||
/// 2. Misspelled flags will be forwarded to user code - e.g. --allow-ne would
|
||||
/// not cause an error. I also think this is ok because missing any of the
|
||||
/// privileged flags is not destructive. Userland flag parsing would catch these
|
||||
/// errors.
|
||||
fn set_recognized_flags(
|
||||
opts: &Options,
|
||||
flags: &mut DenoFlags,
|
||||
args: Vec<String>,
|
||||
) -> Result<Vec<String>, getopts::Fail> {
|
||||
let mut rest = Vec::<String>::new();
|
||||
// getopts doesn't allow parsing unknown options so we check them
|
||||
// one-by-one and handle unrecognized ones manually
|
||||
// better solution welcome!
|
||||
for arg in args {
|
||||
let fake_args = vec![arg];
|
||||
match opts.parse(&fake_args) {
|
||||
Err(getopts::Fail::UnrecognizedOption(_)) => {
|
||||
rest.extend(fake_args);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(matches) => {
|
||||
if matches.opt_present("help") {
|
||||
flags.help = true;
|
||||
}
|
||||
if matches.opt_present("log-debug") {
|
||||
flags.log_debug = true;
|
||||
}
|
||||
if matches.opt_present("version") {
|
||||
flags.version = true;
|
||||
}
|
||||
if matches.opt_present("reload") {
|
||||
flags.reload = true;
|
||||
}
|
||||
if matches.opt_present("recompile") {
|
||||
flags.recompile = true;
|
||||
}
|
||||
if matches.opt_present("allow-read") {
|
||||
flags.allow_read = true;
|
||||
}
|
||||
if matches.opt_present("allow-write") {
|
||||
flags.allow_write = true;
|
||||
}
|
||||
if matches.opt_present("allow-net") {
|
||||
flags.allow_net = true;
|
||||
}
|
||||
if matches.opt_present("allow-env") {
|
||||
flags.allow_env = true;
|
||||
}
|
||||
if matches.opt_present("allow-run") {
|
||||
flags.allow_run = true;
|
||||
}
|
||||
if matches.opt_present("allow-all") {
|
||||
flags.allow_read = true;
|
||||
flags.allow_env = true;
|
||||
flags.allow_net = true;
|
||||
flags.allow_run = true;
|
||||
flags.allow_read = true;
|
||||
flags.allow_write = true;
|
||||
}
|
||||
if matches.opt_present("no-prompt") {
|
||||
flags.no_prompts = true;
|
||||
}
|
||||
if matches.opt_present("types") {
|
||||
flags.types = true;
|
||||
}
|
||||
if matches.opt_present("prefetch") {
|
||||
flags.prefetch = true;
|
||||
}
|
||||
if matches.opt_present("info") {
|
||||
flags.info = true;
|
||||
}
|
||||
if matches.opt_present("fmt") {
|
||||
flags.fmt = true;
|
||||
}
|
||||
|
||||
if !matches.free.is_empty() {
|
||||
rest.extend(matches.free);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(rest)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
||||
pub fn set_flags(
|
||||
args: Vec<String>,
|
||||
) -> Result<(DenoFlags, Vec<String>, String), String> {
|
||||
// TODO: all flags passed after "--" are swallowed by v8_set_flags
|
||||
// eg. deno --allow-net ./test.ts -- --title foobar
|
||||
// args === ["deno", "--allow-net" "./test.ts"]
|
||||
let args = v8_set_flags(args);
|
||||
|
||||
let mut opts = Options::new();
|
||||
// TODO(kevinkassimo): v8_set_flags intercepts '-help' with single '-'
|
||||
// Resolve that and then uncomment line below (enabling Go style -long-flag)
|
||||
// opts.long_only(true);
|
||||
opts.optflag("", "allow-read", "Allow file system read access");
|
||||
opts.optflag("", "allow-write", "Allow file system write access");
|
||||
opts.optflag("", "allow-net", "Allow network access");
|
||||
opts.optflag("", "allow-env", "Allow environment access");
|
||||
opts.optflag("", "allow-run", "Allow running subprocesses");
|
||||
opts.optflag("A", "allow-all", "Allow all permissions");
|
||||
opts.optflag("", "no-prompt", "Do not use prompts");
|
||||
opts.optflag("", "recompile", "Force recompilation of TypeScript code");
|
||||
opts.optflag("h", "help", "Print this message");
|
||||
opts.optflag("D", "log-debug", "Log debug output");
|
||||
opts.optflag("v", "version", "Print the version");
|
||||
opts.optflag("r", "reload", "Reload cached remote resources");
|
||||
opts.optflag("", "v8-options", "Print V8 command line options");
|
||||
opts.optflag("", "types", "Print runtime TypeScript declarations");
|
||||
opts.optflag("", "prefetch", "Prefetch the dependencies");
|
||||
opts.optflag("", "info", "Show source file related info");
|
||||
opts.optflag("", "fmt", "Format code");
|
||||
|
||||
let mut flags = DenoFlags::default();
|
||||
|
||||
let rest =
|
||||
set_recognized_flags(&opts, &mut flags, args).map_err(|e| e.to_string())?;
|
||||
Ok((flags, rest, get_usage(&opts)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_flags_1() {
|
||||
let (flags, rest, _) = set_flags(svec!["deno", "--version"]).unwrap();
|
||||
assert_eq!(rest, svec!["deno"]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
DenoFlags {
|
||||
version: true,
|
||||
..DenoFlags::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_flags_2() {
|
||||
let (flags, rest, _) =
|
||||
set_flags(svec!["deno", "-r", "-D", "script.ts"]).unwrap();
|
||||
assert_eq!(rest, svec!["deno", "script.ts"]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
DenoFlags {
|
||||
log_debug: true,
|
||||
reload: true,
|
||||
..DenoFlags::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_flags_3() {
|
||||
let (flags, rest, _) =
|
||||
set_flags(svec!["deno", "-r", "script.ts", "--allow-write"]).unwrap();
|
||||
assert_eq!(rest, svec!["deno", "script.ts"]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
DenoFlags {
|
||||
reload: true,
|
||||
allow_write: true,
|
||||
..DenoFlags::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_flags_4() {
|
||||
let (flags, rest, _) =
|
||||
set_flags(svec!["deno", "-Dr", "script.ts", "--allow-write"]).unwrap();
|
||||
assert_eq!(rest, svec!["deno", "script.ts"]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
DenoFlags {
|
||||
log_debug: true,
|
||||
reload: true,
|
||||
allow_write: true,
|
||||
..DenoFlags::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_flags_5() {
|
||||
let (flags, rest, _) = set_flags(svec!["deno", "--types"]).unwrap();
|
||||
assert_eq!(rest, svec!["deno"]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
DenoFlags {
|
||||
types: true,
|
||||
..DenoFlags::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_flags_6() {
|
||||
let (flags, rest, _) =
|
||||
set_flags(svec!["deno", "gist.ts", "--title", "X", "--allow-net"]).unwrap();
|
||||
assert_eq!(rest, svec!["deno", "gist.ts", "--title", "X"]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
DenoFlags {
|
||||
allow_net: true,
|
||||
..DenoFlags::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_flags_7() {
|
||||
let (flags, rest, _) =
|
||||
set_flags(svec!["deno", "gist.ts", "--allow-all"]).unwrap();
|
||||
assert_eq!(rest, svec!["deno", "gist.ts"]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
DenoFlags {
|
||||
allow_net: true,
|
||||
allow_env: true,
|
||||
allow_run: true,
|
||||
allow_read: true,
|
||||
allow_write: true,
|
||||
..DenoFlags::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_flags_8() {
|
||||
let (flags, rest, _) =
|
||||
set_flags(svec!["deno", "gist.ts", "--allow-read"]).unwrap();
|
||||
assert_eq!(rest, svec!["deno", "gist.ts"]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
DenoFlags {
|
||||
allow_read: true,
|
||||
..DenoFlags::default()
|
||||
}
|
||||
)
|
||||
}
|
110
cli/fs.rs
Normal file
110
cli/fs.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use std;
|
||||
use std::fs::{create_dir, DirBuilder, File, OpenOptions};
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(any(unix))]
|
||||
use std::os::unix::fs::DirBuilderExt;
|
||||
#[cfg(any(unix))]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
pub fn write_file<T: AsRef<[u8]>>(
|
||||
filename: &Path,
|
||||
data: T,
|
||||
perm: u32,
|
||||
) -> std::io::Result<()> {
|
||||
write_file_2(filename, data, true, perm, true, false)
|
||||
}
|
||||
|
||||
pub fn write_file_2<T: AsRef<[u8]>>(
|
||||
filename: &Path,
|
||||
data: T,
|
||||
update_perm: bool,
|
||||
perm: u32,
|
||||
is_create: bool,
|
||||
is_append: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.append(is_append)
|
||||
.truncate(!is_append)
|
||||
.create(is_create)
|
||||
.open(filename)?;
|
||||
|
||||
if update_perm {
|
||||
set_permissions(&mut file, perm)?;
|
||||
}
|
||||
|
||||
file.write_all(data.as_ref())
|
||||
}
|
||||
|
||||
#[cfg(any(unix))]
|
||||
fn set_permissions(file: &mut File, perm: u32) -> std::io::Result<()> {
|
||||
debug!("set file perm to {}", perm);
|
||||
file.set_permissions(PermissionsExt::from_mode(perm & 0o777))
|
||||
}
|
||||
#[cfg(not(any(unix)))]
|
||||
fn set_permissions(_file: &mut File, _perm: u32) -> std::io::Result<()> {
|
||||
// NOOP on windows
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn make_temp_dir(
|
||||
dir: Option<&Path>,
|
||||
prefix: Option<&str>,
|
||||
suffix: Option<&str>,
|
||||
) -> std::io::Result<PathBuf> {
|
||||
let prefix_ = prefix.unwrap_or("");
|
||||
let suffix_ = suffix.unwrap_or("");
|
||||
let mut buf: PathBuf = match dir {
|
||||
Some(ref p) => p.to_path_buf(),
|
||||
None => std::env::temp_dir(),
|
||||
}.join("_");
|
||||
let mut rng = rand::thread_rng();
|
||||
loop {
|
||||
let unique = rng.gen::<u32>();
|
||||
buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_));
|
||||
// TODO: on posix, set mode flags to 0o700.
|
||||
let r = create_dir(buf.as_path());
|
||||
match r {
|
||||
Err(ref e) if e.kind() == ErrorKind::AlreadyExists => continue,
|
||||
Ok(_) => return Ok(buf),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mkdir(path: &Path, perm: u32, recursive: bool) -> std::io::Result<()> {
|
||||
debug!("mkdir -p {}", path.display());
|
||||
let mut builder = DirBuilder::new();
|
||||
builder.recursive(recursive);
|
||||
set_dir_permission(&mut builder, perm);
|
||||
builder.create(path)
|
||||
}
|
||||
|
||||
#[cfg(any(unix))]
|
||||
fn set_dir_permission(builder: &mut DirBuilder, perm: u32) {
|
||||
debug!("set dir perm to {}", perm);
|
||||
builder.mode(perm & 0o777);
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix)))]
|
||||
fn set_dir_permission(_builder: &mut DirBuilder, _perm: u32) {
|
||||
// NOOP on windows
|
||||
}
|
||||
|
||||
pub fn normalize_path(path: &Path) -> String {
|
||||
let s = String::from(path.to_str().unwrap());
|
||||
if cfg!(windows) {
|
||||
// TODO This isn't correct. Probbly should iterate over components.
|
||||
s.replace("\\", "/")
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
49
cli/global_timer.rs
Normal file
49
cli/global_timer.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
//! This module helps deno implement timers.
|
||||
//!
|
||||
//! As an optimization, we want to avoid an expensive calls into rust for every
|
||||
//! setTimeout in JavaScript. Thus in //js/timers.ts a data structure is
|
||||
//! implemented that calls into Rust for only the smallest timeout. Thus we
|
||||
//! only need to be able to start and cancel a single timer (or Delay, as Tokio
|
||||
//! calls it) for an entire Isolate. This is what is implemented here.
|
||||
|
||||
use crate::tokio_util::panic_on_error;
|
||||
use futures::Future;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::timer::Delay;
|
||||
|
||||
pub struct GlobalTimer {
|
||||
tx: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl GlobalTimer {
|
||||
pub fn new() -> Self {
|
||||
Self { tx: None }
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self) {
|
||||
if let Some(tx) = self.tx.take() {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_timeout(
|
||||
&mut self,
|
||||
deadline: Instant,
|
||||
) -> impl Future<Item = (), Error = ()> {
|
||||
if self.tx.is_some() {
|
||||
self.cancel();
|
||||
}
|
||||
assert!(self.tx.is_none());
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.tx = Some(tx);
|
||||
|
||||
let delay = panic_on_error(Delay::new(deadline));
|
||||
let rx = panic_on_error(rx);
|
||||
|
||||
delay.select(rx).then(|_| Ok(()))
|
||||
}
|
||||
}
|
112
cli/http_body.rs
Normal file
112
cli/http_body.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use futures::Async;
|
||||
use futures::Poll;
|
||||
use hyper::body::Payload;
|
||||
use hyper::Body;
|
||||
use hyper::Chunk;
|
||||
use std::cmp::min;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
/// Wraps `hyper::Body` so that it can be exposed as an `AsyncRead` and integrated
|
||||
/// into resources more easily.
|
||||
pub struct HttpBody {
|
||||
body: Body,
|
||||
chunk: Option<Chunk>,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl HttpBody {
|
||||
pub fn from(body: Body) -> Self {
|
||||
Self {
|
||||
body,
|
||||
chunk: None,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for HttpBody {
|
||||
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for HttpBody {
|
||||
fn poll_read(&mut self, buf: &mut [u8]) -> Poll<usize, io::Error> {
|
||||
if let Some(chunk) = self.chunk.take() {
|
||||
debug!(
|
||||
"HttpBody Fake Read buf {} chunk {} pos {}",
|
||||
buf.len(),
|
||||
chunk.len(),
|
||||
self.pos
|
||||
);
|
||||
let n = min(buf.len(), chunk.len() - self.pos);
|
||||
{
|
||||
let rest = &chunk[self.pos..];
|
||||
buf[..n].clone_from_slice(&rest[..n]);
|
||||
}
|
||||
self.pos += n;
|
||||
if self.pos == chunk.len() {
|
||||
self.pos = 0;
|
||||
} else {
|
||||
self.chunk = Some(chunk);
|
||||
}
|
||||
return Ok(Async::Ready(n));
|
||||
} else {
|
||||
assert_eq!(self.pos, 0);
|
||||
}
|
||||
|
||||
let p = self.body.poll_data();
|
||||
match p {
|
||||
Err(e) => Err(
|
||||
// TODO Need to map hyper::Error into std::io::Error.
|
||||
io::Error::new(io::ErrorKind::Other, e),
|
||||
),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(maybe_chunk)) => match maybe_chunk {
|
||||
None => Ok(Async::Ready(0)),
|
||||
Some(chunk) => {
|
||||
debug!(
|
||||
"HttpBody Real Read buf {} chunk {} pos {}",
|
||||
buf.len(),
|
||||
chunk.len(),
|
||||
self.pos
|
||||
);
|
||||
let n = min(buf.len(), chunk.len());
|
||||
buf[..n].clone_from_slice(&chunk[..n]);
|
||||
if buf.len() < chunk.len() {
|
||||
self.pos = n;
|
||||
self.chunk = Some(chunk);
|
||||
}
|
||||
Ok(Async::Ready(n))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_body_async_read() {
|
||||
use std::str::from_utf8;
|
||||
let body = Body::from("hello world");
|
||||
let mut body = HttpBody::from(body);
|
||||
|
||||
let buf = &mut [0, 0, 0, 0, 0];
|
||||
let r = body.poll_read(buf);
|
||||
assert!(r.is_ok());
|
||||
assert_eq!(r.unwrap(), Async::Ready(5));
|
||||
assert_eq!(from_utf8(buf).unwrap(), "hello");
|
||||
|
||||
let r = body.poll_read(buf);
|
||||
assert!(r.is_ok());
|
||||
assert_eq!(r.unwrap(), Async::Ready(5));
|
||||
assert_eq!(from_utf8(buf).unwrap(), " worl");
|
||||
|
||||
let r = body.poll_read(buf);
|
||||
assert!(r.is_ok());
|
||||
assert_eq!(r.unwrap(), Async::Ready(1));
|
||||
assert_eq!(from_utf8(&buf[0..1]).unwrap(), "d");
|
||||
}
|
166
cli/http_util.rs
Normal file
166
cli/http_util.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::errors;
|
||||
use crate::errors::{DenoError, DenoResult};
|
||||
use crate::tokio_util;
|
||||
|
||||
use futures::future::{loop_fn, Loop};
|
||||
use futures::{future, Future, Stream};
|
||||
use hyper;
|
||||
use hyper::client::{Client, HttpConnector};
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use hyper::Uri;
|
||||
use hyper_rustls;
|
||||
|
||||
type Connector = hyper_rustls::HttpsConnector<HttpConnector>;
|
||||
|
||||
lazy_static! {
|
||||
static ref CONNECTOR: Connector = {
|
||||
let num_dns_threads = 4;
|
||||
Connector::new(num_dns_threads)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_client() -> Client<Connector, hyper::Body> {
|
||||
// TODO use Hyper's connection pool.
|
||||
let c = CONNECTOR.clone();
|
||||
Client::builder().build(c)
|
||||
}
|
||||
|
||||
/// Construct the next uri based on base uri and location header fragment
|
||||
/// See <https://tools.ietf.org/html/rfc3986#section-4.2>
|
||||
fn resolve_uri_from_location(base_uri: &Uri, location: &str) -> Uri {
|
||||
if location.starts_with("http://") || location.starts_with("https://") {
|
||||
// absolute uri
|
||||
location
|
||||
.parse::<Uri>()
|
||||
.expect("provided redirect url should be a valid url")
|
||||
} else if location.starts_with("//") {
|
||||
// "//" authority path-abempty
|
||||
format!("{}:{}", base_uri.scheme_part().unwrap().as_str(), location)
|
||||
.parse::<Uri>()
|
||||
.expect("provided redirect url should be a valid url")
|
||||
} else if location.starts_with('/') {
|
||||
// path-absolute
|
||||
let mut new_uri_parts = base_uri.clone().into_parts();
|
||||
new_uri_parts.path_and_query = Some(location.parse().unwrap());
|
||||
Uri::from_parts(new_uri_parts).unwrap()
|
||||
} else {
|
||||
// assuming path-noscheme | path-empty
|
||||
let mut new_uri_parts = base_uri.clone().into_parts();
|
||||
new_uri_parts.path_and_query =
|
||||
Some(format!("{}/{}", base_uri.path(), location).parse().unwrap());
|
||||
Uri::from_parts(new_uri_parts).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// The CodeFetch message is used to load HTTP javascript resources and expects a
|
||||
// synchronous response, this utility method supports that.
|
||||
pub fn fetch_sync_string(module_name: &str) -> DenoResult<(String, String)> {
|
||||
let url = module_name.parse::<Uri>().unwrap();
|
||||
let client = get_client();
|
||||
// TODO(kevinkassimo): consider set a max redirection counter
|
||||
// to avoid bouncing between 2 or more urls
|
||||
let fetch_future = loop_fn((client, url), |(client, url)| {
|
||||
client
|
||||
.get(url.clone())
|
||||
.map_err(DenoError::from)
|
||||
.and_then(move |response| {
|
||||
if response.status().is_redirection() {
|
||||
let location_string = response
|
||||
.headers()
|
||||
.get("location")
|
||||
.expect("url redirection should provide 'location' header")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
debug!("Redirecting to {}...", &location_string);
|
||||
let new_url = resolve_uri_from_location(&url, &location_string);
|
||||
return Ok(Loop::Continue((client, new_url)));
|
||||
}
|
||||
if !response.status().is_success() {
|
||||
return Err(errors::new(
|
||||
errors::ErrorKind::NotFound,
|
||||
"module not found".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(Loop::Break(response))
|
||||
})
|
||||
}).and_then(|response| {
|
||||
let content_type = response
|
||||
.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
.map(|content_type| content_type.to_str().unwrap().to_string());
|
||||
let body = response
|
||||
.into_body()
|
||||
.concat2()
|
||||
.map(|body| String::from_utf8(body.to_vec()).unwrap())
|
||||
.map_err(DenoError::from);
|
||||
body.join(future::ok(content_type))
|
||||
}).and_then(|(body_string, maybe_content_type)| {
|
||||
future::ok((body_string, maybe_content_type.unwrap()))
|
||||
});
|
||||
|
||||
tokio_util::block_on(fetch_future)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fetch_sync_string() {
|
||||
// Relies on external http server. See tools/http_server.py
|
||||
tokio_util::init(|| {
|
||||
let (p, m) =
|
||||
fetch_sync_string("http://127.0.0.1:4545/package.json").unwrap();
|
||||
println!("package.json len {}", p.len());
|
||||
assert!(p.len() > 1);
|
||||
assert!(m == "application/json")
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fetch_sync_string_with_redirect() {
|
||||
// Relies on external http server. See tools/http_server.py
|
||||
tokio_util::init(|| {
|
||||
let (p, m) =
|
||||
fetch_sync_string("http://127.0.0.1:4546/package.json").unwrap();
|
||||
println!("package.json len {}", p.len());
|
||||
assert!(p.len() > 1);
|
||||
assert!(m == "application/json")
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_uri_from_location_full_1() {
|
||||
let url = "http://deno.land".parse::<Uri>().unwrap();
|
||||
let new_uri = resolve_uri_from_location(&url, "http://golang.org");
|
||||
assert_eq!(new_uri.host().unwrap(), "golang.org");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_uri_from_location_full_2() {
|
||||
let url = "https://deno.land".parse::<Uri>().unwrap();
|
||||
let new_uri = resolve_uri_from_location(&url, "https://golang.org");
|
||||
assert_eq!(new_uri.host().unwrap(), "golang.org");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_uri_from_location_relative_1() {
|
||||
let url = "http://deno.land/x".parse::<Uri>().unwrap();
|
||||
let new_uri = resolve_uri_from_location(&url, "//rust-lang.org/en-US");
|
||||
assert_eq!(new_uri.host().unwrap(), "rust-lang.org");
|
||||
assert_eq!(new_uri.path(), "/en-US");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_uri_from_location_relative_2() {
|
||||
let url = "http://deno.land/x".parse::<Uri>().unwrap();
|
||||
let new_uri = resolve_uri_from_location(&url, "/y");
|
||||
assert_eq!(new_uri.host().unwrap(), "deno.land");
|
||||
assert_eq!(new_uri.path(), "/y");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_uri_from_location_relative_3() {
|
||||
let url = "http://deno.land/x".parse::<Uri>().unwrap();
|
||||
let new_uri = resolve_uri_from_location(&url, "z");
|
||||
assert_eq!(new_uri.host().unwrap(), "deno.land");
|
||||
assert_eq!(new_uri.path(), "/x/z");
|
||||
}
|
236
cli/isolate.rs
Normal file
236
cli/isolate.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::cli::Cli;
|
||||
use crate::compiler::compile_sync;
|
||||
use crate::compiler::ModuleMetaData;
|
||||
use crate::errors::DenoError;
|
||||
use crate::errors::RustOrJsError;
|
||||
use crate::isolate_state::IsolateState;
|
||||
use crate::js_errors;
|
||||
use crate::msg;
|
||||
use deno_core;
|
||||
use deno_core::deno_mod;
|
||||
use deno_core::JSError;
|
||||
use futures::Async;
|
||||
use futures::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
type CoreIsolate = deno_core::Isolate<Cli>;
|
||||
|
||||
/// Wraps deno_core::Isolate to provide source maps, ops for the CLI, and
|
||||
/// high-level module loading
|
||||
pub struct Isolate {
|
||||
inner: CoreIsolate,
|
||||
state: Arc<IsolateState>,
|
||||
}
|
||||
|
||||
impl Isolate {
|
||||
pub fn new(cli: Cli) -> Isolate {
|
||||
let state = cli.state.clone();
|
||||
Self {
|
||||
inner: CoreIsolate::new(cli),
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as execute2() but the filename defaults to "<anonymous>".
|
||||
pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> {
|
||||
self.execute2("<anonymous>", js_source)
|
||||
}
|
||||
|
||||
/// Executes the provided JavaScript source code. The js_filename argument is
|
||||
/// provided only for debugging purposes.
|
||||
pub fn execute2(
|
||||
&mut self,
|
||||
js_filename: &str,
|
||||
js_source: &str,
|
||||
) -> Result<(), JSError> {
|
||||
self.inner.execute(js_filename, js_source)
|
||||
}
|
||||
|
||||
// TODO(ry) make this return a future.
|
||||
fn mod_load_deps(&self, id: deno_mod) -> Result<(), RustOrJsError> {
|
||||
// basically iterate over the imports, start loading them.
|
||||
|
||||
let referrer_name = {
|
||||
let g = self.state.modules.lock().unwrap();
|
||||
g.get_name(id).unwrap().clone()
|
||||
};
|
||||
|
||||
for specifier in self.inner.mod_get_imports(id) {
|
||||
let (name, _local_filename) = self
|
||||
.state
|
||||
.dir
|
||||
.resolve_module(&specifier, &referrer_name)
|
||||
.map_err(DenoError::from)
|
||||
.map_err(RustOrJsError::from)?;
|
||||
|
||||
debug!("mod_load_deps {}", name);
|
||||
|
||||
if !self.state.modules.lock().unwrap().is_registered(&name) {
|
||||
let out = fetch_module_meta_data_and_maybe_compile(
|
||||
&self.state,
|
||||
&specifier,
|
||||
&referrer_name,
|
||||
)?;
|
||||
let child_id = self.mod_new_and_register(
|
||||
false,
|
||||
&out.module_name.clone(),
|
||||
&out.js_source(),
|
||||
)?;
|
||||
|
||||
self.mod_load_deps(child_id)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes the provided JavaScript module.
|
||||
pub fn execute_mod(
|
||||
&mut self,
|
||||
js_filename: &str,
|
||||
is_prefetch: bool,
|
||||
) -> Result<(), RustOrJsError> {
|
||||
// TODO move isolate_state::execute_mod impl here.
|
||||
self
|
||||
.execute_mod_inner(js_filename, is_prefetch)
|
||||
.map_err(|err| match err {
|
||||
RustOrJsError::Js(err) => RustOrJsError::Js(self.apply_source_map(err)),
|
||||
x => x,
|
||||
})
|
||||
}
|
||||
|
||||
/// High-level way to execute modules.
|
||||
/// This will issue HTTP requests and file system calls.
|
||||
/// Blocks. TODO(ry) Don't block.
|
||||
fn execute_mod_inner(
|
||||
&mut self,
|
||||
url: &str,
|
||||
is_prefetch: bool,
|
||||
) -> Result<(), RustOrJsError> {
|
||||
let out = fetch_module_meta_data_and_maybe_compile(&self.state, url, ".")
|
||||
.map_err(RustOrJsError::from)?;
|
||||
|
||||
let id = self
|
||||
.mod_new_and_register(true, &out.module_name.clone(), &out.js_source())
|
||||
.map_err(RustOrJsError::from)?;
|
||||
|
||||
self.mod_load_deps(id)?;
|
||||
|
||||
self
|
||||
.inner
|
||||
.mod_instantiate(id)
|
||||
.map_err(RustOrJsError::from)?;
|
||||
if !is_prefetch {
|
||||
self.inner.mod_evaluate(id).map_err(RustOrJsError::from)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wraps Isolate::mod_new but registers with modules.
|
||||
fn mod_new_and_register(
|
||||
&self,
|
||||
main: bool,
|
||||
name: &str,
|
||||
source: &str,
|
||||
) -> Result<deno_mod, JSError> {
|
||||
let id = self.inner.mod_new(main, name, source)?;
|
||||
self.state.modules.lock().unwrap().register(id, &name);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn print_file_info(&self, module: &str) {
|
||||
let m = self.state.modules.lock().unwrap();
|
||||
m.print_file_info(&self.state.dir, module.to_string());
|
||||
}
|
||||
|
||||
/// Applies source map to the error.
|
||||
fn apply_source_map(&self, err: JSError) -> JSError {
|
||||
js_errors::apply_source_map(&err, &self.state.dir)
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Isolate {
|
||||
type Item = ();
|
||||
type Error = JSError;
|
||||
|
||||
fn poll(&mut self) -> Result<Async<()>, Self::Error> {
|
||||
self.inner.poll().map_err(|err| self.apply_source_map(err))
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_module_meta_data_and_maybe_compile(
|
||||
state: &Arc<IsolateState>,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
) -> Result<ModuleMetaData, DenoError> {
|
||||
let mut out = state.dir.fetch_module_meta_data(specifier, referrer)?;
|
||||
if (out.media_type == msg::MediaType::TypeScript
|
||||
&& out.maybe_output_code.is_none())
|
||||
|| state.flags.recompile
|
||||
{
|
||||
debug!(">>>>> compile_sync START");
|
||||
out = compile_sync(state, specifier, &referrer, &out);
|
||||
debug!(">>>>> compile_sync END");
|
||||
state.dir.code_cache(&out)?;
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::flags;
|
||||
use crate::permissions::DenoPermissions;
|
||||
use crate::tokio_util;
|
||||
use futures::future::lazy;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[test]
|
||||
fn execute_mod() {
|
||||
let filename = std::env::current_dir()
|
||||
.unwrap()
|
||||
.join("tests/esm_imports_a.js");
|
||||
let filename = filename.to_str().unwrap().to_string();
|
||||
|
||||
let argv = vec![String::from("./deno"), filename.clone()];
|
||||
let (flags, rest_argv, _) = flags::set_flags(argv).unwrap();
|
||||
|
||||
let state = Arc::new(IsolateState::new(flags, rest_argv, None));
|
||||
let state_ = state.clone();
|
||||
tokio_util::run(lazy(move || {
|
||||
let cli = Cli::new(None, state.clone(), DenoPermissions::default());
|
||||
let mut isolate = Isolate::new(cli);
|
||||
if let Err(err) = isolate.execute_mod(&filename, false) {
|
||||
eprintln!("execute_mod err {:?}", err);
|
||||
}
|
||||
tokio_util::panic_on_error(isolate)
|
||||
}));
|
||||
|
||||
let metrics = &state_.metrics;
|
||||
assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_mod_circular() {
|
||||
let filename = std::env::current_dir().unwrap().join("tests/circular1.js");
|
||||
let filename = filename.to_str().unwrap().to_string();
|
||||
|
||||
let argv = vec![String::from("./deno"), filename.clone()];
|
||||
let (flags, rest_argv, _) = flags::set_flags(argv).unwrap();
|
||||
|
||||
let state = Arc::new(IsolateState::new(flags, rest_argv, None));
|
||||
let state_ = state.clone();
|
||||
tokio_util::run(lazy(move || {
|
||||
let cli = Cli::new(None, state.clone(), DenoPermissions::default());
|
||||
let mut isolate = Isolate::new(cli);
|
||||
if let Err(err) = isolate.execute_mod(&filename, false) {
|
||||
eprintln!("execute_mod err {:?}", err);
|
||||
}
|
||||
tokio_util::panic_on_error(isolate)
|
||||
}));
|
||||
|
||||
let metrics = &state_.metrics;
|
||||
assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 2);
|
||||
}
|
||||
}
|
110
cli/isolate_state.rs
Normal file
110
cli/isolate_state.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::cli::Buf;
|
||||
use crate::deno_dir;
|
||||
use crate::flags;
|
||||
use crate::global_timer::GlobalTimer;
|
||||
use crate::modules::Modules;
|
||||
use futures::sync::mpsc as async_mpsc;
|
||||
use std;
|
||||
use std::env;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub type WorkerSender = async_mpsc::Sender<Buf>;
|
||||
pub type WorkerReceiver = async_mpsc::Receiver<Buf>;
|
||||
pub type WorkerChannels = (WorkerSender, WorkerReceiver);
|
||||
|
||||
// AtomicU64 is currently unstable
|
||||
#[derive(Default)]
|
||||
pub struct Metrics {
|
||||
pub ops_dispatched: AtomicUsize,
|
||||
pub ops_completed: AtomicUsize,
|
||||
pub bytes_sent_control: AtomicUsize,
|
||||
pub bytes_sent_data: AtomicUsize,
|
||||
pub bytes_received: AtomicUsize,
|
||||
pub resolve_count: AtomicUsize,
|
||||
}
|
||||
|
||||
// Isolate cannot be passed between threads but IsolateState can.
|
||||
// IsolateState satisfies Send and Sync.
|
||||
// So any state that needs to be accessed outside the main V8 thread should be
|
||||
// inside IsolateState.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
||||
pub struct IsolateState {
|
||||
pub dir: deno_dir::DenoDir,
|
||||
pub argv: Vec<String>,
|
||||
pub flags: flags::DenoFlags,
|
||||
pub metrics: Metrics,
|
||||
pub modules: Mutex<Modules>,
|
||||
pub worker_channels: Option<Mutex<WorkerChannels>>,
|
||||
pub global_timer: Mutex<GlobalTimer>,
|
||||
}
|
||||
|
||||
impl IsolateState {
|
||||
pub fn new(
|
||||
flags: flags::DenoFlags,
|
||||
argv_rest: Vec<String>,
|
||||
worker_channels: Option<WorkerChannels>,
|
||||
) -> Self {
|
||||
let custom_root = env::var("DENO_DIR").map(|s| s.into()).ok();
|
||||
|
||||
Self {
|
||||
dir: deno_dir::DenoDir::new(flags.reload, flags.recompile, custom_root)
|
||||
.unwrap(),
|
||||
argv: argv_rest,
|
||||
flags,
|
||||
metrics: Metrics::default(),
|
||||
modules: Mutex::new(Modules::new()),
|
||||
worker_channels: worker_channels.map(Mutex::new),
|
||||
global_timer: Mutex::new(GlobalTimer::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_module(&self) -> Option<String> {
|
||||
if self.argv.len() <= 1 {
|
||||
None
|
||||
} else {
|
||||
let specifier = self.argv[1].clone();
|
||||
let referrer = ".";
|
||||
match self.dir.resolve_module_url(&specifier, referrer) {
|
||||
Ok(url) => Some(url.to_string()),
|
||||
Err(e) => {
|
||||
debug!("Potentially swallowed error {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn mock() -> IsolateState {
|
||||
let argv = vec![String::from("./deno"), String::from("hello.js")];
|
||||
// For debugging: argv.push_back(String::from("-D"));
|
||||
let (flags, rest_argv, _) = flags::set_flags(argv).unwrap();
|
||||
IsolateState::new(flags, rest_argv, None)
|
||||
}
|
||||
|
||||
pub fn metrics_op_dispatched(
|
||||
&self,
|
||||
bytes_sent_control: usize,
|
||||
bytes_sent_data: usize,
|
||||
) {
|
||||
self.metrics.ops_dispatched.fetch_add(1, Ordering::SeqCst);
|
||||
self
|
||||
.metrics
|
||||
.bytes_sent_control
|
||||
.fetch_add(bytes_sent_control, Ordering::SeqCst);
|
||||
self
|
||||
.metrics
|
||||
.bytes_sent_data
|
||||
.fetch_add(bytes_sent_data, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn metrics_op_completed(&self, bytes_received: usize) {
|
||||
self.metrics.ops_completed.fetch_add(1, Ordering::SeqCst);
|
||||
self
|
||||
.metrics
|
||||
.bytes_received
|
||||
.fetch_add(bytes_received, Ordering::SeqCst);
|
||||
}
|
||||
}
|
424
cli/js_errors.rs
Normal file
424
cli/js_errors.rs
Normal file
|
@ -0,0 +1,424 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
//! This mod adds source maps and ANSI color display to deno_core::JSError.
|
||||
use crate::ansi;
|
||||
use deno_core::JSError;
|
||||
use deno_core::StackFrame;
|
||||
use source_map_mappings::parse_mappings;
|
||||
use source_map_mappings::Bias;
|
||||
use source_map_mappings::Mappings;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
/// Wrapper around JSError which provides color to_string.
|
||||
pub struct JSErrorColor<'a>(pub &'a JSError);
|
||||
|
||||
struct StackFrameColor<'a>(&'a StackFrame);
|
||||
|
||||
pub trait SourceMapGetter {
|
||||
/// Returns the raw source map file.
|
||||
fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>>;
|
||||
}
|
||||
|
||||
/// Cached filename lookups. The key can be None if a previous lookup failed to
|
||||
/// find a SourceMap.
|
||||
type CachedMaps = HashMap<String, Option<SourceMap>>;
|
||||
|
||||
struct SourceMap {
|
||||
mappings: Mappings,
|
||||
sources: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for StackFrameColor<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let frame = self.0;
|
||||
// Note when we print to string, we change from 0-indexed to 1-indexed.
|
||||
let function_name = ansi::italic_bold(frame.function_name.clone());
|
||||
let script_line_column =
|
||||
format_script_line_column(&frame.script_name, frame.line, frame.column);
|
||||
|
||||
if !frame.function_name.is_empty() {
|
||||
write!(f, " at {} ({})", function_name, script_line_column)
|
||||
} else if frame.is_eval {
|
||||
write!(f, " at eval ({})", script_line_column)
|
||||
} else {
|
||||
write!(f, " at {}", script_line_column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_script_line_column(
|
||||
script_name: &str,
|
||||
line: i64,
|
||||
column: i64,
|
||||
) -> String {
|
||||
// TODO match this style with how typescript displays errors.
|
||||
let line = ansi::yellow((1 + line).to_string());
|
||||
let column = ansi::yellow((1 + column).to_string());
|
||||
let script_name = ansi::cyan(script_name.to_string());
|
||||
format!("{}:{}:{}", script_name, line, column)
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for JSErrorColor<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let e = self.0;
|
||||
if e.script_resource_name.is_some() {
|
||||
let script_resource_name = e.script_resource_name.as_ref().unwrap();
|
||||
// Avoid showing internal code from gen/bundle/main.js
|
||||
if script_resource_name != "gen/bundle/main.js"
|
||||
&& script_resource_name != "gen/bundle/compiler.js"
|
||||
{
|
||||
if e.line_number.is_some() && e.start_column.is_some() {
|
||||
assert!(e.line_number.is_some());
|
||||
assert!(e.start_column.is_some());
|
||||
let script_line_column = format_script_line_column(
|
||||
script_resource_name,
|
||||
e.line_number.unwrap() - 1,
|
||||
e.start_column.unwrap() - 1,
|
||||
);
|
||||
write!(f, "{}", script_line_column)?;
|
||||
}
|
||||
if e.source_line.is_some() {
|
||||
write!(f, "\n{}\n", e.source_line.as_ref().unwrap())?;
|
||||
let mut s = String::new();
|
||||
for i in 0..e.end_column.unwrap() {
|
||||
if i >= e.start_column.unwrap() {
|
||||
s.push('^');
|
||||
} else {
|
||||
s.push(' ');
|
||||
}
|
||||
}
|
||||
writeln!(f, "{}", ansi::red_bold(s))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, "{}", ansi::bold(e.message.clone()))?;
|
||||
|
||||
for frame in &e.frames {
|
||||
write!(f, "\n{}", StackFrameColor(&frame).to_string())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceMap {
|
||||
fn from_json(json_str: &str) -> Option<Self> {
|
||||
// Ugly. Maybe use serde_derive.
|
||||
match serde_json::from_str::<serde_json::Value>(json_str) {
|
||||
Ok(serde_json::Value::Object(map)) => match map["mappings"].as_str() {
|
||||
None => None,
|
||||
Some(mappings_str) => {
|
||||
match parse_mappings::<()>(mappings_str.as_bytes()) {
|
||||
Err(_) => None,
|
||||
Ok(mappings) => {
|
||||
if !map["sources"].is_array() {
|
||||
return None;
|
||||
}
|
||||
let sources_val = map["sources"].as_array().unwrap();
|
||||
let mut sources = Vec::<String>::new();
|
||||
|
||||
for source_val in sources_val {
|
||||
match source_val.as_str() {
|
||||
None => return None,
|
||||
Some(source) => {
|
||||
sources.push(source.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(SourceMap { sources, mappings })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn frame_apply_source_map(
|
||||
frame: &StackFrame,
|
||||
mappings_map: &mut CachedMaps,
|
||||
getter: &dyn SourceMapGetter,
|
||||
) -> StackFrame {
|
||||
let maybe_sm = get_mappings(frame.script_name.as_ref(), mappings_map, getter);
|
||||
let frame_pos = (
|
||||
frame.script_name.to_owned(),
|
||||
frame.line as i64,
|
||||
frame.column as i64,
|
||||
);
|
||||
let (script_name, line, column) = match maybe_sm {
|
||||
None => frame_pos,
|
||||
Some(sm) => match sm.mappings.original_location_for(
|
||||
frame.line as u32,
|
||||
frame.column as u32,
|
||||
Bias::default(),
|
||||
) {
|
||||
None => frame_pos,
|
||||
Some(mapping) => match &mapping.original {
|
||||
None => frame_pos,
|
||||
Some(original) => {
|
||||
let orig_source = sm.sources[original.source as usize].clone();
|
||||
(
|
||||
orig_source,
|
||||
i64::from(original.original_line),
|
||||
i64::from(original.original_column),
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
StackFrame {
|
||||
script_name,
|
||||
function_name: frame.function_name.clone(),
|
||||
line,
|
||||
column,
|
||||
is_eval: frame.is_eval,
|
||||
is_constructor: frame.is_constructor,
|
||||
is_wasm: frame.is_wasm,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_source_map(
|
||||
js_error: &JSError,
|
||||
getter: &dyn SourceMapGetter,
|
||||
) -> JSError {
|
||||
let mut mappings_map: CachedMaps = HashMap::new();
|
||||
let mut frames = Vec::<StackFrame>::new();
|
||||
for frame in &js_error.frames {
|
||||
let f = frame_apply_source_map(&frame, &mut mappings_map, getter);
|
||||
frames.push(f);
|
||||
}
|
||||
JSError {
|
||||
message: js_error.message.clone(),
|
||||
frames,
|
||||
error_level: js_error.error_level,
|
||||
source_line: js_error.source_line.clone(),
|
||||
// TODO the following need to be source mapped:
|
||||
script_resource_name: js_error.script_resource_name.clone(),
|
||||
line_number: js_error.line_number,
|
||||
start_position: js_error.start_position,
|
||||
end_position: js_error.end_position,
|
||||
start_column: js_error.start_column,
|
||||
end_column: js_error.end_column,
|
||||
}
|
||||
}
|
||||
|
||||
// The bundle does not get built for 'cargo check', so we don't embed the
|
||||
// bundle source map.
|
||||
#[cfg(feature = "check-only")]
|
||||
fn builtin_source_map(script_name: &str) -> Option<Vec<u8>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "check-only"))]
|
||||
fn builtin_source_map(script_name: &str) -> Option<Vec<u8>> {
|
||||
match script_name {
|
||||
"gen/bundle/main.js" => Some(
|
||||
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js.map"))
|
||||
.to_vec(),
|
||||
),
|
||||
"gen/bundle/compiler.js" => Some(
|
||||
include_bytes!(concat!(
|
||||
env!("GN_OUT_DIR"),
|
||||
"/gen/bundle/compiler.js.map"
|
||||
)).to_vec(),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_map_string(
|
||||
script_name: &str,
|
||||
getter: &dyn SourceMapGetter,
|
||||
) -> Option<SourceMap> {
|
||||
builtin_source_map(script_name)
|
||||
.or_else(|| getter.get_source_map(script_name))
|
||||
.and_then(|raw_source_map| {
|
||||
SourceMap::from_json(str::from_utf8(&raw_source_map).unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_mappings<'a>(
|
||||
script_name: &str,
|
||||
mappings_map: &'a mut CachedMaps,
|
||||
getter: &dyn SourceMapGetter,
|
||||
) -> &'a Option<SourceMap> {
|
||||
mappings_map
|
||||
.entry(script_name.to_string())
|
||||
.or_insert_with(|| parse_map_string(script_name, getter))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ansi::strip_ansi_codes;
|
||||
|
||||
fn error1() -> JSError {
|
||||
JSError {
|
||||
message: "Error: foo bar".to_string(),
|
||||
source_line: None,
|
||||
script_resource_name: None,
|
||||
line_number: None,
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: None,
|
||||
end_column: None,
|
||||
frames: vec![
|
||||
StackFrame {
|
||||
line: 4,
|
||||
column: 16,
|
||||
script_name: "foo_bar.ts".to_string(),
|
||||
function_name: "foo".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 5,
|
||||
column: 20,
|
||||
script_name: "bar_baz.ts".to_string(),
|
||||
function_name: "qat".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 1,
|
||||
column: 1,
|
||||
script_name: "deno_main.js".to_string(),
|
||||
function_name: "".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
struct MockSourceMapGetter {}
|
||||
|
||||
impl SourceMapGetter for MockSourceMapGetter {
|
||||
fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>> {
|
||||
let s = match script_name {
|
||||
"foo_bar.ts" => r#"{"sources": ["foo_bar.ts"], "mappings":";;;IAIA,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#,
|
||||
"bar_baz.ts" => r#"{"sources": ["bar_baz.ts"], "mappings":";;;IAEA,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,GAAG,GAAG,sDAAa,OAAO,2BAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,EAAE,CAAC;IAEQ,QAAA,GAAG,GAAG,KAAK,CAAC;IAEzB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#,
|
||||
_ => return None,
|
||||
};
|
||||
Some(s.as_bytes().to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_error_to_string() {
|
||||
let e = error1();
|
||||
assert_eq!("Error: foo bar\n at foo (foo_bar.ts:5:17)\n at qat (bar_baz.ts:6:21)\n at deno_main.js:2:2", strip_ansi_codes(&e.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_error_apply_source_map_1() {
|
||||
let e = error1();
|
||||
let getter = MockSourceMapGetter {};
|
||||
let actual = apply_source_map(&e, &getter);
|
||||
let expected = JSError {
|
||||
message: "Error: foo bar".to_string(),
|
||||
source_line: None,
|
||||
script_resource_name: None,
|
||||
line_number: None,
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: None,
|
||||
end_column: None,
|
||||
frames: vec![
|
||||
StackFrame {
|
||||
line: 5,
|
||||
column: 12,
|
||||
script_name: "foo_bar.ts".to_string(),
|
||||
function_name: "foo".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 4,
|
||||
column: 14,
|
||||
script_name: "bar_baz.ts".to_string(),
|
||||
function_name: "qat".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 1,
|
||||
column: 1,
|
||||
script_name: "deno_main.js".to_string(),
|
||||
function_name: "".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_error_apply_source_map_2() {
|
||||
let e = JSError {
|
||||
message: "TypeError: baz".to_string(),
|
||||
source_line: None,
|
||||
script_resource_name: None,
|
||||
line_number: None,
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: None,
|
||||
end_column: None,
|
||||
frames: vec![StackFrame {
|
||||
line: 11,
|
||||
column: 12,
|
||||
script_name: "gen/bundle/main.js".to_string(),
|
||||
function_name: "setLogDebug".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
}],
|
||||
};
|
||||
let getter = MockSourceMapGetter {};
|
||||
let actual = apply_source_map(&e, &getter);
|
||||
assert_eq!(actual.message, "TypeError: baz");
|
||||
// Because this is accessing the live bundle, this test might be more fragile
|
||||
assert_eq!(actual.frames.len(), 1);
|
||||
assert!(actual.frames[0].script_name.ends_with("js/util.ts"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_map_from_json() {
|
||||
let json = r#"{"version":3,"file":"error_001.js","sourceRoot":"","sources":["file:///Users/rld/src/deno/tests/error_001.ts"],"names":[],"mappings":"AAAA,SAAS,GAAG;IACV,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,GAAG;IACV,GAAG,EAAE,CAAC;AACR,CAAC;AAED,GAAG,EAAE,CAAC"}"#;
|
||||
let sm = SourceMap::from_json(json).unwrap();
|
||||
assert_eq!(sm.sources.len(), 1);
|
||||
assert_eq!(
|
||||
sm.sources[0],
|
||||
"file:///Users/rld/src/deno/tests/error_001.ts"
|
||||
);
|
||||
let mapping = sm
|
||||
.mappings
|
||||
.original_location_for(1, 10, Bias::default())
|
||||
.unwrap();
|
||||
assert_eq!(mapping.generated_line, 1);
|
||||
assert_eq!(mapping.generated_column, 10);
|
||||
assert_eq!(
|
||||
mapping.original,
|
||||
Some(source_map_mappings::OriginalLocation {
|
||||
source: 0,
|
||||
original_line: 1,
|
||||
original_column: 8,
|
||||
name: None
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
140
cli/main.rs
Normal file
140
cli/main.rs
Normal file
|
@ -0,0 +1,140 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
mod ansi;
|
||||
pub mod cli;
|
||||
pub mod compiler;
|
||||
pub mod deno_dir;
|
||||
pub mod errors;
|
||||
pub mod flags;
|
||||
mod fs;
|
||||
mod global_timer;
|
||||
mod http_body;
|
||||
mod http_util;
|
||||
pub mod isolate;
|
||||
pub mod isolate_state;
|
||||
pub mod js_errors;
|
||||
pub mod modules;
|
||||
pub mod msg;
|
||||
pub mod msg_util;
|
||||
pub mod ops;
|
||||
pub mod permissions;
|
||||
mod repl;
|
||||
pub mod resolve_addr;
|
||||
pub mod resources;
|
||||
mod startup_data;
|
||||
mod tokio_util;
|
||||
mod tokio_write;
|
||||
pub mod version;
|
||||
pub mod workers;
|
||||
|
||||
use crate::cli::Cli;
|
||||
use crate::errors::RustOrJsError;
|
||||
use crate::isolate::Isolate;
|
||||
use crate::isolate_state::IsolateState;
|
||||
use futures::lazy;
|
||||
use futures::Future;
|
||||
use log::{LevelFilter, Metadata, Record};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
static LOGGER: Logger = Logger;
|
||||
|
||||
struct Logger;
|
||||
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= log::max_level()
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
println!("{} RS - {}", record.level(), record.args());
|
||||
}
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
fn print_err_and_exit(err: RustOrJsError) {
|
||||
eprintln!("{}", err.to_string());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
fn js_check<E>(r: Result<(), E>)
|
||||
where
|
||||
E: Into<RustOrJsError>,
|
||||
{
|
||||
if let Err(err) = r {
|
||||
print_err_and_exit(err.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(windows)]
|
||||
ansi_term::enable_ansi_support().ok(); // For Windows 10
|
||||
|
||||
log::set_logger(&LOGGER).unwrap();
|
||||
let args = env::args().collect();
|
||||
let (mut flags, mut rest_argv, usage_string) = flags::set_flags(args)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("{}", err);
|
||||
std::process::exit(1)
|
||||
});
|
||||
|
||||
if flags.help {
|
||||
println!("{}", &usage_string);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
log::set_max_level(if flags.log_debug {
|
||||
LevelFilter::Debug
|
||||
} else {
|
||||
LevelFilter::Warn
|
||||
});
|
||||
|
||||
if flags.fmt {
|
||||
rest_argv.insert(1, "https://deno.land/std/prettier/main.ts".to_string());
|
||||
flags.allow_read = true;
|
||||
flags.allow_write = true;
|
||||
}
|
||||
|
||||
let should_prefetch = flags.prefetch || flags.info;
|
||||
let should_display_info = flags.info;
|
||||
|
||||
let state = Arc::new(IsolateState::new(flags, rest_argv, None));
|
||||
let state_ = state.clone();
|
||||
let startup_data = startup_data::deno_isolate_init();
|
||||
let permissions = permissions::DenoPermissions::from_flags(&state.flags);
|
||||
let cli = Cli::new(Some(startup_data), state_, permissions);
|
||||
let mut isolate = Isolate::new(cli);
|
||||
|
||||
let main_future = lazy(move || {
|
||||
// Setup runtime.
|
||||
js_check(isolate.execute("denoMain()"));
|
||||
|
||||
// Execute main module.
|
||||
if let Some(main_module) = state.main_module() {
|
||||
debug!("main_module {}", main_module);
|
||||
js_check(isolate.execute_mod(&main_module, should_prefetch));
|
||||
if should_display_info {
|
||||
// Display file info and exit. Do not run file
|
||||
isolate.print_file_info(&main_module);
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
isolate.then(|result| {
|
||||
js_check(result);
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
tokio_util::run(main_future);
|
||||
}
|
204
cli/modules.rs
Normal file
204
cli/modules.rs
Normal file
|
@ -0,0 +1,204 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::ansi;
|
||||
use crate::deno_dir::DenoDir;
|
||||
use crate::msg;
|
||||
use deno_core::deno_mod;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
pub struct ModuleInfo {
|
||||
name: String,
|
||||
children: Vec<deno_mod>,
|
||||
}
|
||||
|
||||
/// A collection of JS modules.
|
||||
#[derive(Default)]
|
||||
pub struct Modules {
|
||||
pub info: HashMap<deno_mod, ModuleInfo>,
|
||||
pub by_name: HashMap<String, deno_mod>,
|
||||
}
|
||||
|
||||
impl Modules {
|
||||
pub fn new() -> Modules {
|
||||
Self {
|
||||
info: HashMap::new(),
|
||||
by_name: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self, name: &str) -> Option<deno_mod> {
|
||||
self.by_name.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn get_children(&self, id: deno_mod) -> Option<&Vec<deno_mod>> {
|
||||
self.info.get(&id).map(|i| &i.children)
|
||||
}
|
||||
|
||||
pub fn get_name(&self, id: deno_mod) -> Option<&String> {
|
||||
self.info.get(&id).map(|i| &i.name)
|
||||
}
|
||||
|
||||
pub fn is_registered(&self, name: &str) -> bool {
|
||||
self.by_name.get(name).is_some()
|
||||
}
|
||||
|
||||
pub fn register(&mut self, id: deno_mod, name: &str) {
|
||||
let name = String::from(name);
|
||||
debug!("register {}", name);
|
||||
self.by_name.insert(name.clone(), id);
|
||||
self.info.insert(
|
||||
id,
|
||||
ModuleInfo {
|
||||
name,
|
||||
children: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn resolve_cb(
|
||||
&mut self,
|
||||
deno_dir: &DenoDir,
|
||||
specifier: &str,
|
||||
referrer: deno_mod,
|
||||
) -> deno_mod {
|
||||
debug!("resolve_cb {}", specifier);
|
||||
|
||||
let maybe_info = self.info.get_mut(&referrer);
|
||||
if maybe_info.is_none() {
|
||||
debug!("cant find referrer {}", referrer);
|
||||
return 0;
|
||||
}
|
||||
let info = maybe_info.unwrap();
|
||||
let referrer_name = &info.name;
|
||||
let r = deno_dir.resolve_module(specifier, referrer_name);
|
||||
if let Err(err) = r {
|
||||
debug!("potentially swallowed err: {}", err);
|
||||
return 0;
|
||||
}
|
||||
let (name, _local_filename) = r.unwrap();
|
||||
|
||||
if let Some(id) = self.by_name.get(&name) {
|
||||
let child_id = *id;
|
||||
info.children.push(child_id);
|
||||
return child_id;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_file_info(&self, deno_dir: &DenoDir, filename: String) {
|
||||
let maybe_out = deno_dir.fetch_module_meta_data(&filename, ".");
|
||||
if maybe_out.is_err() {
|
||||
println!("{}", maybe_out.unwrap_err());
|
||||
return;
|
||||
}
|
||||
let out = maybe_out.unwrap();
|
||||
|
||||
println!("{} {}", ansi::bold("local:".to_string()), &(out.filename));
|
||||
println!(
|
||||
"{} {}",
|
||||
ansi::bold("type:".to_string()),
|
||||
msg::enum_name_media_type(out.media_type)
|
||||
);
|
||||
if out.maybe_output_code_filename.is_some() {
|
||||
println!(
|
||||
"{} {}",
|
||||
ansi::bold("compiled:".to_string()),
|
||||
out.maybe_output_code_filename.as_ref().unwrap(),
|
||||
);
|
||||
}
|
||||
if out.maybe_source_map_filename.is_some() {
|
||||
println!(
|
||||
"{} {}",
|
||||
ansi::bold("map:".to_string()),
|
||||
out.maybe_source_map_filename.as_ref().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
let deps = Deps::new(self, &out.module_name);
|
||||
println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name);
|
||||
if let Some(ref depsdeps) = deps.deps {
|
||||
for d in depsdeps {
|
||||
println!("{}", d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Deps {
|
||||
pub name: String,
|
||||
pub deps: Option<Vec<Deps>>,
|
||||
prefix: String,
|
||||
is_last: bool,
|
||||
}
|
||||
|
||||
impl Deps {
|
||||
pub fn new(modules: &Modules, module_name: &str) -> Deps {
|
||||
let mut seen = HashSet::new();
|
||||
let id = modules.get_id(module_name).unwrap();
|
||||
Self::helper(&mut seen, "".to_string(), true, modules, id)
|
||||
}
|
||||
|
||||
fn helper(
|
||||
seen: &mut HashSet<deno_mod>,
|
||||
prefix: String,
|
||||
is_last: bool,
|
||||
modules: &Modules,
|
||||
id: deno_mod,
|
||||
) -> Deps {
|
||||
let name = modules.get_name(id).unwrap().to_string();
|
||||
if seen.contains(&id) {
|
||||
Deps {
|
||||
name,
|
||||
prefix,
|
||||
deps: None,
|
||||
is_last,
|
||||
}
|
||||
} else {
|
||||
seen.insert(id);
|
||||
let child_ids = modules.get_children(id).unwrap();
|
||||
let child_count = child_ids.iter().count();
|
||||
let deps = child_ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, dep_id)| {
|
||||
let new_is_last = index == child_count - 1;
|
||||
let mut new_prefix = prefix.clone();
|
||||
new_prefix.push(if is_last { ' ' } else { '│' });
|
||||
new_prefix.push(' ');
|
||||
Self::helper(seen, new_prefix, new_is_last, modules, *dep_id)
|
||||
}).collect();
|
||||
Deps {
|
||||
name,
|
||||
prefix,
|
||||
deps: Some(deps),
|
||||
is_last,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Deps {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut has_children = false;
|
||||
if let Some(ref deps) = self.deps {
|
||||
has_children = !deps.is_empty();
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"{}{}─{} {}",
|
||||
self.prefix,
|
||||
if self.is_last { "└" } else { "├" },
|
||||
if has_children { "┬" } else { "─" },
|
||||
self.name
|
||||
)?;
|
||||
|
||||
if let Some(ref deps) = self.deps {
|
||||
for d in deps {
|
||||
write!(f, "\n{}", d)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
524
cli/msg.fbs
Normal file
524
cli/msg.fbs
Normal file
|
@ -0,0 +1,524 @@
|
|||
union Any {
|
||||
Accept,
|
||||
Chdir,
|
||||
Chmod,
|
||||
Close,
|
||||
CopyFile,
|
||||
Cwd,
|
||||
CwdRes,
|
||||
Dial,
|
||||
Environ,
|
||||
EnvironRes,
|
||||
Exit,
|
||||
Fetch,
|
||||
FetchModuleMetaData,
|
||||
FetchModuleMetaDataRes,
|
||||
FetchRes,
|
||||
FormatError,
|
||||
FormatErrorRes,
|
||||
GlobalTimer,
|
||||
GlobalTimerRes,
|
||||
GlobalTimerStop,
|
||||
IsTTY,
|
||||
IsTTYRes,
|
||||
Listen,
|
||||
ListenRes,
|
||||
MakeTempDir,
|
||||
MakeTempDirRes,
|
||||
Metrics,
|
||||
MetricsRes,
|
||||
Mkdir,
|
||||
NewConn,
|
||||
Now,
|
||||
NowRes,
|
||||
Open,
|
||||
OpenRes,
|
||||
PermissionRevoke,
|
||||
Permissions,
|
||||
PermissionsRes,
|
||||
Read,
|
||||
ReadDir,
|
||||
ReadDirRes,
|
||||
ReadFile,
|
||||
ReadFileRes,
|
||||
ReadRes,
|
||||
Readlink,
|
||||
ReadlinkRes,
|
||||
Remove,
|
||||
Rename,
|
||||
ReplReadline,
|
||||
ReplReadlineRes,
|
||||
ReplStart,
|
||||
ReplStartRes,
|
||||
Resources,
|
||||
ResourcesRes,
|
||||
Run,
|
||||
RunRes,
|
||||
RunStatus,
|
||||
RunStatusRes,
|
||||
Seek,
|
||||
SetEnv,
|
||||
Shutdown,
|
||||
Start,
|
||||
StartRes,
|
||||
Stat,
|
||||
StatRes,
|
||||
Symlink,
|
||||
Truncate,
|
||||
WorkerGetMessage,
|
||||
WorkerGetMessageRes,
|
||||
WorkerPostMessage,
|
||||
Write,
|
||||
WriteFile,
|
||||
WriteRes,
|
||||
}
|
||||
|
||||
enum ErrorKind: byte {
|
||||
NoError = 0,
|
||||
|
||||
// io errors
|
||||
|
||||
NotFound,
|
||||
PermissionDenied,
|
||||
ConnectionRefused,
|
||||
ConnectionReset,
|
||||
ConnectionAborted,
|
||||
NotConnected,
|
||||
AddrInUse,
|
||||
AddrNotAvailable,
|
||||
BrokenPipe,
|
||||
AlreadyExists,
|
||||
WouldBlock,
|
||||
InvalidInput,
|
||||
InvalidData,
|
||||
TimedOut,
|
||||
Interrupted,
|
||||
WriteZero,
|
||||
Other,
|
||||
UnexpectedEof,
|
||||
BadResource,
|
||||
CommandFailed,
|
||||
|
||||
// url errors
|
||||
|
||||
EmptyHost,
|
||||
IdnaError,
|
||||
InvalidPort,
|
||||
InvalidIpv4Address,
|
||||
InvalidIpv6Address,
|
||||
InvalidDomainCharacter,
|
||||
RelativeUrlWithoutBase,
|
||||
RelativeUrlWithCannotBeABaseBase,
|
||||
SetHostOnCannotBeABaseUrl,
|
||||
Overflow,
|
||||
|
||||
// hyper errors
|
||||
|
||||
HttpUser,
|
||||
HttpClosed,
|
||||
HttpCanceled,
|
||||
HttpParse,
|
||||
HttpOther,
|
||||
TooLarge,
|
||||
|
||||
// custom errors
|
||||
InvalidUri,
|
||||
InvalidSeekMode,
|
||||
}
|
||||
|
||||
table Cwd {}
|
||||
|
||||
table CwdRes {
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
enum MediaType: byte {
|
||||
JavaScript = 0,
|
||||
TypeScript,
|
||||
Json,
|
||||
Unknown
|
||||
}
|
||||
|
||||
table Base {
|
||||
cmd_id: uint32;
|
||||
sync: bool = false;
|
||||
error_kind: ErrorKind = NoError;
|
||||
error: string;
|
||||
inner: Any;
|
||||
}
|
||||
|
||||
table Start {
|
||||
unused: int8;
|
||||
}
|
||||
|
||||
table StartRes {
|
||||
cwd: string;
|
||||
pid: uint32;
|
||||
argv: [string];
|
||||
exec_path: string;
|
||||
main_module: string; // Absolute URL.
|
||||
debug_flag: bool;
|
||||
deps_flag: bool;
|
||||
types_flag: bool;
|
||||
version_flag: bool;
|
||||
deno_version: string;
|
||||
v8_version: string;
|
||||
no_color: bool;
|
||||
}
|
||||
|
||||
table FormatError {
|
||||
error: string;
|
||||
}
|
||||
|
||||
table FormatErrorRes {
|
||||
error: string;
|
||||
}
|
||||
|
||||
table WorkerGetMessage {
|
||||
unused: int8;
|
||||
}
|
||||
|
||||
table WorkerGetMessageRes {
|
||||
data: [ubyte];
|
||||
}
|
||||
|
||||
table WorkerPostMessage {
|
||||
// data passed thru the zero-copy data parameter.
|
||||
}
|
||||
|
||||
table FetchModuleMetaData {
|
||||
specifier: string;
|
||||
referrer: string;
|
||||
}
|
||||
|
||||
table FetchModuleMetaDataRes {
|
||||
// If it's a non-http module, moduleName and filename will be the same.
|
||||
// For http modules, moduleName is its resolved http URL, and filename
|
||||
// is the location of the locally downloaded source code.
|
||||
module_name: string;
|
||||
filename: string;
|
||||
media_type: MediaType;
|
||||
data: [ubyte];
|
||||
}
|
||||
|
||||
table Chdir {
|
||||
directory: string;
|
||||
}
|
||||
|
||||
table GlobalTimer {
|
||||
timeout: int;
|
||||
}
|
||||
|
||||
table GlobalTimerRes { }
|
||||
|
||||
table GlobalTimerStop { }
|
||||
|
||||
table Exit {
|
||||
code: int;
|
||||
}
|
||||
|
||||
table Environ {}
|
||||
|
||||
table SetEnv {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
table EnvironRes {
|
||||
map: [KeyValue];
|
||||
}
|
||||
|
||||
table KeyValue {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
table Permissions {}
|
||||
|
||||
table PermissionRevoke {
|
||||
permission: string;
|
||||
}
|
||||
|
||||
table PermissionsRes {
|
||||
run: bool;
|
||||
read: bool;
|
||||
write: bool;
|
||||
net: bool;
|
||||
env: bool;
|
||||
}
|
||||
|
||||
// Note this represents The WHOLE header of an http message, not just the key
|
||||
// value pairs. That means it includes method and url for Requests and status
|
||||
// for responses. This is why it is singular "Header" instead of "Headers".
|
||||
table HttpHeader {
|
||||
is_request: bool;
|
||||
// Request only:
|
||||
method: string;
|
||||
url: string;
|
||||
// Response only:
|
||||
status: uint16;
|
||||
// Both:
|
||||
fields: [KeyValue];
|
||||
}
|
||||
|
||||
table Fetch {
|
||||
header: HttpHeader;
|
||||
}
|
||||
|
||||
table FetchRes {
|
||||
header: HttpHeader;
|
||||
body_rid: uint32;
|
||||
}
|
||||
|
||||
table MakeTempDir {
|
||||
dir: string;
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
}
|
||||
|
||||
table MakeTempDirRes {
|
||||
path: string;
|
||||
}
|
||||
|
||||
table Mkdir {
|
||||
path: string;
|
||||
recursive: bool;
|
||||
mode: uint; // Specified by https://godoc.org/os#FileMode
|
||||
}
|
||||
|
||||
table Chmod {
|
||||
path: string;
|
||||
mode: uint; // Specified by https://godoc.org/os#FileMode
|
||||
}
|
||||
|
||||
table Remove {
|
||||
path: string;
|
||||
recursive: bool;
|
||||
}
|
||||
|
||||
table ReadFile {
|
||||
filename: string;
|
||||
}
|
||||
|
||||
table ReadFileRes {
|
||||
data: [ubyte];
|
||||
}
|
||||
|
||||
table ReadDir {
|
||||
path: string;
|
||||
}
|
||||
|
||||
table ReadDirRes {
|
||||
entries: [StatRes];
|
||||
}
|
||||
|
||||
table WriteFile {
|
||||
filename: string;
|
||||
data: [ubyte];
|
||||
update_perm: bool;
|
||||
perm: uint;
|
||||
// perm specified by https://godoc.org/os#FileMode
|
||||
is_create: bool;
|
||||
is_append: bool;
|
||||
}
|
||||
|
||||
table CopyFile {
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
table Rename {
|
||||
oldpath: string;
|
||||
newpath: string;
|
||||
}
|
||||
|
||||
table Readlink {
|
||||
name: string;
|
||||
}
|
||||
|
||||
table ReadlinkRes {
|
||||
path: string;
|
||||
}
|
||||
|
||||
table ReplStart {
|
||||
history_file: string;
|
||||
// TODO add config
|
||||
}
|
||||
|
||||
table ReplStartRes {
|
||||
rid: uint32;
|
||||
}
|
||||
|
||||
table ReplReadline {
|
||||
rid: uint32;
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
table ReplReadlineRes {
|
||||
line: string;
|
||||
}
|
||||
|
||||
table Resources {}
|
||||
|
||||
table Resource {
|
||||
rid: uint32;
|
||||
repr: string;
|
||||
}
|
||||
|
||||
table ResourcesRes {
|
||||
resources: [Resource];
|
||||
}
|
||||
|
||||
table Symlink {
|
||||
oldname: string;
|
||||
newname: string;
|
||||
}
|
||||
|
||||
table Stat {
|
||||
filename: string;
|
||||
lstat: bool;
|
||||
}
|
||||
|
||||
table StatRes {
|
||||
is_file: bool;
|
||||
is_symlink: bool;
|
||||
len: ulong;
|
||||
modified:ulong;
|
||||
accessed:ulong;
|
||||
created:ulong;
|
||||
mode: uint;
|
||||
has_mode: bool; // false on windows
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
table Truncate {
|
||||
name: string;
|
||||
len: uint;
|
||||
}
|
||||
|
||||
table Open {
|
||||
filename: string;
|
||||
perm: uint;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
table OpenRes {
|
||||
rid: uint32;
|
||||
}
|
||||
|
||||
table Read {
|
||||
rid: uint32;
|
||||
// (ptr, len) is passed as second parameter to libdeno.send().
|
||||
}
|
||||
|
||||
table ReadRes {
|
||||
nread: uint;
|
||||
eof: bool;
|
||||
}
|
||||
|
||||
table Write {
|
||||
rid: uint32;
|
||||
}
|
||||
|
||||
table WriteRes {
|
||||
nbyte: uint;
|
||||
}
|
||||
|
||||
table Close {
|
||||
rid: uint32;
|
||||
}
|
||||
|
||||
table Shutdown {
|
||||
rid: uint32;
|
||||
how: uint;
|
||||
}
|
||||
|
||||
table Listen {
|
||||
network: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
table ListenRes {
|
||||
rid: uint32;
|
||||
}
|
||||
|
||||
table Accept {
|
||||
rid: uint32;
|
||||
}
|
||||
|
||||
table Dial {
|
||||
network: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
// Response to Accept and Dial.
|
||||
table NewConn {
|
||||
rid: uint32;
|
||||
remote_addr: string;
|
||||
local_addr: string;
|
||||
}
|
||||
|
||||
table Metrics {}
|
||||
|
||||
table MetricsRes {
|
||||
ops_dispatched: uint64;
|
||||
ops_completed: uint64;
|
||||
bytes_sent_control: uint64;
|
||||
bytes_sent_data: uint64;
|
||||
bytes_received: uint64;
|
||||
}
|
||||
|
||||
enum ProcessStdio: byte { Inherit, Piped, Null }
|
||||
|
||||
table Run {
|
||||
args: [string];
|
||||
cwd: string;
|
||||
env: [KeyValue];
|
||||
stdin: ProcessStdio;
|
||||
stdout: ProcessStdio;
|
||||
stderr: ProcessStdio;
|
||||
}
|
||||
|
||||
table RunRes {
|
||||
rid: uint32;
|
||||
pid: uint32;
|
||||
// The following stdio rids are only valid if "Piped" was specified for the
|
||||
// corresponding stdio stream. The caller MUST issue a close op for all valid
|
||||
// stdio streams.
|
||||
stdin_rid: uint32;
|
||||
stdout_rid: uint32;
|
||||
stderr_rid: uint32;
|
||||
}
|
||||
|
||||
table RunStatus {
|
||||
rid: uint32;
|
||||
}
|
||||
|
||||
table RunStatusRes {
|
||||
got_signal: bool;
|
||||
exit_code: int;
|
||||
exit_signal: int;
|
||||
}
|
||||
|
||||
table Now {}
|
||||
|
||||
table NowRes {
|
||||
time: uint64;
|
||||
}
|
||||
|
||||
table IsTTY {}
|
||||
|
||||
table IsTTYRes {
|
||||
stdin: bool;
|
||||
stdout: bool;
|
||||
stderr: bool;
|
||||
}
|
||||
|
||||
table Seek {
|
||||
rid: uint32;
|
||||
offset: int;
|
||||
whence: uint;
|
||||
}
|
||||
|
||||
root_type Base;
|
26
cli/msg.rs
Normal file
26
cli/msg.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
#![allow(unused_imports)]
|
||||
#![allow(dead_code)]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(clippy::all, clippy::pedantic)
|
||||
)]
|
||||
use crate::isolate_state;
|
||||
use flatbuffers;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
// GN_OUT_DIR is set either by build.rs (for the Cargo build), or by
|
||||
// build_extra/rust/run.py (for the GN+Ninja build).
|
||||
include!(concat!(env!("GN_OUT_DIR"), "/gen/msg_generated.rs"));
|
||||
|
||||
impl<'a> From<&'a isolate_state::Metrics> for MetricsResArgs {
|
||||
fn from(m: &'a isolate_state::Metrics) -> Self {
|
||||
MetricsResArgs {
|
||||
ops_dispatched: m.ops_dispatched.load(Ordering::SeqCst) as u64,
|
||||
ops_completed: m.ops_completed.load(Ordering::SeqCst) as u64,
|
||||
bytes_sent_control: m.bytes_sent_control.load(Ordering::SeqCst) as u64,
|
||||
bytes_sent_data: m.bytes_sent_data.load(Ordering::SeqCst) as u64,
|
||||
bytes_received: m.bytes_received.load(Ordering::SeqCst) as u64,
|
||||
}
|
||||
}
|
||||
}
|
127
cli/msg_util.rs
Normal file
127
cli/msg_util.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
// Helpers for serialization.
|
||||
use crate::errors;
|
||||
use crate::errors::DenoResult;
|
||||
use crate::msg;
|
||||
|
||||
use flatbuffers;
|
||||
use http::header::HeaderName;
|
||||
use http::uri::Uri;
|
||||
use http::Method;
|
||||
use hyper::header::HeaderMap;
|
||||
use hyper::header::HeaderValue;
|
||||
use hyper::Body;
|
||||
use hyper::Request;
|
||||
use hyper::Response;
|
||||
use std::str::FromStr;
|
||||
|
||||
type Headers = HeaderMap<HeaderValue>;
|
||||
|
||||
pub fn serialize_key_value<'bldr>(
|
||||
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
|
||||
key: &str,
|
||||
value: &str,
|
||||
) -> flatbuffers::WIPOffset<msg::KeyValue<'bldr>> {
|
||||
let key = builder.create_string(&key);
|
||||
let value = builder.create_string(&value);
|
||||
msg::KeyValue::create(
|
||||
builder,
|
||||
&msg::KeyValueArgs {
|
||||
key: Some(key),
|
||||
value: Some(value),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serialize_request_header<'bldr>(
|
||||
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
|
||||
r: &Request<Body>,
|
||||
) -> flatbuffers::WIPOffset<msg::HttpHeader<'bldr>> {
|
||||
let method = builder.create_string(r.method().as_str());
|
||||
let url = builder.create_string(r.uri().to_string().as_ref());
|
||||
|
||||
let mut fields = Vec::new();
|
||||
for (key, val) in r.headers().iter() {
|
||||
let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap());
|
||||
fields.push(kv);
|
||||
}
|
||||
let fields = builder.create_vector(fields.as_ref());
|
||||
|
||||
msg::HttpHeader::create(
|
||||
builder,
|
||||
&msg::HttpHeaderArgs {
|
||||
is_request: true,
|
||||
method: Some(method),
|
||||
url: Some(url),
|
||||
fields: Some(fields),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serialize_fields<'bldr>(
|
||||
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
|
||||
headers: &Headers,
|
||||
) -> flatbuffers::WIPOffset<
|
||||
flatbuffers::Vector<
|
||||
'bldr,
|
||||
flatbuffers::ForwardsUOffset<msg::KeyValue<'bldr>>,
|
||||
>,
|
||||
> {
|
||||
let mut fields = Vec::new();
|
||||
for (key, val) in headers.iter() {
|
||||
let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap());
|
||||
fields.push(kv);
|
||||
}
|
||||
builder.create_vector(fields.as_ref())
|
||||
}
|
||||
|
||||
// Not to be confused with serialize_response which has nothing to do with HTTP.
|
||||
pub fn serialize_http_response<'bldr>(
|
||||
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
|
||||
r: &Response<Body>,
|
||||
) -> flatbuffers::WIPOffset<msg::HttpHeader<'bldr>> {
|
||||
let status = r.status().as_u16();
|
||||
let fields = serialize_fields(builder, r.headers());
|
||||
msg::HttpHeader::create(
|
||||
builder,
|
||||
&msg::HttpHeaderArgs {
|
||||
is_request: false,
|
||||
status,
|
||||
fields: Some(fields),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn deserialize_request(
|
||||
header_msg: msg::HttpHeader<'_>,
|
||||
body: Body,
|
||||
) -> DenoResult<Request<Body>> {
|
||||
let mut r = Request::new(body);
|
||||
|
||||
assert!(header_msg.is_request());
|
||||
|
||||
let u = header_msg.url().unwrap();
|
||||
let u = Uri::from_str(u)
|
||||
.map_err(|e| errors::new(msg::ErrorKind::InvalidUri, e.to_string()))?;
|
||||
*r.uri_mut() = u;
|
||||
|
||||
if let Some(method) = header_msg.method() {
|
||||
let method = Method::from_str(method).unwrap();
|
||||
*r.method_mut() = method;
|
||||
}
|
||||
|
||||
if let Some(fields) = header_msg.fields() {
|
||||
let headers = r.headers_mut();
|
||||
for i in 0..fields.len() {
|
||||
let kv = fields.get(i);
|
||||
let key = kv.key().unwrap();
|
||||
let name = HeaderName::from_bytes(key.as_bytes()).unwrap();
|
||||
let value = kv.value().unwrap();
|
||||
let v = HeaderValue::from_str(value).unwrap();
|
||||
headers.insert(name, v);
|
||||
}
|
||||
}
|
||||
Ok(r)
|
||||
}
|
2020
cli/ops.rs
Normal file
2020
cli/ops.rs
Normal file
File diff suppressed because it is too large
Load diff
343
cli/permissions.rs
Normal file
343
cli/permissions.rs
Normal file
|
@ -0,0 +1,343 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use atty;
|
||||
|
||||
use crate::flags::DenoFlags;
|
||||
|
||||
use ansi_term::Style;
|
||||
use crate::errors::permission_denied;
|
||||
use crate::errors::DenoResult;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Tri-state value for storing permission state
|
||||
pub enum PermissionAccessorState {
|
||||
Allow = 0,
|
||||
Ask = 1,
|
||||
Deny = 2,
|
||||
}
|
||||
|
||||
impl From<usize> for PermissionAccessorState {
|
||||
fn from(val: usize) -> Self {
|
||||
match val {
|
||||
0 => PermissionAccessorState::Allow,
|
||||
1 => PermissionAccessorState::Ask,
|
||||
2 => PermissionAccessorState::Deny,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for PermissionAccessorState {
|
||||
fn from(val: bool) -> Self {
|
||||
match val {
|
||||
true => PermissionAccessorState::Allow,
|
||||
false => PermissionAccessorState::Ask,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PermissionAccessorState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PermissionAccessorState::Allow => f.pad("Allow"),
|
||||
PermissionAccessorState::Ask => f.pad("Ask"),
|
||||
PermissionAccessorState::Deny => f.pad("Deny"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PermissionAccessor {
|
||||
state: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl PermissionAccessor {
|
||||
pub fn new(state: PermissionAccessorState) -> Self {
|
||||
Self {
|
||||
state: Arc::new(AtomicUsize::new(state as usize)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_allow(&self) -> bool {
|
||||
match self.get_state() {
|
||||
PermissionAccessorState::Allow => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// If the state is "Allow" walk it back to the default "Ask"
|
||||
/// Don't do anything if state is "Deny"
|
||||
pub fn revoke(&self) {
|
||||
if self.is_allow() {
|
||||
self.ask();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allow(&self) {
|
||||
self.set_state(PermissionAccessorState::Allow)
|
||||
}
|
||||
|
||||
pub fn ask(&self) {
|
||||
self.set_state(PermissionAccessorState::Ask)
|
||||
}
|
||||
|
||||
pub fn deny(&self) {
|
||||
self.set_state(PermissionAccessorState::Deny)
|
||||
}
|
||||
|
||||
/// Update this accessors state based on a PromptResult value
|
||||
/// This will only update the state if the PromptResult value
|
||||
/// is one of the "Always" values
|
||||
pub fn update_with_prompt_result(&self, prompt_result: &PromptResult) {
|
||||
match prompt_result {
|
||||
PromptResult::AllowAlways => self.allow(),
|
||||
PromptResult::DenyAlways => self.deny(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_state(&self) -> PermissionAccessorState {
|
||||
self.state.load(Ordering::SeqCst).into()
|
||||
}
|
||||
fn set_state(&self, state: PermissionAccessorState) {
|
||||
self.state.store(state as usize, Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for PermissionAccessor {
|
||||
fn from(val: bool) -> Self {
|
||||
Self::new(PermissionAccessorState::from(val))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PermissionAccessor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
state: Arc::new(AtomicUsize::new(PermissionAccessorState::Ask as usize)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DenoPermissions {
|
||||
// Keep in sync with src/permissions.ts
|
||||
pub allow_read: PermissionAccessor,
|
||||
pub allow_write: PermissionAccessor,
|
||||
pub allow_net: PermissionAccessor,
|
||||
pub allow_env: PermissionAccessor,
|
||||
pub allow_run: PermissionAccessor,
|
||||
pub no_prompts: AtomicBool,
|
||||
}
|
||||
|
||||
impl DenoPermissions {
|
||||
pub fn from_flags(flags: &DenoFlags) -> Self {
|
||||
Self {
|
||||
allow_read: PermissionAccessor::from(flags.allow_read),
|
||||
allow_write: PermissionAccessor::from(flags.allow_write),
|
||||
allow_env: PermissionAccessor::from(flags.allow_env),
|
||||
allow_net: PermissionAccessor::from(flags.allow_net),
|
||||
allow_run: PermissionAccessor::from(flags.allow_run),
|
||||
no_prompts: AtomicBool::new(flags.no_prompts),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_run(&self) -> DenoResult<()> {
|
||||
match self.allow_run.get_state() {
|
||||
PermissionAccessorState::Allow => Ok(()),
|
||||
PermissionAccessorState::Ask => {
|
||||
match self.try_permissions_prompt("access to run a subprocess") {
|
||||
Err(e) => Err(e),
|
||||
Ok(v) => {
|
||||
self.allow_run.update_with_prompt_result(&v);
|
||||
v.check()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
PermissionAccessorState::Deny => Err(permission_denied()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_read(&self, filename: &str) -> DenoResult<()> {
|
||||
match self.allow_read.get_state() {
|
||||
PermissionAccessorState::Allow => Ok(()),
|
||||
PermissionAccessorState::Ask => match self
|
||||
.try_permissions_prompt(&format!("read access to \"{}\"", filename))
|
||||
{
|
||||
Err(e) => Err(e),
|
||||
Ok(v) => {
|
||||
self.allow_read.update_with_prompt_result(&v);
|
||||
v.check()?;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
PermissionAccessorState::Deny => Err(permission_denied()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_write(&self, filename: &str) -> DenoResult<()> {
|
||||
match self.allow_write.get_state() {
|
||||
PermissionAccessorState::Allow => Ok(()),
|
||||
PermissionAccessorState::Ask => match self
|
||||
.try_permissions_prompt(&format!("write access to \"{}\"", filename))
|
||||
{
|
||||
Err(e) => Err(e),
|
||||
Ok(v) => {
|
||||
self.allow_write.update_with_prompt_result(&v);
|
||||
v.check()?;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
PermissionAccessorState::Deny => Err(permission_denied()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_net(&self, domain_name: &str) -> DenoResult<()> {
|
||||
match self.allow_net.get_state() {
|
||||
PermissionAccessorState::Allow => Ok(()),
|
||||
PermissionAccessorState::Ask => match self.try_permissions_prompt(
|
||||
&format!("network access to \"{}\"", domain_name),
|
||||
) {
|
||||
Err(e) => Err(e),
|
||||
Ok(v) => {
|
||||
self.allow_net.update_with_prompt_result(&v);
|
||||
v.check()?;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
PermissionAccessorState::Deny => Err(permission_denied()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_env(&self) -> DenoResult<()> {
|
||||
match self.allow_env.get_state() {
|
||||
PermissionAccessorState::Allow => Ok(()),
|
||||
PermissionAccessorState::Ask => {
|
||||
match self.try_permissions_prompt("access to environment variables") {
|
||||
Err(e) => Err(e),
|
||||
Ok(v) => {
|
||||
self.allow_env.update_with_prompt_result(&v);
|
||||
v.check()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
PermissionAccessorState::Deny => Err(permission_denied()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to present the user with a permission prompt
|
||||
/// will error with permission_denied if no_prompts is enabled
|
||||
fn try_permissions_prompt(&self, message: &str) -> DenoResult<PromptResult> {
|
||||
if self.no_prompts.load(Ordering::SeqCst) {
|
||||
return Err(permission_denied());
|
||||
}
|
||||
if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
|
||||
return Err(permission_denied());
|
||||
};
|
||||
permission_prompt(message)
|
||||
}
|
||||
|
||||
pub fn allows_run(&self) -> bool {
|
||||
return self.allow_run.is_allow();
|
||||
}
|
||||
|
||||
pub fn allows_read(&self) -> bool {
|
||||
return self.allow_read.is_allow();
|
||||
}
|
||||
|
||||
pub fn allows_write(&self) -> bool {
|
||||
return self.allow_write.is_allow();
|
||||
}
|
||||
|
||||
pub fn allows_net(&self) -> bool {
|
||||
return self.allow_net.is_allow();
|
||||
}
|
||||
|
||||
pub fn allows_env(&self) -> bool {
|
||||
return self.allow_env.is_allow();
|
||||
}
|
||||
|
||||
pub fn revoke_run(&self) -> DenoResult<()> {
|
||||
self.allow_run.revoke();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn revoke_read(&self) -> DenoResult<()> {
|
||||
self.allow_read.revoke();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn revoke_write(&self) -> DenoResult<()> {
|
||||
self.allow_write.revoke();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn revoke_net(&self) -> DenoResult<()> {
|
||||
self.allow_net.revoke();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn revoke_env(&self) -> DenoResult<()> {
|
||||
self.allow_env.revoke();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
/// Quad-state value for representing user input on permission prompt
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PromptResult {
|
||||
AllowAlways = 0,
|
||||
AllowOnce = 1,
|
||||
DenyOnce = 2,
|
||||
DenyAlways = 3,
|
||||
}
|
||||
|
||||
impl PromptResult {
|
||||
/// If value is any form of deny this will error with permission_denied
|
||||
pub fn check(&self) -> DenoResult<()> {
|
||||
match self {
|
||||
PromptResult::DenyOnce => Err(permission_denied()),
|
||||
PromptResult::DenyAlways => Err(permission_denied()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PromptResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PromptResult::AllowAlways => f.pad("AllowAlways"),
|
||||
PromptResult::AllowOnce => f.pad("AllowOnce"),
|
||||
PromptResult::DenyOnce => f.pad("DenyOnce"),
|
||||
PromptResult::DenyAlways => f.pad("DenyAlways"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn permission_prompt(message: &str) -> DenoResult<PromptResult> {
|
||||
let msg = format!("⚠️ Deno requests {}. Grant? [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", message);
|
||||
// print to stderr so that if deno is > to a file this is still displayed.
|
||||
eprint!("{}", Style::new().bold().paint(msg));
|
||||
loop {
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
let _nread = stdin.read_line(&mut input)?;
|
||||
let ch = input.chars().next().unwrap();
|
||||
match ch.to_ascii_lowercase() {
|
||||
'a' => return Ok(PromptResult::AllowAlways),
|
||||
'y' => return Ok(PromptResult::AllowOnce),
|
||||
'n' => return Ok(PromptResult::DenyOnce),
|
||||
'd' => return Ok(PromptResult::DenyAlways),
|
||||
_ => {
|
||||
// If we don't get a recognized option try again.
|
||||
let msg_again = format!("Unrecognized option '{}' [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", ch);
|
||||
eprint!("{}", Style::new().bold().paint(msg_again));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
114
cli/repl.rs
Normal file
114
cli/repl.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use rustyline;
|
||||
|
||||
use crate::msg::ErrorKind;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::deno_dir::DenoDir;
|
||||
use crate::errors::new as deno_error;
|
||||
use crate::errors::DenoResult;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use rustyline::Editor;
|
||||
|
||||
// Work around the issue that on Windows, `struct Editor` does not implement the
|
||||
// `Send` trait, because it embeds a windows HANDLE which is a type alias for
|
||||
// *mut c_void. This value isn't actually a pointer and there's nothing that
|
||||
// can be mutated through it, so hack around it. TODO: a prettier solution.
|
||||
#[cfg(windows)]
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[cfg(windows)]
|
||||
struct Editor<T: rustyline::Helper> {
|
||||
inner: rustyline::Editor<T>,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe impl<T: rustyline::Helper> Send for Editor<T> {}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl<T: rustyline::Helper> Editor<T> {
|
||||
pub fn new() -> Editor<T> {
|
||||
Editor {
|
||||
inner: rustyline::Editor::<T>::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl<T: rustyline::Helper> Deref for Editor<T> {
|
||||
type Target = rustyline::Editor<T>;
|
||||
|
||||
fn deref(&self) -> &rustyline::Editor<T> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl<T: rustyline::Helper> DerefMut for Editor<T> {
|
||||
fn deref_mut(&mut self) -> &mut rustyline::Editor<T> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Repl {
|
||||
editor: Editor<()>,
|
||||
history_file: PathBuf,
|
||||
}
|
||||
|
||||
impl Repl {
|
||||
pub fn new(history_file: PathBuf) -> Self {
|
||||
let mut repl = Self {
|
||||
editor: Editor::<()>::new(),
|
||||
history_file,
|
||||
};
|
||||
|
||||
repl.load_history();
|
||||
repl
|
||||
}
|
||||
|
||||
fn load_history(&mut self) {
|
||||
debug!("Loading REPL history: {:?}", self.history_file);
|
||||
self
|
||||
.editor
|
||||
.load_history(&self.history_file.to_str().unwrap())
|
||||
.map_err(|e| debug!("Unable to load history file: {:?} {}", self.history_file, e))
|
||||
// ignore this error (e.g. it occurs on first load)
|
||||
.unwrap_or(())
|
||||
}
|
||||
|
||||
fn save_history(&mut self) -> DenoResult<()> {
|
||||
self
|
||||
.editor
|
||||
.save_history(&self.history_file.to_str().unwrap())
|
||||
.map(|_| debug!("Saved REPL history to: {:?}", self.history_file))
|
||||
.map_err(|e| {
|
||||
eprintln!("Unable to save REPL history: {:?} {}", self.history_file, e);
|
||||
deno_error(ErrorKind::Other, e.description().to_string())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn readline(&mut self, prompt: &str) -> DenoResult<String> {
|
||||
self
|
||||
.editor
|
||||
.readline(&prompt)
|
||||
.map(|line| {
|
||||
self.editor.add_history_entry(line.as_ref());
|
||||
line
|
||||
}).map_err(|e| deno_error(ErrorKind::Other, e.description().to_string()))
|
||||
// Forward error to TS side for processing
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Repl {
|
||||
fn drop(&mut self) {
|
||||
self.save_history().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn history_path(dir: &DenoDir, history_file: &str) -> PathBuf {
|
||||
let mut p: PathBuf = dir.root.clone();
|
||||
p.push(history_file);
|
||||
p
|
||||
}
|
156
cli/resolve_addr.rs
Normal file
156
cli/resolve_addr.rs
Normal file
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use futures::Async;
|
||||
use futures::Future;
|
||||
use futures::Poll;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
/// Go-style network address parsing. Returns a future.
|
||||
/// Examples:
|
||||
/// "192.0.2.1:25"
|
||||
/// ":80"
|
||||
/// "[2001:db8::1]:80"
|
||||
/// "198.51.100.1:80"
|
||||
/// "deno.land:443"
|
||||
pub fn resolve_addr(address: &str) -> ResolveAddrFuture {
|
||||
ResolveAddrFuture {
|
||||
address: address.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ResolveAddrError {
|
||||
Syntax,
|
||||
Resolution(std::io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for ResolveAddrError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.write_str(self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ResolveAddrError {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
ResolveAddrError::Syntax => "invalid address syntax",
|
||||
ResolveAddrError::Resolution(e) => e.description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResolveAddrFuture {
|
||||
address: String,
|
||||
}
|
||||
|
||||
impl Future for ResolveAddrFuture {
|
||||
type Item = SocketAddr;
|
||||
type Error = ResolveAddrError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
// The implementation of this is not actually async at the moment,
|
||||
// however we intend to use async DNS resolution in the future and
|
||||
// so we expose this as a future instead of Result.
|
||||
match split(&self.address) {
|
||||
None => Err(ResolveAddrError::Syntax),
|
||||
Some(addr_port_pair) => {
|
||||
// I absolutely despise the .to_socket_addrs() API.
|
||||
let r = addr_port_pair
|
||||
.to_socket_addrs()
|
||||
.map_err(ResolveAddrError::Resolution);
|
||||
|
||||
r.and_then(|mut iter| match iter.next() {
|
||||
Some(a) => Ok(Async::Ready(a)),
|
||||
None => panic!("There should be at least one result"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split(address: &str) -> Option<(&str, u16)> {
|
||||
address.rfind(':').and_then(|i| {
|
||||
let (a, p) = address.split_at(i);
|
||||
// Default to localhost if given just the port. Example: ":80"
|
||||
let addr = if !a.is_empty() { a } else { "0.0.0.0" };
|
||||
// If this looks like an ipv6 IP address. Example: "[2001:db8::1]"
|
||||
// Then we remove the brackets.
|
||||
let addr = if addr.starts_with('[') && addr.ends_with(']') {
|
||||
let l = addr.len() - 1;
|
||||
addr.get(1..l).unwrap()
|
||||
} else {
|
||||
addr
|
||||
};
|
||||
|
||||
let p = p.trim_start_matches(':');
|
||||
match p.parse::<u16>() {
|
||||
Err(_) => None,
|
||||
Ok(port) => Some((addr, port)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::net::SocketAddrV4;
|
||||
use std::net::SocketAddrV6;
|
||||
|
||||
#[test]
|
||||
fn split1() {
|
||||
assert_eq!(split("127.0.0.1:80"), Some(("127.0.0.1", 80)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split2() {
|
||||
assert_eq!(split(":80"), Some(("0.0.0.0", 80)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split3() {
|
||||
assert_eq!(split("no colon"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split4() {
|
||||
assert_eq!(split("deno.land:443"), Some(("deno.land", 443)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split5() {
|
||||
assert_eq!(split("[2001:db8::1]:8080"), Some(("2001:db8::1", 8080)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_addr1() {
|
||||
let expected =
|
||||
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80));
|
||||
let actual = resolve_addr("127.0.0.1:80").wait().unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_addr3() {
|
||||
let expected =
|
||||
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25));
|
||||
let actual = resolve_addr("192.0.2.1:25").wait().unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_addr_ipv6() {
|
||||
let expected = SocketAddr::V6(SocketAddrV6::new(
|
||||
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
|
||||
8080,
|
||||
0,
|
||||
0,
|
||||
));
|
||||
let actual = resolve_addr("[2001:db8::1]:8080").wait().unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
494
cli/resources.rs
Normal file
494
cli/resources.rs
Normal file
|
@ -0,0 +1,494 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// Think of Resources as File Descriptors. They are integers that are allocated
|
||||
// by the privileged side of Deno to refer to various resources. The simplest
|
||||
// example are standard file system files and stdio - but there will be other
|
||||
// resources added in the future that might not correspond to operating system
|
||||
// level File Descriptors. To avoid confusion we call them "resources" not "file
|
||||
// descriptors". This module implements a global resource table. Ops (AKA
|
||||
// handlers) look up resources by their integer id here.
|
||||
|
||||
use crate::cli::Buf;
|
||||
use crate::errors;
|
||||
use crate::errors::bad_resource;
|
||||
use crate::errors::DenoError;
|
||||
use crate::errors::DenoResult;
|
||||
use crate::http_body::HttpBody;
|
||||
use crate::isolate_state::WorkerChannels;
|
||||
use crate::repl::Repl;
|
||||
|
||||
use futures;
|
||||
use futures::Future;
|
||||
use futures::Poll;
|
||||
use futures::Sink;
|
||||
use futures::Stream;
|
||||
use hyper;
|
||||
use std;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Error, Read, Seek, SeekFrom, Write};
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::process::ExitStatus;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_process;
|
||||
|
||||
pub type ResourceId = u32; // Sometimes referred to RID.
|
||||
|
||||
// These store Deno's file descriptors. These are not necessarily the operating
|
||||
// system ones.
|
||||
type ResourceTable = HashMap<ResourceId, Repr>;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::FromRawHandle;
|
||||
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
|
||||
lazy_static! {
|
||||
// Starts at 3 because stdio is [0-2].
|
||||
static ref NEXT_RID: AtomicUsize = AtomicUsize::new(3);
|
||||
static ref RESOURCE_TABLE: Mutex<ResourceTable> = Mutex::new({
|
||||
let mut m = HashMap::new();
|
||||
// TODO Load these lazily during lookup?
|
||||
m.insert(0, Repr::Stdin(tokio::io::stdin()));
|
||||
|
||||
m.insert(1, Repr::Stdout({
|
||||
#[cfg(not(windows))]
|
||||
let stdout = unsafe { std::fs::File::from_raw_fd(1) };
|
||||
#[cfg(windows)]
|
||||
let stdout = unsafe {
|
||||
std::fs::File::from_raw_handle(winapi::um::processenv::GetStdHandle(
|
||||
winapi::um::winbase::STD_OUTPUT_HANDLE))
|
||||
};
|
||||
tokio::fs::File::from_std(stdout)
|
||||
}));
|
||||
|
||||
m.insert(2, Repr::Stderr(tokio::io::stderr()));
|
||||
m
|
||||
});
|
||||
}
|
||||
|
||||
// Internal representation of Resource.
|
||||
enum Repr {
|
||||
Stdin(tokio::io::Stdin),
|
||||
Stdout(tokio::fs::File),
|
||||
Stderr(tokio::io::Stderr),
|
||||
FsFile(tokio::fs::File),
|
||||
// Since TcpListener might be closed while there is a pending accept task,
|
||||
// we need to track the task so that when the listener is closed,
|
||||
// this pending task could be notified and die.
|
||||
// Currently TcpListener itself does not take care of this issue.
|
||||
// See: https://github.com/tokio-rs/tokio/issues/846
|
||||
TcpListener(tokio::net::TcpListener, Option<futures::task::Task>),
|
||||
TcpStream(tokio::net::TcpStream),
|
||||
HttpBody(HttpBody),
|
||||
Repl(Arc<Mutex<Repl>>),
|
||||
// Enum size is bounded by the largest variant.
|
||||
// Use `Box` around large `Child` struct.
|
||||
// https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
|
||||
Child(Box<tokio_process::Child>),
|
||||
ChildStdin(tokio_process::ChildStdin),
|
||||
ChildStdout(tokio_process::ChildStdout),
|
||||
ChildStderr(tokio_process::ChildStderr),
|
||||
Worker(WorkerChannels),
|
||||
}
|
||||
|
||||
/// If the given rid is open, this returns the type of resource, E.G. "worker".
|
||||
/// If the rid is closed or was never open, it returns None.
|
||||
pub fn get_type(rid: ResourceId) -> Option<String> {
|
||||
let table = RESOURCE_TABLE.lock().unwrap();
|
||||
table.get(&rid).map(inspect_repr)
|
||||
}
|
||||
|
||||
pub fn table_entries() -> Vec<(u32, String)> {
|
||||
let table = RESOURCE_TABLE.lock().unwrap();
|
||||
|
||||
table
|
||||
.iter()
|
||||
.map(|(key, value)| (*key, inspect_repr(&value)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_entries() {
|
||||
let mut entries = table_entries();
|
||||
entries.sort();
|
||||
assert_eq!(entries[0], (0, String::from("stdin")));
|
||||
assert_eq!(entries[1], (1, String::from("stdout")));
|
||||
assert_eq!(entries[2], (2, String::from("stderr")));
|
||||
}
|
||||
|
||||
fn inspect_repr(repr: &Repr) -> String {
|
||||
let h_repr = match repr {
|
||||
Repr::Stdin(_) => "stdin",
|
||||
Repr::Stdout(_) => "stdout",
|
||||
Repr::Stderr(_) => "stderr",
|
||||
Repr::FsFile(_) => "fsFile",
|
||||
Repr::TcpListener(_, _) => "tcpListener",
|
||||
Repr::TcpStream(_) => "tcpStream",
|
||||
Repr::HttpBody(_) => "httpBody",
|
||||
Repr::Repl(_) => "repl",
|
||||
Repr::Child(_) => "child",
|
||||
Repr::ChildStdin(_) => "childStdin",
|
||||
Repr::ChildStdout(_) => "childStdout",
|
||||
Repr::ChildStderr(_) => "childStderr",
|
||||
Repr::Worker(_) => "worker",
|
||||
};
|
||||
|
||||
String::from(h_repr)
|
||||
}
|
||||
|
||||
// Abstract async file interface.
|
||||
// Ideally in unix, if Resource represents an OS rid, it will be the same.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Resource {
|
||||
pub rid: ResourceId,
|
||||
}
|
||||
|
||||
impl Resource {
|
||||
// TODO Should it return a Resource instead of net::TcpStream?
|
||||
pub fn poll_accept(&mut self) -> Poll<(TcpStream, SocketAddr), Error> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&self.rid);
|
||||
match maybe_repr {
|
||||
None => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Listener has been closed",
|
||||
)),
|
||||
Some(repr) => match repr {
|
||||
Repr::TcpListener(ref mut s, _) => s.poll_accept(),
|
||||
_ => panic!("Cannot accept"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// close(2) is done by dropping the value. Therefore we just need to remove
|
||||
// the resource from the RESOURCE_TABLE.
|
||||
pub fn close(&self) {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let r = table.remove(&self.rid);
|
||||
assert!(r.is_some());
|
||||
}
|
||||
|
||||
pub fn shutdown(&mut self, how: Shutdown) -> Result<(), DenoError> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&self.rid);
|
||||
match maybe_repr {
|
||||
None => panic!("bad rid"),
|
||||
Some(repr) => match repr {
|
||||
Repr::TcpStream(ref mut f) => {
|
||||
TcpStream::shutdown(f, how).map_err(DenoError::from)
|
||||
}
|
||||
_ => panic!("Cannot shutdown"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Resource {
|
||||
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Resource {
|
||||
fn poll_read(&mut self, buf: &mut [u8]) -> Poll<usize, Error> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&self.rid);
|
||||
match maybe_repr {
|
||||
None => panic!("bad rid"),
|
||||
Some(repr) => match repr {
|
||||
Repr::FsFile(ref mut f) => f.poll_read(buf),
|
||||
Repr::Stdin(ref mut f) => f.poll_read(buf),
|
||||
Repr::TcpStream(ref mut f) => f.poll_read(buf),
|
||||
Repr::HttpBody(ref mut f) => f.poll_read(buf),
|
||||
Repr::ChildStdout(ref mut f) => f.poll_read(buf),
|
||||
Repr::ChildStderr(ref mut f) => f.poll_read(buf),
|
||||
_ => panic!("Cannot read"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Resource {
|
||||
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Resource {
|
||||
fn poll_write(&mut self, buf: &[u8]) -> Poll<usize, Error> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&self.rid);
|
||||
match maybe_repr {
|
||||
None => panic!("bad rid"),
|
||||
Some(repr) => match repr {
|
||||
Repr::FsFile(ref mut f) => f.poll_write(buf),
|
||||
Repr::Stdout(ref mut f) => f.poll_write(buf),
|
||||
Repr::Stderr(ref mut f) => f.poll_write(buf),
|
||||
Repr::TcpStream(ref mut f) => f.poll_write(buf),
|
||||
Repr::ChildStdin(ref mut f) => f.poll_write(buf),
|
||||
_ => panic!("Cannot write"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn new_rid() -> ResourceId {
|
||||
let next_rid = NEXT_RID.fetch_add(1, Ordering::SeqCst);
|
||||
next_rid as ResourceId
|
||||
}
|
||||
|
||||
pub fn add_fs_file(fs_file: tokio::fs::File) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
match tg.insert(rid, Repr::FsFile(fs_file)) {
|
||||
Some(_) => panic!("There is already a file with that rid"),
|
||||
None => Resource { rid },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_tcp_listener(listener: tokio::net::TcpListener) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
let r = tg.insert(rid, Repr::TcpListener(listener, None));
|
||||
assert!(r.is_none());
|
||||
Resource { rid }
|
||||
}
|
||||
|
||||
pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
let r = tg.insert(rid, Repr::TcpStream(stream));
|
||||
assert!(r.is_none());
|
||||
Resource { rid }
|
||||
}
|
||||
|
||||
pub fn add_hyper_body(body: hyper::Body) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
let body = HttpBody::from(body);
|
||||
let r = tg.insert(rid, Repr::HttpBody(body));
|
||||
assert!(r.is_none());
|
||||
Resource { rid }
|
||||
}
|
||||
|
||||
pub fn add_repl(repl: Repl) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
let r = tg.insert(rid, Repr::Repl(Arc::new(Mutex::new(repl))));
|
||||
assert!(r.is_none());
|
||||
Resource { rid }
|
||||
}
|
||||
|
||||
pub fn add_worker(wc: WorkerChannels) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
let r = tg.insert(rid, Repr::Worker(wc));
|
||||
assert!(r.is_none());
|
||||
Resource { rid }
|
||||
}
|
||||
|
||||
pub fn worker_post_message(
|
||||
rid: ResourceId,
|
||||
buf: Buf,
|
||||
) -> futures::sink::Send<futures::sync::mpsc::Sender<Buf>> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&rid);
|
||||
match maybe_repr {
|
||||
Some(Repr::Worker(ref mut wc)) => {
|
||||
// unwrap here is incorrect, but doing it anyway
|
||||
wc.0.clone().send(buf)
|
||||
}
|
||||
_ => panic!("bad resource"), // futures::future::err(bad_resource()).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorkerReceiver {
|
||||
rid: ResourceId,
|
||||
}
|
||||
|
||||
// Invert the dumbness that tokio_process causes by making Child itself a future.
|
||||
impl Future for WorkerReceiver {
|
||||
type Item = Option<Buf>;
|
||||
type Error = DenoError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Buf>, DenoError> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&self.rid);
|
||||
match maybe_repr {
|
||||
Some(Repr::Worker(ref mut wc)) => wc.1.poll().map_err(|()| {
|
||||
errors::new(errors::ErrorKind::Other, "recv msg error".to_string())
|
||||
}),
|
||||
_ => Err(bad_resource()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn worker_recv_message(rid: ResourceId) -> WorkerReceiver {
|
||||
WorkerReceiver { rid }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
||||
pub struct ChildResources {
|
||||
pub child_rid: ResourceId,
|
||||
pub stdin_rid: Option<ResourceId>,
|
||||
pub stdout_rid: Option<ResourceId>,
|
||||
pub stderr_rid: Option<ResourceId>,
|
||||
}
|
||||
|
||||
pub fn add_child(mut c: tokio_process::Child) -> ChildResources {
|
||||
let child_rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
|
||||
let mut resources = ChildResources {
|
||||
child_rid,
|
||||
stdin_rid: None,
|
||||
stdout_rid: None,
|
||||
stderr_rid: None,
|
||||
};
|
||||
|
||||
if c.stdin().is_some() {
|
||||
let stdin = c.stdin().take().unwrap();
|
||||
let rid = new_rid();
|
||||
let r = tg.insert(rid, Repr::ChildStdin(stdin));
|
||||
assert!(r.is_none());
|
||||
resources.stdin_rid = Some(rid);
|
||||
}
|
||||
if c.stdout().is_some() {
|
||||
let stdout = c.stdout().take().unwrap();
|
||||
let rid = new_rid();
|
||||
let r = tg.insert(rid, Repr::ChildStdout(stdout));
|
||||
assert!(r.is_none());
|
||||
resources.stdout_rid = Some(rid);
|
||||
}
|
||||
if c.stderr().is_some() {
|
||||
let stderr = c.stderr().take().unwrap();
|
||||
let rid = new_rid();
|
||||
let r = tg.insert(rid, Repr::ChildStderr(stderr));
|
||||
assert!(r.is_none());
|
||||
resources.stderr_rid = Some(rid);
|
||||
}
|
||||
|
||||
let r = tg.insert(child_rid, Repr::Child(Box::new(c)));
|
||||
assert!(r.is_none());
|
||||
|
||||
resources
|
||||
}
|
||||
|
||||
pub struct ChildStatus {
|
||||
rid: ResourceId,
|
||||
}
|
||||
|
||||
// Invert the dumbness that tokio_process causes by making Child itself a future.
|
||||
impl Future for ChildStatus {
|
||||
type Item = ExitStatus;
|
||||
type Error = DenoError;
|
||||
|
||||
fn poll(&mut self) -> Poll<ExitStatus, DenoError> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&self.rid);
|
||||
match maybe_repr {
|
||||
Some(Repr::Child(ref mut child)) => child.poll().map_err(DenoError::from),
|
||||
_ => Err(bad_resource()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child_status(rid: ResourceId) -> DenoResult<ChildStatus> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&rid);
|
||||
match maybe_repr {
|
||||
Some(Repr::Child(ref mut _child)) => Ok(ChildStatus { rid }),
|
||||
_ => Err(bad_resource()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_repl(rid: ResourceId) -> DenoResult<Arc<Mutex<Repl>>> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&rid);
|
||||
match maybe_repr {
|
||||
Some(Repr::Repl(ref mut r)) => Ok(r.clone()),
|
||||
_ => Err(bad_resource()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(rid: ResourceId) -> Option<Resource> {
|
||||
debug!("resource lookup {}", rid);
|
||||
let table = RESOURCE_TABLE.lock().unwrap();
|
||||
table.get(&rid).map(|_| Resource { rid })
|
||||
}
|
||||
|
||||
// TODO(kevinkassimo): revamp this after the following lands:
|
||||
// https://github.com/tokio-rs/tokio/pull/785
|
||||
pub fn seek(
|
||||
resource: Resource,
|
||||
offset: i32,
|
||||
whence: u32,
|
||||
) -> Box<dyn Future<Item = (), Error = DenoError> + Send> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
// We take ownership of File here.
|
||||
// It is put back below while still holding the lock.
|
||||
let maybe_repr = table.remove(&resource.rid);
|
||||
match maybe_repr {
|
||||
None => panic!("bad rid"),
|
||||
Some(Repr::FsFile(f)) => {
|
||||
let seek_from = match whence {
|
||||
0 => SeekFrom::Start(offset as u64),
|
||||
1 => SeekFrom::Current(offset as i64),
|
||||
2 => SeekFrom::End(offset as i64),
|
||||
_ => {
|
||||
return Box::new(futures::future::err(errors::new(
|
||||
errors::ErrorKind::InvalidSeekMode,
|
||||
format!("Invalid seek mode: {}", whence),
|
||||
)));
|
||||
}
|
||||
};
|
||||
// Trait Clone not implemented on tokio::fs::File,
|
||||
// so convert to std File first.
|
||||
let std_file = f.into_std();
|
||||
// Create a copy and immediately put back.
|
||||
// We don't want to block other resource ops.
|
||||
// try_clone() would yield a copy containing the same
|
||||
// underlying fd, so operations on the copy would also
|
||||
// affect the one in resource table, and we don't need
|
||||
// to write back.
|
||||
let maybe_std_file_copy = std_file.try_clone();
|
||||
// Insert the entry back with the same rid.
|
||||
table.insert(
|
||||
resource.rid,
|
||||
Repr::FsFile(tokio_fs::File::from_std(std_file)),
|
||||
);
|
||||
if maybe_std_file_copy.is_err() {
|
||||
return Box::new(futures::future::err(DenoError::from(
|
||||
maybe_std_file_copy.unwrap_err(),
|
||||
)));
|
||||
}
|
||||
let mut std_file_copy = maybe_std_file_copy.unwrap();
|
||||
return Box::new(futures::future::lazy(move || {
|
||||
let result = std_file_copy
|
||||
.seek(seek_from)
|
||||
.map(|_| {
|
||||
return ();
|
||||
}).map_err(DenoError::from);
|
||||
futures::future::result(result)
|
||||
}));
|
||||
}
|
||||
_ => panic!("cannot seek"),
|
||||
}
|
||||
}
|
57
cli/startup_data.rs
Normal file
57
cli/startup_data.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use deno_core::deno_buf;
|
||||
use deno_core::{StartupData, StartupScript};
|
||||
|
||||
pub fn deno_isolate_init() -> StartupData {
|
||||
if cfg!(feature = "no-snapshot-init") {
|
||||
debug!("Deno isolate init without snapshots.");
|
||||
#[cfg(not(feature = "check-only"))]
|
||||
let source_bytes =
|
||||
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js"));
|
||||
#[cfg(feature = "check-only")]
|
||||
let source_bytes = vec![];
|
||||
|
||||
StartupData::Script(StartupScript {
|
||||
filename: "gen/bundle/main.js".to_string(),
|
||||
source: std::str::from_utf8(source_bytes).unwrap().to_string(),
|
||||
})
|
||||
} else {
|
||||
debug!("Deno isolate init with snapshots.");
|
||||
#[cfg(not(any(feature = "check-only", feature = "no-snapshot-init")))]
|
||||
let data =
|
||||
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_deno.bin"));
|
||||
#[cfg(any(feature = "check-only", feature = "no-snapshot-init"))]
|
||||
let data = vec![];
|
||||
|
||||
unsafe {
|
||||
StartupData::Snapshot(deno_buf::from_raw_parts(data.as_ptr(), data.len()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compiler_isolate_init() -> StartupData {
|
||||
if cfg!(feature = "no-snapshot-init") {
|
||||
debug!("Deno isolate init without snapshots.");
|
||||
#[cfg(not(feature = "check-only"))]
|
||||
let source_bytes =
|
||||
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/compiler.js"));
|
||||
#[cfg(feature = "check-only")]
|
||||
let source_bytes = vec![];
|
||||
|
||||
StartupData::Script(StartupScript {
|
||||
filename: "gen/bundle/compiler.js".to_string(),
|
||||
source: std::str::from_utf8(source_bytes).unwrap().to_string(),
|
||||
})
|
||||
} else {
|
||||
debug!("Deno isolate init with snapshots.");
|
||||
#[cfg(not(any(feature = "check-only", feature = "no-snapshot-init")))]
|
||||
let data =
|
||||
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_compiler.bin"));
|
||||
#[cfg(any(feature = "check-only", feature = "no-snapshot-init"))]
|
||||
let data = vec![];
|
||||
|
||||
unsafe {
|
||||
StartupData::Snapshot(deno_buf::from_raw_parts(data.as_ptr(), data.len()))
|
||||
}
|
||||
}
|
||||
}
|
118
cli/tokio_util.rs
Normal file
118
cli/tokio_util.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::resources::Resource;
|
||||
use futures;
|
||||
use futures::Future;
|
||||
use futures::Poll;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::net::SocketAddr;
|
||||
use tokio;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
pub fn run<F>(future: F)
|
||||
where
|
||||
F: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
// tokio::runtime::current_thread::run(future)
|
||||
tokio::run(future)
|
||||
}
|
||||
|
||||
pub fn block_on<F, R, E>(future: F) -> Result<R, E>
|
||||
where
|
||||
F: Send + 'static + Future<Item = R, Error = E>,
|
||||
R: Send + 'static,
|
||||
E: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = futures::sync::oneshot::channel();
|
||||
tokio::spawn(future.then(move |r| tx.send(r).map_err(|_| unreachable!())));
|
||||
rx.wait().unwrap()
|
||||
}
|
||||
|
||||
// Set the default executor so we can use tokio::spawn(). It's difficult to
|
||||
// pass around mut references to the runtime, so using with_default is
|
||||
// preferable. Ideally Tokio would provide this function.
|
||||
#[cfg(test)]
|
||||
pub fn init<F>(f: F)
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
use tokio_executor;
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut executor = rt.executor();
|
||||
let mut enter = tokio_executor::enter().expect("Multiple executors at once");
|
||||
tokio_executor::with_default(&mut executor, &mut enter, move |_enter| f());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AcceptState {
|
||||
Pending(Resource),
|
||||
Empty,
|
||||
}
|
||||
|
||||
/// Simply accepts a connection.
|
||||
pub fn accept(r: Resource) -> Accept {
|
||||
Accept {
|
||||
state: AcceptState::Pending(r),
|
||||
}
|
||||
}
|
||||
|
||||
/// A future which can be used to easily read available number of bytes to fill
|
||||
/// a buffer.
|
||||
///
|
||||
/// Created by the [`read`] function.
|
||||
#[derive(Debug)]
|
||||
pub struct Accept {
|
||||
state: AcceptState,
|
||||
}
|
||||
|
||||
impl Future for Accept {
|
||||
type Item = (TcpStream, SocketAddr);
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let (stream, addr) = match self.state {
|
||||
AcceptState::Pending(ref mut r) => try_ready!(r.poll_accept()),
|
||||
AcceptState::Empty => panic!("poll Accept after it's done"),
|
||||
};
|
||||
|
||||
match mem::replace(&mut self.state, AcceptState::Empty) {
|
||||
AcceptState::Pending(_) => Ok((stream, addr).into()),
|
||||
AcceptState::Empty => panic!("invalid internal state"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `futures::future::poll_fn` only support `F: FnMut()->Poll<T, E>`
|
||||
/// However, we require that `F: FnOnce()->Poll<T, E>`.
|
||||
/// Therefore, we created our version of `poll_fn`.
|
||||
pub fn poll_fn<T, E, F>(f: F) -> PollFn<F>
|
||||
where
|
||||
F: FnOnce() -> Poll<T, E>,
|
||||
{
|
||||
PollFn { inner: Some(f) }
|
||||
}
|
||||
|
||||
pub struct PollFn<F> {
|
||||
inner: Option<F>,
|
||||
}
|
||||
|
||||
impl<T, E, F> Future for PollFn<F>
|
||||
where
|
||||
F: FnOnce() -> Poll<T, E>,
|
||||
{
|
||||
type Item = T;
|
||||
type Error = E;
|
||||
|
||||
fn poll(&mut self) -> Poll<T, E> {
|
||||
let f = self.inner.take().expect("Inner fn has been taken.");
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn panic_on_error<I, E, F>(f: F) -> impl Future<Item = I, Error = ()>
|
||||
where
|
||||
F: Future<Item = I, Error = E>,
|
||||
E: std::fmt::Debug,
|
||||
{
|
||||
f.map_err(|err| panic!("Future got unexpected error: {:?}", err))
|
||||
}
|
62
cli/tokio_write.rs
Normal file
62
cli/tokio_write.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
// TODO Submit this file upstream into tokio-io/src/io/write.rs
|
||||
use std::io;
|
||||
use std::mem;
|
||||
|
||||
use futures::{Future, Poll};
|
||||
use tokio::io::AsyncWrite;
|
||||
|
||||
/// A future used to write some data to a stream.
|
||||
///
|
||||
/// This is created by the [`write`] top-level method.
|
||||
///
|
||||
/// [`write`]: fn.write.html
|
||||
#[derive(Debug)]
|
||||
pub struct Write<A, T> {
|
||||
state: State<A, T>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State<A, T> {
|
||||
Pending { a: A, buf: T },
|
||||
Empty,
|
||||
}
|
||||
|
||||
/// Creates a future that will write some of the buffer `buf` to
|
||||
/// the stream `a` provided.
|
||||
///
|
||||
/// Any error which happens during writing will cause both the stream and the
|
||||
/// buffer to get destroyed.
|
||||
pub fn write<A, T>(a: A, buf: T) -> Write<A, T>
|
||||
where
|
||||
A: AsyncWrite,
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
Write {
|
||||
state: State::Pending { a, buf },
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, T> Future for Write<A, T>
|
||||
where
|
||||
A: AsyncWrite,
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
type Item = (A, T, usize);
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<(A, T, usize), io::Error> {
|
||||
let nwritten = match self.state {
|
||||
State::Pending {
|
||||
ref mut a,
|
||||
ref mut buf,
|
||||
} => try_ready!(a.poll_write(buf.as_ref())),
|
||||
State::Empty => panic!("poll a Read after it's done"),
|
||||
};
|
||||
|
||||
match mem::replace(&mut self.state, State::Empty) {
|
||||
State::Pending { a, buf } => Ok((a, buf, nwritten).into()),
|
||||
State::Empty => panic!("invalid internal state"),
|
||||
}
|
||||
}
|
||||
}
|
6
cli/version.rs
Normal file
6
cli/version.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
pub const DENO: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub fn v8() -> &'static str {
|
||||
deno_core::v8_version()
|
||||
}
|
181
cli/workers.rs
Normal file
181
cli/workers.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::cli::Buf;
|
||||
use crate::cli::Cli;
|
||||
use crate::flags::DenoFlags;
|
||||
use crate::isolate::Isolate;
|
||||
use crate::isolate_state::IsolateState;
|
||||
use crate::isolate_state::WorkerChannels;
|
||||
use crate::js_errors::JSErrorColor;
|
||||
use crate::permissions::DenoPermissions;
|
||||
use crate::resources;
|
||||
use crate::tokio_util;
|
||||
use deno_core::JSError;
|
||||
use deno_core::StartupData;
|
||||
use futures::future::lazy;
|
||||
use futures::sync::mpsc;
|
||||
use futures::sync::oneshot;
|
||||
use futures::Future;
|
||||
use futures::Poll;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
/// Rust interface for WebWorkers.
|
||||
pub struct Worker {
|
||||
isolate: Isolate,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
pub fn new(
|
||||
startup_data: Option<StartupData>,
|
||||
flags: DenoFlags,
|
||||
argv: Vec<String>,
|
||||
permissions: DenoPermissions,
|
||||
) -> (Self, WorkerChannels) {
|
||||
let (worker_in_tx, worker_in_rx) = mpsc::channel::<Buf>(1);
|
||||
let (worker_out_tx, worker_out_rx) = mpsc::channel::<Buf>(1);
|
||||
|
||||
let internal_channels = (worker_out_tx, worker_in_rx);
|
||||
let external_channels = (worker_in_tx, worker_out_rx);
|
||||
|
||||
let state =
|
||||
Arc::new(IsolateState::new(flags, argv, Some(internal_channels)));
|
||||
|
||||
let cli = Cli::new(startup_data, state, permissions);
|
||||
let isolate = Isolate::new(cli);
|
||||
|
||||
let worker = Worker { isolate };
|
||||
(worker, external_channels)
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> {
|
||||
self.isolate.execute(js_source)
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Worker {
|
||||
type Item = ();
|
||||
type Error = JSError;
|
||||
|
||||
fn poll(&mut self) -> Poll<(), JSError> {
|
||||
self.isolate.poll()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(
|
||||
startup_data: Option<StartupData>,
|
||||
state: &IsolateState,
|
||||
js_source: String,
|
||||
permissions: DenoPermissions,
|
||||
) -> resources::Resource {
|
||||
// TODO This function should return a Future, so that the caller can retrieve
|
||||
// the JSError if one is thrown. Currently it just prints to stderr and calls
|
||||
// exit(1).
|
||||
// let (js_error_tx, js_error_rx) = oneshot::channel::<JSError>();
|
||||
let (p, c) = oneshot::channel::<resources::Resource>();
|
||||
let builder = thread::Builder::new().name("worker".to_string());
|
||||
|
||||
let flags = state.flags.clone();
|
||||
let argv = state.argv.clone();
|
||||
|
||||
let _tid = builder
|
||||
.spawn(move || {
|
||||
tokio_util::run(lazy(move || {
|
||||
let (mut worker, external_channels) =
|
||||
Worker::new(startup_data, flags, argv, permissions);
|
||||
let resource = resources::add_worker(external_channels);
|
||||
p.send(resource.clone()).unwrap();
|
||||
|
||||
worker
|
||||
.execute("denoMain()")
|
||||
.expect("worker denoMain failed");
|
||||
worker
|
||||
.execute("workerMain()")
|
||||
.expect("worker workerMain failed");
|
||||
worker.execute(&js_source).expect("worker js_source failed");
|
||||
|
||||
worker.then(move |r| -> Result<(), ()> {
|
||||
resource.close();
|
||||
debug!("workers.rs after resource close");
|
||||
if let Err(err) = r {
|
||||
eprintln!("{}", JSErrorColor(&err).to_string());
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}));
|
||||
|
||||
debug!("workers.rs after spawn");
|
||||
}).unwrap();
|
||||
|
||||
c.wait().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::startup_data;
|
||||
|
||||
#[test]
|
||||
fn test_spawn() {
|
||||
let startup_data = startup_data::compiler_isolate_init();
|
||||
let resource = spawn(
|
||||
Some(startup_data),
|
||||
&IsolateState::mock(),
|
||||
r#"
|
||||
onmessage = function(e) {
|
||||
let s = new TextDecoder().decode(e.data);;
|
||||
console.log("msg from main script", s);
|
||||
if (s == "exit") {
|
||||
close();
|
||||
return;
|
||||
} else {
|
||||
console.assert(s === "hi");
|
||||
}
|
||||
postMessage(new Uint8Array([1, 2, 3]));
|
||||
console.log("after postMessage");
|
||||
}
|
||||
"#.into(),
|
||||
DenoPermissions::default(),
|
||||
);
|
||||
let msg = String::from("hi").into_boxed_str().into_boxed_bytes();
|
||||
|
||||
let r = resources::worker_post_message(resource.rid, msg).wait();
|
||||
assert!(r.is_ok());
|
||||
|
||||
let maybe_msg =
|
||||
resources::worker_recv_message(resource.rid).wait().unwrap();
|
||||
assert!(maybe_msg.is_some());
|
||||
assert_eq!(*maybe_msg.unwrap(), [1, 2, 3]);
|
||||
|
||||
let msg = String::from("exit").into_boxed_str().into_boxed_bytes();
|
||||
let r = resources::worker_post_message(resource.rid, msg).wait();
|
||||
assert!(r.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_from_resource_table_on_close() {
|
||||
let startup_data = startup_data::compiler_isolate_init();
|
||||
let resource = spawn(
|
||||
Some(startup_data),
|
||||
&IsolateState::mock(),
|
||||
"onmessage = () => close();".into(),
|
||||
DenoPermissions::default(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resources::get_type(resource.rid),
|
||||
Some("worker".to_string())
|
||||
);
|
||||
|
||||
let msg = String::from("hi").into_boxed_str().into_boxed_bytes();
|
||||
let r = resources::worker_post_message(resource.rid, msg).wait();
|
||||
assert!(r.is_ok());
|
||||
println!("rid {:?}", resource.rid);
|
||||
|
||||
// TODO Need a way to get a future for when a resource closes.
|
||||
// For now, just sleep for a bit.
|
||||
// resource.close();
|
||||
thread::sleep(std::time::Duration::from_millis(1000));
|
||||
assert_eq!(resources::get_type(resource.rid), None);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue