#! /bin/false
$Qgoda::JavaScript::Environment::VERSION
=
'0.9.8'
;
BEGIN {
if
(
$JavaScript::Duktape::XS::VERSION
ne
'0.000074'
) {
die
__x(
"You are using version '{version}' of 'JavaScript::Duktape::XS'"
.
" which is too new. Please downgrade to version 0.000074."
.
" See '{url}' for details!\n"
,
version
=>
$JavaScript::Duktape::XS::VERSION
,
}
}
sub
new {
my
(
$class
,
%args
) =
@_
;
my
$global
=
$args
{global};
$global
= [
$global
]
if
defined
$global
;
my
@global
= @{
$global
|| []};
my
$exchange
= empty
$args
{exchange} ?
'__perl__'
:
$args
{exchange};
my
$self
= {
__global
=> [
@global
],
__exchange_name
=>
$exchange
,
};
my
$module_resolve
=
sub
{
my
(
$module
,
$path
) =
@_
;
return
$self
->__normalize(
$self
->__moduleResolve(
$module
,
$path
));
};
my
$module_load
=
sub
{
my
(
$filename
) =
@_
;
return
$self
->__moduleLoad(
$filename
);
};
my
$perl
= {
output
=> {}
};
$self
->{__vm} = JavaScript::Duktape::XS->new(
{
save_messages
=>
$args
{no_output}
}
);
$self
->{__vm}->set(
perl_module_resolve
=>
$module_resolve
);
$self
->{__vm}->set(
perl_module_load
=>
$module_load
);
my
$no_console
=
$args
{
'no_console'
};
$self
->{__jsout} =
''
;
$self
->{__jserr} -
''
;
$self
->{__no_output} =
$args
{
'no_output'
};
unless
(
$no_console
) {
$perl
->{modules}->{console} = {
log
=>
sub
{
if
(
$args
{no_output}) {
$self
->{__jsout} .=
shift
.
"\n"
;
}
else
{
print
shift
.
"\n"
;
}
},
error
=>
sub
{
if
(
$args
{no_output}) {
$self
->{__jserr} .=
shift
.
"\n"
;
}
else
{
print
STDERR
shift
.
"\n"
;
}
},
warn
=>
sub
{
if
(
$args
{no_output}) {
$self
->{__jserr} .=
shift
.
"\n"
;
}
else
{
print
STDERR
shift
.
"\n"
;
}
}
};
$self
->{__vm}->set(
console
=> {});
}
$self
->{__vm}->set(
$exchange
,
$perl
);
$self
->{__exchange} =
$perl
;
unless
(
$no_console
) {
require
'Qgoda/JavaScript/console.js'
;
$self
->{__vm}->
eval
(Qgoda::JavaScript::console->code);
}
bless
$self
,
$class
;
}
sub
exchange {
my
(
$self
,
$key
,
$value
) =
@_
;
my
$exchange
=
$self
->{__vm}->get(
$self
->{__exchange_name}) or
return
;
if
(
@_
> 2) {
$exchange
->{
$key
} =
$value
;
$self
->{__vm}->set(
$self
->{__exchange_name},
$exchange
);
}
return
$exchange
->{
$key
};
}
sub
run {
my
(
$self
,
$code
) =
@_
;
my
$q
= Qgoda->new;
if
(
$self
->{__no_output}) {
$self
->{__jsout} =
''
;
$self
->{__jserr} =
''
;
$self
->{__vm}->reset_msgs;
}
my
$retval
=
$self
->{__vm}->
eval
(
$code
);
if
(
$self
->{__no_output}) {
my
$stdout
=
$self
->{__jsout};
$stdout
=
''
if
empty
$stdout
;
my
$vm_stdout
=
$self
->{__vm}->get_msgs->{stdout};
$vm_stdout
=
''
if
empty
$vm_stdout
;
my
$stderr
=
$self
->{__jserr};
$stderr
=
''
if
empty
$stderr
;
my
$vm_stderr
=
$self
->{__vm}->get_msgs->{stderr};
$vm_stderr
=
''
if
empty
$vm_stderr
;
my
$q
= Qgoda->new;
$q
->jsout(
$stdout
.
$vm_stdout
);
$q
->jserr(
$stderr
.
$vm_stderr
);
}
$q
->jsreturn(
$self
->{__vm}->get(
$self
->{__exchange_name} .
'.return'
));
return
$retval
;
}
sub
vm {
shift
->{__vm};
}
sub
__moduleResolve {
my
(
$self
,
$module
,
$path
) =
@_
;
$path
=
''
if
!
defined
$path
;
$path
=
''
if
$module
=~ m{^/};
$path
= dirname
$path
if
''
ne
$path
;
if
(
$module
=~ m{^\.{0,2}/}) {
my
$resolved
=
eval
{
$self
->__loadAsFile(
"$path/$module"
) };
$@ or
return
$resolved
;
$resolved
=
eval
{
$self
->__loadAsDirectory(
"$path/$module"
) };
$@ or
return
$resolved
;
}
my
$resolved
=
eval
{
$self
->__loadNodeModules(
$module
,
$path
) };
$@ or
return
$resolved
;
$self
->__jsError(__x(
"Cannot find module '{module}'!\n"
,
module
=>
$module
));
}
sub
__moduleLoad {
my
(
$self
,
$filename
) =
@_
;
open
my
$fh
,
'<'
,
$filename
or
$self
->__jsError(__x(
"error reading '{filename}': {err}\n"
,
filename
=>
$filename
,
err
=> $!));
my
$code
=
join
''
, <
$fh
>;
if
(
$filename
=~ /\.json$/) {
$code
=
'module.exports = '
.
$code
;
}
return
$code
;
}
sub
__normalize {
my
(
$self
,
$path
) =
@_
;
my
$here
=
$path
=~ s{^./}{};
my
$ret
= File::Spec->canonpath(
$path
);
while
(
$ret
=~ s{
(^|/)
(
[^/]|
[^./][^/]|
[^/][^./]|
[^/][^/][^/]+
)
/\.\./
}{$1}x
) {
}
if
(
$path
=~ m!/$! &&
$ret
!~ m!/$!) {
$ret
.=
'/'
;
}
$ret
=
'./'
.
$ret
if
$here
;
return
$ret
;
}
sub
__jsError {
my
(
$self
,
$error
) =
@_
;
die
$error
;
}
sub
__isFile {
my
(
$self
,
$filename
) =
@_
;
return
1
if
-e
$filename
&& !-d
$filename
;
}
sub
__loadAsFile {
my
(
$self
,
$name
) =
@_
;
my
$filename
=
$name
;
return
$filename
if
$self
->__isFile(
$filename
);
$filename
=
"$name.js"
;
return
$filename
if
$self
->__isFile(
$filename
);
$filename
=
"$name.json"
;
return
$filename
if
$self
->__isFile(
$filename
);
die
;
}
sub
__loadAsIndex {
my
(
$self
,
$name
) =
@_
;
return
"$name/index.js"
if
$self
->__isFile(
"$name/index.js"
);
return
"$name/index.json"
if
$self
->__isFile(
"$name/index.json"
);
die
;
}
sub
__loadAsDirectory {
my
(
$self
,
$name
) =
@_
;
my
$package_json
=
join
'/'
,
$name
,
'package.json'
;
if
(
$self
->__isFile(
$package_json
)) {
open
my
$fh
,
'<'
,
$package_json
or
die
;
my
$decoder
= Cpanel::JSON::XS->new;
my
$package
=
join
''
, <
$fh
>;
$package
=
$decoder
->decode(
$package
);
my
$main
=
$package
->{main};
$main
=
''
if
!
defined
$main
;
$main
=
"$name/$main"
;
my
$resolved
=
eval
{
$self
->__loadAsFile(
$main
) };
$@ or
return
$resolved
;
return
$self
->__loadAsIndex(
$main
);
}
return
$self
->__loadAsIndex(
$name
);
}
sub
__nodeModulesPath {
my
(
$self
,
$start
) =
@_
;
$start
=
'.'
if
empty
$start
;
my
@parts
=
split
/\//,
$start
;
my
$i
= -1 +
@parts
;
my
@dirs
= @{
$self
->{__global}};
while
(
$i
>= 0) {
if
(
'node_modules'
eq
$parts
[
$i
]) {
--
$i
;
next
;
}
push
@dirs
,
join
'/'
,
@parts
[0 ..
$i
],
'node_modules'
;
--
$i
;
}
return
@dirs
;
}
sub
__loadNodeModules {
my
(
$self
,
$module
,
$path
) =
@_
;
my
@dirs
=
$self
->__nodeModulesPath(
$path
);
foreach
my
$dir
(
@dirs
) {
my
$resolved
=
eval
{
$self
->__loadAsFile(
"$dir/$module"
) };
$@ or
return
$resolved
;
$resolved
=
eval
{
$self
->__loadAsDirectory(
"$dir/$module"
) };
$@ or
return
$resolved
;
}
die
;
}
1;