mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-03 18:03:37 +00:00
Merge branch 'hotfix/0.12.6'
This commit is contained in:
commit
bd86a20adf
84 changed files with 64845 additions and 379 deletions
21
CHANGES.md
21
CHANGES.md
|
@ -1,4 +1,23 @@
|
|||
### 0.12.5 (2017-01-16 16:22:00 UTC)
|
||||
### 0.12.6 (2017-02-17 03:48:00 UTC)
|
||||
|
||||
* Change skip episodes that have no wanted qualities
|
||||
* Change download picked .nzb file on demand and not before
|
||||
* Change improve provider title processing
|
||||
* Change improve handling erroneous JSON responses
|
||||
* Change improve find show with unicode characters
|
||||
* Change improve results for providers Omgwtf, SpeedCD, Transmithenet, Zoogle
|
||||
* Change validate .torrent files that contain optional header data
|
||||
* Fix case where an episode status was not restored on failure
|
||||
* Add raise log error if no wanted qualities are found
|
||||
* Change add un/pw to Config/Media providers/Options for BTN API graceful fallback (can remove Api key for security)
|
||||
* Change only download torrent once when using blackhole
|
||||
* Add Cloudflare module 1.6.8 (be0a536) to handle specific CF connections
|
||||
* Add Js2Py 0.43 (c1442f1) Cloudflare dependency
|
||||
* Add pyjsparser 2.4.5 (cd5b829) Js2Py dependency
|
||||
* Remove Torrentshack
|
||||
|
||||
|
||||
### 0.12.5 (2017-01-16 16:22:00 UTC)
|
||||
|
||||
* Change TD search URL
|
||||
* Fix saving Media Providers when either Search NZBs/Torrents is disabled
|
||||
|
|
|
@ -8,6 +8,7 @@ Libs with customisations...
|
|||
/lib/hachoir_metadata/metadata.py
|
||||
/lib/hachoir_metadata/riff.py
|
||||
/lib/hachoir_parser/guess.py
|
||||
/lib/hachoir_parser/misc/torrent.py
|
||||
/lib/lockfile/mkdirlockfile.py
|
||||
/lib/pynma/pynma.py
|
||||
/lib/requests/packages/urllib3/connectionpool.py
|
||||
|
|
|
@ -420,9 +420,8 @@ class SickGear(object):
|
|||
startup_background_tasks = threading.Thread(name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids)
|
||||
startup_background_tasks.start()
|
||||
|
||||
# sure, why not?
|
||||
if sickbeard.USE_FAILED_DOWNLOADS:
|
||||
failed_history.trimHistory()
|
||||
failed_history.remove_old_history()
|
||||
|
||||
# Start an update if we're supposed to
|
||||
if self.force_update or sickbeard.UPDATE_SHOWS_ON_START:
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 861 B |
|
@ -35,7 +35,6 @@ $(document).ready(function () {
|
|||
? text
|
||||
.replace(/["]/g, '"')
|
||||
: text
|
||||
.replace(/Pokémon/, 'Pokemon')
|
||||
.replace(/(?:["]|")/g, '')
|
||||
);
|
||||
}
|
||||
|
|
161
lib/cfscrape.py
Normal file
161
lib/cfscrape.py
Normal file
|
@ -0,0 +1,161 @@
|
|||
from time import sleep
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
from requests.sessions import Session
|
||||
import js2py
|
||||
from copy import deepcopy
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
DEFAULT_USER_AGENTS = [
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0",
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0"
|
||||
]
|
||||
|
||||
DEFAULT_USER_AGENT = random.choice(DEFAULT_USER_AGENTS)
|
||||
|
||||
|
||||
class CloudflareScraper(Session):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CloudflareScraper, self).__init__(*args, **kwargs)
|
||||
|
||||
if "requests" in self.headers["User-Agent"]:
|
||||
# Spoof Firefox on Linux if no custom User-Agent has been set
|
||||
self.headers["User-Agent"] = DEFAULT_USER_AGENT
|
||||
|
||||
def request(self, method, url, *args, **kwargs):
|
||||
resp = super(CloudflareScraper, self).request(method, url, *args, **kwargs)
|
||||
|
||||
# Check if Cloudflare anti-bot is on
|
||||
if ( resp.status_code == 503
|
||||
and resp.headers.get("Server") == "cloudflare-nginx"
|
||||
and b"jschl_vc" in resp.content
|
||||
and b"jschl_answer" in resp.content
|
||||
):
|
||||
return self.solve_cf_challenge(resp, **kwargs)
|
||||
|
||||
# Otherwise, no Cloudflare anti-bot detected
|
||||
return resp
|
||||
|
||||
def solve_cf_challenge(self, resp, **original_kwargs):
|
||||
sleep(5) # Cloudflare requires a delay before solving the challenge
|
||||
|
||||
body = resp.text
|
||||
parsed_url = urlparse(resp.url)
|
||||
domain = urlparse(resp.url).netloc
|
||||
submit_url = "%s://%s/cdn-cgi/l/chk_jschl" % (parsed_url.scheme, domain)
|
||||
|
||||
cloudflare_kwargs = deepcopy(original_kwargs)
|
||||
params = cloudflare_kwargs.setdefault("params", {})
|
||||
headers = cloudflare_kwargs.setdefault("headers", {})
|
||||
headers["Referer"] = resp.url
|
||||
|
||||
try:
|
||||
params["jschl_vc"] = re.search(r'name="jschl_vc" value="(\w+)"', body).group(1)
|
||||
params["pass"] = re.search(r'name="pass" value="(.+?)"', body).group(1)
|
||||
|
||||
# Extract the arithmetic operation
|
||||
js = self.extract_js(body)
|
||||
|
||||
except Exception:
|
||||
# Something is wrong with the page.
|
||||
# This may indicate Cloudflare has changed their anti-bot
|
||||
# technique. If you see this and are running the latest version,
|
||||
# please open a GitHub issue so I can update the code accordingly.
|
||||
logging.error("[!] Unable to parse Cloudflare anti-bots page. "
|
||||
"Try upgrading cloudflare-scrape, or submit a bug report "
|
||||
"if you are running the latest version. Please read "
|
||||
"https://github.com/Anorov/cloudflare-scrape#updates "
|
||||
"before submitting a bug report.")
|
||||
raise
|
||||
|
||||
# Safely evaluate the Javascript expression
|
||||
params["jschl_answer"] = str(int(js2py.eval_js(js)) + len(domain))
|
||||
|
||||
# Requests transforms any request into a GET after a redirect,
|
||||
# so the redirect has to be handled manually here to allow for
|
||||
# performing other types of requests even as the first request.
|
||||
method = resp.request.method
|
||||
cloudflare_kwargs["allow_redirects"] = False
|
||||
redirect = self.request(method, submit_url, **cloudflare_kwargs)
|
||||
return self.request(method, redirect.headers["Location"], **original_kwargs)
|
||||
|
||||
def extract_js(self, body):
|
||||
js = re.search(r"setTimeout\(function\(\){\s+(var "
|
||||
"s,t,o,p,b,r,e,a,k,i,n,g,f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n", body).group(1)
|
||||
js = re.sub(r"a\.value = (parseInt\(.+?\)).+", r"\1", js)
|
||||
js = re.sub(r"\s{3,}[a-z](?: = |\.).+", "", js)
|
||||
|
||||
# Strip characters that could be used to exit the string context
|
||||
# These characters are not currently used in Cloudflare's arithmetic snippet
|
||||
js = re.sub(r"[\n\\']", "", js)
|
||||
|
||||
return js
|
||||
|
||||
@classmethod
|
||||
def create_scraper(cls, sess=None, **kwargs):
|
||||
"""
|
||||
Convenience function for creating a ready-to-go requests.Session (subclass) object.
|
||||
"""
|
||||
scraper = cls()
|
||||
|
||||
if sess:
|
||||
attrs = ["auth", "cert", "cookies", "headers", "hooks", "params", "proxies", "data"]
|
||||
for attr in attrs:
|
||||
val = getattr(sess, attr, None)
|
||||
if val:
|
||||
setattr(scraper, attr, val)
|
||||
|
||||
return scraper
|
||||
|
||||
|
||||
## Functions for integrating cloudflare-scrape with other applications and scripts
|
||||
|
||||
@classmethod
|
||||
def get_tokens(cls, url, user_agent=None, **kwargs):
|
||||
scraper = cls.create_scraper()
|
||||
if user_agent:
|
||||
scraper.headers["User-Agent"] = user_agent
|
||||
|
||||
try:
|
||||
resp = scraper.get(url)
|
||||
resp.raise_for_status()
|
||||
except Exception as e:
|
||||
logging.error("'%s' returned an error. Could not collect tokens." % url)
|
||||
raise
|
||||
|
||||
domain = urlparse(resp.url).netloc
|
||||
cookie_domain = None
|
||||
|
||||
for d in scraper.cookies.list_domains():
|
||||
if d.startswith(".") and d in ("." + domain):
|
||||
cookie_domain = d
|
||||
break
|
||||
else:
|
||||
raise ValueError("Unable to find Cloudflare cookies. Does the site actually have Cloudflare IUAM (\"I'm Under Attack Mode\") enabled?")
|
||||
|
||||
return ({
|
||||
"__cfduid": scraper.cookies.get("__cfduid", "", domain=cookie_domain),
|
||||
"cf_clearance": scraper.cookies.get("cf_clearance", "", domain=cookie_domain)
|
||||
},
|
||||
scraper.headers["User-Agent"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_cookie_string(cls, url, user_agent=None, **kwargs):
|
||||
"""
|
||||
Convenience function for building a Cookie HTTP header value.
|
||||
"""
|
||||
tokens, user_agent = cls.get_tokens(url, user_agent=user_agent)
|
||||
return "; ".join("=".join(pair) for pair in tokens.items()), user_agent
|
||||
|
||||
create_scraper = CloudflareScraper.create_scraper
|
||||
get_tokens = CloudflareScraper.get_tokens
|
||||
get_cookie_string = CloudflareScraper.get_cookie_string
|
|
@ -143,6 +143,7 @@ def Entry(parent, name):
|
|||
class TorrentFile(Parser):
|
||||
endian = LITTLE_ENDIAN
|
||||
MAGIC = "d8:announce"
|
||||
MAGIC_EXTENSION = "d13:announce-list"
|
||||
PARSER_TAGS = {
|
||||
"id": "torrent",
|
||||
"category": "misc",
|
||||
|
@ -154,7 +155,8 @@ class TorrentFile(Parser):
|
|||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, len(self.MAGIC)) != self.MAGIC:
|
||||
if self.stream.readBytes(0, len(self.MAGIC)) != self.MAGIC and \
|
||||
self.stream.readBytes(0, len(self.MAGIC_EXTENSION)) != self.MAGIC_EXTENSION:
|
||||
return "Invalid magic"
|
||||
return True
|
||||
|
||||
|
|
71
lib/js2py/__init__.py
Normal file
71
lib/js2py/__init__.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright 2014, 2015 Piotr Dabkowski
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the 'Software'),
|
||||
# to deal in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
# the Software, and to permit persons to whom the Software is furnished to do so, subject
|
||||
# to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all copies or
|
||||
# substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
||||
|
||||
""" This module allows you to translate and execute Javascript in pure python.
|
||||
Basically its implementation of ECMAScript 5.1 in pure python.
|
||||
|
||||
Use eval_js method to execute javascript code and get resulting python object (builtin if possible).
|
||||
|
||||
EXAMPLE:
|
||||
>>> import js2py
|
||||
>>> add = js2py.eval_js('function add(a, b) {return a + b}')
|
||||
>>> add(1, 2) + 3
|
||||
6
|
||||
>>> add('1', 2, 3)
|
||||
u'12'
|
||||
>>> add.constructor
|
||||
function Function() { [python code] }
|
||||
|
||||
|
||||
Or use EvalJs to execute many javascript code fragments under same context - you would be able to get any
|
||||
variable from the context!
|
||||
|
||||
>>> js = js2py.EvalJs()
|
||||
>>> js.execute('var a = 10; function f(x) {return x*x};')
|
||||
>>> js.f(9)
|
||||
81
|
||||
>>> js.a
|
||||
10
|
||||
|
||||
Also you can use its console method to play with interactive javascript console.
|
||||
|
||||
|
||||
Use parse_js to parse (syntax tree is just like in esprima.js) and translate_js to trasnlate JavaScript.
|
||||
|
||||
Finally, you can use pyimport statement from inside JS code to import and use python libraries.
|
||||
|
||||
>>> js2py.eval_js('pyimport urllib; urllib.urlopen("https://www.google.com")')
|
||||
|
||||
NOTE: This module is still not fully finished:
|
||||
|
||||
Date and JSON builtin objects are not implemented
|
||||
Array prototype is not fully finished (will be soon)
|
||||
|
||||
Other than that everything should work fine.
|
||||
|
||||
"""
|
||||
|
||||
__author__ = 'Piotr Dabkowski'
|
||||
__all__ = ['EvalJs', 'translate_js', 'import_js', 'eval_js', 'parse_js', 'translate_file',
|
||||
'run_file', 'disable_pyimport', 'eval_js6', 'translate_js6', 'PyJsException', 'get_file_contents', 'write_file_contents']
|
||||
from .base import PyJsException
|
||||
from .evaljs import *
|
||||
from .translators import parse as parse_js
|
||||
|
2781
lib/js2py/base.py
Normal file
2781
lib/js2py/base.py
Normal file
File diff suppressed because it is too large
Load diff
1
lib/js2py/constructors/__init__.py
Normal file
1
lib/js2py/constructors/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
__author__ = 'Piotr Dabkowski'
|
38
lib/js2py/constructors/jsarray.py
Normal file
38
lib/js2py/constructors/jsarray.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from ..base import *
|
||||
|
||||
@Js
|
||||
def Array():
|
||||
if len(arguments)==0 or len(arguments)>1:
|
||||
return arguments.to_list()
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber):
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js([])
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
return [a]
|
||||
|
||||
Array.create = Array
|
||||
Array.own['length']['value'] = Js(1)
|
||||
|
||||
@Js
|
||||
def isArray(arg):
|
||||
return arg.Class=='Array'
|
||||
|
||||
|
||||
Array.define_own_property('isArray', {'value': isArray,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Array.define_own_property('prototype', {'value': ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
ArrayPrototype.define_own_property('constructor', {'value': Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
34
lib/js2py/constructors/jsarraybuffer.py
Normal file
34
lib/js2py/constructors/jsarraybuffer.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
# todo check everything :)
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@Js
|
||||
def ArrayBuffer():
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber):
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(bytearray([0]*length))
|
||||
return temp
|
||||
return Js(bytearray([0]))
|
||||
|
||||
ArrayBuffer.create = ArrayBuffer
|
||||
ArrayBuffer.own['length']['value'] = Js(None)
|
||||
|
||||
ArrayBuffer.define_own_property('prototype', {'value': ArrayBufferPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
ArrayBufferPrototype.define_own_property('constructor', {'value': ArrayBuffer,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': True})
|
11
lib/js2py/constructors/jsboolean.py
Normal file
11
lib/js2py/constructors/jsboolean.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from ..base import *
|
||||
|
||||
BooleanPrototype.define_own_property('constructor', {'value': Boolean,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Boolean.define_own_property('prototype', {'value': BooleanPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
368
lib/js2py/constructors/jsdate.py
Normal file
368
lib/js2py/constructors/jsdate.py
Normal file
|
@ -0,0 +1,368 @@
|
|||
from ..base import *
|
||||
from .time_helpers import *
|
||||
|
||||
TZ_OFFSET = (time.altzone//3600)
|
||||
ABS_OFFSET = abs(TZ_OFFSET)
|
||||
TZ_NAME = time.tzname[1]
|
||||
ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ'
|
||||
@Js
|
||||
def Date(year, month, date, hours, minutes, seconds, ms):
|
||||
return now().to_string()
|
||||
|
||||
Date.Class = 'Date'
|
||||
|
||||
def now():
|
||||
return PyJsDate(int(time.time()*1000), prototype=DatePrototype)
|
||||
|
||||
|
||||
@Js
|
||||
def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this
|
||||
args = arguments
|
||||
y = args[0].to_number()
|
||||
m = args[1].to_number()
|
||||
l = len(args)
|
||||
dt = args[2].to_number() if l>2 else Js(1)
|
||||
h = args[3].to_number() if l>3 else Js(0)
|
||||
mi = args[4].to_number() if l>4 else Js(0)
|
||||
sec = args[5].to_number() if l>5 else Js(0)
|
||||
mili = args[6].to_number() if l>6 else Js(0)
|
||||
if not y.is_nan() and 0<=y.value<=99:
|
||||
y = y + Js(1900)
|
||||
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
|
||||
return PyJsDate(t, prototype=DatePrototype)
|
||||
|
||||
@Js
|
||||
def parse(string):
|
||||
return PyJsDate(TimeClip(parse_date(string.to_string().value)), prototype=DatePrototype)
|
||||
|
||||
|
||||
|
||||
Date.define_own_property('now', {'value': Js(now),
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Date.define_own_property('parse', {'value': parse,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Date.define_own_property('UTC', {'value': UTC,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
class PyJsDate(PyJs):
|
||||
Class = 'Date'
|
||||
extensible = True
|
||||
def __init__(self, value, prototype=None):
|
||||
self.value = value
|
||||
self.own = {}
|
||||
self.prototype = prototype
|
||||
|
||||
# todo fix this problematic datetime part
|
||||
def to_local_dt(self):
|
||||
return datetime.datetime.utcfromtimestamp(UTCToLocal(self.value)//1000)
|
||||
|
||||
def to_utc_dt(self):
|
||||
return datetime.datetime.utcfromtimestamp(self.value//1000)
|
||||
|
||||
def local_strftime(self, pattern):
|
||||
if self.value is NaN:
|
||||
return 'Invalid Date'
|
||||
try:
|
||||
dt = self.to_local_dt()
|
||||
except:
|
||||
raise MakeError('TypeError', 'unsupported date range. Will fix in future versions')
|
||||
try:
|
||||
return dt.strftime(pattern)
|
||||
except:
|
||||
raise MakeError('TypeError', 'Could not generate date string from this date (limitations of python.datetime)')
|
||||
|
||||
def utc_strftime(self, pattern):
|
||||
if self.value is NaN:
|
||||
return 'Invalid Date'
|
||||
try:
|
||||
dt = self.to_utc_dt()
|
||||
except:
|
||||
raise MakeError('TypeError', 'unsupported date range. Will fix in future versions')
|
||||
try:
|
||||
return dt.strftime(pattern)
|
||||
except:
|
||||
raise MakeError('TypeError', 'Could not generate date string from this date (limitations of python.datetime)')
|
||||
|
||||
|
||||
|
||||
|
||||
def parse_date(py_string): # todo support all date string formats
|
||||
try:
|
||||
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
return MakeDate(MakeDay(Js(dt.year), Js(dt.month-1), Js(dt.day)), MakeTime(Js(dt.hour), Js(dt.minute), Js(dt.second), Js(dt.microsecond//1000)))
|
||||
except:
|
||||
raise MakeError('TypeError', 'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!' % py_string)
|
||||
|
||||
|
||||
|
||||
def date_constructor(*args):
|
||||
if len(args)>=2:
|
||||
return date_constructor2(*args)
|
||||
elif len(args)==1:
|
||||
return date_constructor1(args[0])
|
||||
else:
|
||||
return date_constructor0()
|
||||
|
||||
|
||||
def date_constructor0():
|
||||
return now()
|
||||
|
||||
|
||||
def date_constructor1(value):
|
||||
v = value.to_primitive()
|
||||
if v._type()=='String':
|
||||
v = parse_date(v.value)
|
||||
else:
|
||||
v = v.to_int()
|
||||
return PyJsDate(TimeClip(v), prototype=DatePrototype)
|
||||
|
||||
|
||||
def date_constructor2(*args):
|
||||
y = args[0].to_number()
|
||||
m = args[1].to_number()
|
||||
l = len(args)
|
||||
dt = args[2].to_number() if l>2 else Js(1)
|
||||
h = args[3].to_number() if l>3 else Js(0)
|
||||
mi = args[4].to_number() if l>4 else Js(0)
|
||||
sec = args[5].to_number() if l>5 else Js(0)
|
||||
mili = args[6].to_number() if l>6 else Js(0)
|
||||
if not y.is_nan() and 0<=y.value<=99:
|
||||
y = y + Js(1900)
|
||||
t = TimeClip(LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))))
|
||||
return PyJsDate(t, prototype=DatePrototype)
|
||||
|
||||
Date.create = date_constructor
|
||||
|
||||
DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype)
|
||||
|
||||
def check_date(obj):
|
||||
if obj.Class!='Date':
|
||||
raise MakeError('TypeError', 'this is not a Date object')
|
||||
|
||||
|
||||
class DateProto:
|
||||
def toString():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return 'Invalid Date'
|
||||
offset = (UTCToLocal(this.value) - this.value)//msPerHour
|
||||
return this.local_strftime('%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad(offset, 2, True), GetTimeZoneName(this.value))
|
||||
|
||||
def toDateString():
|
||||
check_date(this)
|
||||
return this.local_strftime('%d %B %Y')
|
||||
|
||||
def toTimeString():
|
||||
check_date(this)
|
||||
return this.local_strftime('%H:%M:%S')
|
||||
|
||||
def toLocaleString():
|
||||
check_date(this)
|
||||
return this.local_strftime('%d %B %Y %H:%M:%S')
|
||||
|
||||
def toLocaleDateString():
|
||||
check_date(this)
|
||||
return this.local_strftime('%d %B %Y')
|
||||
|
||||
def toLocaleTimeString():
|
||||
check_date(this)
|
||||
return this.local_strftime('%H:%M:%S')
|
||||
|
||||
def valueOf():
|
||||
check_date(this)
|
||||
return this.value
|
||||
|
||||
def getTime():
|
||||
check_date(this)
|
||||
return this.value
|
||||
|
||||
def getFullYear():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return YearFromTime(UTCToLocal(this.value))
|
||||
|
||||
def getUTCFullYear():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return YearFromTime(this.value)
|
||||
|
||||
def getMonth():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return MonthFromTime(UTCToLocal(this.value))
|
||||
|
||||
def getDate():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return DateFromTime(UTCToLocal(this.value))
|
||||
|
||||
def getUTCMonth():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return MonthFromTime(this.value)
|
||||
|
||||
def getUTCDate():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return DateFromTime(this.value)
|
||||
|
||||
def getDay():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return WeekDay(UTCToLocal(this.value))
|
||||
|
||||
def getUTCDay():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return WeekDay(this.value)
|
||||
|
||||
def getHours():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return HourFromTime(UTCToLocal(this.value))
|
||||
|
||||
def getUTCHours():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return HourFromTime(this.value)
|
||||
|
||||
def getMinutes():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return MinFromTime(UTCToLocal(this.value))
|
||||
|
||||
def getUTCMinutes():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return MinFromTime(this.value)
|
||||
|
||||
def getSeconds():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return SecFromTime(UTCToLocal(this.value))
|
||||
|
||||
def getUTCSeconds():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return SecFromTime(this.value)
|
||||
|
||||
def getMilliseconds():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return msFromTime(UTCToLocal(this.value))
|
||||
|
||||
def getUTCMilliseconds():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return msFromTime(this.value)
|
||||
|
||||
def getTimezoneOffset():
|
||||
check_date(this)
|
||||
if this.value is NaN:
|
||||
return NaN
|
||||
return (this.value - UTCToLocal(this.value))//60000
|
||||
|
||||
|
||||
def setTime(time):
|
||||
check_date(this)
|
||||
this.value = TimeClip(time.to_number().to_int())
|
||||
return this.value
|
||||
|
||||
def setMilliseconds(ms):
|
||||
check_date(this)
|
||||
t = UTCToLocal(this.value)
|
||||
tim = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
|
||||
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
|
||||
this.value = u
|
||||
return u
|
||||
|
||||
def setUTCMilliseconds(ms):
|
||||
check_date(this)
|
||||
t = this.value
|
||||
tim = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
|
||||
u = TimeClip(MakeDate(Day(t), tim))
|
||||
this.value = u
|
||||
return u
|
||||
|
||||
# todo Complete all setters!
|
||||
|
||||
def toUTCString():
|
||||
check_date(this)
|
||||
return this.utc_strftime('%d %B %Y %H:%M:%S')
|
||||
|
||||
def toISOString():
|
||||
check_date(this)
|
||||
t = this.value
|
||||
year = YearFromTime(t)
|
||||
month, day, hour, minute, second, milli = pad(MonthFromTime(t)+1), pad(DateFromTime(t)), pad(HourFromTime(t)), pad(MinFromTime(t)), pad(SecFromTime(t)), pad(msFromTime(t))
|
||||
return ISO_FORMAT % (unicode(year) if 0<=year<=9999 else pad(year, 6, True), month, day, hour, minute, second, milli)
|
||||
|
||||
def toJSON(key):
|
||||
o = this.to_object()
|
||||
tv = o.to_primitive('Number')
|
||||
if tv.Class=='Number' and not tv.is_finite():
|
||||
return this.null
|
||||
toISO = o.get('toISOString')
|
||||
if not toISO.is_callable():
|
||||
raise this.MakeError('TypeError', 'toISOString is not callable')
|
||||
return toISO.call(o, ())
|
||||
|
||||
|
||||
def pad(num, n=2, sign=False):
|
||||
'''returns n digit string representation of the num'''
|
||||
s = unicode(abs(num))
|
||||
if len(s)<n:
|
||||
s = '0'*(n-len(s)) + s
|
||||
if not sign:
|
||||
return s
|
||||
if num>=0:
|
||||
return '+'+s
|
||||
else:
|
||||
return '-'+s
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
fill_prototype(DatePrototype, DateProto, default_attrs)
|
||||
|
||||
|
||||
|
||||
Date.define_own_property('prototype', {'value': DatePrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
DatePrototype.define_own_property('constructor', {'value': Date,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
69
lib/js2py/constructors/jsfloat32array.py
Normal file
69
lib/js2py/constructors/jsfloat32array.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@Js
|
||||
def Float32Array():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.float32))
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.float32))
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
temp = Js(numpy.array(array, dtype=numpy.float32))
|
||||
temp.put('length', Js(len(array)))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(a.obj) % 4 != 0:
|
||||
raise MakeError('RangeError', 'Byte length of Float32Array should be a multiple of 4')
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
if offset % 4 != 0:
|
||||
raise MakeError('RangeError', 'Start offset of Float32Array should be a multiple of 4')
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int((len(a.obj)-offset)/4)
|
||||
array = numpy.frombuffer(a.obj, dtype=numpy.float32, count=length, offset=offset)
|
||||
temp = Js(array)
|
||||
temp.put('length', Js(length))
|
||||
temp.buff = array
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.float32))
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Float32Array.create = Float32Array
|
||||
Float32Array.own['length']['value'] = Js(3)
|
||||
|
||||
Float32Array.define_own_property('prototype', {'value': Float32ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Float32ArrayPrototype.define_own_property('constructor', {'value': Float32Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Float32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(4),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
69
lib/js2py/constructors/jsfloat64array.py
Normal file
69
lib/js2py/constructors/jsfloat64array.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@Js
|
||||
def Float64Array():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.float64))
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.float64))
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
temp = Js(numpy.array(array, dtype=numpy.float64))
|
||||
temp.put('length', Js(len(array)))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(a.obj) % 8 != 0:
|
||||
raise MakeError('RangeError', 'Byte length of Float64Array should be a multiple of 8')
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
if offset % 8 != 0:
|
||||
raise MakeError('RangeError', 'Start offset of Float64Array should be a multiple of 8')
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int((len(a.obj)-offset)/8)
|
||||
array = numpy.frombuffer(a.obj, dtype=numpy.float64, count=length, offset=offset)
|
||||
temp = Js(array)
|
||||
temp.put('length', Js(length))
|
||||
temp.buff = array
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.float64))
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Float64Array.create = Float64Array
|
||||
Float64Array.own['length']['value'] = Js(3)
|
||||
|
||||
Float64Array.define_own_property('prototype', {'value': Float64ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Float64ArrayPrototype.define_own_property('constructor', {'value': Float64Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Float64ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(8),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
49
lib/js2py/constructors/jsfunction.py
Normal file
49
lib/js2py/constructors/jsfunction.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
from ..base import *
|
||||
try:
|
||||
from ..translators.translator import translate_js
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@Js
|
||||
def Function():
|
||||
# convert arguments to python list of strings
|
||||
a = [e.to_string().value for e in arguments.to_list()]
|
||||
body = ';'
|
||||
args = ()
|
||||
if len(a):
|
||||
body = '%s;' % a[-1]
|
||||
args = a[:-1]
|
||||
# translate this function to js inline function
|
||||
js_func = '(function (%s) {%s})' % (','.join(args), body)
|
||||
# now translate js inline to python function
|
||||
py_func = translate_js(js_func, '')
|
||||
# add set func scope to global scope
|
||||
# a but messy solution but works :)
|
||||
globals()['var'] = PyJs.GlobalObject
|
||||
# define py function and return it
|
||||
temp = executor(py_func, globals())
|
||||
temp.source = '{%s}'%body
|
||||
temp.func_name = 'anonymous'
|
||||
return temp
|
||||
|
||||
def executor(f, glob):
|
||||
exec(f, globals())
|
||||
return globals()['PyJs_anonymous_0_']
|
||||
|
||||
|
||||
#new statement simply calls Function
|
||||
Function.create = Function
|
||||
|
||||
#set constructor property inside FunctionPrototype
|
||||
|
||||
fill_in_props(FunctionPrototype, {'constructor':Function}, default_attrs)
|
||||
|
||||
#attach prototype to Function constructor
|
||||
Function.define_own_property('prototype', {'value': FunctionPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
#Fix Function length (its 0 and should be 1)
|
||||
Function.own['length']['value'] = Js(1)
|
||||
|
68
lib/js2py/constructors/jsint16array.py
Normal file
68
lib/js2py/constructors/jsint16array.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
@Js
|
||||
def Int16Array():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.int16))
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.int16))
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
temp = Js(numpy.array(array, dtype=numpy.int16))
|
||||
temp.put('length', Js(len(array)))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(a.obj) % 2 != 0:
|
||||
raise MakeError('RangeError', 'Byte length of Int16Array should be a multiple of 2')
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
if offset % 2 != 0:
|
||||
raise MakeError('RangeError', 'Start offset of Int16Array should be a multiple of 2')
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int((len(a.obj)-offset)/2)
|
||||
array = numpy.frombuffer(a.obj, dtype=numpy.int16, count=length, offset=offset)
|
||||
temp = Js(array)
|
||||
temp.put('length', Js(length))
|
||||
temp.buff = array
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.int16))
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Int16Array.create = Int16Array
|
||||
Int16Array.own['length']['value'] = Js(3)
|
||||
|
||||
Int16Array.define_own_property('prototype', {'value': Int16ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Int16ArrayPrototype.define_own_property('constructor', {'value': Int16Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Int16ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(2),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
69
lib/js2py/constructors/jsint32array.py
Normal file
69
lib/js2py/constructors/jsint32array.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@Js
|
||||
def Int32Array():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.int32))
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.int32))
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
temp = Js(numpy.array(array, dtype=numpy.int32))
|
||||
temp.put('length', Js(len(array)))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(a.obj) % 4 != 0:
|
||||
raise MakeError('RangeError', 'Byte length of Int32Array should be a multiple of 4')
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
if offset % 4 != 0:
|
||||
raise MakeError('RangeError', 'Start offset of Int32Array should be a multiple of 4')
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int((len(a.obj)-offset)/4)
|
||||
array = numpy.frombuffer(a.obj, dtype=numpy.int32, count=length, offset=offset)
|
||||
temp = Js(array)
|
||||
temp.put('length', Js(length))
|
||||
temp.buff = array
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.int32))
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Int32Array.create = Int32Array
|
||||
Int32Array.own['length']['value'] = Js(3)
|
||||
|
||||
Int32Array.define_own_property('prototype', {'value': Int32ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Int32ArrayPrototype.define_own_property('constructor', {'value': Int32Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Int32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(4),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
64
lib/js2py/constructors/jsint8array.py
Normal file
64
lib/js2py/constructors/jsint8array.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
@Js
|
||||
def Int8Array():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.int8))
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.int8))
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
temp = Js(numpy.array(array, dtype=numpy.int8))
|
||||
temp.put('length', Js(len(array)))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int(len(a.obj)-offset)
|
||||
array = numpy.frombuffer(a.obj, dtype=numpy.int8, count=length, offset=offset)
|
||||
temp = Js(array)
|
||||
temp.put('length', Js(length))
|
||||
temp.buff = array
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.int8))
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Int8Array.create = Int8Array
|
||||
Int8Array.own['length']['value'] = Js(3)
|
||||
|
||||
Int8Array.define_own_property('prototype', {'value': Int8ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Int8ArrayPrototype.define_own_property('constructor', {'value': Int8Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Int8ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(1),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
151
lib/js2py/constructors/jsmath.py
Normal file
151
lib/js2py/constructors/jsmath.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from ..base import *
|
||||
import math
|
||||
import random
|
||||
|
||||
Math = PyJsObject(prototype=ObjectPrototype)
|
||||
Math.Class = 'Math'
|
||||
|
||||
CONSTANTS = {'E': 2.7182818284590452354,
|
||||
'LN10': 2.302585092994046,
|
||||
'LN2': 0.6931471805599453,
|
||||
'LOG2E': 1.4426950408889634,
|
||||
'LOG10E': 0.4342944819032518,
|
||||
'PI': 3.1415926535897932,
|
||||
'SQRT1_2': 0.7071067811865476,
|
||||
'SQRT2': 1.4142135623730951}
|
||||
|
||||
for constant, value in CONSTANTS.items():
|
||||
Math.define_own_property(constant, {'value': Js(value),
|
||||
'writable': False,
|
||||
'enumerable': False,
|
||||
'configurable': False})
|
||||
|
||||
class MathFunctions:
|
||||
def abs(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return abs(a)
|
||||
|
||||
def acos(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return math.acos(a)
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def asin(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return math.asin(a)
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def atan(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return math.atan(a)
|
||||
|
||||
def atan2(y, x):
|
||||
a = x.to_number().value
|
||||
b = y.to_number().value
|
||||
if a!=a or b!=b: # it must be a nan
|
||||
return NaN
|
||||
return math.atan2(b, a)
|
||||
|
||||
def ceil(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return math.ceil(a)
|
||||
|
||||
def floor(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return math.floor(a)
|
||||
|
||||
def round(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return round(a)
|
||||
|
||||
def sin(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return math.sin(a)
|
||||
|
||||
def cos(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return math.cos(a)
|
||||
|
||||
def tan(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return math.tan(a)
|
||||
|
||||
def log(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return math.log(a)
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def exp(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
return math.exp(a)
|
||||
|
||||
def pow(x, y):
|
||||
a = x.to_number().value
|
||||
b = y.to_number().value
|
||||
if a!=a or b!=b: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return a**b
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def sqrt(x):
|
||||
a = x.to_number().value
|
||||
if a!=a: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return a**0.5
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def min():
|
||||
if not len(arguments):
|
||||
return -Infinity
|
||||
lis = tuple(e.to_number().value for e in arguments.to_list())
|
||||
if any(e!=e for e in lis): # we dont want NaNs
|
||||
return NaN
|
||||
return min(*lis)
|
||||
|
||||
def max():
|
||||
if not len(arguments):
|
||||
return -Infinity
|
||||
lis = tuple(e.to_number().value for e in arguments.to_list())
|
||||
if any(e!=e for e in lis): # we dont want NaNs
|
||||
return NaN
|
||||
return max(*lis)
|
||||
|
||||
def random():
|
||||
return random.random()
|
||||
|
||||
|
||||
fill_prototype(Math, MathFunctions, default_attrs)
|
18
lib/js2py/constructors/jsnumber.py
Normal file
18
lib/js2py/constructors/jsnumber.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from ..base import *
|
||||
|
||||
|
||||
CONSTS = {'prototype': NumberPrototype,
|
||||
'MAX_VALUE':1.7976931348623157e308,
|
||||
'MIN_VALUE': 5.0e-324,
|
||||
'NaN': NaN,
|
||||
'NEGATIVE_INFINITY': float('-inf'),
|
||||
'POSITIVE_INFINITY': float('inf')}
|
||||
|
||||
fill_in_props(Number, CONSTS, {'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
NumberPrototype.define_own_property('constructor', {'value': Number,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
172
lib/js2py/constructors/jsobject.py
Normal file
172
lib/js2py/constructors/jsobject.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
from ..base import *
|
||||
import six
|
||||
|
||||
#todo Double check everything is OK
|
||||
|
||||
@Js
|
||||
def Object():
|
||||
val = arguments.get('0')
|
||||
if val.is_null() or val.is_undefined():
|
||||
return PyJsObject(prototype=ObjectPrototype)
|
||||
return val.to_object()
|
||||
|
||||
|
||||
@Js
|
||||
def object_constructor():
|
||||
if len(arguments):
|
||||
val = arguments.get('0')
|
||||
if val.TYPE=='Object':
|
||||
#Implementation dependent, but my will simply return :)
|
||||
return val
|
||||
elif val.TYPE in ('Number', 'String', 'Boolean'):
|
||||
return val.to_object()
|
||||
return PyJsObject(prototype=ObjectPrototype)
|
||||
|
||||
Object.create = object_constructor
|
||||
Object.own['length']['value'] = Js(1)
|
||||
|
||||
|
||||
class ObjectMethods:
|
||||
def getPrototypeOf(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.getPrototypeOf called on non-object')
|
||||
return null if obj.prototype is None else obj.prototype
|
||||
|
||||
def getOwnPropertyDescriptor (obj, prop):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.getOwnPropertyDescriptor called on non-object')
|
||||
return obj.own.get(prop.to_string().value) # will return undefined if we dont have this prop
|
||||
|
||||
def getOwnPropertyNames(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.getOwnPropertyDescriptor called on non-object')
|
||||
return obj.own.keys()
|
||||
|
||||
def create(obj):
|
||||
if not (obj.is_object() or obj.is_null()):
|
||||
raise MakeError('TypeError', 'Object prototype may only be an Object or null')
|
||||
temp = PyJsObject(prototype=(None if obj.is_null() else obj))
|
||||
if len(arguments)>1 and not arguments[1].is_undefined():
|
||||
if six.PY2:
|
||||
ObjectMethods.defineProperties.__func__(temp, arguments[1])
|
||||
else:
|
||||
ObjectMethods.defineProperties(temp, arguments[1])
|
||||
return temp
|
||||
|
||||
def defineProperty(obj, prop, attrs):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.defineProperty called on non-object')
|
||||
name = prop.to_string().value
|
||||
if not obj.define_own_property(name, ToPropertyDescriptor(attrs)):
|
||||
raise MakeError('TypeError', 'Cannot redefine property: %s' % name)
|
||||
return obj
|
||||
|
||||
def defineProperties(obj, properties):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.defineProperties called on non-object')
|
||||
props = properties.to_object()
|
||||
for name in props:
|
||||
desc = ToPropertyDescriptor(props.get(name.value))
|
||||
if not obj.define_own_property(name.value, desc):
|
||||
raise MakeError('TypeError', 'Failed to define own property: %s'%name.value)
|
||||
return obj
|
||||
|
||||
def seal(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.seal called on non-object')
|
||||
for desc in obj.own.values():
|
||||
desc['configurable'] = False
|
||||
obj.extensible = False
|
||||
return obj
|
||||
|
||||
def freeze(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.freeze called on non-object')
|
||||
for desc in obj.own.values():
|
||||
desc['configurable'] = False
|
||||
if is_data_descriptor(desc):
|
||||
desc['writable'] = False
|
||||
obj.extensible = False
|
||||
return obj
|
||||
|
||||
def preventExtensions(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.preventExtensions on non-object')
|
||||
obj.extensible = False
|
||||
return obj
|
||||
|
||||
def isSealed(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.isSealed called on non-object')
|
||||
if obj.extensible:
|
||||
return False
|
||||
for desc in obj.own.values():
|
||||
if desc['configurable']:
|
||||
return False
|
||||
return True
|
||||
|
||||
def isFrozen(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.isFrozen called on non-object')
|
||||
if obj.extensible:
|
||||
return False
|
||||
for desc in obj.own.values():
|
||||
if desc['configurable']:
|
||||
return False
|
||||
if is_data_descriptor(desc) and desc['writable']:
|
||||
return False
|
||||
return True
|
||||
|
||||
def isExtensible(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.isExtensible called on non-object')
|
||||
return obj.extensible
|
||||
|
||||
def keys(obj):
|
||||
if not obj.is_object():
|
||||
raise MakeError('TypeError', 'Object.keys called on non-object')
|
||||
return [e for e,d in six.iteritems(obj.own) if d.get('enumerable')]
|
||||
|
||||
|
||||
# add methods attached to Object constructor
|
||||
fill_prototype(Object, ObjectMethods, default_attrs)
|
||||
# add constructor to prototype
|
||||
fill_in_props(ObjectPrototype, {'constructor':Object}, default_attrs)
|
||||
# add prototype property to the constructor.
|
||||
Object.define_own_property('prototype', {'value': ObjectPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
|
||||
|
||||
# some utility functions:
|
||||
|
||||
def ToPropertyDescriptor(obj): # page 38 (50 absolute)
|
||||
if obj.TYPE!='Object':
|
||||
raise MakeError('TypeError', 'Can\'t convert non-object to property descriptor')
|
||||
desc = {}
|
||||
if obj.has_property('enumerable'):
|
||||
desc['enumerable'] = obj.get('enumerable').to_boolean().value
|
||||
if obj.has_property('configurable'):
|
||||
desc['configurable'] = obj.get('configurable').to_boolean().value
|
||||
if obj.has_property('value'):
|
||||
desc['value'] = obj.get('value')
|
||||
if obj.has_property('writable'):
|
||||
desc['writable'] = obj.get('writable').to_boolean().value
|
||||
if obj.has_property('get'):
|
||||
cand = obj.get('get')
|
||||
if not (cand.is_undefined() or cand.is_callable()):
|
||||
raise MakeError('TypeError', 'Invalid getter (it has to be a function or undefined)')
|
||||
desc['get'] = cand
|
||||
if obj.has_property('set'):
|
||||
cand = obj.get('set')
|
||||
if not (cand.is_undefined() or cand.is_callable()):
|
||||
raise MakeError('TypeError', 'Invalid setter (it has to be a function or undefined)')
|
||||
desc['set'] = cand
|
||||
if ('get' in desc or 'set' in desc) and ('value' in desc or 'writable' in desc):
|
||||
raise MakeError('TypeError', 'Invalid property. A property cannot both have accessors and be writable or have a value.')
|
||||
return desc
|
||||
|
||||
|
||||
|
11
lib/js2py/constructors/jsregexp.py
Normal file
11
lib/js2py/constructors/jsregexp.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from ..base import *
|
||||
|
||||
RegExpPrototype.define_own_property('constructor', {'value': RegExp,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
RegExp.define_own_property('prototype', {'value': RegExpPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
30
lib/js2py/constructors/jsstring.py
Normal file
30
lib/js2py/constructors/jsstring.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from ..base import *
|
||||
# python 3 support
|
||||
import six
|
||||
if six.PY3:
|
||||
unichr = chr
|
||||
|
||||
@Js
|
||||
def fromCharCode():
|
||||
args = arguments.to_list()
|
||||
res = u''
|
||||
for e in args:
|
||||
res +=unichr(e.to_uint16())
|
||||
return this.Js(res)
|
||||
|
||||
fromCharCode.own['length']['value'] = Js(1)
|
||||
|
||||
String.define_own_property('fromCharCode', {'value': fromCharCode,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
String.define_own_property('prototype', {'value': StringPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
StringPrototype.define_own_property('constructor', {'value': String,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
68
lib/js2py/constructors/jsuint16array.py
Normal file
68
lib/js2py/constructors/jsuint16array.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
@Js
|
||||
def Uint16Array():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.uint16))
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.uint16))
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
temp = Js(numpy.array(array, dtype=numpy.uint16))
|
||||
temp.put('length', Js(len(array)))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(a.obj) % 2 != 0:
|
||||
raise MakeError('RangeError', 'Byte length of Uint16Array should be a multiple of 2')
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
if offset % 2 != 0:
|
||||
raise MakeError('RangeError', 'Start offset of Uint16Array should be a multiple of 2')
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int((len(a.obj)-offset)/2)
|
||||
array = numpy.frombuffer(a.obj, dtype=numpy.uint16, count=length, offset=offset)
|
||||
temp = Js(array)
|
||||
temp.put('length', Js(length))
|
||||
temp.buff = array
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.uint16))
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Uint16Array.create = Uint16Array
|
||||
Uint16Array.own['length']['value'] = Js(3)
|
||||
|
||||
Uint16Array.define_own_property('prototype', {'value': Uint16ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Uint16ArrayPrototype.define_own_property('constructor', {'value': Uint16Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Uint16ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(2),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
74
lib/js2py/constructors/jsuint32array.py
Normal file
74
lib/js2py/constructors/jsuint32array.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
@Js
|
||||
def Uint32Array():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.uint32))
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.uint32))
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = len(array)-offset
|
||||
temp = Js(numpy.array(array[offset:offset+length], dtype=numpy.uint32))
|
||||
temp.put('length', Js(length))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(a.obj) % 4 != 0:
|
||||
raise MakeError('RangeError', 'Byte length of Uint32Array should be a multiple of 4')
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
if offset % 4 != 0:
|
||||
raise MakeError('RangeError', 'Start offset of Uint32Array should be a multiple of 4')
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int((len(a.obj)-offset)/4)
|
||||
temp = Js(numpy.frombuffer(a.obj, dtype=numpy.uint32, count=length, offset=offset))
|
||||
temp.put('length', Js(length))
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.uint32))
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Uint32Array.create = Uint32Array
|
||||
Uint32Array.own['length']['value'] = Js(3)
|
||||
|
||||
Uint32Array.define_own_property('prototype', {'value': Uint32ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Uint32ArrayPrototype.define_own_property('constructor', {'value': Uint32Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Uint32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(4),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
65
lib/js2py/constructors/jsuint8array.py
Normal file
65
lib/js2py/constructors/jsuint8array.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@Js
|
||||
def Uint8Array():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.uint8))
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.uint8))
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
temp = Js(numpy.array(array, dtype=numpy.uint8))
|
||||
temp.put('length', Js(len(array)))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int(len(a.obj)-offset)
|
||||
array = numpy.frombuffer(a.obj, dtype=numpy.uint8, count=length, offset=offset)
|
||||
temp = Js(array)
|
||||
temp.put('length', Js(length))
|
||||
temp.buff = array
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.uint8))
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Uint8Array.create = Uint8Array
|
||||
Uint8Array.own['length']['value'] = Js(3)
|
||||
|
||||
Uint8Array.define_own_property('prototype', {'value': Uint8ArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Uint8ArrayPrototype.define_own_property('constructor', {'value': Uint8Array,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Uint8ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(1),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
65
lib/js2py/constructors/jsuint8clampedarray.py
Normal file
65
lib/js2py/constructors/jsuint8clampedarray.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
from ..base import *
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@Js
|
||||
def Uint8ClampedArray():
|
||||
TypedArray = (PyJsInt8Array,PyJsUint8Array,PyJsUint8ClampedArray,PyJsInt16Array,PyJsUint16Array,PyJsInt32Array,PyJsUint32Array,PyJsFloat32Array,PyJsFloat64Array)
|
||||
a = arguments[0]
|
||||
if isinstance(a, PyJsNumber): # length
|
||||
length = a.to_uint32()
|
||||
if length!=a.value:
|
||||
raise MakeError('RangeError', 'Invalid array length')
|
||||
temp = Js(numpy.full(length, 0, dtype=numpy.uint8), Clamped=True)
|
||||
temp.put('length', a)
|
||||
return temp
|
||||
elif isinstance(a, PyJsString): # object (string)
|
||||
temp = Js(numpy.array(list(a.value), dtype=numpy.uint8), Clamped=True)
|
||||
temp.put('length', Js(len(list(a.value))))
|
||||
return temp
|
||||
elif isinstance(a, PyJsArray) or isinstance(a,TypedArray) or isinstance(a,PyJsArrayBuffer): # object (Array, TypedArray)
|
||||
array = a.to_list()
|
||||
array = [(int(item.value) if item.value != None else 0) for item in array]
|
||||
temp = Js(numpy.array(array, dtype=numpy.uint8), Clamped=True)
|
||||
temp.put('length', Js(len(array)))
|
||||
return temp
|
||||
elif isinstance(a,PyObjectWrapper): # object (ArrayBuffer, etc)
|
||||
if len(arguments) > 1:
|
||||
offset = int(arguments[1].value)
|
||||
else:
|
||||
offset = 0
|
||||
if len(arguments) > 2:
|
||||
length = int(arguments[2].value)
|
||||
else:
|
||||
length = int(len(a.obj)-offset)
|
||||
array = numpy.frombuffer(a.obj, dtype=numpy.uint8, count=length, offset=offset)
|
||||
temp = Js(array, Clamped=True)
|
||||
temp.put('length', Js(length))
|
||||
temp.buff = array
|
||||
return temp
|
||||
temp = Js(numpy.full(0, 0, dtype=numpy.uint8), Clamped=True)
|
||||
temp.put('length', Js(0))
|
||||
return temp
|
||||
|
||||
Uint8ClampedArray.create = Uint8ClampedArray
|
||||
Uint8ClampedArray.own['length']['value'] = Js(3)
|
||||
|
||||
Uint8ClampedArray.define_own_property('prototype', {'value': Uint8ClampedArrayPrototype,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
||||
|
||||
Uint8ClampedArrayPrototype.define_own_property('constructor', {'value': Uint8ClampedArray,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
Uint8ClampedArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {'value': Js(1),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False})
|
184
lib/js2py/constructors/time_helpers.py
Normal file
184
lib/js2py/constructors/time_helpers.py
Normal file
|
@ -0,0 +1,184 @@
|
|||
# NOTE: t must be INT!!!
|
||||
import time
|
||||
import datetime
|
||||
import warnings
|
||||
|
||||
try:
|
||||
from tzlocal import get_localzone
|
||||
LOCAL_ZONE = get_localzone()
|
||||
except: # except all problems...
|
||||
warnings.warn('Please install or fix tzlocal library (pip install tzlocal) in order to make Date object work better. Otherwise I will assume DST is in effect all the time')
|
||||
class LOCAL_ZONE:
|
||||
@staticmethod
|
||||
def dst(*args):
|
||||
return 1
|
||||
|
||||
from js2py.base import MakeError
|
||||
CUM = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
|
||||
msPerDay = 86400000
|
||||
msPerYear = int(86400000*365.242)
|
||||
msPerSecond = 1000
|
||||
msPerMinute = 60000
|
||||
msPerHour = 3600000
|
||||
HoursPerDay = 24
|
||||
MinutesPerHour = 60
|
||||
SecondsPerMinute = 60
|
||||
NaN = float('nan')
|
||||
LocalTZA = - time.timezone * msPerSecond
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def DaylightSavingTA(t):
|
||||
if t is NaN:
|
||||
return t
|
||||
try:
|
||||
return int(LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp(t//1000)).seconds)*1000
|
||||
except:
|
||||
warnings.warn('Invalid datetime date, assumed DST time, may be inaccurate...', Warning)
|
||||
return 1
|
||||
#raise MakeError('TypeError', 'date not supported by python.datetime. I will solve it in future versions')
|
||||
|
||||
def GetTimeZoneName(t):
|
||||
return time.tzname[DaylightSavingTA(t)>0]
|
||||
|
||||
def LocalToUTC(t):
|
||||
return t - LocalTZA - DaylightSavingTA(t - LocalTZA)
|
||||
|
||||
def UTCToLocal(t):
|
||||
return t + LocalTZA + DaylightSavingTA(t)
|
||||
|
||||
|
||||
def Day(t):
|
||||
return t//86400000
|
||||
|
||||
|
||||
def TimeWithinDay(t):
|
||||
return t%86400000
|
||||
|
||||
def DaysInYear(y):
|
||||
if y%4:
|
||||
return 365
|
||||
elif y%100:
|
||||
return 366
|
||||
elif y%400:
|
||||
return 365
|
||||
else:
|
||||
return 366
|
||||
|
||||
|
||||
def DayFromYear(y):
|
||||
return 365 * (y-1970) + (y-1969)//4 -(y-1901)//100 + (y-1601)//400
|
||||
|
||||
def TimeFromYear(y):
|
||||
return 86400000 * DayFromYear(y)
|
||||
|
||||
def YearFromTime(t):
|
||||
guess = 1970 - t//31556908800 # msPerYear
|
||||
gt = TimeFromYear(guess)
|
||||
if gt<=t:
|
||||
while gt<=t:
|
||||
guess += 1
|
||||
gt = TimeFromYear(guess)
|
||||
return guess-1
|
||||
else:
|
||||
while gt>t:
|
||||
guess -= 1
|
||||
gt = TimeFromYear(guess)
|
||||
return guess
|
||||
|
||||
|
||||
def DayWithinYear(t):
|
||||
return Day(t) - DayFromYear(YearFromTime(t))
|
||||
|
||||
def InLeapYear(t):
|
||||
y = YearFromTime(t)
|
||||
if y%4:
|
||||
return 0
|
||||
elif y%100:
|
||||
return 1
|
||||
elif y%400:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def MonthFromTime(t):
|
||||
day = DayWithinYear(t)
|
||||
leap = InLeapYear(t)
|
||||
if day<31:
|
||||
return 0
|
||||
day -= leap
|
||||
if day<59:
|
||||
return 1
|
||||
elif day<90:
|
||||
return 2
|
||||
elif day<120:
|
||||
return 3
|
||||
elif day<151:
|
||||
return 4
|
||||
elif day<181:
|
||||
return 5
|
||||
elif day<212:
|
||||
return 6
|
||||
elif day<243:
|
||||
return 7
|
||||
elif day<273:
|
||||
return 8
|
||||
elif day<304:
|
||||
return 9
|
||||
elif day<334:
|
||||
return 10
|
||||
else:
|
||||
return 11
|
||||
|
||||
def DateFromTime(t):
|
||||
mon = MonthFromTime(t)
|
||||
day = DayWithinYear(t)
|
||||
return day-CUM[mon] - (1 if InLeapYear(t) and mon>=2 else 0) + 1
|
||||
|
||||
def WeekDay(t):
|
||||
# 0 == sunday
|
||||
return (Day(t) + 4) % 7
|
||||
|
||||
def msFromTime(t):
|
||||
return t % 1000
|
||||
|
||||
def SecFromTime(t):
|
||||
return (t//1000) % 60
|
||||
|
||||
def MinFromTime(t):
|
||||
return (t//60000) % 60
|
||||
|
||||
def HourFromTime(t):
|
||||
return (t//3600000) % 24
|
||||
|
||||
def MakeTime (hour, Min, sec, ms):
|
||||
# takes PyJs objects and returns t
|
||||
if not (hour.is_finite() and Min.is_finite() and sec.is_finite() and ms.is_finite()):
|
||||
return NaN
|
||||
h, m, s, milli = hour.to_int(), Min.to_int(), sec.to_int(), ms.to_int()
|
||||
return h*3600000 + m*60000 + s*1000 + milli
|
||||
|
||||
|
||||
def MakeDay(year, month, date):
|
||||
# takes PyJs objects and returns t
|
||||
if not (year.is_finite() and month.is_finite() and date.is_finite()):
|
||||
return NaN
|
||||
y, m, dt = year.to_int(), month.to_int(), date.to_int()
|
||||
y += m//12
|
||||
mn = m % 12
|
||||
d = DayFromYear(y) + CUM[mn] + dt - 1 + (1 if DaysInYear(y)==366 and mn>=2 else 0)
|
||||
return d # ms per day
|
||||
|
||||
def MakeDate(day, time):
|
||||
return 86400000*day + time
|
||||
|
||||
|
||||
def TimeClip(t):
|
||||
if t!=t or abs(t)==float('inf'):
|
||||
return NaN
|
||||
if abs(t) > 8.64 *10**15:
|
||||
return NaN
|
||||
return int(t)
|
||||
|
32
lib/js2py/es6/__init__.py
Normal file
32
lib/js2py/es6/__init__.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
INITIALISED = False
|
||||
babel = None
|
||||
babelPresetEs2015 = None
|
||||
|
||||
def js6_to_js5(code):
|
||||
global INITIALISED, babel, babelPresetEs2015
|
||||
if not INITIALISED:
|
||||
import signal, warnings, time
|
||||
warnings.warn('\nImporting babel.py for the first time - this can take some time. \nPlease note that currently Javascript 6 in Js2Py is unstable and slow. Use only for tiny scripts!')
|
||||
|
||||
from .babel import babel as _babel
|
||||
babel = _babel.Object.babel
|
||||
babelPresetEs2015 = _babel.Object.babelPresetEs2015
|
||||
|
||||
# very weird hack. Somehow this helps babel to initialise properly!
|
||||
try:
|
||||
babel.transform('warmup', {'presets': {}})
|
||||
signal.alarm(2)
|
||||
def kill_it(a,b): raise KeyboardInterrupt('Better work next time!')
|
||||
signal.signal(signal.SIGALRM, kill_it)
|
||||
babel.transform('stuckInALoop', {'presets': babelPresetEs2015}).code
|
||||
for n in range(3):
|
||||
time.sleep(1)
|
||||
except:
|
||||
print("Initialised babel!")
|
||||
INITIALISED = True
|
||||
return babel.transform(code, {'presets': babelPresetEs2015}).code
|
||||
|
||||
if __name__=='__main__':
|
||||
print(js6_to_js5('obj={}; obj.x = function() {return () => this}'))
|
||||
print()
|
||||
print(js6_to_js5('const a = 1;'))
|
6
lib/js2py/es6/babel.js
Normal file
6
lib/js2py/es6/babel.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
// run buildBabel in this folder to convert this code to python!
|
||||
var babel = require("babel-core");
|
||||
var babelPresetEs2015 = require("babel-preset-es2015");
|
||||
|
||||
Object.babelPresetEs2015 = babelPresetEs2015;
|
||||
Object.babel = babel;
|
52077
lib/js2py/es6/babel.py
Normal file
52077
lib/js2py/es6/babel.py
Normal file
File diff suppressed because one or more lines are too long
12
lib/js2py/es6/buildBabel
Normal file
12
lib/js2py/es6/buildBabel
Normal file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
# install all the dependencies and pack everything into one file babel_bundle.js (converted to es2015).
|
||||
npm install babel-core babel-cli babel-preset-es2015 browserify
|
||||
browserify babel.js -o babel_bundle.js -t [ babelify --presets [ es2015 ] ]
|
||||
|
||||
# translate babel_bundle.js using js2py -> generates babel.py
|
||||
echo "Generating babel.py..."
|
||||
python -c "import js2py;js2py.translate_file('babel_bundle.js', 'babel.py');"
|
||||
rm babel_bundle.js
|
||||
|
258
lib/js2py/evaljs.py
Normal file
258
lib/js2py/evaljs.py
Normal file
|
@ -0,0 +1,258 @@
|
|||
# coding=utf-8
|
||||
from .translators import translate_js, DEFAULT_HEADER
|
||||
from .es6 import js6_to_js5
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import six
|
||||
import os
|
||||
import hashlib
|
||||
import codecs
|
||||
|
||||
__all__ = ['EvalJs', 'translate_js', 'import_js', 'eval_js', 'translate_file', 'eval_js6', 'translate_js6', 'run_file', 'disable_pyimport', 'get_file_contents', 'write_file_contents']
|
||||
DEBUG = False
|
||||
|
||||
def disable_pyimport():
|
||||
import pyjsparser.parser
|
||||
pyjsparser.parser.ENABLE_PYIMPORT = False
|
||||
|
||||
def path_as_local(path):
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
# relative to cwd
|
||||
return os.path.join(os.getcwd(), path)
|
||||
|
||||
def import_js(path, lib_name, globals):
|
||||
"""Imports from javascript source file.
|
||||
globals is your globals()"""
|
||||
with codecs.open(path_as_local(path), "r", "utf-8") as f:
|
||||
js = f.read()
|
||||
e = EvalJs()
|
||||
e.execute(js)
|
||||
var = e.context['var']
|
||||
globals[lib_name] = var.to_python()
|
||||
|
||||
|
||||
def get_file_contents(path_or_file):
|
||||
if hasattr(path_or_file, 'read'):
|
||||
js = path_or_file.read()
|
||||
else:
|
||||
with codecs.open(path_as_local(path_or_file), "r", "utf-8") as f:
|
||||
js = f.read()
|
||||
return js
|
||||
|
||||
|
||||
def write_file_contents(path_or_file, contents):
|
||||
if hasattr(path_or_file, 'write'):
|
||||
path_or_file.write(contents)
|
||||
else:
|
||||
with open(path_as_local(path_or_file), 'w') as f:
|
||||
f.write(contents)
|
||||
|
||||
def translate_file(input_path, output_path):
|
||||
'''
|
||||
Translates input JS file to python and saves the it to the output path.
|
||||
It appends some convenience code at the end so that it is easy to import JS objects.
|
||||
|
||||
For example we have a file 'example.js' with: var a = function(x) {return x}
|
||||
translate_file('example.js', 'example.py')
|
||||
|
||||
Now example.py can be easily importend and used:
|
||||
>>> from example import example
|
||||
>>> example.a(30)
|
||||
30
|
||||
'''
|
||||
js = get_file_contents(input_path)
|
||||
|
||||
py_code = translate_js(js)
|
||||
lib_name = os.path.basename(output_path).split('.')[0]
|
||||
head = '__all__ = [%s]\n\n# Don\'t look below, you will not understand this Python code :) I don\'t.\n\n' % repr(lib_name)
|
||||
tail = '\n\n# Add lib to the module scope\n%s = var.to_python()' % lib_name
|
||||
out = head + py_code + tail
|
||||
write_file_contents(output_path, out)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def run_file(path_or_file, context=None):
|
||||
''' Context must be EvalJS object. Runs given path as a JS program. Returns (eval_value, context).
|
||||
'''
|
||||
if context is None:
|
||||
context = EvalJs()
|
||||
if not isinstance(context, EvalJs):
|
||||
raise TypeError('context must be the instance of EvalJs')
|
||||
eval_value = context.eval(get_file_contents(path_or_file))
|
||||
return eval_value, context
|
||||
|
||||
|
||||
|
||||
def eval_js(js):
|
||||
"""Just like javascript eval. Translates javascript to python,
|
||||
executes and returns python object.
|
||||
js is javascript source code
|
||||
|
||||
EXAMPLE:
|
||||
>>> import js2py
|
||||
>>> add = js2py.eval_js('function add(a, b) {return a + b}')
|
||||
>>> add(1, 2) + 3
|
||||
6
|
||||
>>> add('1', 2, 3)
|
||||
u'12'
|
||||
>>> add.constructor
|
||||
function Function() { [python code] }
|
||||
|
||||
NOTE: For Js Number, String, Boolean and other base types returns appropriate python BUILTIN type.
|
||||
For Js functions and objects, returns Python wrapper - basically behaves like normal python object.
|
||||
If you really want to convert object to python dict you can use to_dict method.
|
||||
"""
|
||||
e = EvalJs()
|
||||
return e.eval(js)
|
||||
|
||||
def eval_js6(js):
|
||||
return eval_js(js6_to_js5(js))
|
||||
|
||||
|
||||
def translate_js6(js):
|
||||
return translate_js(js6_to_js5(js))
|
||||
|
||||
|
||||
|
||||
class EvalJs(object):
|
||||
"""This class supports continuous execution of javascript under same context.
|
||||
|
||||
>>> js = EvalJs()
|
||||
>>> js.execute('var a = 10;function f(x) {return x*x};')
|
||||
>>> js.f(9)
|
||||
81
|
||||
>>> js.a
|
||||
10
|
||||
|
||||
context is a python dict or object that contains python variables that should be available to JavaScript
|
||||
For example:
|
||||
>>> js = EvalJs({'a': 30})
|
||||
>>> js.execute('var x = a')
|
||||
>>> js.x
|
||||
30
|
||||
|
||||
You can run interactive javascript console with console method!"""
|
||||
def __init__(self, context={}):
|
||||
self.__dict__['_context'] = {}
|
||||
exec(DEFAULT_HEADER, self._context)
|
||||
self.__dict__['_var'] = self._context['var'].to_python()
|
||||
if not isinstance(context, dict):
|
||||
try:
|
||||
context = context.__dict__
|
||||
except:
|
||||
raise TypeError('context has to be either a dict or have __dict__ attr')
|
||||
for k, v in six.iteritems(context):
|
||||
setattr(self._var, k, v)
|
||||
|
||||
def execute(self, js=None, use_compilation_plan=False):
|
||||
"""executes javascript js in current context
|
||||
|
||||
During initial execute() the converted js is cached for re-use. That means next time you
|
||||
run the same javascript snippet you save many instructions needed to parse and convert the
|
||||
js code to python code.
|
||||
|
||||
This cache causes minor overhead (a cache dicts is updated) but the Js=>Py conversion process
|
||||
is typically expensive compared to actually running the generated python code.
|
||||
|
||||
Note that the cache is just a dict, it has no expiration or cleanup so when running this
|
||||
in automated situations with vast amounts of snippets it might increase memory usage.
|
||||
"""
|
||||
try:
|
||||
cache = self.__dict__['cache']
|
||||
except KeyError:
|
||||
cache = self.__dict__['cache'] = {}
|
||||
hashkey = hashlib.md5(js.encode('utf-8')).digest()
|
||||
try:
|
||||
compiled = cache[hashkey]
|
||||
except KeyError:
|
||||
code = translate_js(js, '', use_compilation_plan=use_compilation_plan)
|
||||
compiled = cache[hashkey] = compile(code, '<EvalJS snippet>', 'exec')
|
||||
exec(compiled, self._context)
|
||||
|
||||
def eval(self, expression, use_compilation_plan=False):
|
||||
"""evaluates expression in current context and returns its value"""
|
||||
code = 'PyJsEvalResult = eval(%s)'%json.dumps(expression)
|
||||
self.execute(code, use_compilation_plan=use_compilation_plan)
|
||||
return self['PyJsEvalResult']
|
||||
|
||||
def execute_debug(self, js):
|
||||
"""executes javascript js in current context
|
||||
as opposed to the (faster) self.execute method, you can use your regular debugger
|
||||
to set breakpoints and inspect the generated python code
|
||||
"""
|
||||
code = translate_js(js, '')
|
||||
# make sure you have a temp folder:
|
||||
filename = 'temp' + os.sep + '_' + hashlib.md5(code).hexdigest() + '.py'
|
||||
try:
|
||||
with open(filename, mode='w') as f:
|
||||
f.write(code)
|
||||
execfile(filename, self._context)
|
||||
except Exception as err:
|
||||
raise err
|
||||
finally:
|
||||
os.remove(filename)
|
||||
try:
|
||||
os.remove(filename + 'c')
|
||||
except:
|
||||
pass
|
||||
|
||||
def eval_debug(self, expression):
|
||||
"""evaluates expression in current context and returns its value
|
||||
as opposed to the (faster) self.execute method, you can use your regular debugger
|
||||
to set breakpoints and inspect the generated python code
|
||||
"""
|
||||
code = 'PyJsEvalResult = eval(%s)'%json.dumps(expression)
|
||||
self.execute_debug(code)
|
||||
return self['PyJsEvalResult']
|
||||
|
||||
def __getattr__(self, var):
|
||||
return getattr(self._var, var)
|
||||
|
||||
def __getitem__(self, var):
|
||||
return getattr(self._var, var)
|
||||
|
||||
def __setattr__(self, var, val):
|
||||
return setattr(self._var, var, val)
|
||||
|
||||
def __setitem__(self, var, val):
|
||||
return setattr(self._var, var, val)
|
||||
|
||||
def console(self):
|
||||
"""starts to interact (starts interactive console) Something like code.InteractiveConsole"""
|
||||
while True:
|
||||
if six.PY2:
|
||||
code = raw_input('>>> ')
|
||||
else:
|
||||
code = input('>>>')
|
||||
try:
|
||||
print(self.eval(code))
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
except Exception as e:
|
||||
import traceback
|
||||
if DEBUG:
|
||||
sys.stderr.write(traceback.format_exc())
|
||||
else:
|
||||
sys.stderr.write('EXCEPTION: '+str(e)+'\n')
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
|
||||
|
||||
#print x
|
||||
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
#with open('C:\Users\Piotrek\Desktop\esprima.js', 'rb') as f:
|
||||
# x = f.read()
|
||||
e = EvalJs()
|
||||
e.execute('square(x)')
|
||||
#e.execute(x)
|
||||
e.console()
|
||||
|
0
lib/js2py/host/__init__.py
Normal file
0
lib/js2py/host/__init__.py
Normal file
11
lib/js2py/host/console.py
Normal file
11
lib/js2py/host/console.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from ..base import *
|
||||
|
||||
@Js
|
||||
def console():
|
||||
pass
|
||||
|
||||
@Js
|
||||
def log():
|
||||
print(arguments[0])
|
||||
|
||||
console.put('log', log)
|
0
lib/js2py/host/dom/__init__.py
Normal file
0
lib/js2py/host/dom/__init__.py
Normal file
47
lib/js2py/host/dom/constants.py
Normal file
47
lib/js2py/host/dom/constants.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from js2py.base import *
|
||||
|
||||
def _get_conts(idl):
|
||||
def is_valid(c):
|
||||
try:
|
||||
exec(c)
|
||||
return 1
|
||||
except:
|
||||
pass
|
||||
return '\n'.join(filter(is_valid, (' '.join(e.strip(' ;').split()[-3:]) for e in idl.splitlines())))
|
||||
|
||||
|
||||
default_attrs = {'writable':True, 'enumerable':True, 'configurable':True}
|
||||
|
||||
|
||||
def compose_prototype(Class, attrs=default_attrs):
|
||||
prototype = Class()
|
||||
for i in dir(Class):
|
||||
e = getattr(Class, i)
|
||||
if hasattr(e, '__func__'):
|
||||
temp = PyJsFunction(e.__func__, FunctionPrototype)
|
||||
attrs = {k:v for k,v in attrs.iteritems()}
|
||||
attrs['value'] = temp
|
||||
prototype.define_own_property(i, attrs)
|
||||
return prototype
|
||||
|
||||
|
||||
# Error codes
|
||||
|
||||
INDEX_SIZE_ERR = 1
|
||||
DOMSTRING_SIZE_ERR = 2
|
||||
HIERARCHY_REQUEST_ERR = 3
|
||||
WRONG_DOCUMENT_ERR = 4
|
||||
INVALID_CHARACTER_ERR = 5
|
||||
NO_DATA_ALLOWED_ERR = 6
|
||||
NO_MODIFICATION_ALLOWED_ERR = 7
|
||||
NOT_FOUND_ERR = 8
|
||||
NOT_SUPPORTED_ERR = 9
|
||||
INUSE_ATTRIBUTE_ERR = 10
|
||||
INVALID_STATE_ERR = 11
|
||||
SYNTAX_ERR = 12
|
||||
INVALID_MODIFICATION_ERR = 13
|
||||
NAMESPACE_ERR = 14
|
||||
INVALID_ACCESS_ERR = 15
|
||||
VALIDATION_ERR = 16
|
||||
TYPE_MISMATCH_ERR = 17
|
||||
|
73
lib/js2py/host/dom/interface.py
Normal file
73
lib/js2py/host/dom/interface.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
from StringIO import StringIO
|
||||
from constants import *
|
||||
from bs4 import BeautifulSoup
|
||||
from js2py.base import *
|
||||
try:
|
||||
import lxml
|
||||
def parse(source):
|
||||
return BeautifulSoup(source, 'lxml')
|
||||
except:
|
||||
def parse(source):
|
||||
return BeautifulSoup(source)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
x = '''<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Shady Grove</td>
|
||||
<td>Aeolian</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Over the River, Charlie</td>
|
||||
<td>Dorian</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>'''
|
||||
|
||||
|
||||
|
||||
class DOM(PyJs):
|
||||
prototype = ObjectPrototype
|
||||
def __init__(self):
|
||||
self.own = {}
|
||||
|
||||
def readonly(self, name, val):
|
||||
self.define_own_property(name, {'writable':False, 'enumerable':False, 'configurable':False, 'value': Js(val)})
|
||||
|
||||
|
||||
|
||||
# DOMStringList
|
||||
|
||||
class DOMStringListPrototype(DOM):
|
||||
Class = 'DOMStringListPrototype'
|
||||
|
||||
def contains(element):
|
||||
return element.to_string().value in this._string_list
|
||||
|
||||
def item(index):
|
||||
return this._string_list[index.to_int()] if 0<=index.to_int()<len(this._string_list) else index.null
|
||||
|
||||
|
||||
class DOMStringList(DOM):
|
||||
Class = 'DOMStringList'
|
||||
prototype = compose_prototype(DOMStringListPrototype)
|
||||
def __init__(self, _string_list):
|
||||
self.own = {}
|
||||
|
||||
self._string_list = _string_list
|
||||
|
||||
|
||||
# NameList
|
||||
|
||||
class NameListPrototype(DOM):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
50
lib/js2py/host/jseval.py
Normal file
50
lib/js2py/host/jseval.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from ..base import *
|
||||
import inspect
|
||||
try:
|
||||
from js2py.translators.translator import translate_js
|
||||
except:
|
||||
pass
|
||||
|
||||
@Js
|
||||
def Eval(code):
|
||||
local_scope = inspect.stack()[3][0].f_locals['var']
|
||||
global_scope = this.GlobalObject
|
||||
# todo fix scope - we have to behave differently if called through variable other than eval
|
||||
# we will use local scope (default)
|
||||
globals()['var'] = local_scope
|
||||
try:
|
||||
py_code = translate_js(code.to_string().value, '')
|
||||
except SyntaxError as syn_err:
|
||||
raise MakeError('SyntaxError', str(syn_err))
|
||||
lines = py_code.split('\n')
|
||||
# a simple way to return value from eval. Will not work in complex cases.
|
||||
has_return = False
|
||||
for n in xrange(len(lines)):
|
||||
line = lines[len(lines)-n-1]
|
||||
if line.strip():
|
||||
if line.startswith(' '):
|
||||
break
|
||||
elif line.strip()=='pass':
|
||||
continue
|
||||
elif any(line.startswith(e) for e in ['return ', 'continue ', 'break', 'raise ']):
|
||||
break
|
||||
else:
|
||||
has_return = True
|
||||
cand = 'EVAL_RESULT = (%s)\n'%line
|
||||
try:
|
||||
compile(cand, '', 'exec')
|
||||
except SyntaxError:
|
||||
break
|
||||
lines[len(lines)-n-1] = cand
|
||||
py_code = '\n'.join(lines)
|
||||
break
|
||||
#print py_code
|
||||
executor(py_code)
|
||||
if has_return:
|
||||
return globals()['EVAL_RESULT']
|
||||
|
||||
|
||||
|
||||
def executor(code):
|
||||
exec(code, globals())
|
||||
|
85
lib/js2py/host/jsfunctions.py
Normal file
85
lib/js2py/host/jsfunctions.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
from ..base import *
|
||||
|
||||
RADIX_CHARS = {'1': 1, '0': 0, '3': 3, '2': 2, '5': 5, '4': 4, '7': 7, '6': 6, '9': 9, '8': 8, 'a': 10, 'c': 12,
|
||||
'b': 11, 'e': 14, 'd': 13, 'g': 16, 'f': 15, 'i': 18, 'h': 17, 'k': 20, 'j': 19, 'm': 22, 'l': 21,
|
||||
'o': 24, 'n': 23, 'q': 26, 'p': 25, 's': 28, 'r': 27, 'u': 30, 't': 29, 'w': 32, 'v': 31, 'y': 34,
|
||||
'x': 33, 'z': 35, 'A': 10, 'C': 12, 'B': 11, 'E': 14, 'D': 13, 'G': 16, 'F': 15, 'I': 18, 'H': 17,
|
||||
'K': 20, 'J': 19, 'M': 22, 'L': 21, 'O': 24, 'N': 23, 'Q': 26, 'P': 25, 'S': 28, 'R': 27, 'U': 30,
|
||||
'T': 29, 'W': 32, 'V': 31, 'Y': 34, 'X': 33, 'Z': 35}
|
||||
@Js
|
||||
def parseInt (string , radix):
|
||||
string = string.to_string().value.lstrip()
|
||||
sign = 1
|
||||
if string and string[0] in ('+', '-'):
|
||||
if string[0]=='-':
|
||||
sign = -1
|
||||
string = string[1:]
|
||||
r = radix.to_int32()
|
||||
strip_prefix = True
|
||||
if r:
|
||||
if r<2 or r>36:
|
||||
return NaN
|
||||
if r!=16:
|
||||
strip_prefix = False
|
||||
else:
|
||||
r = 10
|
||||
if strip_prefix:
|
||||
if len(string)>=2 and string[:2] in ('0x', '0X'):
|
||||
string = string[2:]
|
||||
r = 16
|
||||
n = 0
|
||||
num = 0
|
||||
while n<len(string):
|
||||
cand = RADIX_CHARS.get(string[n])
|
||||
if cand is None or not cand < r:
|
||||
break
|
||||
num = cand + num*r
|
||||
n += 1
|
||||
if not n:
|
||||
return NaN
|
||||
return sign*num
|
||||
|
||||
@Js
|
||||
def parseFloat(string):
|
||||
string = string.to_string().value.strip()
|
||||
sign = 1
|
||||
if string and string[0] in ('+', '-'):
|
||||
if string[0]=='-':
|
||||
sign = -1
|
||||
string = string[1:]
|
||||
num = None
|
||||
length = 1
|
||||
max_len = None
|
||||
failed = 0
|
||||
while length<=len(string):
|
||||
try:
|
||||
num = float(string[:length])
|
||||
max_len = length
|
||||
failed = 0
|
||||
except:
|
||||
failed += 1
|
||||
if failed>4: # cant be a number anymore
|
||||
break
|
||||
length += 1
|
||||
if num is None:
|
||||
return NaN
|
||||
return sign*float(string[:max_len])
|
||||
|
||||
@Js
|
||||
def isNaN(number):
|
||||
if number.to_number().is_nan():
|
||||
return true
|
||||
return false
|
||||
|
||||
@Js
|
||||
def isFinite(number):
|
||||
num = number.to_number()
|
||||
if num.is_nan() or num.is_infinity():
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
#todo URI handling!
|
||||
|
||||
|
||||
|
1
lib/js2py/prototypes/__init__.py
Normal file
1
lib/js2py/prototypes/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
__author__ = 'Piotr Dabkowski'
|
458
lib/js2py/prototypes/jsarray.py
Normal file
458
lib/js2py/prototypes/jsarray.py
Normal file
|
@ -0,0 +1,458 @@
|
|||
import six
|
||||
|
||||
if six.PY3:
|
||||
xrange = range
|
||||
import functools
|
||||
|
||||
def to_arr(this):
|
||||
"""Returns Python array from Js array"""
|
||||
return [this.get(str(e)) for e in xrange(len(this))]
|
||||
|
||||
|
||||
ARR_STACK = set({})
|
||||
|
||||
class ArrayPrototype:
|
||||
def toString():
|
||||
# this function is wrong but I will leave it here fore debugging purposes.
|
||||
func = this.get('join')
|
||||
if not func.is_callable():
|
||||
@this.Js
|
||||
def func():
|
||||
return '[object %s]'%this.Class
|
||||
return func.call(this, ())
|
||||
|
||||
def toLocaleString():
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
# separator is simply a comma ','
|
||||
if not arr_len:
|
||||
return ''
|
||||
res = []
|
||||
for i in xrange(arr_len):
|
||||
element = array[str(i)]
|
||||
if element.is_undefined() or element.is_null():
|
||||
res.append('')
|
||||
else:
|
||||
cand = element.to_object()
|
||||
str_func = element.get('toLocaleString')
|
||||
if not str_func.is_callable():
|
||||
raise this.MakeError('TypeError', 'toLocaleString method of item at index %d is not callable'%i)
|
||||
res.append(element.callprop('toLocaleString').value)
|
||||
return ','.join(res)
|
||||
|
||||
def concat():
|
||||
array = this.to_object()
|
||||
A = this.Js([])
|
||||
items = [array]
|
||||
items.extend(to_arr(arguments))
|
||||
n = 0
|
||||
for E in items:
|
||||
if E.Class=='Array':
|
||||
k = 0
|
||||
e_len = len(E)
|
||||
while k<e_len:
|
||||
if E.has_property(str(k)):
|
||||
A.put(str(n), E.get(str(k)))
|
||||
n+=1
|
||||
k+=1
|
||||
else:
|
||||
A.put(str(n), E)
|
||||
n+=1
|
||||
return A
|
||||
|
||||
def join(separator):
|
||||
ARR_STACK.add(this)
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
separator = ',' if separator.is_undefined() else separator.to_string().value
|
||||
elems = []
|
||||
for e in xrange(arr_len):
|
||||
elem = array.get(str(e))
|
||||
if elem in ARR_STACK:
|
||||
s = ''
|
||||
else:
|
||||
s = elem.to_string().value
|
||||
elems.append(s if not (elem.is_undefined() or elem.is_null()) else '')
|
||||
res = separator.join(elems)
|
||||
ARR_STACK.remove(this)
|
||||
return res
|
||||
|
||||
def pop(): #todo check
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not arr_len:
|
||||
array.put('length', this.Js(arr_len))
|
||||
return None
|
||||
ind = str(arr_len-1)
|
||||
element = array.get(ind)
|
||||
array.delete(ind)
|
||||
array.put('length', this.Js(arr_len-1))
|
||||
return element
|
||||
|
||||
|
||||
def push(item): # todo check
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
to_put = arguments.to_list()
|
||||
i = arr_len
|
||||
for i, e in enumerate(to_put, arr_len):
|
||||
array.put(str(i), e)
|
||||
if to_put:
|
||||
i+=1
|
||||
array.put('length', this.Js(i))
|
||||
return i
|
||||
|
||||
|
||||
def reverse():
|
||||
array = this.to_object() # my own algorithm
|
||||
vals = to_arr(array)
|
||||
has_props = [array.has_property(str(e)) for e in xrange(len(array))]
|
||||
vals.reverse()
|
||||
has_props.reverse()
|
||||
for i, val in enumerate(vals):
|
||||
if has_props[i]:
|
||||
array.put(str(i), val)
|
||||
else:
|
||||
array.delete(str(i))
|
||||
return array
|
||||
|
||||
|
||||
def shift(): #todo check
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not arr_len:
|
||||
array.put('length', this.Js(0))
|
||||
return None
|
||||
first = array.get('0')
|
||||
for k in xrange(1, arr_len):
|
||||
from_s, to_s = str(k), str(k-1)
|
||||
if array.has_property(from_s):
|
||||
array.put(to_s, array.get(from_s))
|
||||
else:
|
||||
array.delete(to)
|
||||
array.delete(str(arr_len-1))
|
||||
array.put('length', this.Js(str(arr_len-1)))
|
||||
return first
|
||||
|
||||
def slice(start, end): # todo check
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
relative_start = start.to_int()
|
||||
k = max((arr_len + relative_start), 0) if relative_start<0 else min(relative_start, arr_len)
|
||||
relative_end = arr_len if end.is_undefined() else end.to_int()
|
||||
final = max((arr_len + relative_end), 0) if relative_end<0 else min(relative_end, arr_len)
|
||||
res = []
|
||||
n = 0
|
||||
while k<final:
|
||||
pk = str(k)
|
||||
if array.has_property(pk):
|
||||
res.append(array.get(pk))
|
||||
k += 1
|
||||
n += 1
|
||||
return res
|
||||
|
||||
def sort(cmpfn):
|
||||
if not this.Class in ('Array', 'Arguments'):
|
||||
return this.to_object() # do nothing
|
||||
arr = []
|
||||
for i in xrange(len(this)):
|
||||
arr.append(this.get(six.text_type(i)))
|
||||
|
||||
if not arr:
|
||||
return this
|
||||
if not cmpfn.is_callable():
|
||||
cmpfn = None
|
||||
cmp = lambda a,b: sort_compare(a, b, cmpfn)
|
||||
if six.PY3:
|
||||
key = functools.cmp_to_key(cmp)
|
||||
arr.sort(key=key)
|
||||
else:
|
||||
arr.sort(cmp=cmp)
|
||||
for i in xrange(len(arr)):
|
||||
this.put(six.text_type(i), arr[i])
|
||||
|
||||
return this
|
||||
|
||||
def splice(start, deleteCount):
|
||||
# 1-8
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
relative_start = start.to_int()
|
||||
actual_start = max((arr_len + relative_start),0) if relative_start<0 else min(relative_start, arr_len)
|
||||
actual_delete_count = min(max(deleteCount.to_int(),0 ), arr_len - actual_start)
|
||||
k = 0
|
||||
A = this.Js([])
|
||||
# 9
|
||||
while k<actual_delete_count:
|
||||
if array.has_property(str(actual_start+k)):
|
||||
A.put(str(k), array.get(str(actual_start+k)))
|
||||
k += 1
|
||||
# 10-11
|
||||
items = to_arr(arguments)[2:]
|
||||
items_len = len(items)
|
||||
# 12
|
||||
if items_len<actual_delete_count:
|
||||
k = actual_start
|
||||
while k < (arr_len-actual_delete_count):
|
||||
fr = str(k+actual_delete_count)
|
||||
to = str(k+items_len)
|
||||
if array.has_property(fr):
|
||||
array.put(to, array.get(fr))
|
||||
else:
|
||||
array.delete(to)
|
||||
k += 1
|
||||
k = arr_len
|
||||
while k > (arr_len - actual_delete_count + items_len):
|
||||
array.delete(str(k-1))
|
||||
k -= 1
|
||||
# 13
|
||||
elif items_len>actual_delete_count:
|
||||
k = arr_len - actual_delete_count
|
||||
while k>actual_start:
|
||||
fr = str(k + actual_delete_count - 1)
|
||||
to = str(k + items_len - 1)
|
||||
if array.has_property(fr):
|
||||
array.put(to, array.get(fr))
|
||||
else:
|
||||
array.delete(to)
|
||||
k -= 1
|
||||
# 14-17
|
||||
k = actual_start
|
||||
while items:
|
||||
E = items.pop(0)
|
||||
array.put(str(k), E)
|
||||
k += 1
|
||||
array.put('length', this.Js(arr_len - actual_delete_count + items_len))
|
||||
return A
|
||||
|
||||
def unshift():
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
argCount = len(arguments)
|
||||
k = arr_len
|
||||
while k > 0:
|
||||
fr = str(k - 1)
|
||||
to = str(k + argCount - 1)
|
||||
if array.has_property(fr):
|
||||
array.put(to, array.get(fr))
|
||||
else:
|
||||
array.delete(to)
|
||||
k -= 1
|
||||
j = 0
|
||||
items = to_arr(arguments)
|
||||
while items:
|
||||
E = items.pop(0)
|
||||
array.put(str(j), E)
|
||||
j += 1
|
||||
array.put('length', this.Js(arr_len + argCount))
|
||||
return arr_len + argCount
|
||||
|
||||
def indexOf(searchElement):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if arr_len == 0:
|
||||
return -1
|
||||
if len(arguments)>1:
|
||||
n = arguments[1].to_int()
|
||||
else:
|
||||
n = 0
|
||||
if n >= arr_len:
|
||||
return -1
|
||||
if n >= 0:
|
||||
k = n
|
||||
else:
|
||||
k = arr_len - abs(n)
|
||||
if k < 0:
|
||||
k = 0
|
||||
while k < arr_len:
|
||||
if array.has_property(str(k)):
|
||||
elementK = array.get(str(k))
|
||||
if searchElement.strict_equality_comparison(elementK):
|
||||
return k
|
||||
k += 1
|
||||
return -1
|
||||
|
||||
def lastIndexOf(searchElement):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if arr_len == 0:
|
||||
return -1
|
||||
if len(arguments)>1:
|
||||
n = arguments[1].to_int()
|
||||
else:
|
||||
n = arr_len - 1
|
||||
if n >= 0:
|
||||
k = min(n, arr_len-1)
|
||||
else:
|
||||
k = arr_len - abs(n)
|
||||
while k >= 0:
|
||||
if array.has_property(str(k)):
|
||||
elementK = array.get(str(k))
|
||||
if searchElement.strict_equality_comparison(elementK):
|
||||
return k
|
||||
k -= 1
|
||||
return -1
|
||||
|
||||
|
||||
def every(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
if not callbackfn.call(T, (kValue, this.Js(k), array)).to_boolean().value:
|
||||
return False
|
||||
k += 1
|
||||
return True
|
||||
|
||||
|
||||
def some(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
if callbackfn.call(T, (kValue, this.Js(k), array)).to_boolean().value:
|
||||
return True
|
||||
k += 1
|
||||
return False
|
||||
|
||||
def forEach(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
callbackfn.call(T, (kValue, this.Js(k), array))
|
||||
k+=1
|
||||
|
||||
def map(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
A = this.Js([])
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
Pk = str(k)
|
||||
if array.has_property(Pk):
|
||||
kValue = array.get(Pk)
|
||||
mappedValue = callbackfn.call(T, (kValue, this.Js(k), array))
|
||||
A.define_own_property(Pk, {'value': mappedValue, 'writable': True,
|
||||
'enumerable': True, 'configurable': True})
|
||||
k += 1
|
||||
return A
|
||||
|
||||
def filter(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
res = []
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
if callbackfn.call(T, (kValue, this.Js(k), array)).to_boolean().value:
|
||||
res.append(kValue)
|
||||
k += 1
|
||||
return res # converted to js array automatically
|
||||
|
||||
def reduce(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
if not arr_len and len(arguments)<2:
|
||||
raise this.MakeError('TypeError', 'Reduce of empty array with no initial value')
|
||||
k = 0
|
||||
if len(arguments)>1: # initial value present
|
||||
accumulator = arguments[1]
|
||||
else:
|
||||
kPresent = False
|
||||
while not kPresent and k<arr_len:
|
||||
kPresent = array.has_property(str(k))
|
||||
if kPresent:
|
||||
accumulator = array.get(str(k))
|
||||
k += 1
|
||||
if not kPresent:
|
||||
raise this.MakeError('TypeError', 'Reduce of empty array with no initial value')
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
accumulator = callbackfn.call(this.undefined, (accumulator, kValue, this.Js(k), array))
|
||||
k += 1
|
||||
return accumulator
|
||||
|
||||
|
||||
def reduceRight(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get('length').to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
if not arr_len and len(arguments)<2:
|
||||
raise this.MakeError('TypeError', 'Reduce of empty array with no initial value')
|
||||
k = arr_len - 1
|
||||
if len(arguments)>1: # initial value present
|
||||
accumulator = arguments[1]
|
||||
else:
|
||||
kPresent = False
|
||||
while not kPresent and k>=0:
|
||||
kPresent = array.has_property(str(k))
|
||||
if kPresent:
|
||||
accumulator = array.get(str(k))
|
||||
k -= 1
|
||||
if not kPresent:
|
||||
raise this.MakeError('TypeError', 'Reduce of empty array with no initial value')
|
||||
while k>=0:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
accumulator = callbackfn.call(this.undefined, (accumulator, kValue, this.Js(k), array))
|
||||
k -= 1
|
||||
return accumulator
|
||||
|
||||
|
||||
def sort_compare(a, b, comp):
|
||||
if a is None:
|
||||
if b is None:
|
||||
return 0
|
||||
return 1
|
||||
if b is None:
|
||||
if a is None:
|
||||
return 0
|
||||
return -1
|
||||
if a.is_undefined():
|
||||
if b.is_undefined():
|
||||
return 0
|
||||
return 1
|
||||
if b.is_undefined():
|
||||
if a.is_undefined():
|
||||
return 0
|
||||
return -1
|
||||
if comp is not None:
|
||||
res = comp.call(a.undefined, (a, b))
|
||||
return res.to_int()
|
||||
x, y = a.to_string(), b.to_string()
|
||||
if x<y:
|
||||
return -1
|
||||
elif x>y:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
|
17
lib/js2py/prototypes/jsarraybuffer.py
Normal file
17
lib/js2py/prototypes/jsarraybuffer.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
xrange = range
|
||||
import functools
|
||||
|
||||
def to_arr(this):
|
||||
"""Returns Python array from Js array"""
|
||||
return [this.get(str(e)) for e in xrange(len(this))]
|
||||
|
||||
|
||||
ARR_STACK = set({})
|
||||
|
||||
class ArrayBufferPrototype:
|
||||
pass
|
16
lib/js2py/prototypes/jsboolean.py
Normal file
16
lib/js2py/prototypes/jsboolean.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
|
||||
class BooleanPrototype:
|
||||
def toString():
|
||||
if this.Class!='Boolean':
|
||||
raise this.Js(TypeError)('this must be a boolean')
|
||||
return 'true' if this.value else 'false'
|
||||
|
||||
def valueOf():
|
||||
if this.Class!='Boolean':
|
||||
raise this.Js(TypeError)('this must be a boolean')
|
||||
return this.value
|
||||
|
||||
|
||||
|
||||
|
10
lib/js2py/prototypes/jserror.py
Normal file
10
lib/js2py/prototypes/jserror.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
class ErrorPrototype:
|
||||
def toString():
|
||||
if this.TYPE!='Object':
|
||||
raise this.MakeError('TypeError', 'Error.prototype.toString called on non-object')
|
||||
name = this.get('name')
|
||||
name = 'Error' if name.is_undefined() else name.to_string().value
|
||||
msg = this.get('message')
|
||||
msg = '' if msg.is_undefined() else msg.to_string().value
|
||||
return name + (name and msg and ': ') + msg
|
53
lib/js2py/prototypes/jsfunction.py
Normal file
53
lib/js2py/prototypes/jsfunction.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
# python 3 support
|
||||
import six
|
||||
if six.PY3:
|
||||
basestring = str
|
||||
long = int
|
||||
xrange = range
|
||||
unicode = str
|
||||
|
||||
|
||||
# todo fix apply and bind
|
||||
|
||||
class FunctionPrototype:
|
||||
def toString():
|
||||
if not this.is_callable():
|
||||
raise TypeError('toString is not generic!')
|
||||
args = ', '.join(this.code.__code__.co_varnames[:this.argcount])
|
||||
return 'function %s(%s) '%(this.func_name, args)+this.source
|
||||
|
||||
def call():
|
||||
arguments_ = arguments
|
||||
if not len(arguments):
|
||||
obj = this.Js(None)
|
||||
else:
|
||||
obj = arguments[0]
|
||||
if len(arguments)<=1:
|
||||
args = ()
|
||||
else:
|
||||
args = tuple([arguments_[e] for e in xrange(1, len(arguments_))])
|
||||
return this.call(obj, args)
|
||||
|
||||
def apply():
|
||||
if not len(arguments):
|
||||
obj = this.Js(None)
|
||||
else:
|
||||
obj = arguments[0]
|
||||
if len(arguments)<=1:
|
||||
args = ()
|
||||
else:
|
||||
appl = arguments[1]
|
||||
args = tuple([appl[e] for e in xrange(len(appl))])
|
||||
return this.call(obj, args)
|
||||
|
||||
def bind(thisArg):
|
||||
target = this
|
||||
if not target.is_callable():
|
||||
raise this.MakeError('Object must be callable in order to be used with bind method')
|
||||
if len(arguments) <= 1:
|
||||
args = ()
|
||||
else:
|
||||
args = tuple([arguments[e] for e in xrange(1, len(arguments))])
|
||||
return this.PyJsBoundFunction(target, thisArg, args)
|
||||
|
||||
|
210
lib/js2py/prototypes/jsjson.py
Normal file
210
lib/js2py/prototypes/jsjson.py
Normal file
|
@ -0,0 +1,210 @@
|
|||
import json
|
||||
from ..base import Js
|
||||
indent = ''
|
||||
# python 3 support
|
||||
import six
|
||||
if six.PY3:
|
||||
basestring = str
|
||||
long = int
|
||||
xrange = range
|
||||
unicode = str
|
||||
|
||||
|
||||
def parse(text):
|
||||
reviver = arguments[1]
|
||||
s = text.to_string().value
|
||||
try:
|
||||
unfiltered = json.loads(s)
|
||||
except:
|
||||
raise this.MakeError('SyntaxError', 'Could not parse JSON string - Invalid syntax')
|
||||
unfiltered = to_js(this, unfiltered)
|
||||
if reviver.is_callable():
|
||||
root = this.Js({'': unfiltered})
|
||||
walk(root, '', reviver)
|
||||
else:
|
||||
return unfiltered
|
||||
|
||||
|
||||
def stringify(value, replacer, space):
|
||||
global indent
|
||||
stack = set([])
|
||||
indent = ''
|
||||
property_list = replacer_function = this.undefined
|
||||
if replacer.is_object():
|
||||
if replacer.is_callable():
|
||||
replacer_function = replacer
|
||||
elif replacer.Class=='Array':
|
||||
property_list = []
|
||||
for e in replacer:
|
||||
v = replacer[e]
|
||||
item = this.undefined
|
||||
if v._type()=='Number':
|
||||
item = v.to_string()
|
||||
elif v._type()=='String':
|
||||
item = v
|
||||
elif v.is_object():
|
||||
if v.Class in ('String', 'Number'):
|
||||
item = v.to_string()
|
||||
if not item.is_undefined() and item.value not in property_list:
|
||||
property_list.append(item.value)
|
||||
if space.is_object():
|
||||
if space.Class=='Number':
|
||||
space = space.to_number()
|
||||
elif space.Class=='String':
|
||||
space = space.to_string()
|
||||
if space._type()=='Number':
|
||||
space = this.Js(min(10, space.to_int()))
|
||||
gap = max(int(space.value), 0)* ' '
|
||||
elif space._type()=='String':
|
||||
gap = space.value[:10]
|
||||
else:
|
||||
gap = ''
|
||||
return this.Js(Str('', this.Js({'':value}), replacer_function, property_list, gap, stack, space))
|
||||
|
||||
|
||||
|
||||
def Str(key, holder, replacer_function, property_list, gap, stack, space):
|
||||
value = holder[key]
|
||||
if value.is_object():
|
||||
to_json = value.get('toJSON')
|
||||
if to_json.is_callable():
|
||||
value = to_json.call(value, (key,))
|
||||
if not replacer_function.is_undefined():
|
||||
value = replacer_function.call(holder, (key, value))
|
||||
|
||||
if value.is_object():
|
||||
if value.Class=='String':
|
||||
value = value.to_string()
|
||||
elif value.Class=='Number':
|
||||
value = value.to_number()
|
||||
elif value.Class=='Boolean':
|
||||
value = value.to_boolean()
|
||||
if value.is_null():
|
||||
return 'null'
|
||||
elif value.Class=='Boolean':
|
||||
return 'true' if value.value else 'false'
|
||||
elif value._type()=='String':
|
||||
return Quote(value)
|
||||
elif value._type()=='Number':
|
||||
if not value.is_infinity():
|
||||
return value.to_string()
|
||||
return 'null'
|
||||
if value.is_object() and not value.is_callable():
|
||||
if value.Class=='Array':
|
||||
return ja(value, stack, gap, property_list, replacer_function, space)
|
||||
else:
|
||||
return jo(value, stack, gap, property_list, replacer_function, space)
|
||||
return None # undefined
|
||||
|
||||
|
||||
|
||||
def jo(value, stack, gap, property_list, replacer_function, space):
|
||||
global indent
|
||||
if value in stack:
|
||||
raise value.MakeError('TypeError', 'Converting circular structure to JSON')
|
||||
stack.add(value)
|
||||
stepback = indent
|
||||
indent += gap
|
||||
if not property_list.is_undefined():
|
||||
k = property_list
|
||||
else:
|
||||
k = [e.value for e in value]
|
||||
partial = []
|
||||
for p in k:
|
||||
str_p = value.Js(Str(p, value, replacer_function, property_list, gap, stack, space))
|
||||
if not str_p.is_undefined():
|
||||
member = json.dumps(p) + ':' + (' ' if gap else '') + str_p.value # todo not sure here - what space character?
|
||||
partial.append(member)
|
||||
if not partial:
|
||||
final = '{}'
|
||||
else:
|
||||
if not gap:
|
||||
final = '{%s}' % ','.join(partial)
|
||||
else:
|
||||
sep = ',\n'+indent
|
||||
properties = sep.join(partial)
|
||||
final = '{\n'+indent+properties+'\n'+stepback+'}'
|
||||
stack.remove(value)
|
||||
indent = stepback
|
||||
return final
|
||||
|
||||
|
||||
def ja(value, stack, gap, property_list, replacer_function, space):
|
||||
global indent
|
||||
if value in stack:
|
||||
raise value.MakeError('TypeError', 'Converting circular structure to JSON')
|
||||
stack.add(value)
|
||||
stepback = indent
|
||||
indent += gap
|
||||
partial = []
|
||||
length = len(value)
|
||||
for index in xrange(length):
|
||||
index = str(index)
|
||||
str_index = value.Js(Str(index, value, replacer_function, property_list, gap, stack, space))
|
||||
if str_index.is_undefined():
|
||||
partial.append('null')
|
||||
else:
|
||||
partial.append(str_index.value)
|
||||
if not partial:
|
||||
final = '[]'
|
||||
else:
|
||||
if not gap:
|
||||
final = '[%s]' % ','.join(partial)
|
||||
else:
|
||||
sep = ',\n'+indent
|
||||
properties = sep.join(partial)
|
||||
final = '[\n'+indent +properties+'\n'+stepback+']'
|
||||
stack.remove(value)
|
||||
indent = stepback
|
||||
return final
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def Quote(string):
|
||||
return string.Js(json.dumps(string.value))
|
||||
|
||||
|
||||
def to_js(this, d):
|
||||
if isinstance(d, dict):
|
||||
return this.Js(dict((k,this.Js(v)) for k, v in six.iteritems(d)))
|
||||
return this.Js(d)
|
||||
|
||||
|
||||
|
||||
def walk(holder, name, reviver):
|
||||
val = holder.get(name)
|
||||
if val.Class=='Array':
|
||||
for i in xrange(len(val)):
|
||||
i = unicode(i)
|
||||
new_element = walk(val, i, reviver)
|
||||
if new_element.is_undefined():
|
||||
val.delete(i)
|
||||
else:
|
||||
new_element.put(i, new_element)
|
||||
elif val.is_object():
|
||||
for key in val:
|
||||
new_element = walk(val, key, reviver)
|
||||
if new_element.is_undefined():
|
||||
val.delete(key)
|
||||
else:
|
||||
val.put(key, new_element)
|
||||
return reviver.call(holder, (name, val))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
JSON = Js({})
|
||||
|
||||
JSON.define_own_property('parse', {'value': Js(parse),
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
||||
|
||||
JSON.define_own_property('stringify', {'value': Js(stringify),
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': True})
|
100
lib/js2py/prototypes/jsnumber.py
Normal file
100
lib/js2py/prototypes/jsnumber.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
import six
|
||||
if six.PY3:
|
||||
basestring = str
|
||||
long = int
|
||||
xrange = range
|
||||
unicode = str
|
||||
|
||||
|
||||
RADIX_SYMBOLS = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9',
|
||||
10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f', 16: 'g', 17: 'h', 18: 'i', 19: 'j', 20: 'k',
|
||||
21: 'l', 22: 'm', 23: 'n', 24: 'o', 25: 'p', 26: 'q', 27: 'r', 28: 's', 29: 't', 30: 'u', 31: 'v',
|
||||
32: 'w', 33: 'x', 34: 'y', 35: 'z'}
|
||||
|
||||
|
||||
def to_str_rep(num):
|
||||
if num.is_nan():
|
||||
return num.Js('NaN')
|
||||
elif num.is_infinity():
|
||||
sign = '-' if num.value<0 else ''
|
||||
return num.Js(sign+'Infinity')
|
||||
elif isinstance(num.value, (long, int)) or num.value.is_integer(): # dont print .0
|
||||
return num.Js(unicode(int(num.value)))
|
||||
return num.Js(unicode(num.value)) # accurate enough
|
||||
|
||||
|
||||
class NumberPrototype:
|
||||
def toString(radix):
|
||||
if this.Class!='Number':
|
||||
raise this.MakeError('TypeError', 'Number.prototype.valueOf is not generic')
|
||||
if radix.is_undefined():
|
||||
return to_str_rep(this)
|
||||
r = radix.to_int()
|
||||
if r==10:
|
||||
return to_str_rep(this)
|
||||
if r not in xrange(2, 37):
|
||||
raise this.MakeError('RangeError', 'Number.prototype.toString() radix argument must be between 2 and 36')
|
||||
num = this.to_int()
|
||||
if num < 0:
|
||||
num = -num
|
||||
sign = '-'
|
||||
else:
|
||||
sign = ''
|
||||
res = ''
|
||||
while num:
|
||||
s = RADIX_SYMBOLS[num % r]
|
||||
num = num // r
|
||||
res = s + res
|
||||
return sign + (res if res else '0')
|
||||
|
||||
def valueOf():
|
||||
if this.Class!='Number':
|
||||
raise this.MakeError('TypeError', 'Number.prototype.valueOf is not generic')
|
||||
return this.value
|
||||
|
||||
def toLocaleString():
|
||||
return this.to_string()
|
||||
|
||||
def toFixed (fractionDigits):
|
||||
if this.Class!='Number':
|
||||
raise this.MakeError('TypeError', 'Number.prototype.toFixed called on incompatible receiver')
|
||||
digs = fractionDigits.to_int()
|
||||
if digs<0 or digs>20:
|
||||
raise this.MakeError('RangeError', 'toFixed() digits argument must be between 0 and 20')
|
||||
elif this.is_infinity():
|
||||
return 'Infinity' if this.value>0 else '-Infinity'
|
||||
elif this.is_nan():
|
||||
return 'NaN'
|
||||
return format(this.value, '-.%df'%digs)
|
||||
|
||||
|
||||
def toExponential (fractionDigits):
|
||||
if this.Class!='Number':
|
||||
raise this.MakeError('TypeError', 'Number.prototype.toExponential called on incompatible receiver')
|
||||
digs = fractionDigits.to_int()
|
||||
if digs<0 or digs>20:
|
||||
raise this.MakeError('RangeError', 'toFixed() digits argument must be between 0 and 20')
|
||||
elif this.is_infinity():
|
||||
return 'Infinity' if this.value>0 else '-Infinity'
|
||||
elif this.is_nan():
|
||||
return 'NaN'
|
||||
return format(this.value, '-.%de'%digs)
|
||||
|
||||
def toPrecision (precision):
|
||||
if this.Class!='Number':
|
||||
raise this.MakeError('TypeError', 'Number.prototype.toPrecision called on incompatible receiver')
|
||||
if precision.is_undefined():
|
||||
return this.to_string()
|
||||
prec = precision.to_int()
|
||||
if this.is_nan():
|
||||
return 'NaN'
|
||||
elif this.is_infinity():
|
||||
return 'Infinity' if this.value>0 else '-Infinity'
|
||||
digs = prec - len(str(int(this.value)))
|
||||
if digs>=0:
|
||||
return format(this.value, '-.%df'%digs)
|
||||
else:
|
||||
return format(this.value, '-.%df'%(prec-1))
|
||||
|
||||
|
||||
|
36
lib/js2py/prototypes/jsobject.py
Normal file
36
lib/js2py/prototypes/jsobject.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
class ObjectPrototype:
|
||||
def toString():
|
||||
return '[object %s]'%this.Class
|
||||
|
||||
def valueOf():
|
||||
return this.to_object()
|
||||
|
||||
|
||||
def toLocaleString():
|
||||
return this.callprop('toString')
|
||||
|
||||
def hasOwnProperty(prop):
|
||||
return this.get_own_property(prop.to_string().value) is not None
|
||||
|
||||
def isPrototypeOf(obj):
|
||||
#a bit stupid specification but well
|
||||
# for example Object.prototype.isPrototypeOf.call((5).__proto__, 5) gives false
|
||||
if not obj.is_object():
|
||||
return False
|
||||
while 1:
|
||||
obj = obj.prototype
|
||||
if obj is None or obj.is_null():
|
||||
return False
|
||||
if obj is this:
|
||||
return True
|
||||
|
||||
def propertyIsEnumerable(prop):
|
||||
cand = this.own.get(prop.to_string().value)
|
||||
return cand is not None and cand.get('enumerable')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
46
lib/js2py/prototypes/jsregexp.py
Normal file
46
lib/js2py/prototypes/jsregexp.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
class RegExpPrototype:
|
||||
def toString():
|
||||
flags = u''
|
||||
try:
|
||||
if this.glob:
|
||||
flags += u'g'
|
||||
if this.ignore_case:
|
||||
flags += u'i'
|
||||
if this.multiline:
|
||||
flags += u'm'
|
||||
except:
|
||||
pass
|
||||
v = this.value if this.value else '(?:)'
|
||||
return u'/%s/'%v + flags
|
||||
|
||||
def test(string):
|
||||
return Exec(this, string) is not this.null
|
||||
|
||||
def exec2(string): # will be changed to exec in base.py. cant name it exec here
|
||||
return Exec(this, string)
|
||||
|
||||
|
||||
|
||||
|
||||
def Exec(this, string):
|
||||
if this.Class!='RegExp':
|
||||
raise this.MakeError('TypeError', 'RegExp.prototype.exec is not generic!')
|
||||
string = string.to_string()
|
||||
length = len(string)
|
||||
i = this.get('lastIndex').to_int() if this.glob else 0
|
||||
matched = False
|
||||
while not matched:
|
||||
if i < 0 or i > length:
|
||||
this.put('lastIndex', this.Js(0))
|
||||
return this.null
|
||||
matched = this.match(string.value, i)
|
||||
i += 1
|
||||
start, end = matched.span()#[0]+i-1, matched.span()[1]+i-1
|
||||
if this.glob:
|
||||
this.put('lastIndex', this.Js(end))
|
||||
arr = this.Js([this.Js(e) for e in [matched.group()]+list(matched.groups())])
|
||||
arr.put('index', this.Js(start))
|
||||
arr.put('input', string)
|
||||
return arr
|
||||
|
307
lib/js2py/prototypes/jsstring.py
Normal file
307
lib/js2py/prototypes/jsstring.py
Normal file
|
@ -0,0 +1,307 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .jsregexp import Exec
|
||||
import re
|
||||
DIGS = set('0123456789')
|
||||
WHITE = u"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
|
||||
|
||||
def replacement_template(rep, source, span, npar):
|
||||
"""Takes the replacement template and some info about the match and returns filled template
|
||||
"""
|
||||
n = 0
|
||||
res = ''
|
||||
while n < len(rep)-1:
|
||||
char = rep[n]
|
||||
if char=='$':
|
||||
if rep[n+1]=='$':
|
||||
res += '$'
|
||||
n += 2
|
||||
continue
|
||||
elif rep[n+1]=='`':
|
||||
# replace with string that is BEFORE match
|
||||
res += source[:span[0]]
|
||||
n += 2
|
||||
continue
|
||||
elif rep[n+1]=='\'':
|
||||
# replace with string that is AFTER match
|
||||
res += source[span[1]:]
|
||||
n += 2
|
||||
continue
|
||||
elif rep[n+1] in DIGS:
|
||||
dig = rep[n+1]
|
||||
if n+2<len(rep) and rep[n+2] in DIGS:
|
||||
dig += rep[n+2]
|
||||
num = int(dig)
|
||||
# we will not do any replacements if we dont have this npar or dig is 0
|
||||
if not num or num>len(npar):
|
||||
res += '$'+dig
|
||||
else:
|
||||
# None - undefined has to be replaced with ''
|
||||
res += npar[num-1] if npar[num-1] else ''
|
||||
n += 1 + len(dig)
|
||||
continue
|
||||
res += char
|
||||
n += 1
|
||||
if n<len(rep):
|
||||
res += rep[-1]
|
||||
return res
|
||||
|
||||
|
||||
###################################################
|
||||
|
||||
class StringPrototype:
|
||||
def toString():
|
||||
if this.Class!='String':
|
||||
raise this.MakeError('TypeError', 'String.prototype.toString is not generic')
|
||||
return this.value
|
||||
|
||||
def valueOf():
|
||||
if this.Class!='String':
|
||||
raise this.MakeError('TypeError', 'String.prototype.valueOf is not generic')
|
||||
return this.value
|
||||
|
||||
def charAt(pos):
|
||||
this.cok()
|
||||
pos = pos.to_int()
|
||||
s = this.to_string()
|
||||
if 0<= pos < len(s.value):
|
||||
char = s.value[pos]
|
||||
if char not in s.CHAR_BANK:
|
||||
s.Js(char) # add char to char bank
|
||||
return s.CHAR_BANK[char]
|
||||
return s.CHAR_BANK['']
|
||||
|
||||
def charCodeAt(pos):
|
||||
this.cok()
|
||||
pos = pos.to_int()
|
||||
s = this.to_string()
|
||||
if 0<= pos < len(s.value):
|
||||
return s.Js(ord(s.value[pos]))
|
||||
return s.NaN
|
||||
|
||||
def concat():
|
||||
this.cok()
|
||||
s = this.to_string()
|
||||
res = s.value
|
||||
for e in arguments.to_list():
|
||||
res += e.to_string().value
|
||||
return res
|
||||
|
||||
def indexOf(searchString, position):
|
||||
this.cok()
|
||||
s = this.to_string().value
|
||||
search = searchString.to_string().value
|
||||
pos = position.to_int()
|
||||
return this.Js(s.find(search, min(max(pos, 0), len(s))) )
|
||||
|
||||
def lastIndexOf(searchString, position):
|
||||
this.cok()
|
||||
s = this.to_string().value
|
||||
search = searchString.to_string().value
|
||||
pos = position.to_number()
|
||||
pos = 10**15 if pos.is_nan() else pos.to_int()
|
||||
return s.rfind(search, 0, min(max(pos, 0)+1, len(s)))
|
||||
|
||||
def localeCompare(that):
|
||||
this.cok()
|
||||
s = this.to_string()
|
||||
that = that.to_string()
|
||||
if s<that:
|
||||
return this.Js(-1)
|
||||
elif s>that:
|
||||
return this.Js(1)
|
||||
return this.Js(0)
|
||||
|
||||
def match(regexp):
|
||||
this.cok()
|
||||
s = this.to_string()
|
||||
r = this.RegExp(regexp) if regexp.Class!='RegExp' else regexp
|
||||
if not r.glob:
|
||||
return Exec(r, s)
|
||||
r.put('lastIndex', this.Js(0))
|
||||
found = []
|
||||
previous_last_index = 0
|
||||
last_match = True
|
||||
while last_match:
|
||||
result = Exec(r, s)
|
||||
if result.is_null():
|
||||
last_match=False
|
||||
else:
|
||||
this_index = r.get('lastIndex').value
|
||||
if this_index==previous_last_index:
|
||||
r.put('lastIndex', this.Js(this_index+1))
|
||||
previous_last_index += 1
|
||||
else:
|
||||
previous_last_index = this_index
|
||||
matchStr = result.get('0')
|
||||
found.append(matchStr)
|
||||
if not found:
|
||||
return this.null
|
||||
return found
|
||||
|
||||
|
||||
def replace(searchValue, replaceValue):
|
||||
# VERY COMPLICATED. to check again.
|
||||
this.cok()
|
||||
string = this.to_string()
|
||||
s = string.value
|
||||
res = ''
|
||||
if not replaceValue.is_callable():
|
||||
replaceValue = replaceValue.to_string().value
|
||||
func = False
|
||||
else:
|
||||
func = True
|
||||
# Replace all ( global )
|
||||
if searchValue.Class == 'RegExp' and searchValue.glob:
|
||||
last = 0
|
||||
for e in re.finditer(searchValue.pat, s):
|
||||
res += s[last:e.span()[0]]
|
||||
if func:
|
||||
# prepare arguments for custom func (replaceValue)
|
||||
args = (e.group(),) + e.groups() + (e.span()[1], string)
|
||||
# convert all types to JS
|
||||
args = map(this.Js, args)
|
||||
res += replaceValue(*args).to_string().value
|
||||
else:
|
||||
res += replacement_template(replaceValue, s, e.span(), e.groups())
|
||||
last = e.span()[1]
|
||||
res += s[last:]
|
||||
return this.Js(res)
|
||||
elif searchValue.Class=='RegExp':
|
||||
e = re.search(searchValue.pat, s)
|
||||
if e is None:
|
||||
return string
|
||||
span = e.span()
|
||||
pars = e.groups()
|
||||
match = e.group()
|
||||
else:
|
||||
match = searchValue.to_string().value
|
||||
ind = s.find(match)
|
||||
if ind==-1:
|
||||
return string
|
||||
span = ind, ind + len(match)
|
||||
pars = ()
|
||||
res = s[:span[0]]
|
||||
if func:
|
||||
args = (match,) + pars + (span[1], string)
|
||||
# convert all types to JS
|
||||
this_ = this
|
||||
args = tuple([this_.Js(x) for x in args])
|
||||
res += replaceValue(*args).to_string().value
|
||||
else:
|
||||
res += replacement_template(replaceValue, s, span, pars)
|
||||
res += s[span[1]:]
|
||||
return res
|
||||
|
||||
def search(regexp):
|
||||
this.cok()
|
||||
string = this.to_string()
|
||||
if regexp.Class=='RegExp':
|
||||
rx = regexp
|
||||
else:
|
||||
rx = this.RegExp(regexp)
|
||||
res = re.search(rx.pat, string.value)
|
||||
if res is not None:
|
||||
return this.Js(res.span()[0])
|
||||
return -1
|
||||
|
||||
def slice(start, end):
|
||||
this.cok()
|
||||
s = this.to_string()
|
||||
start = start.to_int()
|
||||
length = len(s.value)
|
||||
end = length if end.is_undefined() else end.to_int()
|
||||
#From = max(length+start, 0) if start<0 else min(length, start)
|
||||
#To = max(length+end, 0) if end<0 else min(length, end)
|
||||
return s.value[start:end]
|
||||
|
||||
|
||||
def split (separator, limit):
|
||||
# its a bit different that re.split!
|
||||
this.cok()
|
||||
S = this.to_string()
|
||||
s = S.value
|
||||
lim = 2**32-1 if limit.is_undefined() else limit.to_uint32()
|
||||
if not lim:
|
||||
return []
|
||||
if separator.is_undefined():
|
||||
return [s]
|
||||
len_s = len(s)
|
||||
res = []
|
||||
R = separator if separator.Class=='RegExp' else separator.to_string()
|
||||
if not len_s:
|
||||
if SplitMatch(s, 0, R) is None:
|
||||
return [S]
|
||||
return []
|
||||
p = q = 0
|
||||
while q!=len_s:
|
||||
e, cap = SplitMatch(s, q, R)
|
||||
if e is None or e==p:
|
||||
q += 1
|
||||
continue
|
||||
res.append(s[p:q])
|
||||
p = q = e
|
||||
if len(res)==lim:
|
||||
return res
|
||||
for element in cap:
|
||||
res.append(this.Js(element))
|
||||
if len(res)==lim:
|
||||
return res
|
||||
res.append(s[p:])
|
||||
return res
|
||||
|
||||
|
||||
def substring (start, end):
|
||||
this.cok()
|
||||
s = this.to_string().value
|
||||
start = start.to_int()
|
||||
length = len(s)
|
||||
end = length if end.is_undefined() else end.to_int()
|
||||
fstart = min(max(start, 0), length)
|
||||
fend = min(max(end, 0), length)
|
||||
return this.Js(s[min(fstart, fend):max(fstart, fend)])
|
||||
|
||||
def substr(start, length):
|
||||
#I hate this function and its description in specification
|
||||
r1 = this.to_string().value
|
||||
r2 = start.to_int()
|
||||
r3 = 10**20 if length.is_undefined() else length.to_int()
|
||||
r4 = len(r1)
|
||||
r5 = r2 if r2>=0 else max(0, r2+r4)
|
||||
r6 = min(max(r3 ,0), r4 - r5)
|
||||
if r6<=0:
|
||||
return ''
|
||||
return r1[r5:r5+r6]
|
||||
|
||||
def toLowerCase():
|
||||
this.cok()
|
||||
return this.Js(this.to_string().value.lower())
|
||||
|
||||
def toLocaleLowerCase():
|
||||
this.cok()
|
||||
return this.Js(this.to_string().value.lower())
|
||||
|
||||
def toUpperCase():
|
||||
this.cok()
|
||||
return this.Js(this.to_string().value.upper())
|
||||
|
||||
def toLocaleUpperCase():
|
||||
this.cok()
|
||||
return this.Js(this.to_string().value.upper())
|
||||
|
||||
def trim():
|
||||
this.cok()
|
||||
return this.Js(this.to_string().value.strip(WHITE))
|
||||
|
||||
|
||||
|
||||
|
||||
def SplitMatch(s, q, R):
|
||||
# s is Py String to match, q is the py int match start and R is Js RegExp or String.
|
||||
if R.Class=='RegExp':
|
||||
res = R.match(s, q)
|
||||
return (None, ()) if res is None else (res.span()[1], res.groups())
|
||||
# R is just a string
|
||||
if s[q:].startswith(R.value):
|
||||
return q+len(R.value), ()
|
||||
return None, ()
|
||||
|
324
lib/js2py/prototypes/jstypedarray.py
Normal file
324
lib/js2py/prototypes/jstypedarray.py
Normal file
|
@ -0,0 +1,324 @@
|
|||
# this is based on jsarray.py
|
||||
|
||||
import six
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
pass
|
||||
|
||||
if six.PY3:
|
||||
xrange = range
|
||||
import functools
|
||||
|
||||
def to_arr(this):
|
||||
"""Returns Python array from Js array"""
|
||||
return [this.get(str(e)) for e in xrange(len(this))]
|
||||
|
||||
|
||||
ARR_STACK = set({})
|
||||
|
||||
class TypedArrayPrototype:
|
||||
|
||||
def toString():
|
||||
# this function is wrong
|
||||
func = this.get('join')
|
||||
if not func.is_callable():
|
||||
@this.Js
|
||||
def func():
|
||||
return '[object %s]'%this.Class
|
||||
return func.call(this, ())
|
||||
|
||||
def toLocaleString(locales=None,options=None):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
# separator is simply a comma ','
|
||||
if not arr_len:
|
||||
return ''
|
||||
res = []
|
||||
for i in xrange(arr_len):
|
||||
element = array[str(i)]
|
||||
if element.is_undefined() or element.is_null():
|
||||
res.append('')
|
||||
else:
|
||||
cand = element.to_object()
|
||||
str_func = element.get('toLocaleString')
|
||||
if not str_func.is_callable():
|
||||
raise this.MakeError('TypeError', 'toLocaleString method of item at index %d is not callable'%i)
|
||||
res.append(element.callprop('toLocaleString').value)
|
||||
return ','.join(res)
|
||||
|
||||
def join(separator):
|
||||
ARR_STACK.add(this)
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
separator = ',' if separator.is_undefined() else separator.to_string().value
|
||||
elems = []
|
||||
for e in xrange(arr_len):
|
||||
elem = array.get(str(e))
|
||||
if elem in ARR_STACK:
|
||||
s = ''
|
||||
else:
|
||||
s = elem.to_string().value
|
||||
elems.append(s if not (elem.is_undefined() or elem.is_null()) else '')
|
||||
res = separator.join(elems)
|
||||
ARR_STACK.remove(this)
|
||||
return res
|
||||
|
||||
def reverse():
|
||||
array = this.to_object() # my own algorithm
|
||||
vals = to_arr(array)
|
||||
has_props = [array.has_property(str(e)) for e in xrange(len(array))]
|
||||
vals.reverse()
|
||||
has_props.reverse()
|
||||
for i, val in enumerate(vals):
|
||||
if has_props[i]:
|
||||
array.put(str(i), val)
|
||||
else:
|
||||
array.delete(str(i))
|
||||
return array
|
||||
|
||||
def slice(start, end): # todo check
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
relative_start = start.to_int()
|
||||
k = max((arr_len + relative_start), 0) if relative_start<0 else min(relative_start, arr_len)
|
||||
relative_end = arr_len if end.is_undefined() else end.to_int()
|
||||
final = max((arr_len + relative_end), 0) if relative_end<0 else min(relative_end, arr_len)
|
||||
res = []
|
||||
n = 0
|
||||
while k<final:
|
||||
pk = str(k)
|
||||
if array.has_property(pk):
|
||||
res.append(array.get(pk))
|
||||
k += 1
|
||||
n += 1
|
||||
return res
|
||||
|
||||
def sort(cmpfn):
|
||||
if not this.Class in ('Array', 'Arguments'):
|
||||
return this.to_object() # do nothing
|
||||
arr = []
|
||||
for i in xrange(len(this)):
|
||||
arr.append(this.get(six.text_type(i)))
|
||||
|
||||
if not arr:
|
||||
return this
|
||||
if not cmpfn.is_callable():
|
||||
cmpfn = None
|
||||
cmp = lambda a,b: sort_compare(a, b, cmpfn)
|
||||
if six.PY3:
|
||||
key = functools.cmp_to_key(cmp)
|
||||
arr.sort(key=key)
|
||||
else:
|
||||
arr.sort(cmp=cmp)
|
||||
for i in xrange(len(arr)):
|
||||
this.put(six.text_type(i), arr[i])
|
||||
|
||||
return this
|
||||
|
||||
def indexOf(searchElement):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if arr_len == 0:
|
||||
return -1
|
||||
if len(arguments)>1:
|
||||
n = arguments[1].to_int()
|
||||
else:
|
||||
n = 0
|
||||
if n >= arr_len:
|
||||
return -1
|
||||
if n >= 0:
|
||||
k = n
|
||||
else:
|
||||
k = arr_len - abs(n)
|
||||
if k < 0:
|
||||
k = 0
|
||||
while k < arr_len:
|
||||
if array.has_property(str(k)):
|
||||
elementK = array.get(str(k))
|
||||
if searchElement.strict_equality_comparison(elementK):
|
||||
return k
|
||||
k += 1
|
||||
return -1
|
||||
|
||||
def lastIndexOf(searchElement):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if arr_len == 0:
|
||||
return -1
|
||||
if len(arguments)>1:
|
||||
n = arguments[1].to_int()
|
||||
else:
|
||||
n = arr_len - 1
|
||||
if n >= 0:
|
||||
k = min(n, arr_len-1)
|
||||
else:
|
||||
k = arr_len - abs(n)
|
||||
while k >= 0:
|
||||
if array.has_property(str(k)):
|
||||
elementK = array.get(str(k))
|
||||
if searchElement.strict_equality_comparison(elementK):
|
||||
return k
|
||||
k -= 1
|
||||
return -1
|
||||
|
||||
def every(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
if not callbackfn.call(T, (kValue, this.Js(k), array)).to_boolean().value:
|
||||
return False
|
||||
k += 1
|
||||
return True
|
||||
|
||||
def some(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
if callbackfn.call(T, (kValue, this.Js(k), array)).to_boolean().value:
|
||||
return True
|
||||
k += 1
|
||||
return False
|
||||
|
||||
def forEach(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
callbackfn.call(T, (kValue, this.Js(k), array))
|
||||
k+=1
|
||||
|
||||
def map(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
A = this.Js([])
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
Pk = str(k)
|
||||
if array.has_property(Pk):
|
||||
kValue = array.get(Pk)
|
||||
mappedValue = callbackfn.call(T, (kValue, this.Js(k), array))
|
||||
A.define_own_property(Pk, {'value': mappedValue, 'writable': True,
|
||||
'enumerable': True, 'configurable': True})
|
||||
k += 1
|
||||
return A
|
||||
|
||||
def filter(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = arguments[1]
|
||||
res = []
|
||||
k = 0
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
if callbackfn.call(T, (kValue, this.Js(k), array)).to_boolean().value:
|
||||
res.append(kValue)
|
||||
k += 1
|
||||
return res # converted to js array automatically
|
||||
|
||||
def reduce(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
if not arr_len and len(arguments)<2:
|
||||
raise this.MakeError('TypeError', 'Reduce of empty array with no initial value')
|
||||
k = 0
|
||||
if len(arguments)>1: # initial value present
|
||||
accumulator = arguments[1]
|
||||
else:
|
||||
kPresent = False
|
||||
while not kPresent and k<arr_len:
|
||||
kPresent = array.has_property(str(k))
|
||||
if kPresent:
|
||||
accumulator = array.get(str(k))
|
||||
k += 1
|
||||
if not kPresent:
|
||||
raise this.MakeError('TypeError', 'Reduce of empty array with no initial value')
|
||||
while k<arr_len:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
accumulator = callbackfn.call(this.undefined, (accumulator, kValue, this.Js(k), array))
|
||||
k += 1
|
||||
return accumulator
|
||||
|
||||
|
||||
def reduceRight(callbackfn):
|
||||
array = this.to_object()
|
||||
arr_len = array.get("length").to_uint32()
|
||||
if not callbackfn.is_callable():
|
||||
raise this.MakeError('TypeError', 'callbackfn must be a function')
|
||||
if not arr_len and len(arguments)<2:
|
||||
raise this.MakeError('TypeError', 'Reduce of empty array with no initial value')
|
||||
k = arr_len - 1
|
||||
if len(arguments)>1: # initial value present
|
||||
accumulator = arguments[1]
|
||||
else:
|
||||
kPresent = False
|
||||
while not kPresent and k>=0:
|
||||
kPresent = array.has_property(str(k))
|
||||
if kPresent:
|
||||
accumulator = array.get(str(k))
|
||||
k -= 1
|
||||
if not kPresent:
|
||||
raise this.MakeError('TypeError', 'Reduce of empty array with no initial value')
|
||||
while k>=0:
|
||||
if array.has_property(str(k)):
|
||||
kValue = array.get(str(k))
|
||||
accumulator = callbackfn.call(this.undefined, (accumulator, kValue, this.Js(k), array))
|
||||
k -= 1
|
||||
return accumulator
|
||||
|
||||
def sort_compare(a, b, comp):
|
||||
if a is None:
|
||||
if b is None:
|
||||
return 0
|
||||
return 1
|
||||
if b is None:
|
||||
if a is None:
|
||||
return 0
|
||||
return -1
|
||||
if a.is_undefined():
|
||||
if b.is_undefined():
|
||||
return 0
|
||||
return 1
|
||||
if b.is_undefined():
|
||||
if a.is_undefined():
|
||||
return 0
|
||||
return -1
|
||||
if comp is not None:
|
||||
res = comp.call(a.undefined, (a, b))
|
||||
return res.to_int()
|
||||
x, y = a.to_string(), b.to_string()
|
||||
if x<y:
|
||||
return -1
|
||||
elif x>y:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
|
66
lib/js2py/pyjs.py
Normal file
66
lib/js2py/pyjs.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
from .base import *
|
||||
from .constructors.jsmath import Math
|
||||
from .constructors.jsdate import Date
|
||||
from .constructors.jsobject import Object
|
||||
from .constructors.jsfunction import Function
|
||||
from .constructors.jsstring import String
|
||||
from .constructors.jsnumber import Number
|
||||
from .constructors.jsboolean import Boolean
|
||||
from .constructors.jsregexp import RegExp
|
||||
from .constructors.jsarray import Array
|
||||
from .constructors.jsarraybuffer import ArrayBuffer
|
||||
from .constructors.jsint8array import Int8Array
|
||||
from .constructors.jsuint8array import Uint8Array
|
||||
from .constructors.jsuint8clampedarray import Uint8ClampedArray
|
||||
from .constructors.jsint16array import Int16Array
|
||||
from .constructors.jsuint16array import Uint16Array
|
||||
from .constructors.jsint32array import Int32Array
|
||||
from .constructors.jsuint32array import Uint32Array
|
||||
from .constructors.jsfloat32array import Float32Array
|
||||
from .constructors.jsfloat64array import Float64Array
|
||||
from .prototypes.jsjson import JSON
|
||||
from .host.console import console
|
||||
from .host.jseval import Eval
|
||||
from .host.jsfunctions import parseFloat, parseInt, isFinite, isNaN
|
||||
|
||||
# Now we have all the necessary items to create global environment for script
|
||||
__all__ = ['Js', 'PyJsComma', 'PyJsStrictEq', 'PyJsStrictNeq',
|
||||
'PyJsException', 'PyJsBshift', 'Scope', 'PyExceptionToJs',
|
||||
'JsToPyException', 'JS_BUILTINS', 'appengine', 'set_global_object',
|
||||
'JsRegExp', 'PyJsException', 'PyExceptionToJs', 'JsToPyException', 'PyJsSwitchException']
|
||||
|
||||
|
||||
# these were defined in base.py
|
||||
builtins = ('true','false','null','undefined','Infinity',
|
||||
'NaN', 'console', 'String', 'Number', 'Boolean', 'RegExp',
|
||||
'Math', 'Date', 'Object', 'Function', 'Array',
|
||||
'Int8Array', 'Uint8Array', 'Uint8ClampedArray',
|
||||
'Int16Array','Uint16Array',
|
||||
'Int32Array', 'Uint32Array',
|
||||
'Float32Array', 'Float64Array',
|
||||
'ArrayBuffer',
|
||||
'parseFloat', 'parseInt', 'isFinite', 'isNaN')
|
||||
#Array, Function, JSON, Error is done later :)
|
||||
# also some built in functions like eval...
|
||||
|
||||
def set_global_object(obj):
|
||||
obj.IS_CHILD_SCOPE = False
|
||||
this = This({})
|
||||
this.own = obj.own
|
||||
this.prototype = obj.prototype
|
||||
PyJs.GlobalObject = this
|
||||
# make this available
|
||||
obj.register('this')
|
||||
obj.put('this', this)
|
||||
|
||||
|
||||
|
||||
scope = dict(zip(builtins, [globals()[e] for e in builtins]))
|
||||
# Now add errors:
|
||||
for name, error in ERRORS.items():
|
||||
scope[name] = error
|
||||
#add eval
|
||||
scope['eval'] = Eval
|
||||
scope['JSON'] = JSON
|
||||
JS_BUILTINS = dict((k,v) for k,v in scope.items())
|
||||
|
38
lib/js2py/translators/__init__.py
Normal file
38
lib/js2py/translators/__init__.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright 2014, 2015 Piotr Dabkowski
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the 'Software'),
|
||||
# to deal in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
# the Software, and to permit persons to whom the Software is furnished to do so, subject
|
||||
# to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all copies or
|
||||
# substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
||||
|
||||
__all__ = ['PyJsParser', 'Node', 'WrappingNode', 'node_to_dict', 'parse', 'translate_js', 'translate', 'syntax_tree_translate',
|
||||
'DEFAULT_HEADER']
|
||||
__author__ = 'Piotr Dabkowski'
|
||||
__version__ = '2.2.0'
|
||||
from pyjsparser import PyJsParser
|
||||
from .translator import translate_js, trasnlate, syntax_tree_translate, DEFAULT_HEADER
|
||||
|
||||
|
||||
def parse(javascript_code):
|
||||
"""Returns syntax tree of javascript_code.
|
||||
|
||||
Syntax tree has the same structure as syntax tree produced by esprima.js
|
||||
|
||||
Same as PyJsParser().parse For your convenience :) """
|
||||
p = PyJsParser()
|
||||
return p.parse(javascript_code)
|
||||
|
||||
|
327
lib/js2py/translators/friendly_nodes.py
Normal file
327
lib/js2py/translators/friendly_nodes.py
Normal file
|
@ -0,0 +1,327 @@
|
|||
import binascii
|
||||
|
||||
from pyjsparser import PyJsParser
|
||||
import six
|
||||
if six.PY3:
|
||||
basestring = str
|
||||
long = int
|
||||
xrange = range
|
||||
unicode = str
|
||||
|
||||
REGEXP_CONVERTER = PyJsParser()
|
||||
|
||||
def to_hex(s):
|
||||
return binascii.hexlify(s.encode('utf8')).decode('utf8') # fucking python 3, I hate it so much
|
||||
# wtf was wrong with s.encode('hex') ???
|
||||
def indent(lines, ind=4):
|
||||
return ind*' '+lines.replace('\n', '\n'+ind*' ').rstrip(' ')
|
||||
|
||||
def inject_before_lval(source, lval, code):
|
||||
if source.count(lval)>1:
|
||||
print()
|
||||
print(lval)
|
||||
raise RuntimeError('To many lvals (%s)' % lval)
|
||||
elif not source.count(lval):
|
||||
print()
|
||||
print(lval)
|
||||
assert lval not in source
|
||||
raise RuntimeError('No lval found "%s"' % lval)
|
||||
end = source.index(lval)
|
||||
inj = source.rfind('\n', 0, end)
|
||||
ind = inj
|
||||
while source[ind+1]==' ':
|
||||
ind+=1
|
||||
ind -= inj
|
||||
return source[:inj+1]+ indent(code, ind) + source[inj+1:]
|
||||
|
||||
|
||||
def get_continue_label(label):
|
||||
return CONTINUE_LABEL%to_hex(label)
|
||||
|
||||
def get_break_label(label):
|
||||
return BREAK_LABEL%to_hex(label)
|
||||
|
||||
|
||||
def is_valid_py_name(name):
|
||||
try:
|
||||
compile(name+' = 11', 'a','exec')
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
def indent(lines, ind=4):
|
||||
return ind*' '+lines.replace('\n', '\n'+ind*' ').rstrip(' ')
|
||||
|
||||
def compose_regex(val):
|
||||
reg, flags = val
|
||||
#reg = REGEXP_CONVERTER._unescape_string(reg)
|
||||
return u'/%s/%s' % (reg, flags)
|
||||
|
||||
def float_repr(f):
|
||||
if int(f)==f:
|
||||
return repr(int(f))
|
||||
return repr(f)
|
||||
|
||||
def argsplit(args, sep=','):
|
||||
"""used to split JS args (it is not that simple as it seems because
|
||||
sep can be inside brackets).
|
||||
|
||||
pass args *without* brackets!
|
||||
|
||||
Used also to parse array and object elements, and more"""
|
||||
parsed_len = 0
|
||||
last = 0
|
||||
splits = []
|
||||
for e in bracket_split(args, brackets=['()', '[]', '{}']):
|
||||
if e[0] not in ('(', '[', '{'):
|
||||
for i, char in enumerate(e):
|
||||
if char==sep:
|
||||
splits.append(args[last:parsed_len+i])
|
||||
last = parsed_len + i + 1
|
||||
parsed_len += len(e)
|
||||
splits.append(args[last:])
|
||||
return splits
|
||||
|
||||
|
||||
def bracket_split(source, brackets=('()','{}','[]'), strip=False):
|
||||
"""DOES NOT RETURN EMPTY STRINGS (can only return empty bracket content if strip=True)"""
|
||||
starts = [e[0] for e in brackets]
|
||||
in_bracket = 0
|
||||
n = 0
|
||||
last = 0
|
||||
while n<len(source):
|
||||
e = source[n]
|
||||
if not in_bracket and e in starts:
|
||||
in_bracket = 1
|
||||
start = n
|
||||
b_start, b_end = brackets[starts.index(e)]
|
||||
elif in_bracket:
|
||||
if e==b_start:
|
||||
in_bracket += 1
|
||||
elif e==b_end:
|
||||
in_bracket -= 1
|
||||
if not in_bracket:
|
||||
if source[last:start]:
|
||||
yield source[last:start]
|
||||
last = n+1
|
||||
yield source[start+strip:n+1-strip]
|
||||
n+=1
|
||||
if source[last:]:
|
||||
yield source[last:]
|
||||
|
||||
|
||||
def js_comma(a, b):
|
||||
return 'PyJsComma('+a+','+b+')'
|
||||
|
||||
def js_or(a, b):
|
||||
return '('+a+' or '+b+')'
|
||||
|
||||
def js_bor(a, b):
|
||||
return '('+a+'|'+b+')'
|
||||
|
||||
def js_bxor(a, b):
|
||||
return '('+a+'^'+b+')'
|
||||
|
||||
def js_band(a, b):
|
||||
return '('+a+'&'+b+')'
|
||||
|
||||
def js_and(a, b):
|
||||
return '('+a+' and '+b+')'
|
||||
def js_strict_eq(a, b):
|
||||
return 'PyJsStrictEq('+a+','+b+')'
|
||||
|
||||
def js_strict_neq(a, b):
|
||||
return 'PyJsStrictNeq('+a+','+b+')'
|
||||
|
||||
#Not handled by python in the same way like JS. For example 2==2==True returns false.
|
||||
# In JS above would return true so we need brackets.
|
||||
def js_abstract_eq(a, b):
|
||||
return '('+a+'=='+b+')'
|
||||
|
||||
#just like ==
|
||||
def js_abstract_neq(a, b):
|
||||
return '('+a+'!='+b+')'
|
||||
|
||||
def js_lt(a, b):
|
||||
return '('+a+'<'+b+')'
|
||||
|
||||
def js_le(a, b):
|
||||
return '('+a+'<='+b+')'
|
||||
|
||||
def js_ge(a, b):
|
||||
return '('+a+'>='+b+')'
|
||||
|
||||
def js_gt(a, b):
|
||||
return '('+a+'>'+b+')'
|
||||
|
||||
def js_in(a, b):
|
||||
return b+'.contains('+a+')'
|
||||
|
||||
def js_instanceof(a, b):
|
||||
return a+'.instanceof('+b+')'
|
||||
|
||||
def js_lshift(a, b):
|
||||
return '('+a+'<<'+b+')'
|
||||
|
||||
def js_rshift(a, b):
|
||||
return '('+a+'>>'+b+')'
|
||||
|
||||
def js_shit(a, b):
|
||||
return 'PyJsBshift('+a+','+b+')'
|
||||
|
||||
def js_add(a, b): # To simplify later process of converting unary operators + and ++
|
||||
return '(%s+%s)'%(a, b)
|
||||
|
||||
def js_sub(a, b): # To simplify
|
||||
return '(%s-%s)'%(a, b)
|
||||
|
||||
def js_mul(a, b):
|
||||
return '('+a+'*'+b+')'
|
||||
|
||||
def js_div(a, b):
|
||||
return '('+a+'/'+b+')'
|
||||
|
||||
def js_mod(a, b):
|
||||
return '('+a+'%'+b+')'
|
||||
|
||||
def js_typeof(a):
|
||||
cand = list(bracket_split(a, ('()',)))
|
||||
if len(cand)==2 and cand[0]=='var.get':
|
||||
return cand[0]+cand[1][:-1]+',throw=False).typeof()'
|
||||
return a+'.typeof()'
|
||||
|
||||
def js_void(a):
|
||||
# eval and return undefined
|
||||
return 'PyJsComma(%s, Js(None))' % a
|
||||
|
||||
def js_new(a):
|
||||
cands = list(bracket_split(a, ('()',)))
|
||||
lim = len(cands)
|
||||
if lim < 2:
|
||||
return a + '.create()'
|
||||
n = 0
|
||||
while n < lim:
|
||||
c = cands[n]
|
||||
if c[0]=='(':
|
||||
if cands[n-1].endswith('.get') and n+1>=lim: # last get operation.
|
||||
return a + '.create()'
|
||||
elif cands[n-1][0]=='(':
|
||||
return ''.join(cands[:n])+'.create' + c + ''.join(cands[n+1:])
|
||||
elif cands[n-1]=='.callprop':
|
||||
beg = ''.join(cands[:n-1])
|
||||
args = argsplit(c[1:-1],',')
|
||||
prop = args[0]
|
||||
new_args = ','.join(args[1:])
|
||||
create = '.get(%s).create(%s)' % (prop, new_args)
|
||||
return beg + create + ''.join(cands[n+1:])
|
||||
n+=1
|
||||
return a + '.create()'
|
||||
|
||||
|
||||
def js_delete(a):
|
||||
#replace last get with delete.
|
||||
c = list(bracket_split(a, ['()']))
|
||||
beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip() #strips just to make sure... I will remove it later
|
||||
if beg[-4:]!='.get':
|
||||
print(a)
|
||||
raise SyntaxError('Invalid delete operation')
|
||||
return beg[:-3]+'delete'+arglist
|
||||
|
||||
|
||||
def js_neg(a):
|
||||
return '(-'+a+')'
|
||||
|
||||
def js_pos(a):
|
||||
return '(+'+a+')'
|
||||
|
||||
def js_inv(a):
|
||||
return '(~'+a+')'
|
||||
|
||||
def js_not(a):
|
||||
return a+'.neg()'
|
||||
|
||||
def js_postfix(a, inc, post):
|
||||
bra = list(bracket_split(a, ('()',)))
|
||||
meth = bra[-2]
|
||||
if not meth.endswith('get'):
|
||||
raise SyntaxError('Invalid ++ or -- operation.')
|
||||
bra[-2] = bra[-2][:-3] + 'put'
|
||||
bra[-1] = '(%s,Js(%s.to_number())%sJs(1))' % (bra[-1][1:-1], a, '+' if inc else '-')
|
||||
res = ''.join(bra)
|
||||
return res if not post else '(%s%sJs(1))' % (res, '-' if inc else '+')
|
||||
|
||||
def js_pre_inc(a):
|
||||
return js_postfix(a, True, False)
|
||||
|
||||
def js_post_inc(a):
|
||||
return js_postfix(a, True, True)
|
||||
|
||||
def js_pre_dec(a):
|
||||
return js_postfix(a, False, False)
|
||||
|
||||
def js_post_dec(a):
|
||||
return js_postfix(a, False, True)
|
||||
|
||||
|
||||
|
||||
CONTINUE_LABEL = 'JS_CONTINUE_LABEL_%s'
|
||||
BREAK_LABEL = 'JS_BREAK_LABEL_%s'
|
||||
PREPARE = '''HOLDER = var.own.get(NAME)\nvar.force_own_put(NAME, PyExceptionToJs(PyJsTempException))\n'''
|
||||
RESTORE = '''if HOLDER is not None:\n var.own[NAME] = HOLDER\nelse:\n del var.own[NAME]\ndel HOLDER\n'''
|
||||
TRY_CATCH = '''%stry:\nBLOCKfinally:\n%s''' % (PREPARE, indent(RESTORE))
|
||||
|
||||
|
||||
|
||||
OR = {'||': js_or}
|
||||
AND = {'&&': js_and}
|
||||
BOR = {'|': js_bor}
|
||||
BXOR = {'^': js_bxor}
|
||||
BAND = {'&': js_band}
|
||||
|
||||
EQS = {'===': js_strict_eq,
|
||||
'!==': js_strict_neq,
|
||||
'==': js_abstract_eq, # we need == and != too. Read a note above method
|
||||
'!=': js_abstract_neq}
|
||||
|
||||
#Since JS does not have chained comparisons we need to implement all cmp methods.
|
||||
COMPS = {'<': js_lt,
|
||||
'<=': js_le,
|
||||
'>=': js_ge,
|
||||
'>': js_gt,
|
||||
'instanceof': js_instanceof, #todo change to validitate
|
||||
'in': js_in}
|
||||
|
||||
BSHIFTS = {'<<': js_lshift,
|
||||
'>>': js_rshift,
|
||||
'>>>': js_shit}
|
||||
|
||||
ADDS = {'+': js_add,
|
||||
'-': js_sub}
|
||||
|
||||
MULTS = {'*': js_mul,
|
||||
'/': js_div,
|
||||
'%': js_mod}
|
||||
BINARY = {}
|
||||
BINARY.update(ADDS)
|
||||
BINARY.update(MULTS)
|
||||
BINARY.update(BSHIFTS)
|
||||
BINARY.update(COMPS)
|
||||
BINARY.update(EQS)
|
||||
BINARY.update(BAND)
|
||||
BINARY.update(BXOR)
|
||||
BINARY.update(BOR)
|
||||
BINARY.update(AND)
|
||||
BINARY.update(OR)
|
||||
#Note they dont contain ++ and -- methods because they both have 2 different methods
|
||||
# correct method will be found automatically in translate function
|
||||
UNARY = {'typeof': js_typeof,
|
||||
'void': js_void,
|
||||
'new': js_new,
|
||||
'delete': js_delete,
|
||||
'!': js_not,
|
||||
'-': js_neg,
|
||||
'+': js_pos,
|
||||
'~': js_inv,
|
||||
'++': None,
|
||||
'--': None
|
||||
}
|
219
lib/js2py/translators/jsregexps.py
Normal file
219
lib/js2py/translators/jsregexps.py
Normal file
|
@ -0,0 +1,219 @@
|
|||
from pyjsparser.pyjsparserdata import *
|
||||
|
||||
REGEXP_SPECIAL_SINGLE = {'\\', '^', '$', '*', '+', '?', '.'}
|
||||
|
||||
NOT_PATTERN_CHARS = {'^', '$', '\\', '.', '*', '+', '?', '(', ')', '[', ']', '|'} # what about '{', '}', ???
|
||||
|
||||
CHAR_CLASS_ESCAPE = {'d', 'D', 's', 'S', 'w', 'W'}
|
||||
CONTROL_ESCAPE_CHARS = {'f', 'n', 'r', 't', 'v'}
|
||||
CONTROL_LETTERS = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
||||
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
|
||||
|
||||
def SpecialChar(char):
|
||||
return {'type': 'SpecialChar',
|
||||
'content': char}
|
||||
|
||||
|
||||
def isPatternCharacter(char):
|
||||
return char not in NOT_PATTERN_CHARS
|
||||
|
||||
class JsRegExpParser:
|
||||
def __init__(self, source, flags):
|
||||
self.source = source
|
||||
self.flags = flags
|
||||
self.index = 0
|
||||
self.length = len(source)
|
||||
self.lineNumber = 0
|
||||
self.lineStart = 0
|
||||
|
||||
|
||||
def parsePattern(self):
|
||||
'''Perform sctring escape - for regexp literals'''
|
||||
return {'type': 'Pattern',
|
||||
'contents': self.parseDisjunction()}
|
||||
|
||||
def parseDisjunction(self):
|
||||
alternatives = []
|
||||
while True:
|
||||
alternatives.append(self.parseAlternative())
|
||||
if not self.isEOF():
|
||||
self.expect_character('|')
|
||||
else:
|
||||
break
|
||||
return {'type': 'Disjunction',
|
||||
'contents': alternatives}
|
||||
|
||||
def isEOF(self):
|
||||
if self.index>=self.length:
|
||||
return True
|
||||
return False
|
||||
|
||||
def expect_character(self, character):
|
||||
if self.source[self.index]!=character:
|
||||
self.throwUnexpected(character)
|
||||
self.index += 1
|
||||
|
||||
def parseAlternative(self):
|
||||
contents = []
|
||||
while not self.isEOF() and self.source[self.index]!='|':
|
||||
contents.append(self.parseTerm())
|
||||
return {'type': 'Alternative',
|
||||
'contents': contents}
|
||||
|
||||
def follows(self, chars):
|
||||
for i, c in enumerate(chars):
|
||||
if self.index+i>=self.length or self.source[self.index+i] != c:
|
||||
return False
|
||||
return True
|
||||
|
||||
def parseTerm(self):
|
||||
assertion = self.parseAssertion()
|
||||
if assertion:
|
||||
return assertion
|
||||
else:
|
||||
return {'type': 'Term',
|
||||
'contents': self.parseAtom()} # quantifier will go inside atom!
|
||||
|
||||
|
||||
def parseAssertion(self):
|
||||
if self.follows('$'):
|
||||
content = SpecialChar('$')
|
||||
self.index += 1
|
||||
elif self.follows('^'):
|
||||
content = SpecialChar('^')
|
||||
self.index += 1
|
||||
elif self.follows('\\b'):
|
||||
content = SpecialChar('\\b')
|
||||
self.index += 2
|
||||
elif self.follows('\\B'):
|
||||
content = SpecialChar('\\B')
|
||||
self.index += 2
|
||||
elif self.follows('(?='):
|
||||
self.index += 3
|
||||
dis = self.parseDisjunction()
|
||||
self.expect_character(')')
|
||||
content = {'type': 'Lookached',
|
||||
'contents': dis,
|
||||
'negated': False}
|
||||
elif self.follows('(?!'):
|
||||
self.index += 3
|
||||
dis = self.parseDisjunction()
|
||||
self.expect_character(')')
|
||||
content = {'type': 'Lookached',
|
||||
'contents': dis,
|
||||
'negated': True}
|
||||
else:
|
||||
return None
|
||||
return {'type': 'Assertion',
|
||||
'content': content}
|
||||
|
||||
def parseAtom(self):
|
||||
if self.follows('.'):
|
||||
content = SpecialChar('.')
|
||||
self.index += 1
|
||||
elif self.follows('\\'):
|
||||
self.index += 1
|
||||
content = self.parseAtomEscape()
|
||||
elif self.follows('['):
|
||||
content = self.parseCharacterClass()
|
||||
elif self.follows('(?:'):
|
||||
self.index += 3
|
||||
dis = self.parseDisjunction()
|
||||
self.expect_character(')')
|
||||
content = 'idk'
|
||||
elif self.follows('('):
|
||||
self.index += 1
|
||||
dis = self.parseDisjunction()
|
||||
self.expect_character(')')
|
||||
content = 'idk'
|
||||
elif isPatternCharacter(self.source[self.index]):
|
||||
content = self.source[self.index]
|
||||
self.index += 1
|
||||
else:
|
||||
return None
|
||||
quantifier = self.parseQuantifier()
|
||||
return {'type': 'Atom',
|
||||
'content': content,
|
||||
'quantifier': quantifier}
|
||||
|
||||
def parseQuantifier(self):
|
||||
prefix = self.parseQuantifierPrefix()
|
||||
if not prefix:
|
||||
return None
|
||||
greedy = True
|
||||
if self.follows('?'):
|
||||
self.index += 1
|
||||
greedy = False
|
||||
return {'type': 'Quantifier',
|
||||
'contents': prefix,
|
||||
'greedy': greedy}
|
||||
|
||||
def parseQuantifierPrefix(self):
|
||||
if self.isEOF():
|
||||
return None
|
||||
if self.follows('+'):
|
||||
content = '+'
|
||||
self.index += 1
|
||||
elif self.follows('?'):
|
||||
content = '?'
|
||||
self.index += 1
|
||||
elif self.follows('*'):
|
||||
content = '*'
|
||||
self.index += 1
|
||||
elif self.follows('{'): # try matching otherwise return None and restore the state
|
||||
i = self.index
|
||||
self.index += 1
|
||||
digs1 = self.scanDecimalDigs()
|
||||
# if no minimal number of digs provided then return no quantifier
|
||||
if not digs1:
|
||||
self.index = i
|
||||
return None
|
||||
# scan char limit if provided
|
||||
if self.follows(','):
|
||||
self.index += 1
|
||||
digs2 = self.scanDecimalDigs()
|
||||
else:
|
||||
digs2 = ''
|
||||
# must be valid!
|
||||
if not self.follows('}'):
|
||||
self.index = i
|
||||
return None
|
||||
else:
|
||||
self.expect_character('}')
|
||||
content = int(digs1), int(digs2) if digs2 else None
|
||||
else:
|
||||
return None
|
||||
return content
|
||||
|
||||
|
||||
def parseAtomEscape(self):
|
||||
ch = self.source[self.index]
|
||||
if isDecimalDigit(ch) and ch!=0:
|
||||
digs = self.scanDecimalDigs()
|
||||
elif ch in CHAR_CLASS_ESCAPE:
|
||||
self.index += 1
|
||||
return SpecialChar('\\' + ch)
|
||||
else:
|
||||
return self.parseCharacterEscape()
|
||||
|
||||
def parseCharacterEscape(self):
|
||||
ch = self.source[self.index]
|
||||
if ch in CONTROL_ESCAPE_CHARS:
|
||||
return SpecialChar('\\' + ch)
|
||||
if ch=='c':
|
||||
'ok, fuck this shit.'
|
||||
|
||||
|
||||
def scanDecimalDigs(self):
|
||||
s = self.index
|
||||
while not self.isEOF() and isDecimalDigit(self.source[self.index]):
|
||||
self.index += 1
|
||||
return self.source[s:self.index]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
a = JsRegExpParser('a(?=x)', '')
|
||||
print(a.parsePattern())
|
641
lib/js2py/translators/translating_nodes.py
Normal file
641
lib/js2py/translators/translating_nodes.py
Normal file
|
@ -0,0 +1,641 @@
|
|||
from __future__ import unicode_literals
|
||||
from pyjsparser.pyjsparserdata import *
|
||||
from .friendly_nodes import *
|
||||
import random
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
from functools import reduce
|
||||
xrange = range
|
||||
unicode = str
|
||||
# number of characters above which expression will be split to multiple lines in order to avoid python parser stack overflow
|
||||
# still experimental so I suggest to set it to 400 in order to avoid common errors
|
||||
# set it to smaller value only if you have problems with parser stack overflow
|
||||
LINE_LEN_LIMIT = 400 # 200 # or any other value - the larger the smaller probability of errors :)
|
||||
|
||||
class ForController:
|
||||
def __init__(self):
|
||||
self.inside = [False]
|
||||
self.update = ''
|
||||
|
||||
def enter_for(self, update):
|
||||
self.inside.append(True)
|
||||
self.update = update
|
||||
|
||||
def leave_for(self):
|
||||
self.inside.pop()
|
||||
|
||||
def enter_other(self):
|
||||
self.inside.append(False)
|
||||
|
||||
def leave_other(self):
|
||||
self.inside.pop()
|
||||
|
||||
def is_inside(self):
|
||||
return self.inside[-1]
|
||||
|
||||
|
||||
|
||||
class InlineStack:
|
||||
NAME = 'PyJs_%s_%d_'
|
||||
def __init__(self):
|
||||
self.reps = {}
|
||||
self.names = []
|
||||
|
||||
def inject_inlines(self, source):
|
||||
for lval in self.names: # first in first out! Its important by the way
|
||||
source = inject_before_lval(source, lval, self.reps[lval])
|
||||
return source
|
||||
|
||||
def require(self, typ):
|
||||
name = self.NAME % (typ, len(self.names))
|
||||
self.names.append(name)
|
||||
return name
|
||||
|
||||
def define(self, name, val):
|
||||
self.reps[name] = val
|
||||
|
||||
def reset(self):
|
||||
self.rel = {}
|
||||
self.names = []
|
||||
|
||||
|
||||
class ContextStack:
|
||||
def __init__(self):
|
||||
self.to_register = set([])
|
||||
self.to_define = {}
|
||||
|
||||
def reset(self):
|
||||
self.to_register = set([])
|
||||
self.to_define = {}
|
||||
|
||||
def register(self, var):
|
||||
self.to_register.add(var)
|
||||
|
||||
def define(self, name, code):
|
||||
self.to_define[name] = code
|
||||
self.register(name)
|
||||
|
||||
def get_code(self):
|
||||
code = 'var.registers([%s])\n' % ', '.join(repr(e) for e in self.to_register)
|
||||
for name, func_code in six.iteritems(self.to_define):
|
||||
code += func_code
|
||||
return code
|
||||
|
||||
|
||||
|
||||
def clean_stacks():
|
||||
global Context, inline_stack
|
||||
Context = ContextStack()
|
||||
inline_stack = InlineStack()
|
||||
|
||||
|
||||
|
||||
|
||||
def to_key(literal_or_identifier):
|
||||
''' returns string representation of this object'''
|
||||
if literal_or_identifier['type']=='Identifier':
|
||||
return literal_or_identifier['name']
|
||||
elif literal_or_identifier['type']=='Literal':
|
||||
k = literal_or_identifier['value']
|
||||
if isinstance(k, float):
|
||||
return unicode(float_repr(k))
|
||||
elif 'regex' in literal_or_identifier:
|
||||
return compose_regex(k)
|
||||
elif isinstance(k, bool):
|
||||
return 'true' if k else 'false'
|
||||
elif k is None:
|
||||
return 'null'
|
||||
else:
|
||||
return unicode(k)
|
||||
|
||||
def trans(ele, standard=False):
|
||||
"""Translates esprima syntax tree to python by delegating to appropriate translating node"""
|
||||
try:
|
||||
node = globals().get(ele['type'])
|
||||
if not node:
|
||||
raise NotImplementedError('%s is not supported!' % ele['type'])
|
||||
if standard:
|
||||
node = node.__dict__['standard'] if 'standard' in node.__dict__ else node
|
||||
return node(**ele)
|
||||
except:
|
||||
#print ele
|
||||
raise
|
||||
|
||||
|
||||
def limited(func):
|
||||
'''Decorator limiting resulting line length in order to avoid python parser stack overflow -
|
||||
If expression longer than LINE_LEN_LIMIT characters then it will be moved to upper line
|
||||
USE ONLY ON EXPRESSIONS!!! '''
|
||||
def f(standard=False, **args):
|
||||
insert_pos = len(inline_stack.names) # in case line is longer than limit we will have to insert the lval at current position
|
||||
# this is because calling func will change inline_stack.
|
||||
# we cant use inline_stack.require here because we dont know whether line overflows yet
|
||||
res = func(**args)
|
||||
if len(res)>LINE_LEN_LIMIT:
|
||||
name = inline_stack.require('LONG')
|
||||
inline_stack.names.pop()
|
||||
inline_stack.names.insert(insert_pos, name)
|
||||
res = 'def %s(var=var):\n return %s\n' % (name, res)
|
||||
inline_stack.define(name, res)
|
||||
return name+'()'
|
||||
else:
|
||||
return res
|
||||
f.__dict__['standard'] = func
|
||||
return f
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ==== IDENTIFIERS AND LITERALS =======
|
||||
|
||||
|
||||
inf = float('inf')
|
||||
|
||||
|
||||
def Literal(type, value, raw, regex=None):
|
||||
if regex: # regex
|
||||
return 'JsRegExp(%s)' % repr(compose_regex(value))
|
||||
elif value is None: # null
|
||||
return 'var.get(u"null")'
|
||||
# Todo template
|
||||
# String, Bool, Float
|
||||
return 'Js(%s)' % repr(value) if value!=inf else 'Js(float("inf"))'
|
||||
|
||||
def Identifier(type, name):
|
||||
return 'var.get(%s)' % repr(name)
|
||||
|
||||
@limited
|
||||
def MemberExpression(type, computed, object, property):
|
||||
far_left = trans(object)
|
||||
if computed: # obj[prop] type accessor
|
||||
# may be literal which is the same in every case so we can save some time on conversion
|
||||
if property['type'] == 'Literal':
|
||||
prop = repr(to_key(property))
|
||||
else: # worst case
|
||||
prop = trans(property)
|
||||
else: # always the same since not computed (obj.prop accessor)
|
||||
prop = repr(to_key(property))
|
||||
return far_left + '.get(%s)' % prop
|
||||
|
||||
|
||||
def ThisExpression(type):
|
||||
return 'var.get(u"this")'
|
||||
|
||||
@limited
|
||||
def CallExpression(type, callee, arguments):
|
||||
arguments = [trans(e) for e in arguments]
|
||||
if callee['type']=='MemberExpression':
|
||||
far_left = trans(callee['object'])
|
||||
if callee['computed']: # obj[prop] type accessor
|
||||
# may be literal which is the same in every case so we can save some time on conversion
|
||||
if callee['property']['type'] == 'Literal':
|
||||
prop = repr(to_key(callee['property']))
|
||||
else: # worst case
|
||||
prop = trans(callee['property']) # its not a string literal! so no repr
|
||||
else: # always the same since not computed (obj.prop accessor)
|
||||
prop = repr(to_key(callee['property']))
|
||||
arguments.insert(0, prop)
|
||||
return far_left + '.callprop(%s)' % ', '.join(arguments)
|
||||
else: # standard call
|
||||
return trans(callee) + '(%s)' % ', '.join(arguments)
|
||||
|
||||
|
||||
|
||||
# ========== ARRAYS ============
|
||||
|
||||
|
||||
def ArrayExpression(type, elements): # todo fix null inside problem
|
||||
return 'Js([%s])' % ', '.join(trans(e) if e else 'None' for e in elements)
|
||||
|
||||
|
||||
|
||||
# ========== OBJECTS =============
|
||||
|
||||
def ObjectExpression(type, properties):
|
||||
name = inline_stack.require('Object')
|
||||
elems = []
|
||||
after = ''
|
||||
for p in properties:
|
||||
if p['kind']=='init':
|
||||
elems.append('%s:%s' % Property(**p))
|
||||
elif p['kind']=='set':
|
||||
k, setter = Property(**p) # setter is just a lval referring to that function, it will be defined in InlineStack automatically
|
||||
after += '%s.define_own_property(%s, {"set":%s, "configurable":True, "enumerable":True})\n' % (name, k, setter)
|
||||
elif p['kind']=='get':
|
||||
k, getter = Property(**p)
|
||||
after += '%s.define_own_property(%s, {"get":%s, "configurable":True, "enumerable":True})\n' % (name, k, getter)
|
||||
else:
|
||||
raise RuntimeError('Unexpected object propery kind')
|
||||
obj = '%s = Js({%s})\n' % (name, ','.join(elems))
|
||||
inline_stack.define(name, obj+after)
|
||||
return name
|
||||
|
||||
|
||||
|
||||
def Property(type, kind, key, computed, value, method, shorthand):
|
||||
if shorthand or computed:
|
||||
raise NotImplementedError('Shorthand and Computed properties not implemented!')
|
||||
k = to_key(key)
|
||||
if k is None:
|
||||
raise SyntaxError('Invalid key in dictionary! Or bug in Js2Py')
|
||||
v = trans(value)
|
||||
return repr(k), v
|
||||
|
||||
|
||||
# ========== EXPRESSIONS ============
|
||||
|
||||
|
||||
@limited
|
||||
def UnaryExpression(type, operator, argument, prefix):
|
||||
a = trans(argument, standard=True) # unary involve some complex operations so we cant use line shorteners here
|
||||
if operator=='delete':
|
||||
if argument['type'] in ('Identifier', 'MemberExpression'):
|
||||
# means that operation is valid
|
||||
return js_delete(a)
|
||||
return 'PyJsComma(%s, Js(True))' % a # otherwise not valid, just perform expression and return true.
|
||||
elif operator=='typeof':
|
||||
return js_typeof(a)
|
||||
return UNARY[operator](a)
|
||||
|
||||
@limited
|
||||
def BinaryExpression(type, operator, left, right):
|
||||
a = trans(left)
|
||||
b = trans(right)
|
||||
# delegate to our friends
|
||||
return BINARY[operator](a,b)
|
||||
|
||||
@limited
|
||||
def UpdateExpression(type, operator, argument, prefix):
|
||||
a = trans(argument, standard=True) # also complex operation involving parsing of the result so no line length reducing here
|
||||
return js_postfix(a, operator=='++', not prefix)
|
||||
|
||||
@limited
|
||||
def AssignmentExpression(type, operator, left, right):
|
||||
operator = operator[:-1]
|
||||
if left['type']=='Identifier':
|
||||
if operator:
|
||||
return 'var.put(%s, %s, %s)' % (repr(to_key(left)), trans(right), repr(operator))
|
||||
else:
|
||||
return 'var.put(%s, %s)' % (repr(to_key(left)), trans(right))
|
||||
elif left['type']=='MemberExpression':
|
||||
far_left = trans(left['object'])
|
||||
if left['computed']: # obj[prop] type accessor
|
||||
# may be literal which is the same in every case so we can save some time on conversion
|
||||
if left['property']['type'] == 'Literal':
|
||||
prop = repr(to_key(left['property']))
|
||||
else: # worst case
|
||||
prop = trans(left['property']) # its not a string literal! so no repr
|
||||
else: # always the same since not computed (obj.prop accessor)
|
||||
prop = repr(to_key(left['property']))
|
||||
if operator:
|
||||
return far_left + '.put(%s, %s, %s)' % (prop, trans(right), repr(operator))
|
||||
else:
|
||||
return far_left + '.put(%s, %s)' % (prop, trans(right))
|
||||
else:
|
||||
raise SyntaxError('Invalid left hand side in assignment!')
|
||||
six
|
||||
@limited
|
||||
def SequenceExpression(type, expressions):
|
||||
return reduce(js_comma, (trans(e) for e in expressions))
|
||||
|
||||
@limited
|
||||
def NewExpression(type, callee, arguments):
|
||||
return trans(callee) + '.create(%s)' % ', '.join(trans(e) for e in arguments)
|
||||
|
||||
@limited
|
||||
def ConditionalExpression(type, test, consequent, alternate): # caused plenty of problems in my home-made translator :)
|
||||
return '(%s if %s else %s)' % (trans(consequent), trans(test), trans(alternate))
|
||||
|
||||
|
||||
|
||||
# =========== STATEMENTS =============
|
||||
|
||||
|
||||
def BlockStatement(type, body):
|
||||
return StatementList(body) # never returns empty string! In the worst case returns pass\n
|
||||
|
||||
|
||||
def ExpressionStatement(type, expression):
|
||||
return trans(expression) + '\n' # end expression space with new line
|
||||
|
||||
|
||||
def BreakStatement(type, label):
|
||||
if label:
|
||||
return 'raise %s("Breaked")\n' % (get_break_label(label['name']))
|
||||
else:
|
||||
return 'break\n'
|
||||
|
||||
|
||||
def ContinueStatement(type, label):
|
||||
if label:
|
||||
return 'raise %s("Continued")\n' % (get_continue_label(label['name']))
|
||||
else:
|
||||
return 'continue\n'
|
||||
|
||||
def ReturnStatement(type, argument):
|
||||
return 'return %s\n' % (trans(argument) if argument else "var.get('undefined')")
|
||||
|
||||
|
||||
def EmptyStatement(type):
|
||||
return 'pass\n'
|
||||
|
||||
|
||||
def DebuggerStatement(type):
|
||||
return 'pass\n'
|
||||
|
||||
|
||||
def DoWhileStatement(type, body, test):
|
||||
inside = trans(body) + 'if not %s:\n' % trans(test) + indent('break\n')
|
||||
result = 'while 1:\n' + indent(inside)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def ForStatement(type, init, test, update, body):
|
||||
update = indent(trans(update)) if update else ''
|
||||
init = trans(init) if init else ''
|
||||
if not init.endswith('\n'):
|
||||
init += '\n'
|
||||
test = trans(test) if test else '1'
|
||||
if not update:
|
||||
result = '#for JS loop\n%swhile %s:\n%s%s\n' % (init, test, indent(trans(body)), update)
|
||||
else:
|
||||
result = '#for JS loop\n%swhile %s:\n' % (init, test)
|
||||
body = 'try:\n%sfinally:\n %s\n' % (indent(trans(body)), update)
|
||||
result += indent(body)
|
||||
return result
|
||||
|
||||
|
||||
def ForInStatement(type, left, right, body, each):
|
||||
res = 'for PyJsTemp in %s:\n' % trans(right)
|
||||
if left['type']=="VariableDeclaration":
|
||||
addon = trans(left) # make sure variable is registered
|
||||
if addon != 'pass\n':
|
||||
res = addon + res # we have to execute this expression :(
|
||||
# now extract the name
|
||||
try:
|
||||
name = left['declarations'][0]['id']['name']
|
||||
except:
|
||||
raise RuntimeError('Unusual ForIn loop')
|
||||
elif left['type']=='Identifier':
|
||||
name = left['name']
|
||||
else:
|
||||
raise RuntimeError('Unusual ForIn loop')
|
||||
res += indent('var.put(%s, PyJsTemp)\n' % repr(name) + trans(body))
|
||||
return res
|
||||
|
||||
|
||||
def IfStatement(type, test, consequent, alternate):
|
||||
# NOTE we cannot do elif because function definition inside elif statement would not be possible!
|
||||
IF = 'if %s:\n' % trans(test)
|
||||
IF += indent(trans(consequent))
|
||||
if not alternate:
|
||||
return IF
|
||||
ELSE = 'else:\n' + indent(trans(alternate))
|
||||
return IF + ELSE
|
||||
|
||||
|
||||
def LabeledStatement(type, label, body):
|
||||
# todo consider using smarter approach!
|
||||
inside = trans(body)
|
||||
defs = ''
|
||||
if inside.startswith('while ') or inside.startswith('for ') or inside.startswith('#for'):
|
||||
# we have to add contine label as well...
|
||||
# 3 or 1 since #for loop type has more lines before real for.
|
||||
sep = 1 if not inside.startswith('#for') else 3
|
||||
cont_label = get_continue_label(label['name'])
|
||||
temp = inside.split('\n')
|
||||
injected = 'try:\n'+'\n'.join(temp[sep:])
|
||||
injected += 'except %s:\n pass\n'%cont_label
|
||||
inside = '\n'.join(temp[:sep])+'\n'+indent(injected)
|
||||
defs += 'class %s(Exception): pass\n'%cont_label
|
||||
break_label = get_break_label(label['name'])
|
||||
inside = 'try:\n%sexcept %s:\n pass\n'% (indent(inside), break_label)
|
||||
defs += 'class %s(Exception): pass\n'%break_label
|
||||
return defs + inside
|
||||
|
||||
|
||||
def StatementList(lis):
|
||||
if lis: # ensure we don't return empty string because it may ruin indentation!
|
||||
code = ''.join(trans(e) for e in lis)
|
||||
return code if code else 'pass\n'
|
||||
else:
|
||||
return 'pass\n'
|
||||
|
||||
def PyimportStatement(type, imp):
|
||||
lib = imp['name']
|
||||
jlib = 'PyImport_%s' % lib
|
||||
code = 'import %s as %s\n' % (lib, jlib)
|
||||
#check whether valid lib name...
|
||||
try:
|
||||
compile(code, '', 'exec')
|
||||
except:
|
||||
raise SyntaxError('Invalid Python module name (%s) in pyimport statement'%lib)
|
||||
# var.pyimport will handle module conversion to PyJs object
|
||||
code += 'var.pyimport(%s, %s)\n' % (repr(lib), jlib)
|
||||
return code
|
||||
|
||||
def SwitchStatement(type, discriminant, cases):
|
||||
#TODO there will be a problem with continue in a switch statement.... FIX IT
|
||||
code = 'while 1:\n' + indent('SWITCHED = False\nCONDITION = (%s)\n')
|
||||
code = code % trans(discriminant)
|
||||
for case in cases:
|
||||
case_code = None
|
||||
if case['test']: # case (x):
|
||||
case_code = 'if SWITCHED or PyJsStrictEq(CONDITION, %s):\n' % (trans(case['test']))
|
||||
else: # default:
|
||||
case_code = 'if True:\n'
|
||||
case_code += indent('SWITCHED = True\n')
|
||||
case_code += indent(StatementList(case['consequent']))
|
||||
# one more indent for whole
|
||||
code += indent(case_code)
|
||||
# prevent infinite loop and sort out nested switch...
|
||||
code += indent('SWITCHED = True\nbreak\n')
|
||||
return code
|
||||
|
||||
|
||||
def ThrowStatement(type, argument):
|
||||
return 'PyJsTempException = JsToPyException(%s)\nraise PyJsTempException\n' % trans(argument)
|
||||
|
||||
|
||||
def TryStatement(type, block, handler, handlers, guardedHandlers, finalizer):
|
||||
result = 'try:\n%s' % indent(trans(block))
|
||||
# complicated catch statement...
|
||||
if handler:
|
||||
identifier = handler['param']['name']
|
||||
holder = 'PyJsHolder_%s_%d'%(to_hex(identifier), random.randrange(1e8))
|
||||
identifier = repr(identifier)
|
||||
result += 'except PyJsException as PyJsTempException:\n'
|
||||
# fill in except ( catch ) block and remember to recover holder variable to its previous state
|
||||
result += indent(TRY_CATCH.replace('HOLDER', holder).replace('NAME', identifier).replace('BLOCK', indent(trans(handler['body']))))
|
||||
# translate finally statement if present
|
||||
if finalizer:
|
||||
result += 'finally:\n%s' % indent(trans(finalizer))
|
||||
return result
|
||||
|
||||
|
||||
def LexicalDeclaration(type, declarations, kind):
|
||||
raise NotImplementedError('let and const not implemented yet but they will be soon! Check github for updates.')
|
||||
|
||||
|
||||
def VariableDeclarator(type, id, init):
|
||||
name = id['name']
|
||||
# register the name if not already registered
|
||||
Context.register(name)
|
||||
if init:
|
||||
return 'var.put(%s, %s)\n' % (repr(name), trans(init))
|
||||
return ''
|
||||
|
||||
|
||||
def VariableDeclaration(type, declarations, kind):
|
||||
code = ''.join(trans(d) for d in declarations)
|
||||
return code if code else 'pass\n'
|
||||
|
||||
|
||||
def WhileStatement(type, test, body):
|
||||
result = 'while %s:\n'%trans(test) + indent(trans(body))
|
||||
return result
|
||||
|
||||
|
||||
def WithStatement(type, object, body):
|
||||
raise NotImplementedError('With statement not implemented!')
|
||||
|
||||
|
||||
def Program(type, body):
|
||||
inline_stack.reset()
|
||||
code = ''.join(trans(e) for e in body)
|
||||
# here add hoisted elements (register variables and define functions)
|
||||
code = Context.get_code() + code
|
||||
# replace all inline variables
|
||||
code = inline_stack.inject_inlines(code)
|
||||
return code
|
||||
|
||||
|
||||
|
||||
# ======== FUNCTIONS ============
|
||||
|
||||
def FunctionDeclaration(type, id, params, defaults, body, generator, expression):
|
||||
if generator:
|
||||
raise NotImplementedError('Generators not supported')
|
||||
if defaults:
|
||||
raise NotImplementedError('Defaults not supported')
|
||||
if not id:
|
||||
return FunctionExpression(type, id, params, defaults, body, generator, expression) + '\n'
|
||||
JsName = id['name']
|
||||
PyName = 'PyJsHoisted_%s_' % JsName
|
||||
PyName = PyName if is_valid_py_name(PyName) else 'PyJsHoistedNonPyName'
|
||||
# this is quite complicated
|
||||
global Context
|
||||
previous_context = Context
|
||||
# change context to the context of this function
|
||||
Context = ContextStack()
|
||||
# translate body within current context
|
||||
code = trans(body)
|
||||
# get arg names
|
||||
vars = [v['name'] for v in params]
|
||||
# args are automaticaly registered variables
|
||||
Context.to_register.update(vars)
|
||||
# add all hoisted elements inside function
|
||||
code = Context.get_code() + code
|
||||
# check whether args are valid python names:
|
||||
used_vars = []
|
||||
for v in vars:
|
||||
if is_valid_py_name(v):
|
||||
used_vars.append(v)
|
||||
else: # invalid arg in python, for example $, replace with alternatice arg
|
||||
used_vars.append('PyJsArg_%s_' % to_hex(v))
|
||||
header = '@Js\n'
|
||||
header+= 'def %s(%sthis, arguments, var=var):\n' % (PyName, ', '.join(used_vars) +(', ' if vars else ''))
|
||||
# transfer names from Py scope to Js scope
|
||||
arg_map = dict(zip(vars, used_vars))
|
||||
arg_map.update({'this':'this', 'arguments':'arguments'})
|
||||
arg_conv = 'var = Scope({%s}, var)\n' % ', '.join(repr(k)+':'+v for k,v in six.iteritems(arg_map))
|
||||
# and finally set the name of the function to its real name:
|
||||
footer = '%s.func_name = %s\n' % (PyName, repr(JsName))
|
||||
footer+= 'var.put(%s, %s)\n' % (repr(JsName), PyName)
|
||||
whole_code = header + indent(arg_conv+code) + footer
|
||||
# restore context
|
||||
Context = previous_context
|
||||
# define in upper context
|
||||
Context.define(JsName, whole_code)
|
||||
return 'pass\n'
|
||||
|
||||
|
||||
def FunctionExpression(type, id, params, defaults, body, generator, expression):
|
||||
if generator:
|
||||
raise NotImplementedError('Generators not supported')
|
||||
if defaults:
|
||||
raise NotImplementedError('Defaults not supported')
|
||||
JsName = id['name'] if id else 'anonymous'
|
||||
if not is_valid_py_name(JsName):
|
||||
ScriptName = 'InlineNonPyName'
|
||||
else:
|
||||
ScriptName = JsName
|
||||
PyName = inline_stack.require(ScriptName) # this is unique
|
||||
|
||||
# again quite complicated
|
||||
global Context
|
||||
previous_context = Context
|
||||
# change context to the context of this function
|
||||
Context = ContextStack()
|
||||
# translate body within current context
|
||||
code = trans(body)
|
||||
# get arg names
|
||||
vars = [v['name'] for v in params]
|
||||
# args are automaticaly registered variables
|
||||
Context.to_register.update(vars)
|
||||
# add all hoisted elements inside function
|
||||
code = Context.get_code() + code
|
||||
# check whether args are valid python names:
|
||||
used_vars = []
|
||||
for v in vars:
|
||||
if is_valid_py_name(v):
|
||||
used_vars.append(v)
|
||||
else: # invalid arg in python, for example $, replace with alternatice arg
|
||||
used_vars.append('PyJsArg_%s_' % to_hex(v))
|
||||
header = '@Js\n'
|
||||
header+= 'def %s(%sthis, arguments, var=var):\n' % (PyName, ', '.join(used_vars) +(', ' if vars else ''))
|
||||
# transfer names from Py scope to Js scope
|
||||
arg_map = dict(zip(vars, used_vars))
|
||||
arg_map.update({'this':'this', 'arguments':'arguments'})
|
||||
if id: # make self available from inside...
|
||||
if id['name'] not in arg_map:
|
||||
arg_map[id['name']] = PyName
|
||||
arg_conv = 'var = Scope({%s}, var)\n' % ', '.join(repr(k)+':'+v for k,v in six.iteritems(arg_map))
|
||||
# and finally set the name of the function to its real name:
|
||||
footer = '%s._set_name(%s)\n' % (PyName, repr(JsName))
|
||||
whole_code = header + indent(arg_conv+code) + footer
|
||||
# restore context
|
||||
Context = previous_context
|
||||
# define in upper context
|
||||
inline_stack.define(PyName, whole_code)
|
||||
return PyName
|
||||
|
||||
|
||||
LogicalExpression = BinaryExpression
|
||||
PostfixExpression = UpdateExpression
|
||||
|
||||
clean_stacks()
|
||||
|
||||
if __name__=='__main__':
|
||||
import codecs
|
||||
import time
|
||||
import pyjsparser
|
||||
|
||||
c = None#'''`ijfdij`'''
|
||||
if not c:
|
||||
with codecs.open("esp.js", "r", "utf-8") as f:
|
||||
c = f.read()
|
||||
|
||||
print('Started')
|
||||
t = time.time()
|
||||
res = trans(pyjsparser.PyJsParser().parse(c))
|
||||
dt = time.time() - t+ 0.000000001
|
||||
print('Translated everyting in', round(dt,5), 'seconds.')
|
||||
print('Thats %d characters per second' % int(len(c)/dt))
|
||||
with open('res.py', 'w') as f:
|
||||
f.write(res)
|
||||
|
179
lib/js2py/translators/translator.py
Normal file
179
lib/js2py/translators/translator.py
Normal file
|
@ -0,0 +1,179 @@
|
|||
import pyjsparser
|
||||
import pyjsparser.parser
|
||||
from . import translating_nodes
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
|
||||
# Enable Js2Py exceptions and pyimport in parser
|
||||
pyjsparser.parser.ENABLE_JS2PY_ERRORS = True
|
||||
pyjsparser.parser.ENABLE_PYIMPORT = True
|
||||
|
||||
# the re below is how we'll recognise numeric constants.
|
||||
# it finds any 'simple numeric that is not preceded with an alphanumeric character
|
||||
# the numeric can be a float (so a dot is found) but
|
||||
# it does not recognise notation such as 123e5, 0xFF, infinity or NaN
|
||||
CP_NUMERIC_RE = re.compile(r'(?<![a-zA-Z0-9_"\'])([0-9\.]+)')
|
||||
CP_NUMERIC_PLACEHOLDER = '__PyJsNUM_%i_PyJsNUM__'
|
||||
CP_NUMERIC_PLACEHOLDER_REVERSE_RE = re.compile(
|
||||
CP_NUMERIC_PLACEHOLDER.replace('%i', '([0-9\.]+)')
|
||||
)
|
||||
|
||||
# the re below is how we'll recognise string constants
|
||||
# it finds a ' or ", then reads until the next matching ' or "
|
||||
# this re only services simple cases, it can not be used when
|
||||
# there are escaped quotes in the expression
|
||||
|
||||
#CP_STRING_1 = re.compile(r'(["\'])(.*?)\1') # this is how we'll recognise string constants
|
||||
|
||||
CP_STRING = '"([^\\\\"]+|\\\\([bfnrtv\'"\\\\]|[0-3]?[0-7]{1,2}|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}))*"|\'([^\\\\\']+|\\\\([bfnrtv\'"\\\\]|[0-3]?[0-7]{1,2}|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}))*\''
|
||||
CP_STRING_RE = re.compile(CP_STRING) # this is how we'll recognise string constants
|
||||
CP_STRING_PLACEHOLDER = '__PyJsSTR_%i_PyJsSTR__'
|
||||
CP_STRING_PLACEHOLDER_REVERSE_RE = re.compile(
|
||||
CP_STRING_PLACEHOLDER.replace('%i', '([0-9\.]+)')
|
||||
)
|
||||
|
||||
cache = {}
|
||||
|
||||
# This crap is still needed but I removed it for speed reasons. Have to think ofa better idea
|
||||
# import js2py.pyjs, sys
|
||||
# # Redefine builtin objects... Do you have a better idea?
|
||||
# for m in list(sys.modules):
|
||||
# if m.startswith('js2py'):
|
||||
# del sys.modules[m]
|
||||
# del js2py.pyjs
|
||||
# del js2py
|
||||
|
||||
DEFAULT_HEADER = u'''from js2py.pyjs import *
|
||||
# setting scope
|
||||
var = Scope( JS_BUILTINS )
|
||||
set_global_object(var)
|
||||
|
||||
# Code follows:
|
||||
'''
|
||||
|
||||
|
||||
def dbg(x):
|
||||
"""does nothing, legacy dummy function"""
|
||||
return ''
|
||||
|
||||
def translate_js(js, HEADER=DEFAULT_HEADER, use_compilation_plan=False):
|
||||
"""js has to be a javascript source code.
|
||||
returns equivalent python code."""
|
||||
if use_compilation_plan and not '//' in js and not '/*' in js:
|
||||
return translate_js_with_compilation_plan(js, HEADER=HEADER)
|
||||
parser = pyjsparser.PyJsParser()
|
||||
parsed = parser.parse(js) # js to esprima syntax tree
|
||||
# Another way of doing that would be with my auto esprima translation but its much slower and causes import problems:
|
||||
# parsed = esprima.parse(js).to_dict()
|
||||
translating_nodes.clean_stacks()
|
||||
return HEADER + translating_nodes.trans(parsed) # syntax tree to python code
|
||||
|
||||
class match_unumerator(object):
|
||||
"""This class ise used """
|
||||
matchcount = -1
|
||||
|
||||
def __init__(self, placeholder_mask):
|
||||
self.placeholder_mask = placeholder_mask
|
||||
self.matches = []
|
||||
|
||||
def __call__(self, match):
|
||||
self.matchcount += 1
|
||||
self.matches.append(match.group(0))
|
||||
return self.placeholder_mask%self.matchcount
|
||||
|
||||
def __repr__(self):
|
||||
return '\n'.join(self.placeholder_mask%counter + '=' + match for counter, match in enumerate(self.matches))
|
||||
|
||||
def wrap_up(self, output):
|
||||
for counter, value in enumerate(self.matches):
|
||||
output = output.replace("u'" + self.placeholder_mask%(counter) + "'", value, 1)
|
||||
return output
|
||||
|
||||
def get_compilation_plan(js):
|
||||
match_increaser_str = match_unumerator(CP_STRING_PLACEHOLDER)
|
||||
compilation_plan = re.sub(
|
||||
CP_STRING, match_increaser_str, js
|
||||
)
|
||||
|
||||
match_increaser_num = match_unumerator(CP_NUMERIC_PLACEHOLDER)
|
||||
compilation_plan = re.sub(CP_NUMERIC_RE, match_increaser_num, compilation_plan)
|
||||
# now put quotes, note that just patching string replaces is somewhat faster than
|
||||
# using another re:
|
||||
compilation_plan = compilation_plan.replace('__PyJsNUM_', '"__PyJsNUM_').replace('_PyJsNUM__', '_PyJsNUM__"')
|
||||
compilation_plan = compilation_plan.replace('__PyJsSTR_', '"__PyJsSTR_').replace('_PyJsSTR__', '_PyJsSTR__"')
|
||||
|
||||
return match_increaser_str, match_increaser_num, compilation_plan
|
||||
|
||||
def translate_js_with_compilation_plan(js, HEADER=DEFAULT_HEADER):
|
||||
"""js has to be a javascript source code.
|
||||
returns equivalent python code.
|
||||
|
||||
compile plans only work with the following restrictions:
|
||||
- only enabled for oneliner expressions
|
||||
- when there are comments in the js code string substitution is disabled
|
||||
- when there nested escaped quotes string substitution is disabled, so
|
||||
|
||||
cacheable:
|
||||
Q1 == 1 && name == 'harry'
|
||||
|
||||
not cacheable:
|
||||
Q1 == 1 && name == 'harry' // some comment
|
||||
|
||||
not cacheable:
|
||||
Q1 == 1 && name == 'o\'Reilly'
|
||||
|
||||
not cacheable:
|
||||
Q1 == 1 && name /* some comment */ == 'o\'Reilly'
|
||||
"""
|
||||
|
||||
match_increaser_str, match_increaser_num, compilation_plan = get_compilation_plan(js)
|
||||
|
||||
cp_hash = hashlib.md5(compilation_plan.encode('utf-8')).digest()
|
||||
try:
|
||||
python_code = cache[cp_hash]['proto_python_code']
|
||||
except:
|
||||
parser = pyjsparser.PyJsParser()
|
||||
parsed = parser.parse(compilation_plan) # js to esprima syntax tree
|
||||
# Another way of doing that would be with my auto esprima translation but its much slower and causes import problems:
|
||||
# parsed = esprima.parse(js).to_dict()
|
||||
translating_nodes.clean_stacks()
|
||||
python_code = translating_nodes.trans(parsed) # syntax tree to python code
|
||||
cache[cp_hash] = {
|
||||
'compilation_plan': compilation_plan,
|
||||
'proto_python_code': python_code,
|
||||
}
|
||||
|
||||
python_code = match_increaser_str.wrap_up(python_code)
|
||||
python_code = match_increaser_num.wrap_up(python_code)
|
||||
|
||||
return HEADER + python_code
|
||||
|
||||
def trasnlate(js, HEADER=DEFAULT_HEADER):
|
||||
"""js has to be a javascript source code.
|
||||
returns equivalent python code.
|
||||
|
||||
Equivalent to translate_js"""
|
||||
return translate_js(js, HEADER)
|
||||
|
||||
|
||||
syntax_tree_translate = translating_nodes.trans
|
||||
|
||||
if __name__=='__main__':
|
||||
PROFILE = False
|
||||
import js2py
|
||||
import codecs
|
||||
def main():
|
||||
with codecs.open("esprima.js", "r", "utf-8") as f:
|
||||
d = f.read()
|
||||
r = js2py.translate_js(d)
|
||||
|
||||
with open('res.py','wb') as f2:
|
||||
f2.write(r)
|
||||
exec(r, {})
|
||||
if PROFILE:
|
||||
import cProfile
|
||||
cProfile.run('main()', sort='tottime')
|
||||
else:
|
||||
main()
|
0
lib/js2py/utils/__init__.py
Normal file
0
lib/js2py/utils/__init__.py
Normal file
239
lib/js2py/utils/injector.py
Normal file
239
lib/js2py/utils/injector.py
Normal file
|
@ -0,0 +1,239 @@
|
|||
__all__ = ['fix_js_args']
|
||||
|
||||
import types
|
||||
from collections import namedtuple
|
||||
import opcode
|
||||
import six
|
||||
import sys
|
||||
import dis
|
||||
|
||||
if six.PY3:
|
||||
xrange = range
|
||||
chr = lambda x: x
|
||||
|
||||
# Opcode constants used for comparison and replacecment
|
||||
LOAD_FAST = opcode.opmap['LOAD_FAST']
|
||||
LOAD_GLOBAL = opcode.opmap['LOAD_GLOBAL']
|
||||
STORE_FAST = opcode.opmap['STORE_FAST']
|
||||
|
||||
def fix_js_args(func):
|
||||
'''Use this function when unsure whether func takes this and arguments as its last 2 args.
|
||||
It will append 2 args if it does not.'''
|
||||
fcode = six.get_function_code(func)
|
||||
fargs = fcode.co_varnames[fcode.co_argcount-2:fcode.co_argcount]
|
||||
if fargs==('this', 'arguments') or fargs==('arguments', 'var'):
|
||||
return func
|
||||
code = append_arguments(six.get_function_code(func), ('this','arguments'))
|
||||
|
||||
return types.FunctionType(code, six.get_function_globals(func), func.__name__, closure=six.get_function_closure(func))
|
||||
|
||||
|
||||
def append_arguments(code_obj, new_locals):
|
||||
co_varnames = code_obj.co_varnames # Old locals
|
||||
co_names = code_obj.co_names # Old globals
|
||||
co_names+=tuple(e for e in new_locals if e not in co_names)
|
||||
co_argcount = code_obj.co_argcount # Argument count
|
||||
co_code = code_obj.co_code # The actual bytecode as a string
|
||||
|
||||
# Make one pass over the bytecode to identify names that should be
|
||||
# left in code_obj.co_names.
|
||||
not_removed = set(opcode.hasname) - set([LOAD_GLOBAL])
|
||||
saved_names = set()
|
||||
for inst in instructions(code_obj):
|
||||
if inst[0] in not_removed:
|
||||
saved_names.add(co_names[inst[1]])
|
||||
|
||||
# Build co_names for the new code object. This should consist of
|
||||
# globals that were only accessed via LOAD_GLOBAL
|
||||
names = tuple(name for name in co_names
|
||||
if name not in set(new_locals) - saved_names)
|
||||
|
||||
# Build a dictionary that maps the indices of the entries in co_names
|
||||
# to their entry in the new co_names
|
||||
name_translations = dict((co_names.index(name), i)
|
||||
for i, name in enumerate(names))
|
||||
|
||||
# Build co_varnames for the new code object. This should consist of
|
||||
# the entirety of co_varnames with new_locals spliced in after the
|
||||
# arguments
|
||||
new_locals_len = len(new_locals)
|
||||
varnames = (co_varnames[:co_argcount] + new_locals +
|
||||
co_varnames[co_argcount:])
|
||||
|
||||
# Build the dictionary that maps indices of entries in the old co_varnames
|
||||
# to their indices in the new co_varnames
|
||||
range1, range2 = xrange(co_argcount), xrange(co_argcount, len(co_varnames))
|
||||
varname_translations = dict((i, i) for i in range1)
|
||||
varname_translations.update((i, i + new_locals_len) for i in range2)
|
||||
|
||||
# Build the dictionary that maps indices of deleted entries of co_names
|
||||
# to their indices in the new co_varnames
|
||||
names_to_varnames = dict((co_names.index(name), varnames.index(name))
|
||||
for name in new_locals)
|
||||
|
||||
# Now we modify the actual bytecode
|
||||
modified = []
|
||||
for inst in instructions(code_obj):
|
||||
op, arg = inst.opcode, inst.arg
|
||||
# If the instruction is a LOAD_GLOBAL, we have to check to see if
|
||||
# it's one of the globals that we are replacing. Either way,
|
||||
# update its arg using the appropriate dict.
|
||||
if inst.opcode == LOAD_GLOBAL:
|
||||
if inst.arg in names_to_varnames:
|
||||
op = LOAD_FAST
|
||||
arg = names_to_varnames[inst.arg]
|
||||
elif inst.arg in name_translations:
|
||||
arg = name_translations[inst.arg]
|
||||
else:
|
||||
raise ValueError("a name was lost in translation")
|
||||
# If it accesses co_varnames or co_names then update its argument.
|
||||
elif inst.opcode in opcode.haslocal:
|
||||
arg = varname_translations[inst.arg]
|
||||
elif inst.opcode in opcode.hasname:
|
||||
arg = name_translations[inst.arg]
|
||||
modified.extend(write_instruction(op, arg))
|
||||
if six.PY2:
|
||||
code = ''.join(modified)
|
||||
args = (co_argcount + new_locals_len,
|
||||
code_obj.co_nlocals + new_locals_len,
|
||||
code_obj.co_stacksize,
|
||||
code_obj.co_flags,
|
||||
code,
|
||||
code_obj.co_consts,
|
||||
names,
|
||||
varnames,
|
||||
code_obj.co_filename,
|
||||
code_obj.co_name,
|
||||
code_obj.co_firstlineno,
|
||||
code_obj.co_lnotab,
|
||||
code_obj.co_freevars,
|
||||
code_obj.co_cellvars)
|
||||
else:
|
||||
code = bytes(modified)
|
||||
args = (co_argcount + new_locals_len,
|
||||
0,
|
||||
code_obj.co_nlocals + new_locals_len,
|
||||
code_obj.co_stacksize,
|
||||
code_obj.co_flags,
|
||||
code,
|
||||
code_obj.co_consts,
|
||||
names,
|
||||
varnames,
|
||||
code_obj.co_filename,
|
||||
code_obj.co_name,
|
||||
code_obj.co_firstlineno,
|
||||
code_obj.co_lnotab,
|
||||
code_obj.co_freevars,
|
||||
code_obj.co_cellvars)
|
||||
|
||||
# Done modifying codestring - make the code object
|
||||
return types.CodeType(*args)
|
||||
|
||||
|
||||
def instructions(code_obj):
|
||||
# easy for python 3.4+
|
||||
if sys.version_info >= (3, 4):
|
||||
for inst in dis.Bytecode(code_obj):
|
||||
yield inst
|
||||
else:
|
||||
# otherwise we have to manually parse
|
||||
code = code_obj.co_code
|
||||
NewInstruction = namedtuple('Instruction', ('opcode', 'arg'))
|
||||
if six.PY2:
|
||||
code = map(ord, code)
|
||||
i, L = 0, len(code)
|
||||
extended_arg = 0
|
||||
while i < L:
|
||||
op = code[i]
|
||||
i+= 1
|
||||
if op < opcode.HAVE_ARGUMENT:
|
||||
yield NewInstruction(op, None)
|
||||
continue
|
||||
oparg = code[i] + (code[i+1] << 8) + extended_arg
|
||||
extended_arg = 0
|
||||
i += 2
|
||||
if op == opcode.EXTENDED_ARG:
|
||||
extended_arg = oparg << 16
|
||||
continue
|
||||
yield NewInstruction(op, oparg)
|
||||
|
||||
def write_instruction(op, arg):
|
||||
if sys.version_info < (3, 6):
|
||||
if arg is None:
|
||||
return [chr(op)]
|
||||
elif arg <= 65536:
|
||||
return [chr(op), chr(arg & 255), chr((arg >> 8) & 255)]
|
||||
elif arg <= 4294967296:
|
||||
return [chr(opcode.EXTENDED_ARG),
|
||||
chr((arg >> 16) & 255),
|
||||
chr((arg >> 24) & 255),
|
||||
chr(op),
|
||||
chr(arg & 255),
|
||||
chr((arg >> 8) & 255)]
|
||||
else:
|
||||
raise ValueError("Invalid oparg: {0} is too large".format(oparg))
|
||||
else: # python 3.6+ uses wordcode instead of bytecode and they already supply all the EXTENDEND_ARG ops :)
|
||||
if arg is None:
|
||||
return [chr(op), 0]
|
||||
return [chr(op), arg & 255]
|
||||
# the code below is for case when extended args are to be determined automatically
|
||||
# if op == opcode.EXTENDED_ARG:
|
||||
# return [] # this will be added automatically
|
||||
# elif arg < 1 << 8:
|
||||
# return [chr(op), arg]
|
||||
# elif arg < 1 << 32:
|
||||
# subs = [1<<24, 1<<16, 1<<8] # allowed op extension sizes
|
||||
# for sub in subs:
|
||||
# if arg >= sub:
|
||||
# fit = int(arg / sub)
|
||||
# return [chr(opcode.EXTENDED_ARG), fit] + write_instruction(op, arg - fit * sub)
|
||||
# else:
|
||||
# raise ValueError("Invalid oparg: {0} is too large".format(oparg))
|
||||
|
||||
|
||||
def check(code_obj):
|
||||
old_bytecode = code_obj.co_code
|
||||
insts = list(instructions(code_obj))
|
||||
|
||||
pos_to_inst = {}
|
||||
bytelist = []
|
||||
|
||||
for inst in insts:
|
||||
pos_to_inst[len(bytelist)] = inst
|
||||
bytelist.extend(write_instruction(inst.opcode, inst.arg))
|
||||
if six.PY2:
|
||||
new_bytecode = ''.join(bytelist)
|
||||
else:
|
||||
new_bytecode = bytes(bytelist)
|
||||
if new_bytecode != old_bytecode:
|
||||
print(new_bytecode)
|
||||
print(old_bytecode)
|
||||
for i in range(min(len(new_bytecode), len(old_bytecode))):
|
||||
if old_bytecode[i] != new_bytecode[i]:
|
||||
while 1:
|
||||
if i in pos_to_inst:
|
||||
print(pos_to_inst[i])
|
||||
print(pos_to_inst[i-2])
|
||||
print(list(map(chr, old_bytecode))[i-4:i+8])
|
||||
print(bytelist[i-4:i+8])
|
||||
break
|
||||
raise RuntimeError('Your python version made changes to the bytecode')
|
||||
|
||||
|
||||
check(six.get_function_code(check))
|
||||
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
x = 'Wrong'
|
||||
dick = 3000
|
||||
def func(a):
|
||||
print(x,y,z, a)
|
||||
print(dick)
|
||||
d = (x,)
|
||||
for e in (e for e in x):
|
||||
print(e)
|
||||
return x, y, z
|
||||
func2 =types.FunctionType(append_arguments(six.get_function_code(func), ('x', 'y', 'z')), six.get_function_globals(func), func.__name__, closure=six.get_function_closure(func))
|
||||
args = (2,2,3,4),3,4
|
||||
assert func2(1, *args) == args
|
4
lib/pyjsparser/__init__.py
Normal file
4
lib/pyjsparser/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
__all__ = ['PyJsParser', 'parse', 'JsSyntaxError']
|
||||
__author__ = 'Piotr Dabkowski'
|
||||
__version__ = '2.2.0'
|
||||
from .parser import PyJsParser, parse, JsSyntaxError
|
2905
lib/pyjsparser/parser.py
Normal file
2905
lib/pyjsparser/parser.py
Normal file
File diff suppressed because it is too large
Load diff
303
lib/pyjsparser/pyjsparserdata.py
Normal file
303
lib/pyjsparser/pyjsparserdata.py
Normal file
|
@ -0,0 +1,303 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright 2014, 2015 Piotr Dabkowski
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the 'Software'),
|
||||
# to deal in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
# the Software, and to permit persons to whom the Software is furnished to do so, subject
|
||||
# to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all copies or
|
||||
# substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import unicodedata
|
||||
from collections import defaultdict
|
||||
|
||||
PY3 = sys.version_info >= (3,0)
|
||||
|
||||
if PY3:
|
||||
unichr = chr
|
||||
xrange = range
|
||||
unicode = str
|
||||
|
||||
token = {
|
||||
'BooleanLiteral': 1,
|
||||
'EOF': 2,
|
||||
'Identifier': 3,
|
||||
'Keyword': 4,
|
||||
'NullLiteral': 5,
|
||||
'NumericLiteral': 6,
|
||||
'Punctuator': 7,
|
||||
'StringLiteral': 8,
|
||||
'RegularExpression': 9,
|
||||
'Template': 10
|
||||
}
|
||||
|
||||
|
||||
TokenName = dict((v,k) for k,v in token.items())
|
||||
|
||||
FnExprTokens = ['(', '{', '[', 'in', 'typeof', 'instanceof', 'new',
|
||||
'return', 'case', 'delete', 'throw', 'void',
|
||||
# assignment operators
|
||||
'=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=',
|
||||
'&=', '|=', '^=', ',',
|
||||
# binary/unary operators
|
||||
'+', '-', '*', '/', '%', '++', '--', '<<', '>>', '>>>', '&',
|
||||
'|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
|
||||
'<=', '<', '>', '!=', '!==']
|
||||
|
||||
syntax= set(('AssignmentExpression',
|
||||
'AssignmentPattern',
|
||||
'ArrayExpression',
|
||||
'ArrayPattern',
|
||||
'ArrowFunctionExpression',
|
||||
'BlockStatement',
|
||||
'BinaryExpression',
|
||||
'BreakStatement',
|
||||
'CallExpression',
|
||||
'CatchClause',
|
||||
'ClassBody',
|
||||
'ClassDeclaration',
|
||||
'ClassExpression',
|
||||
'ConditionalExpression',
|
||||
'ContinueStatement',
|
||||
'DoWhileStatement',
|
||||
'DebuggerStatement',
|
||||
'EmptyStatement',
|
||||
'ExportAllDeclaration',
|
||||
'ExportDefaultDeclaration',
|
||||
'ExportNamedDeclaration',
|
||||
'ExportSpecifier',
|
||||
'ExpressionStatement',
|
||||
'ForStatement',
|
||||
'ForInStatement',
|
||||
'FunctionDeclaration',
|
||||
'FunctionExpression',
|
||||
'Identifier',
|
||||
'IfStatement',
|
||||
'ImportDeclaration',
|
||||
'ImportDefaultSpecifier',
|
||||
'ImportNamespaceSpecifier',
|
||||
'ImportSpecifier',
|
||||
'Literal',
|
||||
'LabeledStatement',
|
||||
'LogicalExpression',
|
||||
'MemberExpression',
|
||||
'MethodDefinition',
|
||||
'NewExpression',
|
||||
'ObjectExpression',
|
||||
'ObjectPattern',
|
||||
'Program',
|
||||
'Property',
|
||||
'RestElement',
|
||||
'ReturnStatement',
|
||||
'SequenceExpression',
|
||||
'SpreadElement',
|
||||
'Super',
|
||||
'SwitchCase',
|
||||
'SwitchStatement',
|
||||
'TaggedTemplateExpression',
|
||||
'TemplateElement',
|
||||
'TemplateLiteral',
|
||||
'ThisExpression',
|
||||
'ThrowStatement',
|
||||
'TryStatement',
|
||||
'UnaryExpression',
|
||||
'UpdateExpression',
|
||||
'VariableDeclaration',
|
||||
'VariableDeclarator',
|
||||
'WhileStatement',
|
||||
'WithStatement'))
|
||||
|
||||
|
||||
# Error messages should be identical to V8.
|
||||
messages = {
|
||||
'UnexpectedToken': 'Unexpected token %s',
|
||||
'UnexpectedNumber': 'Unexpected number',
|
||||
'UnexpectedString': 'Unexpected string',
|
||||
'UnexpectedIdentifier': 'Unexpected identifier',
|
||||
'UnexpectedReserved': 'Unexpected reserved word',
|
||||
'UnexpectedTemplate': 'Unexpected quasi %s',
|
||||
'UnexpectedEOS': 'Unexpected end of input',
|
||||
'NewlineAfterThrow': 'Illegal newline after throw',
|
||||
'InvalidRegExp': 'Invalid regular expression',
|
||||
'UnterminatedRegExp': 'Invalid regular expression: missing /',
|
||||
'InvalidLHSInAssignment': 'Invalid left-hand side in assignment',
|
||||
'InvalidLHSInForIn': 'Invalid left-hand side in for-in',
|
||||
'MultipleDefaultsInSwitch': 'More than one default clause in switch statement',
|
||||
'NoCatchOrFinally': 'Missing catch or finally after try',
|
||||
'UnknownLabel': 'Undefined label \'%s\'',
|
||||
'Redeclaration': '%s \'%s\' has already been declared',
|
||||
'IllegalContinue': 'Illegal continue statement',
|
||||
'IllegalBreak': 'Illegal break statement',
|
||||
'IllegalReturn': 'Illegal return statement',
|
||||
'StrictModeWith': 'Strict mode code may not include a with statement',
|
||||
'StrictCatchVariable': 'Catch variable may not be eval or arguments in strict mode',
|
||||
'StrictVarName': 'Variable name may not be eval or arguments in strict mode',
|
||||
'StrictParamName': 'Parameter name eval or arguments is not allowed in strict mode',
|
||||
'StrictParamDupe': 'Strict mode function may not have duplicate parameter names',
|
||||
'StrictFunctionName': 'Function name may not be eval or arguments in strict mode',
|
||||
'StrictOctalLiteral': 'Octal literals are not allowed in strict mode.',
|
||||
'StrictDelete': 'Delete of an unqualified identifier in strict mode.',
|
||||
'StrictLHSAssignment': 'Assignment to eval or arguments is not allowed in strict mode',
|
||||
'StrictLHSPostfix': 'Postfix increment/decrement may not have eval or arguments operand in strict mode',
|
||||
'StrictLHSPrefix': 'Prefix increment/decrement may not have eval or arguments operand in strict mode',
|
||||
'StrictReservedWord': 'Use of future reserved word in strict mode',
|
||||
'TemplateOctalLiteral': 'Octal literals are not allowed in template strings.',
|
||||
'ParameterAfterRestParameter': 'Rest parameter must be last formal parameter',
|
||||
'DefaultRestParameter': 'Unexpected token =',
|
||||
'ObjectPatternAsRestParameter': 'Unexpected token {',
|
||||
'DuplicateProtoProperty': 'Duplicate __proto__ fields are not allowed in object literals',
|
||||
'ConstructorSpecialMethod': 'Class constructor may not be an accessor',
|
||||
'DuplicateConstructor': 'A class may only have one constructor',
|
||||
'StaticPrototype': 'Classes may not have static property named prototype',
|
||||
'MissingFromClause': 'Unexpected token',
|
||||
'NoAsAfterImportNamespace': 'Unexpected token',
|
||||
'InvalidModuleSpecifier': 'Unexpected token',
|
||||
'IllegalImportDeclaration': 'Unexpected token',
|
||||
'IllegalExportDeclaration': 'Unexpected token'}
|
||||
|
||||
PRECEDENCE = {'||':1,
|
||||
'&&':2,
|
||||
'|':3,
|
||||
'^':4,
|
||||
'&':5,
|
||||
'==':6,
|
||||
'!=':6,
|
||||
'===':6,
|
||||
'!==':6,
|
||||
'<':7,
|
||||
'>':7,
|
||||
'<=':7,
|
||||
'>=':7,
|
||||
'instanceof':7,
|
||||
'in':7,
|
||||
'<<':8,
|
||||
'>>':8,
|
||||
'>>>':8,
|
||||
'+':9,
|
||||
'-':9,
|
||||
'*':11,
|
||||
'/':11,
|
||||
'%':11}
|
||||
|
||||
class Token: pass
|
||||
class Syntax: pass
|
||||
class Messages: pass
|
||||
class PlaceHolders:
|
||||
ArrowParameterPlaceHolder = 'ArrowParameterPlaceHolder'
|
||||
|
||||
for k,v in token.items():
|
||||
setattr(Token, k, v)
|
||||
|
||||
for e in syntax:
|
||||
setattr(Syntax, e, e)
|
||||
|
||||
for k,v in messages.items():
|
||||
setattr(Messages, k, v)
|
||||
|
||||
#http://stackoverflow.com/questions/14245893/efficiently-list-all-characters-in-a-given-unicode-category
|
||||
BOM = u'\uFEFF'
|
||||
ZWJ = u'\u200D'
|
||||
ZWNJ = u'\u200C'
|
||||
TAB = u'\u0009'
|
||||
VT = u'\u000B'
|
||||
FF = u'\u000C'
|
||||
SP = u'\u0020'
|
||||
NBSP = u'\u00A0'
|
||||
LF = u'\u000A'
|
||||
CR = u'\u000D'
|
||||
LS = u'\u2028'
|
||||
PS = u'\u2029'
|
||||
|
||||
U_CATEGORIES = defaultdict(list)
|
||||
for c in map(unichr, range(sys.maxunicode + 1)):
|
||||
U_CATEGORIES[unicodedata.category(c)].append(c)
|
||||
UNICODE_LETTER = set(U_CATEGORIES['Lu']+U_CATEGORIES['Ll']+
|
||||
U_CATEGORIES['Lt']+U_CATEGORIES['Lm']+
|
||||
U_CATEGORIES['Lo']+U_CATEGORIES['Nl'])
|
||||
UNICODE_COMBINING_MARK = set(U_CATEGORIES['Mn']+U_CATEGORIES['Mc'])
|
||||
UNICODE_DIGIT = set(U_CATEGORIES['Nd'])
|
||||
UNICODE_CONNECTOR_PUNCTUATION = set(U_CATEGORIES['Pc'])
|
||||
IDENTIFIER_START = UNICODE_LETTER.union(set(('$','_', '\\'))) # and some fucking unicode escape sequence
|
||||
IDENTIFIER_PART = IDENTIFIER_START.union(UNICODE_COMBINING_MARK).union(UNICODE_DIGIT)\
|
||||
.union(UNICODE_CONNECTOR_PUNCTUATION).union(set((ZWJ, ZWNJ)))
|
||||
|
||||
WHITE_SPACE = set((0x20, 0x09, 0x0B, 0x0C, 0xA0, 0x1680,
|
||||
0x180E, 0x2000, 0x2001, 0x2002, 0x2003,
|
||||
0x2004, 0x2005, 0x2006, 0x2007, 0x2008,
|
||||
0x2009, 0x200A, 0x202F, 0x205F, 0x3000,
|
||||
0xFEFF))
|
||||
|
||||
LINE_TERMINATORS = set((0x0A, 0x0D, 0x2028, 0x2029))
|
||||
|
||||
def isIdentifierStart(ch):
|
||||
return (ch if isinstance(ch, unicode) else unichr(ch)) in IDENTIFIER_START
|
||||
|
||||
def isIdentifierPart(ch):
|
||||
return (ch if isinstance(ch, unicode) else unichr(ch)) in IDENTIFIER_PART
|
||||
|
||||
def isWhiteSpace(ch):
|
||||
return (ord(ch) if isinstance(ch, unicode) else ch) in WHITE_SPACE
|
||||
|
||||
def isLineTerminator(ch):
|
||||
return (ord(ch) if isinstance(ch, unicode) else ch) in LINE_TERMINATORS
|
||||
|
||||
OCTAL = set(('0', '1', '2', '3', '4', '5', '6', '7'))
|
||||
DEC = set(('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'))
|
||||
HEX = set('0123456789abcdefABCDEF')
|
||||
HEX_CONV = dict(('0123456789abcdef'[n],n) for n in xrange(16))
|
||||
for i,e in enumerate('ABCDEF', 10):
|
||||
HEX_CONV[e] = i
|
||||
|
||||
|
||||
def isDecimalDigit(ch):
|
||||
return (ch if isinstance(ch, unicode) else unichr(ch)) in DEC
|
||||
|
||||
def isHexDigit(ch):
|
||||
return (ch if isinstance(ch, unicode) else unichr(ch)) in HEX
|
||||
|
||||
def isOctalDigit(ch):
|
||||
return (ch if isinstance(ch, unicode) else unichr(ch)) in OCTAL
|
||||
|
||||
def isFutureReservedWord(w):
|
||||
return w in ('enum', 'export', 'import', 'super')
|
||||
|
||||
|
||||
RESERVED_WORD = set(('implements', 'interface', 'package', 'private', 'protected', 'public', 'static', 'yield', 'let'))
|
||||
def isStrictModeReservedWord(w):
|
||||
return w in RESERVED_WORD
|
||||
|
||||
def isRestrictedWord(w):
|
||||
return w in ('eval', 'arguments')
|
||||
|
||||
|
||||
KEYWORDS = set(('if', 'in', 'do', 'var', 'for', 'new', 'try', 'let', 'this', 'else', 'case',
|
||||
'void', 'with', 'enum', 'while', 'break', 'catch', 'throw', 'const', 'yield',
|
||||
'class', 'super', 'return', 'typeof', 'delete', 'switch', 'export', 'import',
|
||||
'default', 'finally', 'extends', 'function', 'continue', 'debugger', 'instanceof', 'pyimport'))
|
||||
def isKeyword(w):
|
||||
# 'const' is specialized as Keyword in V8.
|
||||
# 'yield' and 'let' are for compatibility with SpiderMonkey and ES.next.
|
||||
# Some others are from future reserved words.
|
||||
return w in KEYWORDS
|
||||
|
||||
|
||||
class JsSyntaxError(Exception): pass
|
||||
|
||||
if __name__=='__main__':
|
||||
assert isLineTerminator('\n')
|
||||
assert isLineTerminator(0x0A)
|
||||
assert isIdentifierStart('$')
|
||||
assert isIdentifierStart(100)
|
||||
assert isWhiteSpace(' ')
|
471
lib/pyjsparser/std_nodes.py
Normal file
471
lib/pyjsparser/std_nodes.py
Normal file
|
@ -0,0 +1,471 @@
|
|||
from .pyjsparserdata import *
|
||||
|
||||
|
||||
class BaseNode:
|
||||
def finish(self):
|
||||
pass
|
||||
|
||||
def finishArrayExpression(self, elements):
|
||||
self.type = Syntax.ArrayExpression
|
||||
self.elements = elements
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishArrayPattern(self, elements):
|
||||
self.type = Syntax.ArrayPattern
|
||||
self.elements = elements
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishArrowFunctionExpression(self, params, defaults, body, expression):
|
||||
self.type = Syntax.ArrowFunctionExpression
|
||||
self.id = None
|
||||
self.params = params
|
||||
self.defaults = defaults
|
||||
self.body = body
|
||||
self.generator = False
|
||||
self.expression = expression
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishAssignmentExpression(self, operator, left, right):
|
||||
self.type = Syntax.AssignmentExpression
|
||||
self.operator = operator
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishAssignmentPattern(self, left, right):
|
||||
self.type = Syntax.AssignmentPattern
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishBinaryExpression(self, operator, left, right):
|
||||
self.type = Syntax.LogicalExpression if (operator == '||' or operator == '&&') else Syntax.BinaryExpression
|
||||
self.operator = operator
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishBlockStatement(self, body):
|
||||
self.type = Syntax.BlockStatement
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishBreakStatement(self, label):
|
||||
self.type = Syntax.BreakStatement
|
||||
self.label = label
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishCallExpression(self, callee, args):
|
||||
self.type = Syntax.CallExpression
|
||||
self.callee = callee
|
||||
self.arguments = args
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishCatchClause(self, param, body):
|
||||
self.type = Syntax.CatchClause
|
||||
self.param = param
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishClassBody(self, body):
|
||||
self.type = Syntax.ClassBody
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishClassDeclaration(self, id, superClass, body):
|
||||
self.type = Syntax.ClassDeclaration
|
||||
self.id = id
|
||||
self.superClass = superClass
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishClassExpression(self, id, superClass, body):
|
||||
self.type = Syntax.ClassExpression
|
||||
self.id = id
|
||||
self.superClass = superClass
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishConditionalExpression(self, test, consequent, alternate):
|
||||
self.type = Syntax.ConditionalExpression
|
||||
self.test = test
|
||||
self.consequent = consequent
|
||||
self.alternate = alternate
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishContinueStatement(self, label):
|
||||
self.type = Syntax.ContinueStatement
|
||||
self.label = label
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishDebuggerStatement(self, ):
|
||||
self.type = Syntax.DebuggerStatement
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishDoWhileStatement(self, body, test):
|
||||
self.type = Syntax.DoWhileStatement
|
||||
self.body = body
|
||||
self.test = test
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishEmptyStatement(self, ):
|
||||
self.type = Syntax.EmptyStatement
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishExpressionStatement(self, expression):
|
||||
self.type = Syntax.ExpressionStatement
|
||||
self.expression = expression
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishForStatement(self, init, test, update, body):
|
||||
self.type = Syntax.ForStatement
|
||||
self.init = init
|
||||
self.test = test
|
||||
self.update = update
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishForInStatement(self, left, right, body):
|
||||
self.type = Syntax.ForInStatement
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.body = body
|
||||
self.each = False
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishFunctionDeclaration(self, id, params, defaults, body):
|
||||
self.type = Syntax.FunctionDeclaration
|
||||
self.id = id
|
||||
self.params = params
|
||||
self.defaults = defaults
|
||||
self.body = body
|
||||
self.generator = False
|
||||
self.expression = False
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishFunctionExpression(self, id, params, defaults, body):
|
||||
self.type = Syntax.FunctionExpression
|
||||
self.id = id
|
||||
self.params = params
|
||||
self.defaults = defaults
|
||||
self.body = body
|
||||
self.generator = False
|
||||
self.expression = False
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishIdentifier(self, name):
|
||||
self.type = Syntax.Identifier
|
||||
self.name = name
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishIfStatement(self, test, consequent, alternate):
|
||||
self.type = Syntax.IfStatement
|
||||
self.test = test
|
||||
self.consequent = consequent
|
||||
self.alternate = alternate
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishLabeledStatement(self, label, body):
|
||||
self.type = Syntax.LabeledStatement
|
||||
self.label = label
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishLiteral(self, token):
|
||||
self.type = Syntax.Literal
|
||||
self.value = token['value']
|
||||
self.raw = None # todo fix it?
|
||||
if token.get('regex'):
|
||||
self.regex = token['regex']
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishMemberExpression(self, accessor, object, property):
|
||||
self.type = Syntax.MemberExpression
|
||||
self.computed = accessor == '['
|
||||
self.object = object
|
||||
self.property = property
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishNewExpression(self, callee, args):
|
||||
self.type = Syntax.NewExpression
|
||||
self.callee = callee
|
||||
self.arguments = args
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishObjectExpression(self, properties):
|
||||
self.type = Syntax.ObjectExpression
|
||||
self.properties = properties
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishObjectPattern(self, properties):
|
||||
self.type = Syntax.ObjectPattern
|
||||
self.properties = properties
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishPostfixExpression(self, operator, argument):
|
||||
self.type = Syntax.UpdateExpression
|
||||
self.operator = operator
|
||||
self.argument = argument
|
||||
self.prefix = False
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishProgram(self, body):
|
||||
self.type = Syntax.Program
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishPyimport(self, imp):
|
||||
self.type = 'PyimportStatement'
|
||||
self.imp = imp
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishProperty(self, kind, key, computed, value, method, shorthand):
|
||||
self.type = Syntax.Property
|
||||
self.key = key
|
||||
self.computed = computed
|
||||
self.value = value
|
||||
self.kind = kind
|
||||
self.method = method
|
||||
self.shorthand = shorthand
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishRestElement(self, argument):
|
||||
self.type = Syntax.RestElement
|
||||
self.argument = argument
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishReturnStatement(self, argument):
|
||||
self.type = Syntax.ReturnStatement
|
||||
self.argument = argument
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishSequenceExpression(self, expressions):
|
||||
self.type = Syntax.SequenceExpression
|
||||
self.expressions = expressions
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishSpreadElement(self, argument):
|
||||
self.type = Syntax.SpreadElement
|
||||
self.argument = argument
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishSwitchCase(self, test, consequent):
|
||||
self.type = Syntax.SwitchCase
|
||||
self.test = test
|
||||
self.consequent = consequent
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishSuper(self, ):
|
||||
self.type = Syntax.Super
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishSwitchStatement(self, discriminant, cases):
|
||||
self.type = Syntax.SwitchStatement
|
||||
self.discriminant = discriminant
|
||||
self.cases = cases
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishTaggedTemplateExpression(self, tag, quasi):
|
||||
self.type = Syntax.TaggedTemplateExpression
|
||||
self.tag = tag
|
||||
self.quasi = quasi
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishTemplateElement(self, value, tail):
|
||||
self.type = Syntax.TemplateElement
|
||||
self.value = value
|
||||
self.tail = tail
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishTemplateLiteral(self, quasis, expressions):
|
||||
self.type = Syntax.TemplateLiteral
|
||||
self.quasis = quasis
|
||||
self.expressions = expressions
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishThisExpression(self, ):
|
||||
self.type = Syntax.ThisExpression
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishThrowStatement(self, argument):
|
||||
self.type = Syntax.ThrowStatement
|
||||
self.argument = argument
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishTryStatement(self, block, handler, finalizer):
|
||||
self.type = Syntax.TryStatement
|
||||
self.block = block
|
||||
self.guardedHandlers = []
|
||||
self.handlers = [handler] if handler else []
|
||||
self.handler = handler
|
||||
self.finalizer = finalizer
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishUnaryExpression(self, operator, argument):
|
||||
self.type = Syntax.UpdateExpression if (operator == '++' or operator == '--') else Syntax.UnaryExpression
|
||||
self.operator = operator
|
||||
self.argument = argument
|
||||
self.prefix = True
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishVariableDeclaration(self, declarations):
|
||||
self.type = Syntax.VariableDeclaration
|
||||
self.declarations = declarations
|
||||
self.kind = 'var'
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishLexicalDeclaration(self, declarations, kind):
|
||||
self.type = Syntax.VariableDeclaration
|
||||
self.declarations = declarations
|
||||
self.kind = kind
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishVariableDeclarator(self, id, init):
|
||||
self.type = Syntax.VariableDeclarator
|
||||
self.id = id
|
||||
self.init = init
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishWhileStatement(self, test, body):
|
||||
self.type = Syntax.WhileStatement
|
||||
self.test = test
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishWithStatement(self, object, body):
|
||||
self.type = Syntax.WithStatement
|
||||
self.object = object
|
||||
self.body = body
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishExportSpecifier(self, local, exported):
|
||||
self.type = Syntax.ExportSpecifier
|
||||
self.exported = exported or local
|
||||
self.local = local
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishImportDefaultSpecifier(self, local):
|
||||
self.type = Syntax.ImportDefaultSpecifier
|
||||
self.local = local
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishImportNamespaceSpecifier(self, local):
|
||||
self.type = Syntax.ImportNamespaceSpecifier
|
||||
self.local = local
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishExportNamedDeclaration(self, declaration, specifiers, src):
|
||||
self.type = Syntax.ExportNamedDeclaration
|
||||
self.declaration = declaration
|
||||
self.specifiers = specifiers
|
||||
self.source = src
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishExportDefaultDeclaration(self, declaration):
|
||||
self.type = Syntax.ExportDefaultDeclaration
|
||||
self.declaration = declaration
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishExportAllDeclaration(self, src):
|
||||
self.type = Syntax.ExportAllDeclaration
|
||||
self.source = src
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishImportSpecifier(self, local, imported):
|
||||
self.type = Syntax.ImportSpecifier
|
||||
self.local = local or imported
|
||||
self.imported = imported
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def finishImportDeclaration(self, specifiers, src):
|
||||
self.type = Syntax.ImportDeclaration
|
||||
self.specifiers = specifiers
|
||||
self.source = src
|
||||
self.finish()
|
||||
return self
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class Node(BaseNode):
|
||||
pass
|
||||
|
||||
|
||||
class WrappingNode(BaseNode):
|
||||
def __init__(self, startToken=None):
|
||||
pass
|
||||
|
||||
|
||||
def node_to_dict(node): # extremely important for translation speed
|
||||
if isinstance(node, list):
|
||||
return [node_to_dict(e) for e in node]
|
||||
elif isinstance(node, dict):
|
||||
return dict((k, node_to_dict(v)) for k, v in node.items())
|
||||
elif not isinstance(node, BaseNode):
|
||||
return node
|
||||
return dict((k, node_to_dict(v)) for k, v in node.__dict__.items())
|
|
@ -21,6 +21,7 @@ import datetime
|
|||
import sickbeard
|
||||
from lib.dateutil import parser
|
||||
from sickbeard.common import Quality
|
||||
from unidecode import unidecode
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
|
@ -45,6 +46,9 @@ class SearchResult:
|
|||
# used by some providers to store extra info associated with the result
|
||||
self.extraInfo = []
|
||||
|
||||
# assign function to get the data for the download
|
||||
self.get_data_func = None
|
||||
|
||||
# list of TVEpisode objects that this result is associated with
|
||||
self.episodes = episodes
|
||||
|
||||
|
@ -83,6 +87,15 @@ class SearchResult:
|
|||
def fileName(self):
|
||||
return self.episodes[0].prettyName() + '.' + self.resultType
|
||||
|
||||
def get_data(self):
|
||||
if None is not self.get_data_func:
|
||||
try:
|
||||
return self.get_data_func(self.url)
|
||||
except (StandardError, Exception):
|
||||
pass
|
||||
if self.extraInfo and 0 < len(self.extraInfo):
|
||||
return self.extraInfo[0]
|
||||
return None
|
||||
|
||||
class NZBSearchResult(SearchResult):
|
||||
"""
|
||||
|
@ -121,35 +134,36 @@ class AllShowsListUI:
|
|||
self.log = log
|
||||
|
||||
def selectSeries(self, allSeries):
|
||||
searchResults = []
|
||||
seriesnames = []
|
||||
search_results = []
|
||||
|
||||
# get all available shows
|
||||
if allSeries:
|
||||
if 'searchterm' in self.config:
|
||||
searchterm = self.config['searchterm']
|
||||
search_term = self.config.get('searchterm', '').lower()
|
||||
if search_term:
|
||||
# try to pick a show that's in my show list
|
||||
for curShow in allSeries:
|
||||
if curShow in searchResults:
|
||||
for cur_show in allSeries:
|
||||
if cur_show in search_results:
|
||||
continue
|
||||
|
||||
if 'seriesname' in curShow:
|
||||
seriesnames.append(curShow['seriesname'])
|
||||
if 'aliasnames' in curShow:
|
||||
seriesnames.extend(curShow['aliasnames'].split('|'))
|
||||
seriesnames = []
|
||||
if 'seriesname' in cur_show:
|
||||
name = cur_show['seriesname'].lower()
|
||||
seriesnames += [name, unidecode(name.encode('utf-8').decode('utf-8'))]
|
||||
if 'aliasnames' in cur_show:
|
||||
name = cur_show['aliasnames'].lower()
|
||||
seriesnames += name.split('|') + unidecode(name.encode('utf-8').decode('utf-8')).split('|')
|
||||
|
||||
for name in seriesnames:
|
||||
if searchterm.lower() in name.lower():
|
||||
if 'firstaired' not in curShow:
|
||||
curShow['firstaired'] = str(datetime.date.fromordinal(1))
|
||||
curShow['firstaired'] = re.sub('([-]0{2}){1,}', '', curShow['firstaired'])
|
||||
fixDate = parser.parse(curShow['firstaired'], fuzzy=True).date()
|
||||
curShow['firstaired'] = fixDate.strftime('%Y-%m-%d')
|
||||
if search_term in set(seriesnames):
|
||||
if 'firstaired' not in cur_show:
|
||||
cur_show['firstaired'] = str(datetime.date.fromordinal(1))
|
||||
cur_show['firstaired'] = re.sub('([-]0{2})+', '', cur_show['firstaired'])
|
||||
fix_date = parser.parse(cur_show['firstaired'], fuzzy=True).date()
|
||||
cur_show['firstaired'] = fix_date.strftime('%Y-%m-%d')
|
||||
|
||||
if curShow not in searchResults:
|
||||
searchResults += [curShow]
|
||||
if cur_show not in search_results:
|
||||
search_results += [cur_show]
|
||||
|
||||
return searchResults
|
||||
return search_results
|
||||
|
||||
|
||||
class ShowListUI:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# Author: Tyler Fenby <tylerfenby@gmail.com>
|
||||
# URL: http://code.google.com/p/sickbeard/
|
||||
#
|
||||
# This file is part of SickGear.
|
||||
#
|
||||
|
@ -16,26 +15,41 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import urllib
|
||||
import datetime
|
||||
|
||||
from sickbeard import db
|
||||
from sickbeard import logger
|
||||
from sickbeard.exceptions import ex, EpisodeNotFoundException
|
||||
from sickbeard import db, logger
|
||||
from sickbeard.common import FAILED, WANTED, Quality, statusStrings
|
||||
from sickbeard.exceptions import EpisodeNotFoundException, ex
|
||||
from sickbeard.history import dateFormat
|
||||
from sickbeard.common import Quality
|
||||
from sickbeard.common import WANTED, FAILED
|
||||
|
||||
|
||||
def prepareFailedName(release):
|
||||
def db_cmd(sql, params, select=True):
|
||||
|
||||
my_db = db.DBConnection('failed.db')
|
||||
sql_result = select and my_db.select(sql, params) or my_db.action(sql, params)
|
||||
return sql_result
|
||||
|
||||
|
||||
def db_select(sql, params):
|
||||
|
||||
return db_cmd(sql, params)
|
||||
|
||||
|
||||
def db_action(sql, params=None):
|
||||
|
||||
return db_cmd(sql, params, False)
|
||||
|
||||
|
||||
def prepare_failed_name(release):
|
||||
"""Standardizes release name for failed DB"""
|
||||
|
||||
fixed = urllib.unquote(release)
|
||||
if (fixed.endswith(".nzb")):
|
||||
fixed = fixed.rpartition(".")[0]
|
||||
if fixed.endswith('.nzb'):
|
||||
fixed = fixed.rpartition('.')[0]
|
||||
|
||||
fixed = re.sub("[\.\-\+\ ]", "_", fixed)
|
||||
fixed = re.sub('[.\-+ ]', '_', fixed)
|
||||
|
||||
if not isinstance(fixed, unicode):
|
||||
fixed = unicode(fixed, 'utf-8', 'replace')
|
||||
|
@ -43,57 +57,96 @@ def prepareFailedName(release):
|
|||
return fixed
|
||||
|
||||
|
||||
def logFailed(release):
|
||||
log_str = u""
|
||||
def add_failed(release):
|
||||
|
||||
size = -1
|
||||
provider = ""
|
||||
provider = ''
|
||||
|
||||
release = prepareFailedName(release)
|
||||
release = prepare_failed_name(release)
|
||||
|
||||
myDB = db.DBConnection('failed.db')
|
||||
sql_results = myDB.select("SELECT * FROM history WHERE release=?", [release])
|
||||
sql_results = db_select('SELECT * FROM history t WHERE t.release=?', [release])
|
||||
|
||||
if not any(sql_results):
|
||||
logger.log('Release not found in failed.db snatch history', logger.WARNING)
|
||||
|
||||
elif 1 < len(sql_results):
|
||||
logger.log('Multiple logged snatches found for release in failed.db', logger.WARNING)
|
||||
sizes = len(set(x['size'] for x in sql_results))
|
||||
providers = len(set(x['provider'] for x in sql_results))
|
||||
|
||||
if 1 == sizes:
|
||||
logger.log('However, they\'re all the same size. Continuing with found size', logger.WARNING)
|
||||
size = sql_results[0]['size']
|
||||
|
||||
if len(sql_results) == 0:
|
||||
logger.log(
|
||||
u"Release not found in snatch history.", logger.WARNING)
|
||||
elif len(sql_results) > 1:
|
||||
logger.log(u"Multiple logged snatches found for release", logger.WARNING)
|
||||
sizes = len(set(x["size"] for x in sql_results))
|
||||
providers = len(set(x["provider"] for x in sql_results))
|
||||
if sizes == 1:
|
||||
logger.log(u"However, they're all the same size. Continuing with found size.", logger.WARNING)
|
||||
size = sql_results[0]["size"]
|
||||
else:
|
||||
logger.log(
|
||||
u"They also vary in size. Deleting the logged snatches and recording this release with no size/provider",
|
||||
'They also vary in size. Deleting logged snatches and recording this release with no size/provider',
|
||||
logger.WARNING)
|
||||
for result in sql_results:
|
||||
deleteLoggedSnatch(result["release"], result["size"], result["provider"])
|
||||
remove_snatched(result['release'], result['size'], result['provider'])
|
||||
|
||||
if providers == 1:
|
||||
logger.log(u"They're also from the same provider. Using it as well.")
|
||||
provider = sql_results[0]["provider"]
|
||||
if 1 == providers:
|
||||
logger.log('They\'re also from the same provider. Using it as well')
|
||||
provider = sql_results[0]['provider']
|
||||
else:
|
||||
size = sql_results[0]["size"]
|
||||
provider = sql_results[0]["provider"]
|
||||
size = sql_results[0]['size']
|
||||
provider = sql_results[0]['provider']
|
||||
|
||||
if not hasFailed(release, size, provider):
|
||||
myDB = db.DBConnection('failed.db')
|
||||
myDB.action("INSERT INTO failed (release, size, provider) VALUES (?, ?, ?)", [release, size, provider])
|
||||
if not has_failed(release, size, provider):
|
||||
db_action('INSERT INTO failed (`release`, `size`, `provider`) VALUES (?, ?, ?)', [release, size, provider])
|
||||
|
||||
deleteLoggedSnatch(release, size, provider)
|
||||
|
||||
return log_str
|
||||
remove_snatched(release, size, provider)
|
||||
|
||||
|
||||
def logSuccess(release):
|
||||
release = prepareFailedName(release)
|
||||
def add_snatched(search_result):
|
||||
"""
|
||||
:param search_result: SearchResult object
|
||||
"""
|
||||
|
||||
myDB = db.DBConnection('failed.db')
|
||||
myDB.action("DELETE FROM history WHERE release=?", [release])
|
||||
log_date = datetime.datetime.today().strftime(dateFormat)
|
||||
|
||||
provider = 'unknown'
|
||||
if None is not search_result.provider:
|
||||
provider = search_result.provider.name
|
||||
|
||||
for episode in search_result.episodes:
|
||||
db_action(
|
||||
'INSERT INTO history (`date`, `size`, `release`, `provider`, `showid`, `season`, `episode`, `old_status`)'
|
||||
'VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[log_date, search_result.size, prepare_failed_name(search_result.name), provider,
|
||||
search_result.episodes[0].show.indexerid, episode.season, episode.episode, episode.status])
|
||||
|
||||
|
||||
def hasFailed(release, size, provider="%"):
|
||||
def set_episode_failed(ep_obj):
|
||||
|
||||
try:
|
||||
with ep_obj.lock:
|
||||
quality = Quality.splitCompositeStatus(ep_obj.status)[1]
|
||||
ep_obj.status = Quality.compositeStatus(FAILED, quality)
|
||||
ep_obj.saveToDB()
|
||||
|
||||
except EpisodeNotFoundException as e:
|
||||
logger.log('Unable to get episode, please set its status manually: %s' % ex(e), logger.WARNING)
|
||||
|
||||
|
||||
def remove_failed(release):
|
||||
|
||||
db_action('DELETE FROM history WHERE %s=?' % '`release`', [prepare_failed_name(release)])
|
||||
|
||||
|
||||
def remove_snatched(release, size, provider):
|
||||
|
||||
db_action('DELETE FROM history WHERE %s=? AND %s=? AND %s=?' % ('`release`', '`size`', '`provider`'),
|
||||
[prepare_failed_name(release), size, provider])
|
||||
|
||||
|
||||
def remove_old_history():
|
||||
|
||||
db_action('DELETE FROM history WHERE date < %s' %
|
||||
str((datetime.datetime.today() - datetime.timedelta(days=30)).strftime(dateFormat)))
|
||||
|
||||
|
||||
def has_failed(release, size, provider='%'):
|
||||
"""
|
||||
Returns True if a release has previously failed.
|
||||
|
||||
|
@ -101,125 +154,83 @@ def hasFailed(release, size, provider="%"):
|
|||
with that specific provider. Otherwise, return True if the release
|
||||
is found with any provider.
|
||||
"""
|
||||
|
||||
release = prepareFailedName(release)
|
||||
|
||||
myDB = db.DBConnection('failed.db')
|
||||
sql_results = myDB.select(
|
||||
"SELECT * FROM failed WHERE release=? AND size=? AND provider LIKE ?",
|
||||
[release, size, provider])
|
||||
|
||||
return (len(sql_results) > 0)
|
||||
return any(db_select('SELECT * FROM failed t WHERE t.release=? AND t.size=? AND t.provider LIKE ?',
|
||||
[prepare_failed_name(release), size, provider]))
|
||||
|
||||
|
||||
def revertEpisode(epObj):
|
||||
def revert_episode(ep_obj):
|
||||
"""Restore the episodes of a failed download to their original state"""
|
||||
myDB = db.DBConnection('failed.db')
|
||||
sql_results = myDB.select("SELECT * FROM history WHERE showid=? AND season=?",
|
||||
[epObj.show.indexerid, epObj.season])
|
||||
sql_results = db_select(
|
||||
'SELECT * FROM history t WHERE t.showid=? AND t.season=?', [ep_obj.show.indexerid, ep_obj.season])
|
||||
|
||||
history_eps = dict([(res["episode"], res) for res in sql_results])
|
||||
history_eps = {r['episode']: r for r in sql_results}
|
||||
|
||||
try:
|
||||
logger.log(u"Reverting episode (%s, %s): %s" % (epObj.season, epObj.episode, epObj.name))
|
||||
with epObj.lock:
|
||||
if epObj.episode in history_eps:
|
||||
logger.log(u"Found in history")
|
||||
epObj.status = history_eps[epObj.episode]['old_status']
|
||||
logger.log('Reverting episode %sx%s: [%s]' % (ep_obj.season, ep_obj.episode, ep_obj.name))
|
||||
with ep_obj.lock:
|
||||
if ep_obj.episode in history_eps:
|
||||
status_revert = history_eps[ep_obj.episode]['old_status']
|
||||
|
||||
status, quality = Quality.splitCompositeStatus(status_revert)
|
||||
logger.log('Found in failed.db history with status: %s quality: %s' % (
|
||||
statusStrings[status], Quality.qualityStrings[quality]))
|
||||
else:
|
||||
logger.log(u"WARNING: Episode not found in history. Setting it back to WANTED",
|
||||
logger.WARNING)
|
||||
epObj.status = WANTED
|
||||
epObj.saveToDB()
|
||||
status_revert = WANTED
|
||||
|
||||
logger.log('Episode not found in failed.db history. Setting it to WANTED', logger.WARNING)
|
||||
|
||||
ep_obj.status = status_revert
|
||||
ep_obj.saveToDB()
|
||||
|
||||
except EpisodeNotFoundException as e:
|
||||
logger.log(u"Unable to create episode, please set its status manually: " + ex(e),
|
||||
logger.WARNING)
|
||||
logger.log('Unable to create episode, please set its status manually: %s' % ex(e), logger.WARNING)
|
||||
|
||||
|
||||
def markFailed(epObj):
|
||||
log_str = u""
|
||||
|
||||
try:
|
||||
with epObj.lock:
|
||||
quality = Quality.splitCompositeStatus(epObj.status)[1]
|
||||
epObj.status = Quality.compositeStatus(FAILED, quality)
|
||||
epObj.saveToDB()
|
||||
|
||||
except EpisodeNotFoundException as e:
|
||||
logger.log(u"Unable to get episode, please set its status manually: " + ex(e), logger.WARNING)
|
||||
|
||||
return log_str
|
||||
def find_old_status(ep_obj):
|
||||
"""
|
||||
:param ep_obj:
|
||||
:return: Old status if failed history item found
|
||||
"""
|
||||
# Search for release in snatch history
|
||||
results = db_select(
|
||||
'SELECT t.old_status FROM history t WHERE t.showid=? AND t.season=? AND t.episode=? '
|
||||
+ 'ORDER BY t.date DESC LIMIT 1', [ep_obj.show.indexerid, ep_obj.season, ep_obj.episode])
|
||||
if any(results):
|
||||
return results[0]['old_status']
|
||||
|
||||
|
||||
def logSnatch(searchResult):
|
||||
logDate = datetime.datetime.today().strftime(dateFormat)
|
||||
release = prepareFailedName(searchResult.name)
|
||||
|
||||
providerClass = searchResult.provider
|
||||
if providerClass is not None:
|
||||
provider = providerClass.name
|
||||
else:
|
||||
provider = "unknown"
|
||||
|
||||
show_obj = searchResult.episodes[0].show
|
||||
|
||||
myDB = db.DBConnection('failed.db')
|
||||
for episode in searchResult.episodes:
|
||||
myDB.action(
|
||||
"INSERT INTO history (date, size, release, provider, showid, season, episode, old_status)"
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
[logDate, searchResult.size, release, provider, show_obj.indexerid, episode.season, episode.episode,
|
||||
episode.status])
|
||||
|
||||
|
||||
def deleteLoggedSnatch(release, size, provider):
|
||||
release = prepareFailedName(release)
|
||||
|
||||
myDB = db.DBConnection('failed.db')
|
||||
myDB.action("DELETE FROM history WHERE release=? AND size=? AND provider=?",
|
||||
[release, size, provider])
|
||||
|
||||
|
||||
def trimHistory():
|
||||
myDB = db.DBConnection('failed.db')
|
||||
myDB.action("DELETE FROM history WHERE date < " + str(
|
||||
(datetime.datetime.today() - datetime.timedelta(days=30)).strftime(dateFormat)))
|
||||
|
||||
|
||||
def findRelease(epObj):
|
||||
def find_release(ep_obj):
|
||||
"""
|
||||
Find releases in history by show ID and season.
|
||||
Return None for release if multiple found or no release found.
|
||||
"""
|
||||
# Clear old snatches for this release if any exist
|
||||
from_where = ' FROM history WHERE showid=%s AND season=%s AND episode=%s' % \
|
||||
(ep_obj.show.indexerid, ep_obj.season, ep_obj.episode)
|
||||
db_action('DELETE %s AND date < (SELECT max(date) %s)' % (from_where, from_where))
|
||||
|
||||
# Search for release in snatch history
|
||||
results = db_select(
|
||||
'SELECT t.release, t.provider, t.date FROM history t WHERE t.showid=? AND t.season=? AND t.episode=?',
|
||||
[ep_obj.show.indexerid, ep_obj.season, ep_obj.episode])
|
||||
|
||||
if any(results):
|
||||
r = results[0]
|
||||
release = r['release']
|
||||
provider = r['provider']
|
||||
|
||||
# Clear any incomplete snatch records for this release if any exist
|
||||
db_action('DELETE FROM history WHERE %s=? AND %s!=?' % ('`release`', '`date`'), [release, r['date']])
|
||||
|
||||
# Found a previously failed release
|
||||
logger.log('Found failed.db history release %sx%s: [%s]' % (
|
||||
ep_obj.season, ep_obj.episode, release), logger.DEBUG)
|
||||
else:
|
||||
release = None
|
||||
provider = None
|
||||
|
||||
# Release not found
|
||||
logger.log('No found failed.db history release for %sx%s: [%s]' % (
|
||||
ep_obj.season, ep_obj.episode, ep_obj.show.name), logger.DEBUG)
|
||||
|
||||
# Clear old snatches for this release if any exist
|
||||
myDB = db.DBConnection('failed.db')
|
||||
myDB.action("DELETE FROM history WHERE showid=" + str(epObj.show.indexerid) + " AND season=" + str(
|
||||
epObj.season) + " AND episode=" + str(
|
||||
epObj.episode) + " AND date < (SELECT max(date) FROM history WHERE showid=" + str(
|
||||
epObj.show.indexerid) + " AND season=" + str(epObj.season) + " AND episode=" + str(epObj.episode) + ")")
|
||||
|
||||
# Search for release in snatch history
|
||||
results = myDB.select("SELECT release, provider, date FROM history WHERE showid=? AND season=? AND episode=?",
|
||||
[epObj.show.indexerid, epObj.season, epObj.episode])
|
||||
|
||||
for result in results:
|
||||
release = str(result["release"])
|
||||
provider = str(result["provider"])
|
||||
date = result["date"]
|
||||
|
||||
# Clear any incomplete snatch records for this release if any exist
|
||||
myDB.action("DELETE FROM history WHERE release=? AND date!=?", [release, date])
|
||||
|
||||
# Found a previously failed release
|
||||
logger.log(u"Failed release found for season (%s): (%s)" % (epObj.season, result["release"]), logger.DEBUG)
|
||||
return (release, provider)
|
||||
|
||||
# Release was not found
|
||||
logger.log(u"No releases found for season (%s) of (%s)" % (epObj.season, epObj.show.indexerid), logger.DEBUG)
|
||||
return (release, provider)
|
||||
return release, provider
|
||||
|
|
|
@ -38,6 +38,7 @@ import subprocess
|
|||
import adba
|
||||
import requests
|
||||
import requests.exceptions
|
||||
from cfscrape import CloudflareScraper
|
||||
import sickbeard
|
||||
import subliminal
|
||||
|
||||
|
@ -1106,7 +1107,7 @@ def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=N
|
|||
|
||||
# request session
|
||||
if None is session:
|
||||
session = requests.session()
|
||||
session = CloudflareScraper.create_scraper()
|
||||
|
||||
if not kwargs.get('nocache'):
|
||||
cache_dir = sickbeard.CACHE_DIR or _getTempDir()
|
||||
|
@ -1118,6 +1119,8 @@ def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=N
|
|||
req_headers = {'User-Agent': USER_AGENT, 'Accept-Encoding': 'gzip,deflate'}
|
||||
if headers:
|
||||
req_headers.update(headers)
|
||||
if hasattr(session, 'reserved') and 'headers' in session.reserved:
|
||||
req_headers.update(session.reserved['headers'] or {})
|
||||
session.headers.update(req_headers)
|
||||
|
||||
mute = []
|
||||
|
@ -1216,7 +1219,8 @@ def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=N
|
|||
|
||||
if json:
|
||||
try:
|
||||
return resp.json()
|
||||
data_json = resp.json()
|
||||
return ({}, data_json)[isinstance(data_json, dict)]
|
||||
except (TypeError, Exception) as e:
|
||||
logger.log(u'JSON data issue from URL %s\r\nDetail... %s' % (url, e.message), logger.WARNING)
|
||||
return None
|
||||
|
@ -1231,12 +1235,14 @@ def _maybe_request_url(e, def_url=''):
|
|||
def download_file(url, filename, session=None):
|
||||
# create session
|
||||
if None is session:
|
||||
session = requests.session()
|
||||
session = CloudflareScraper.create_scraper()
|
||||
cache_dir = sickbeard.CACHE_DIR or _getTempDir()
|
||||
session = CacheControl(sess=session, cache=caches.FileCache(ek.ek(os.path.join, cache_dir, 'sessions')))
|
||||
|
||||
# request session headers
|
||||
session.headers.update({'User-Agent': USER_AGENT, 'Accept-Encoding': 'gzip,deflate'})
|
||||
if hasattr(session, 'reserved') and 'headers' in session.reserved:
|
||||
session.headers.update(session.reserved['headers'] or {})
|
||||
|
||||
# request session ssl verify
|
||||
session.verify = False
|
||||
|
|
|
@ -95,7 +95,9 @@ def send_nzb(nzb, proper=False):
|
|||
|
||||
nzbcontent64 = None
|
||||
if 'nzbdata' == nzb.resultType:
|
||||
data = nzb.extraInfo[0]
|
||||
data = nzb.get_data()
|
||||
if not data:
|
||||
return False
|
||||
nzbcontent64 = standard_b64encode(data)
|
||||
elif 'Anizb' == nzb.provider.name and 'nzb' == nzb.resultType:
|
||||
gen_provider = GenericProvider('')
|
||||
|
|
|
@ -946,10 +946,10 @@ class PostProcessor(object):
|
|||
|
||||
# Just want to keep this consistent for failed handling right now
|
||||
release_name = show_name_helpers.determineReleaseName(self.folder_path, self.nzb_name)
|
||||
if None is not release_name:
|
||||
failed_history.logSuccess(release_name)
|
||||
else:
|
||||
if None is release_name:
|
||||
self._log(u'No snatched release found in history', logger.WARNING)
|
||||
elif sickbeard.USE_FAILED_DOWNLOADS:
|
||||
failed_history.remove_failed(release_name)
|
||||
|
||||
# find the destination folder
|
||||
try:
|
||||
|
|
|
@ -31,7 +31,7 @@ from . import alpharatio, beyondhd, bithdtv, bitmetv, btn, btscene, dh, extrator
|
|||
iptorrents, limetorrents, morethan, ncore, pisexy, pretome, privatehd, ptf, \
|
||||
rarbg, revtt, scc, scenetime, shazbat, speedcd, \
|
||||
thepiratebay, torlock, torrentday, torrenting, torrentleech, \
|
||||
torrentshack, torrentz2, transmithe_net, tvchaosuk, zooqle
|
||||
torrentz2, transmithe_net, tvchaosuk, zooqle
|
||||
# anime
|
||||
from . import anizb, nyaatorrents, tokyotoshokan
|
||||
# custom
|
||||
|
@ -80,7 +80,6 @@ __all__ = ['omgwtfnzbs',
|
|||
'torrentday',
|
||||
'torrenting',
|
||||
'torrentleech',
|
||||
'torrentshack',
|
||||
'torrentz2',
|
||||
'transmithe_net',
|
||||
'tvchaosuk',
|
||||
|
|
|
@ -21,7 +21,9 @@ import time
|
|||
|
||||
from . import generic
|
||||
from sickbeard import helpers, logger, scene_exceptions, tvcache
|
||||
from sickbeard.bs4_parser import BS4Parser
|
||||
from sickbeard.helpers import tryInt
|
||||
from lib.unidecode import unidecode
|
||||
|
||||
try:
|
||||
import json
|
||||
|
@ -35,24 +37,34 @@ class BTNProvider(generic.TorrentProvider):
|
|||
def __init__(self):
|
||||
generic.TorrentProvider.__init__(self, 'BTN')
|
||||
|
||||
self.url_base = 'https://broadcasthe.net'
|
||||
self.url_base = 'https://broadcasthe.net/'
|
||||
self.url_api = 'https://api.btnapps.net'
|
||||
|
||||
self.proper_search_terms = ['%.proper.%', '%.repack.%']
|
||||
self.url = self.url_base
|
||||
self.urls = {'config_provider_home_uri': self.url_base, 'login': self.url_base + 'login.php',
|
||||
'search': self.url_base + 'torrents.php?searchstr=%s&action=basic&%s', 'get': self.url_base + '%s'}
|
||||
|
||||
self.api_key, self.minseed, self.minleech = 3 * [None]
|
||||
self.proper_search_terms = ['%.proper.%', '%.repack.%']
|
||||
|
||||
self.categories = {'Season': [2], 'Episode': [1]}
|
||||
self.categories['Cache'] = self.categories['Season'] + self.categories['Episode']
|
||||
|
||||
self.url = self.urls['config_provider_home_uri']
|
||||
|
||||
self.api_key, self.username, self.password, self.auth_html, self.minseed, self.minleech = 6 * [None]
|
||||
self.ua = self.session.headers['User-Agent']
|
||||
self.reject_m2ts = False
|
||||
self.session.headers = {'Content-Type': 'application/json-rpc'}
|
||||
self.cache = BTNCache(self)
|
||||
|
||||
def _authorised(self, **kwargs):
|
||||
|
||||
return self._check_auth()
|
||||
if not self.api_key and not (self.username and self.password):
|
||||
raise AuthException('Must set ApiKey or Username/Password for %s in config provider options' % self.name)
|
||||
return True
|
||||
|
||||
def _search_provider(self, search_params, age=0, **kwargs):
|
||||
|
||||
self._check_auth()
|
||||
self._authorised()
|
||||
self.auth_html = None
|
||||
|
||||
results = []
|
||||
|
||||
|
@ -66,6 +78,7 @@ class BTNProvider(generic.TorrentProvider):
|
|||
else:
|
||||
search_param and params.update(search_param)
|
||||
age and params.update(dict(age='<=%i' % age)) # age in seconds
|
||||
search_string = 'tvdb' in params and '%s %s' % (params.pop('series'), params['name']) or ''
|
||||
|
||||
json_rpc = (lambda param_dct, items_per_page=1000, offset=0:
|
||||
'{"jsonrpc": "2.0", "id": "%s", "method": "getTorrents", "params": ["%s", %s, %s, %s]}' %
|
||||
|
@ -73,7 +86,13 @@ class BTNProvider(generic.TorrentProvider):
|
|||
self.api_key, json.dumps(param_dct), items_per_page, offset))
|
||||
|
||||
try:
|
||||
response = helpers.getURL(self.url_api, post_data=json_rpc(params), session=self.session, json=True)
|
||||
response = None
|
||||
if self.api_key:
|
||||
self.session.headers['Content-Type'] = 'application/json-rpc'
|
||||
response = helpers.getURL(
|
||||
self.url_api, post_data=json_rpc(params), session=self.session, json=True)
|
||||
if not response:
|
||||
results = self.html(mode, search_string, results)
|
||||
error_text = response['error']['message']
|
||||
logger.log(
|
||||
('Call Limit' in error_text
|
||||
|
@ -138,6 +157,81 @@ class BTNProvider(generic.TorrentProvider):
|
|||
('search_param: ' + str(search_param), self.name)['Cache' == mode])
|
||||
|
||||
results = self._sort_seeding(mode, results)
|
||||
break # search first tvdb item only
|
||||
|
||||
return results
|
||||
|
||||
def _authorised_html(self):
|
||||
|
||||
if self.username and self.password:
|
||||
return super(BTNProvider, self)._authorised(
|
||||
post_params={'login': 'Log In!'}, logged_in=(lambda y='': 'casThe' in y[0:4096]))
|
||||
raise AuthException('Password or Username for %s is empty in config provider options' % self.name)
|
||||
|
||||
def html(self, mode, search_string, results):
|
||||
|
||||
if 'Content-Type' in self.session.headers:
|
||||
del (self.session.headers['Content-Type'])
|
||||
setattr(self.session, 'reserved', {'headers': {
|
||||
'Accept': 'text/html, application/xhtml+xml, */*', 'Accept-Language': 'en-GB',
|
||||
'Cache-Control': 'no-cache', 'Referer': 'https://broadcasthe.net/login.php', 'User-Agent': self.ua}})
|
||||
self.headers = None
|
||||
|
||||
if self.auth_html or self._authorised_html():
|
||||
del (self.session.reserved['headers']['Referer'])
|
||||
if 'Referer' in self.session.headers:
|
||||
del (self.session.headers['Referer'])
|
||||
self.auth_html = True
|
||||
|
||||
search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string
|
||||
search_url = self.urls['search'] % (search_string, self._categories_string(mode, 'filter_cat[%s]=1'))
|
||||
|
||||
html = helpers.getURL(search_url, session=self.session)
|
||||
cnt = len(results)
|
||||
try:
|
||||
if not html or self._has_no_results(html):
|
||||
raise generic.HaltParseException
|
||||
|
||||
with BS4Parser(html, features=['html5lib', 'permissive']) as soup:
|
||||
torrent_table = soup.find(id='torrent_table')
|
||||
torrent_rows = [] if not torrent_table else torrent_table.find_all('tr')
|
||||
|
||||
if 2 > len(torrent_rows):
|
||||
raise generic.HaltParseException
|
||||
|
||||
rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {
|
||||
'cats': '(?i)cat\[(?:%s)\]' % self._categories_string(mode, template='', delimiter='|'),
|
||||
'get': 'download'}.items())
|
||||
|
||||
head = None
|
||||
for tr in torrent_rows[1:]:
|
||||
cells = tr.find_all('td')
|
||||
if 5 > len(cells):
|
||||
continue
|
||||
try:
|
||||
head = head if None is not head else self._header_row(tr)
|
||||
seeders, leechers, size = [tryInt(n, n) for n in [
|
||||
cells[head[x]].get_text().strip() for x in 'seed', 'leech', 'size']]
|
||||
if ((self.reject_m2ts and re.search(r'(?i)\[.*?m2?ts.*?\]', tr.get_text('', strip=True))) or
|
||||
self._peers_fail(mode, seeders, leechers) or not tr.find('a', href=rc['cats'])):
|
||||
continue
|
||||
|
||||
title = tr.select('td span[title]')[0].attrs.get('title').strip()
|
||||
download_url = self._link(tr.find('a', href=rc['get'])['href'])
|
||||
except (AttributeError, TypeError, ValueError, KeyError, IndexError):
|
||||
continue
|
||||
|
||||
if title and download_url:
|
||||
results.append((title, download_url, seeders, self._bytesizer(size)))
|
||||
|
||||
except generic.HaltParseException:
|
||||
pass
|
||||
except (StandardError, Exception):
|
||||
logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
|
||||
|
||||
self._log_search(mode, len(results) - cnt, search_url)
|
||||
|
||||
results = self._sort_seeding(mode, results)
|
||||
|
||||
return results
|
||||
|
||||
|
@ -187,17 +281,21 @@ class BTNProvider(generic.TorrentProvider):
|
|||
|
||||
if 1 == ep_obj.show.indexer:
|
||||
base_params['tvdb'] = ep_obj.show.indexerid
|
||||
base_params['series'] = ep_obj.show.name
|
||||
search_params.append(base_params)
|
||||
# elif 2 == ep_obj.show.indexer:
|
||||
# current_params['tvrage'] = ep_obj.show.indexerid
|
||||
# search_params.append(current_params)
|
||||
else:
|
||||
# else:
|
||||
name_exceptions = list(
|
||||
set([helpers.sanitizeSceneName(a) for a in
|
||||
scene_exceptions.get_scene_exceptions(ep_obj.show.indexerid) + [ep_obj.show.name]]))
|
||||
dedupe = [ep_obj.show.name.replace(' ', '.')]
|
||||
for name in name_exceptions:
|
||||
series_param = {'series': name}
|
||||
series_param.update(base_params)
|
||||
if name.replace(' ', '.') not in dedupe:
|
||||
dedupe += [name.replace(' ', '.')]
|
||||
series_param = base_params.copy()
|
||||
series_param['series'] = name
|
||||
search_params.append(series_param)
|
||||
|
||||
return [dict(Season=search_params)]
|
||||
|
@ -228,18 +326,23 @@ class BTNProvider(generic.TorrentProvider):
|
|||
# search
|
||||
if 1 == ep_obj.show.indexer:
|
||||
base_params['tvdb'] = ep_obj.show.indexerid
|
||||
base_params['series'] = ep_obj.show.name
|
||||
search_params.append(base_params)
|
||||
# elif 2 == ep_obj.show.indexer:
|
||||
# search_params['tvrage'] = ep_obj.show.indexerid
|
||||
# to_return.append(search_params)
|
||||
else:
|
||||
|
||||
# else:
|
||||
# add new query string for every exception
|
||||
name_exceptions = list(
|
||||
set([helpers.sanitizeSceneName(a) for a in
|
||||
scene_exceptions.get_scene_exceptions(ep_obj.show.indexerid) + [ep_obj.show.name]]))
|
||||
dedupe = [ep_obj.show.name.replace(' ', '.')]
|
||||
for name in name_exceptions:
|
||||
series_param = {'series': name}
|
||||
series_param.update(base_params)
|
||||
if name.replace(' ', '.') not in dedupe:
|
||||
dedupe += [name.replace(' ', '.')]
|
||||
series_param = base_params.copy()
|
||||
series_param['series'] = name
|
||||
search_params.append(series_param)
|
||||
|
||||
return [dict(Episode=search_params)]
|
||||
|
|
|
@ -33,6 +33,7 @@ from base64 import b16encode, b32decode
|
|||
import sickbeard
|
||||
import requests
|
||||
import requests.cookies
|
||||
from cfscrape import CloudflareScraper
|
||||
from hachoir_parser import guessParser
|
||||
from hachoir_core.error import HachoirError
|
||||
from hachoir_core.stream import FileInputStream
|
||||
|
@ -74,7 +75,7 @@ class GenericProvider:
|
|||
|
||||
self.cache = tvcache.TVCache(self)
|
||||
|
||||
self.session = requests.session()
|
||||
self.session = CloudflareScraper.create_scraper()
|
||||
|
||||
self.headers = {
|
||||
# Using USER_AGENT instead of Mozilla to keep same user agent along authentication and download phases,
|
||||
|
@ -208,7 +209,7 @@ class GenericProvider:
|
|||
cache_file = ek.ek(os.path.join, cache_dir, base_name)
|
||||
|
||||
self.session.headers['Referer'] = url
|
||||
if helpers.download_file(url, cache_file, session=self.session):
|
||||
if getattr(result, 'cache_file', None) or helpers.download_file(url, cache_file, session=self.session):
|
||||
|
||||
if self._verify_download(cache_file):
|
||||
logger.log(u'Downloaded %s result from %s' % (self.name, url))
|
||||
|
|
|
@ -92,21 +92,26 @@ class OmgwtfnzbsProvider(generic.NZBProvider):
|
|||
|
||||
return item['release'].replace('_', '.'), item['getnzb']
|
||||
|
||||
def get_data(self, url):
|
||||
result = None
|
||||
if url and False is self._init_api():
|
||||
data = self.get_url(url, timeout=90)
|
||||
if data:
|
||||
if re.search('(?i)limit.*?reached', data):
|
||||
logger.log('Daily Nzb Download limit reached', logger.DEBUG)
|
||||
elif '</nzb>' not in data or 'seem to be logged in' in data:
|
||||
logger.log('Failed nzb data response: %s' % data, logger.DEBUG)
|
||||
else:
|
||||
result = data
|
||||
return result
|
||||
|
||||
def get_result(self, episodes, url):
|
||||
|
||||
result = None
|
||||
if url and False is self._init_api():
|
||||
data = self.get_url(url, timeout=90)
|
||||
if not data:
|
||||
return result
|
||||
if '<strong>Limit Reached</strong>' in data:
|
||||
logger.log('Daily Nzb Download limit reached', logger.DEBUG)
|
||||
return result
|
||||
if '</nzb>' not in data or 'seem to be logged in' in data:
|
||||
logger.log('Failed nzb data response: %s' % data, logger.DEBUG)
|
||||
return result
|
||||
result = classes.NZBDataSearchResult(episodes)
|
||||
result.extraInfo += [data]
|
||||
result.get_data_func = self.get_data
|
||||
result.url = url
|
||||
|
||||
if None is result:
|
||||
result = classes.NZBSearchResult(episodes)
|
||||
|
@ -193,7 +198,7 @@ class OmgwtfnzbsProvider(generic.NZBProvider):
|
|||
if tr.find('img', src=rc['nuked']) or not tr.find('a', href=rc['cat']):
|
||||
continue
|
||||
|
||||
title = tr.find('a', href=rc['info'])['title']
|
||||
title = tr.find('a', href=rc['info']).get_text().strip()
|
||||
download_url = tr.find('a', href=rc['get'])
|
||||
age = tr.find_all('td')[-1]['data-sort']
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
|
|
|
@ -67,7 +67,7 @@ class SpeedCDProvider(generic.TorrentProvider):
|
|||
|
||||
cnt = len(items[mode])
|
||||
try:
|
||||
html = data_json.get('Fs')[0].get('Cn')[0].get('d')
|
||||
html = data_json.get('Fs', [{}])[0].get('Cn', [{}])[0].get('d')
|
||||
if not html or self._has_no_results(html):
|
||||
raise generic.HaltParseException
|
||||
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
# coding=utf-8
|
||||
#
|
||||
# Author: SickGear
|
||||
#
|
||||
# This file is part of SickGear.
|
||||
#
|
||||
# SickGear is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# SickGear is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from . import generic
|
||||
from sickbeard import logger
|
||||
from sickbeard.bs4_parser import BS4Parser
|
||||
from sickbeard.helpers import tryInt
|
||||
from lib.unidecode import unidecode
|
||||
|
||||
|
||||
class TorrentShackProvider(generic.TorrentProvider):
|
||||
|
||||
def __init__(self):
|
||||
generic.TorrentProvider.__init__(self, 'TorrentShack', cache_update_freq=20)
|
||||
|
||||
self.url_base = 'https://torrentshack.me/'
|
||||
self.urls = {'config_provider_home_uri': self.url_base,
|
||||
'login_action': self.url_base + 'login.php',
|
||||
'search': self.url_base + 'torrents.php?searchstr=%s&%s&' + '&'.join(
|
||||
['release_type=both', 'searchtags=', 'tags_type=0',
|
||||
'order_by=s3', 'order_way=desc', 'torrent_preset=all']),
|
||||
'get': self.url_base + '%s'}
|
||||
|
||||
self.categories = {'shows': [600, 620, 700, 981, 980], 'anime': [850]}
|
||||
|
||||
self.url = self.urls['config_provider_home_uri']
|
||||
|
||||
self.username, self.password, self.minseed, self.minleech = 4 * [None]
|
||||
|
||||
def _authorised(self, **kwargs):
|
||||
|
||||
return super(TorrentShackProvider, self)._authorised(logged_in=(lambda y=None: self.has_all_cookies('session')),
|
||||
post_params={'keeplogged': '1', 'form_tmpl': True})
|
||||
|
||||
def _search_provider(self, search_params, **kwargs):
|
||||
|
||||
results = []
|
||||
if not self._authorised():
|
||||
return results
|
||||
|
||||
items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}
|
||||
|
||||
rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {
|
||||
'info': 'view', 'get': 'download', 'title': 'view\s+torrent\s+', 'size': '\s{2,}.*'}.iteritems())
|
||||
for mode in search_params.keys():
|
||||
for search_string in search_params[mode]:
|
||||
search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string
|
||||
# fetch 15 results by default, and up to 100 if allowed in user profile
|
||||
search_url = self.urls['search'] % (search_string, self._categories_string(mode, 'filter_cat[%s]=1'))
|
||||
|
||||
html = self.get_url(search_url)
|
||||
|
||||
cnt = len(items[mode])
|
||||
try:
|
||||
if not html or self._has_no_results(html):
|
||||
raise generic.HaltParseException
|
||||
|
||||
with BS4Parser(html, features=['html5lib', 'permissive']) as soup:
|
||||
torrent_table = soup.find('table', class_='torrent_table')
|
||||
torrent_rows = [] if not torrent_table else torrent_table.find_all('tr')
|
||||
|
||||
if 2 > len(torrent_rows):
|
||||
raise generic.HaltParseException
|
||||
|
||||
head = None
|
||||
for tr in torrent_rows[1:]:
|
||||
cells = tr.find_all('td')
|
||||
if 5 > len(cells):
|
||||
continue
|
||||
try:
|
||||
head = head if None is not head else self._header_row(tr)
|
||||
seeders, leechers, size = [tryInt(n, n) for n in [
|
||||
cells[head[x]].get_text().strip() for x in 'seed', 'leech', 'size']]
|
||||
if self._peers_fail(mode, seeders, leechers):
|
||||
continue
|
||||
|
||||
size = rc['size'].sub('', size)
|
||||
info = tr.find('a', title=rc['info'])
|
||||
title = (rc['title'].sub('', info.attrs.get('title', '')) or info.get_text()).strip()
|
||||
download_url = self._link(tr.find('a', title=rc['get'])['href'])
|
||||
except (AttributeError, TypeError, ValueError, KeyError):
|
||||
continue
|
||||
|
||||
if title and download_url:
|
||||
items[mode].append((title, download_url, seeders, self._bytesizer(size)))
|
||||
|
||||
except generic.HaltParseException:
|
||||
pass
|
||||
except (StandardError, Exception):
|
||||
logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
|
||||
self._log_search(mode, len(items[mode]) - cnt, search_url)
|
||||
|
||||
results = self._sort_seeding(mode, results + items[mode])
|
||||
|
||||
return results
|
||||
|
||||
def _episode_strings(self, ep_obj, **kwargs):
|
||||
|
||||
return generic.TorrentProvider._episode_strings(self, ep_obj, sep_date='.', **kwargs)
|
||||
|
||||
|
||||
provider = TorrentShackProvider()
|
|
@ -77,7 +77,7 @@ class TransmithenetProvider(generic.TorrentProvider):
|
|||
|
||||
cnt = len(items[mode])
|
||||
try:
|
||||
for item in data_json['response'].get('results', []):
|
||||
for item in data_json.get('response', {}).get('results', []):
|
||||
if self.freeleech and not item.get('isFreeleech'):
|
||||
continue
|
||||
|
||||
|
@ -96,7 +96,7 @@ class TransmithenetProvider(generic.TorrentProvider):
|
|||
(maybe_res and [maybe_res[0]] or []) +
|
||||
[detail[0].strip(), detail[1], maybe_ext and maybe_ext[0].lower() or 'mkv']))
|
||||
except (IndexError, KeyError):
|
||||
title = group_name
|
||||
title = self.regulate_title(item, group_name)
|
||||
download_url = self.urls['get'] % (self.user_authkey, self.user_passkey, torrent_id)
|
||||
|
||||
if title and download_url:
|
||||
|
@ -110,6 +110,53 @@ class TransmithenetProvider(generic.TorrentProvider):
|
|||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def regulate_title(item, t_param):
|
||||
|
||||
if 'tags' not in item or not any(item['tags']):
|
||||
return t_param
|
||||
|
||||
t = ['']
|
||||
bl = '[*\[({]+\s*'
|
||||
br = '\s*[})\]*]+'
|
||||
title = re.sub('(.*?)((?i)%sproper%s)(.*)' % (bl, br), r'\1\3\2', item['groupName'])
|
||||
for r in '\s+-\s+', '(?:19|20)\d\d(?:\-\d\d\-\d\d)?', 'S\d\d+(?:E\d\d+)?':
|
||||
m = re.findall('(.*%s)(.*)' % r, title)
|
||||
if any(m) and len(m[0][0]) > len(t[0]):
|
||||
t = m[0]
|
||||
t = (tuple(title), t)[any(t)]
|
||||
|
||||
tag_str = '_'.join(item['tags'])
|
||||
tags = [re.findall(x, tag_str, flags=re.X) for x in
|
||||
('(?i)%sProper%s|\bProper\b$' % (bl, br),
|
||||
'(?i)\d{3,4}(?:[pi]|hd)',
|
||||
'''
|
||||
(?i)(hr.ws.pdtv|blu.?ray|hddvd|
|
||||
pdtv|hdtv|dsr|tvrip|web.?(?:dl|rip)|dvd.?rip|b[r|d]rip|mpeg-?2)
|
||||
''', '''
|
||||
(?i)([hx].?26[45]|divx|xvid)
|
||||
''', '''
|
||||
(?i)(avi|mkv|mp4|sub(?:b?ed|pack|s))
|
||||
''')]
|
||||
|
||||
title = ('%s`%s' % (
|
||||
re.sub('|'.join(['|'.join([re.escape(y) for y in x]) for x in tags if x]).strip('|'), '', t[-1]),
|
||||
re.sub('(?i)(\d{3,4})hd', r'\1p', '`'.join(['`'.join(x) for x in tags[:-1]]).rstrip('`')) +
|
||||
('', '`hdtv')[not any(tags[2])] + ('', '`x264')[not any(tags[3])]))
|
||||
for r in [('(?i)(?:\W(?:Series|Season))?\W(Repack)\W', r'`\1`'),
|
||||
('(?i)%s(Proper)%s' % (bl, br), r'`\1`'), ('%s\s*%s' % (bl, br), '`')]:
|
||||
title = re.sub(r[0], r[1], title)
|
||||
|
||||
grp = filter(lambda rn: '.release' in rn.lower(), item['tags'])
|
||||
title = '%s%s-%s' % (('', t[0])[1 < len(t)], title,
|
||||
(any(grp) and grp[0] or 'nogrp').upper().replace('.RELEASE', ''))
|
||||
|
||||
for r in [('\s+[-]?\s+|\s+`|`\s+', '`'), ('`+', '.')]:
|
||||
title = re.sub(r[0], r[1], title)
|
||||
|
||||
title += + any(tags[4]) and ('.%s' % tags[4][0]) or ''
|
||||
return title
|
||||
|
||||
def _season_strings(self, ep_obj, **kwargs):
|
||||
|
||||
return generic.TorrentProvider._season_strings(self, ep_obj, scene=False)
|
||||
|
|
|
@ -32,7 +32,7 @@ class ZooqleProvider(generic.TorrentProvider):
|
|||
|
||||
self.url_base = 'https://zooqle.com/'
|
||||
self.urls = {'config_provider_home_uri': self.url_base,
|
||||
'search': self.url_base + 'search?q=%s category:%s&s=dt&v=t&sd=d',
|
||||
'search': self.url_base + 'search?q=%s category:%s&s=ns&v=t&sd=d',
|
||||
'get': self.url_base + 'download/%s.torrent'}
|
||||
|
||||
self.categories = {'Season': ['TV'], 'Episode': ['TV'], 'anime': ['Anime']}
|
||||
|
|
|
@ -60,7 +60,10 @@ def send_nzb(nzb):
|
|||
nzb_type = 'file nzb'
|
||||
params['mode'] = 'addfile'
|
||||
kwargs['post_data'] = params
|
||||
kwargs['files'] = {'nzbfile': ('%s.nzb' % nzb.name, nzb.extraInfo[0])}
|
||||
nzb_data = nzb.get_data()
|
||||
if not nzb_data:
|
||||
return False
|
||||
kwargs['files'] = {'nzbfile': ('%s.nzb' % nzb.name, nzb_data)}
|
||||
|
||||
logger.log(u'Sending %s to SABnzbd: %s' % (nzb_type, nzb.name))
|
||||
|
||||
|
|
|
@ -72,8 +72,12 @@ def _download_result(result):
|
|||
|
||||
# save the data to disk
|
||||
try:
|
||||
data = result.get_data()
|
||||
if not data:
|
||||
new_result = False
|
||||
else:
|
||||
with ek.ek(open, file_name, 'w') as file_out:
|
||||
file_out.write(result.extraInfo[0])
|
||||
file_out.write(data)
|
||||
|
||||
helpers.chmodAsParent(file_name)
|
||||
|
||||
|
@ -140,6 +144,9 @@ def snatch_episode(result, end_status=SNATCHED):
|
|||
# Snatches torrent with client
|
||||
client = clients.get_client_instance(sickbeard.TORRENT_METHOD)()
|
||||
dl_result = client.send_torrent(result)
|
||||
|
||||
if getattr(result, 'cache_file', None):
|
||||
helpers.remove_file_failed(result.cache_file)
|
||||
else:
|
||||
logger.log(u'Unknown result type, unable to download it', logger.ERROR)
|
||||
dl_result = False
|
||||
|
@ -148,7 +155,7 @@ def snatch_episode(result, end_status=SNATCHED):
|
|||
return False
|
||||
|
||||
if sickbeard.USE_FAILED_DOWNLOADS:
|
||||
failed_history.logSnatch(result)
|
||||
failed_history.add_snatched(result)
|
||||
|
||||
ui.notifications.message(u'Episode snatched', result.name)
|
||||
|
||||
|
@ -201,22 +208,22 @@ def pick_best_result(results, show, quality_list=None):
|
|||
best_result = None
|
||||
for cur_result in results:
|
||||
|
||||
logger.log(u'Quality is %s for %s' % (Quality.qualityStrings[cur_result.quality], cur_result.name))
|
||||
logger.log(u'Quality is %s for [%s]' % (Quality.qualityStrings[cur_result.quality], cur_result.name))
|
||||
|
||||
if show.is_anime and not show.release_groups.is_valid(cur_result):
|
||||
continue
|
||||
|
||||
if quality_list and cur_result.quality not in quality_list:
|
||||
logger.log(u'%s is an unwanted quality, rejecting it' % cur_result.name, logger.DEBUG)
|
||||
logger.log(u'Rejecting unwanted quality [%s]' % cur_result.name, logger.DEBUG)
|
||||
continue
|
||||
|
||||
if not pass_show_wordlist_checks(cur_result.name, show):
|
||||
continue
|
||||
|
||||
cur_size = getattr(cur_result, 'size', None)
|
||||
if sickbeard.USE_FAILED_DOWNLOADS and None is not cur_size and failed_history.hasFailed(
|
||||
if sickbeard.USE_FAILED_DOWNLOADS and None is not cur_size and failed_history.has_failed(
|
||||
cur_result.name, cur_size, cur_result.provider.name):
|
||||
logger.log(u'%s has previously failed, rejecting it' % cur_result.name)
|
||||
logger.log(u'Rejecting previously failed [%s]' % cur_result.name)
|
||||
continue
|
||||
|
||||
if not best_result or best_result.quality < cur_result.quality != Quality.UNKNOWN:
|
||||
|
@ -229,11 +236,11 @@ def pick_best_result(results, show, quality_list=None):
|
|||
elif 'internal' in best_result.name.lower() and 'internal' not in cur_result.name.lower():
|
||||
best_result = cur_result
|
||||
elif 'xvid' in best_result.name.lower() and 'x264' in cur_result.name.lower():
|
||||
logger.log(u'Preferring %s (x264 over xvid)' % cur_result.name)
|
||||
logger.log(u'Preferring (x264 over xvid) [%s]' % cur_result.name)
|
||||
best_result = cur_result
|
||||
|
||||
if best_result:
|
||||
logger.log(u'Picked %s as the best' % best_result.name, logger.DEBUG)
|
||||
logger.log(u'Picked as the best [%s]' % best_result.name, logger.DEBUG)
|
||||
else:
|
||||
logger.log(u'No result picked.', logger.DEBUG)
|
||||
|
||||
|
@ -388,6 +395,11 @@ def wanted_episodes(show, from_date, make_dict=False, unaired=False):
|
|||
ep_obj = show.getEpisode(int(result['season']), int(result['episode']))
|
||||
ep_obj.wantedQuality = [i for i in (wanted_qualities, initial_qualities)[not_downloaded]
|
||||
if cur_quality < i]
|
||||
# in case we don't want any quality for this episode, skip the episode
|
||||
if 0 == len(ep_obj.wantedQuality):
|
||||
logger.log('Dropped episode, no wanted quality for %sx%s: [%s]' % (
|
||||
ep_obj.season, ep_obj.episode, ep_obj.show.name), logger.ERROR)
|
||||
continue
|
||||
ep_obj.eps_aired_in_season = ep_count.get(helpers.tryInt(result['season']), 0)
|
||||
ep_obj.eps_aired_in_scene_season = ep_count_scene.get(
|
||||
helpers.tryInt(result['scene_season']), 0) if result['scene_season'] else ep_obj.eps_aired_in_season
|
||||
|
@ -457,7 +469,7 @@ def search_for_needed_episodes(episodes):
|
|||
threading.currentThread().name = orig_thread_name
|
||||
|
||||
if not len(providers):
|
||||
logger.log('No NZB/Torrent sources enabled in Search Provider options to do recent searches', logger.WARNING)
|
||||
logger.log('No NZB/Torrent sources enabled in Media Provider options to do recent searches', logger.WARNING)
|
||||
elif not search_done:
|
||||
logger.log('Failed recent search of %s enabled provider%s. More info in debug log.' % (
|
||||
len(providers), helpers.maybe_plural(len(providers))), logger.ERROR)
|
||||
|
@ -465,7 +477,7 @@ def search_for_needed_episodes(episodes):
|
|||
return found_results.values()
|
||||
|
||||
|
||||
def search_providers(show, episodes, manual_search=False, torrent_only=False, try_other_searches=False):
|
||||
def search_providers(show, episodes, manual_search=False, torrent_only=False, try_other_searches=False, old_status=None):
|
||||
found_results = {}
|
||||
final_results = []
|
||||
|
||||
|
@ -473,6 +485,14 @@ def search_providers(show, episodes, manual_search=False, torrent_only=False, tr
|
|||
|
||||
orig_thread_name = threading.currentThread().name
|
||||
|
||||
use_quality_list = None
|
||||
if any([episodes]):
|
||||
old_status = old_status or failed_history.find_old_status(episodes[0]) or episodes[0].status
|
||||
if old_status:
|
||||
status, quality = Quality.splitCompositeStatus(old_status)
|
||||
use_quality_list = (status not in (
|
||||
common.WANTED, common.FAILED, common.UNAIRED, common.SKIPPED, common.IGNORED, common.UNKNOWN))
|
||||
|
||||
provider_list = [x for x in sickbeard.providers.sortedProviderList() if x.is_active() and x.enable_backlog and
|
||||
(not torrent_only or x.providerType == GenericProvider.TORRENT)]
|
||||
for cur_provider in provider_list:
|
||||
|
@ -642,11 +662,11 @@ def search_providers(show, episodes, manual_search=False, torrent_only=False, tr
|
|||
if MULTI_EP_RESULT in found_results[provider_id]:
|
||||
for multi_result in found_results[provider_id][MULTI_EP_RESULT]:
|
||||
|
||||
logger.log(u'Checking usefulness of multi episode result %s' % multi_result.name, logger.DEBUG)
|
||||
logger.log(u'Checking usefulness of multi episode result [%s]' % multi_result.name, logger.DEBUG)
|
||||
|
||||
if sickbeard.USE_FAILED_DOWNLOADS and failed_history.hasFailed(multi_result.name, multi_result.size,
|
||||
if sickbeard.USE_FAILED_DOWNLOADS and failed_history.has_failed(multi_result.name, multi_result.size,
|
||||
multi_result.provider.name):
|
||||
logger.log(u'%s has previously failed, rejecting this multi episode result' % multi_result.name)
|
||||
logger.log(u'Rejecting previously failed multi episode result [%s]' % multi_result.name)
|
||||
continue
|
||||
|
||||
# see how many of the eps that this result covers aren't covered by single results
|
||||
|
@ -701,6 +721,7 @@ def search_providers(show, episodes, manual_search=False, torrent_only=False, tr
|
|||
|
||||
# of all the single ep results narrow it down to the best one for each episode
|
||||
final_results += set(multi_results.values())
|
||||
quality_list = use_quality_list and (None, best_qualities)[any(best_qualities)] or None
|
||||
for cur_ep in found_results[provider_id]:
|
||||
if cur_ep in (MULTI_EP_RESULT, SEASON_RESULT):
|
||||
continue
|
||||
|
@ -708,7 +729,7 @@ def search_providers(show, episodes, manual_search=False, torrent_only=False, tr
|
|||
if 0 == len(found_results[provider_id][cur_ep]):
|
||||
continue
|
||||
|
||||
best_result = pick_best_result(found_results[provider_id][cur_ep], show)
|
||||
best_result = pick_best_result(found_results[provider_id][cur_ep], show, quality_list)
|
||||
|
||||
# if all results were rejected move on to the next episode
|
||||
if not best_result:
|
||||
|
@ -720,9 +741,18 @@ def search_providers(show, episodes, manual_search=False, torrent_only=False, tr
|
|||
if 'blackhole' != sickbeard.TORRENT_METHOD:
|
||||
best_result.content = None
|
||||
else:
|
||||
td = best_result.provider.get_url(best_result.url)
|
||||
if not td:
|
||||
cache_file = ek.ek(os.path.join, sickbeard.CACHE_DIR or helpers._getTempDir(),
|
||||
'%s.torrent' % (helpers.sanitizeFileName(best_result.name)))
|
||||
if not helpers.download_file(best_result.url, cache_file, session=best_result.provider.session):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(cache_file, 'rb') as fh:
|
||||
td = fh.read()
|
||||
setattr(best_result, 'cache_file', cache_file)
|
||||
except (StandardError, Exception):
|
||||
continue
|
||||
|
||||
if getattr(best_result.provider, 'chk_td', None):
|
||||
name = None
|
||||
try:
|
||||
|
@ -736,13 +766,13 @@ def search_providers(show, episodes, manual_search=False, torrent_only=False, tr
|
|||
if name:
|
||||
name = td[x: x + v]
|
||||
break
|
||||
except:
|
||||
except (StandardError, Exception):
|
||||
continue
|
||||
if name:
|
||||
if not pass_show_wordlist_checks(name, show):
|
||||
continue
|
||||
if not show_name_helpers.pass_wordlist_checks(name):
|
||||
logger.log(u'Ignored: %s (debug log has detail)' % name)
|
||||
logger.log('Ignored: %s (debug log has detail)' % name)
|
||||
continue
|
||||
best_result.name = name
|
||||
|
||||
|
@ -773,9 +803,11 @@ def search_providers(show, episodes, manual_search=False, torrent_only=False, tr
|
|||
break
|
||||
|
||||
if not len(provider_list):
|
||||
logger.log('No NZB/Torrent sources enabled in Search Provider options to do backlog searches', logger.WARNING)
|
||||
logger.log('No NZB/Torrent sources enabled in Media Provider options to do backlog searches', logger.WARNING)
|
||||
elif not search_done:
|
||||
logger.log('Failed backlog search of %s enabled provider%s. More info in debug log.' % (
|
||||
len(provider_list), helpers.maybe_plural(len(provider_list))), logger.ERROR)
|
||||
elif not any(final_results):
|
||||
logger.log('No suitable candidates')
|
||||
|
||||
return final_results
|
||||
|
|
|
@ -248,7 +248,7 @@ class RecentSearchQueueItem(generic_queue.QueueItem):
|
|||
|
||||
helpers.cpu_sleep()
|
||||
|
||||
except Exception:
|
||||
except (StandardError, Exception):
|
||||
logger.log(traceback.format_exc(), logger.DEBUG)
|
||||
|
||||
if None is self.success:
|
||||
|
@ -270,7 +270,8 @@ class RecentSearchQueueItem(generic_queue.QueueItem):
|
|||
cur_time = datetime.datetime.now(network_timezones.sb_timezone)
|
||||
|
||||
my_db = db.DBConnection()
|
||||
sql_results = my_db.select('SELECT * FROM tv_episodes WHERE status = ? AND season > 0 AND airdate <= ? AND airdate > 1',
|
||||
sql_results = my_db.select(
|
||||
'SELECT * FROM tv_episodes WHERE status = ? AND season > 0 AND airdate <= ? AND airdate > 1',
|
||||
[common.UNAIRED, cur_date])
|
||||
|
||||
sql_l = []
|
||||
|
@ -296,7 +297,7 @@ class RecentSearchQueueItem(generic_queue.QueueItem):
|
|||
# filter out any episodes that haven't aired yet
|
||||
if end_time > cur_time:
|
||||
continue
|
||||
except:
|
||||
except (StandardError, Exception):
|
||||
# if an error occurred assume the episode hasn't aired yet
|
||||
continue
|
||||
|
||||
|
@ -396,7 +397,7 @@ class ManualSearchQueueItem(generic_queue.QueueItem):
|
|||
|
||||
logger.log(u'Unable to find a download for: [%s]' % self.segment.prettyName())
|
||||
|
||||
except Exception:
|
||||
except (StandardError, Exception):
|
||||
logger.log(traceback.format_exc(), logger.DEBUG)
|
||||
|
||||
finally:
|
||||
|
@ -425,6 +426,7 @@ class BacklogQueueItem(generic_queue.QueueItem):
|
|||
def run(self):
|
||||
generic_queue.QueueItem.run(self)
|
||||
|
||||
is_error = False
|
||||
try:
|
||||
logger.log(u'Beginning backlog search for: [%s]' % self.show.name)
|
||||
search_result = search.search_providers(
|
||||
|
@ -440,10 +442,12 @@ class BacklogQueueItem(generic_queue.QueueItem):
|
|||
helpers.cpu_sleep()
|
||||
else:
|
||||
logger.log(u'No needed episodes found during backlog search for: [%s]' % self.show.name)
|
||||
except Exception:
|
||||
except (StandardError, Exception):
|
||||
is_error = True
|
||||
logger.log(traceback.format_exc(), logger.DEBUG)
|
||||
|
||||
finally:
|
||||
logger.log('Completed backlog search %sfor: [%s]' % (('', 'with a debug error ')[is_error], self.show.name))
|
||||
self.finish()
|
||||
|
||||
|
||||
|
@ -462,21 +466,23 @@ class FailedQueueItem(generic_queue.QueueItem):
|
|||
self.started = True
|
||||
|
||||
try:
|
||||
for epObj in self.segment:
|
||||
for ep_obj in self.segment:
|
||||
|
||||
logger.log(u'Marking episode as bad: [%s]' % epObj.prettyName())
|
||||
logger.log(u'Marking episode as bad: [%s]' % ep_obj.prettyName())
|
||||
|
||||
failed_history.markFailed(epObj)
|
||||
cur_status = ep_obj.status
|
||||
|
||||
(release, provider) = failed_history.findRelease(epObj)
|
||||
failed_history.set_episode_failed(ep_obj)
|
||||
(release, provider) = failed_history.find_release(ep_obj)
|
||||
failed_history.revert_episode(ep_obj)
|
||||
if release:
|
||||
failed_history.logFailed(release)
|
||||
history.logFailed(epObj, release, provider)
|
||||
failed_history.add_failed(release)
|
||||
history.logFailed(ep_obj, release, provider)
|
||||
|
||||
failed_history.revertEpisode(epObj)
|
||||
logger.log(u'Beginning failed download search for: [%s]' % epObj.prettyName())
|
||||
logger.log(u'Beginning failed download search for: [%s]' % ep_obj.prettyName())
|
||||
|
||||
search_result = search.search_providers(self.show, self.segment, True, try_other_searches=True)
|
||||
search_result = search.search_providers(
|
||||
self.show, self.segment, True, try_other_searches=True, old_status=cur_status)
|
||||
|
||||
if search_result:
|
||||
for result in search_result:
|
||||
|
@ -488,7 +494,7 @@ class FailedQueueItem(generic_queue.QueueItem):
|
|||
else:
|
||||
pass
|
||||
# logger.log(u'No valid episode found to retry for: [%s]' % self.segment.prettyName())
|
||||
except Exception:
|
||||
except (StandardError, Exception):
|
||||
logger.log(traceback.format_exc(), logger.DEBUG)
|
||||
|
||||
finally:
|
||||
|
|
|
@ -46,7 +46,7 @@ class ShowUpdater:
|
|||
|
||||
# sure, why not?
|
||||
if sickbeard.USE_FAILED_DOWNLOADS:
|
||||
failed_history.trimHistory()
|
||||
failed_history.remove_old_history()
|
||||
|
||||
# clear the data of unused providers
|
||||
sickbeard.helpers.clear_unused_providers()
|
||||
|
|
|
@ -63,12 +63,14 @@ from lib import adba
|
|||
from lib import subliminal
|
||||
from lib.dateutil import tz
|
||||
import lib.rarfile.rarfile as rarfile
|
||||
from unidecode import unidecode
|
||||
|
||||
from lib.libtrakt import TraktAPI
|
||||
from lib.libtrakt.exceptions import TraktException, TraktAuthException
|
||||
from trakt_helpers import build_config, trakt_collection_remove_account
|
||||
from sickbeard.bs4_parser import BS4Parser
|
||||
from lib.tmdb_api import TMDB
|
||||
from lib.tvdb_api.tvdb_exceptions import tvdb_exception
|
||||
|
||||
try:
|
||||
import json
|
||||
|
@ -2552,8 +2554,14 @@ class NewHomeAddShows(Home):
|
|||
def searchIndexersForShowName(self, search_term, lang='en', indexer=None):
|
||||
if not lang or lang == 'null':
|
||||
lang = 'en'
|
||||
|
||||
search_term = search_term.strip().encode('utf-8')
|
||||
term = search_term.decode('utf-8').strip()
|
||||
terms = []
|
||||
try:
|
||||
for t in term.encode('utf-8'), unidecode(term), term:
|
||||
if t not in terms:
|
||||
terms += [t]
|
||||
except (StandardError, Exception):
|
||||
terms = [search_term.strip().encode('utf-8')]
|
||||
|
||||
results = {}
|
||||
final_results = []
|
||||
|
@ -2592,14 +2600,32 @@ class NewHomeAddShows(Home):
|
|||
else:
|
||||
logger.log('Searching for shows using search term: %s from tv datasource %s' % (
|
||||
search_term, sickbeard.indexerApi(indexer).name), logger.DEBUG)
|
||||
results.setdefault(indexer, []).extend(t[search_term])
|
||||
tvdb_ids = []
|
||||
for term in terms:
|
||||
try:
|
||||
for r in t[term]:
|
||||
tvdb_id = int(r['id'])
|
||||
if tvdb_id not in tvdb_ids:
|
||||
tvdb_ids.append(tvdb_id)
|
||||
results.setdefault(indexer, []).extend([r.copy()])
|
||||
except tvdb_exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# Query trakt for tvdb ids
|
||||
try:
|
||||
logger.log('Searching for show using search term: %s from tv datasource Trakt' % search_term, logger.DEBUG)
|
||||
resp = self.getTrakt('/search/show?query=%s&extended=full' % search_term)
|
||||
resp = []
|
||||
for term in terms:
|
||||
result = self.getTrakt('/search/show?query=%s&extended=full' % term)
|
||||
resp += result
|
||||
match = False
|
||||
for r in result:
|
||||
if term == r.get('show', {}).get('title', ''):
|
||||
match = True
|
||||
if match:
|
||||
break
|
||||
tvdb_ids = []
|
||||
results_trakt = []
|
||||
for item in resp:
|
||||
|
|
Loading…
Reference in a new issue