Compare commits
238 Commits
2.6.4.2834
...
submod_opt
| Author | SHA1 | Date | |
|---|---|---|---|
| f337b53ae3 | |||
| aea6050d71 | |||
| 13d5e0761e | |||
| ce28d0284c | |||
| 1a0bb9c3e4 | |||
| d0c71b4b67 | |||
| b3f062956d | |||
| 1a853a780c | |||
| 5c47ddeb2d | |||
| b51deb5d01 | |||
| cbf5ea69be | |||
| e139ffefe6 | |||
| dc0a8deb40 | |||
| 97e93cd10a | |||
| 03c934cf21 | |||
| 92d0d70258 | |||
| d44298993c | |||
| 12300d4115 | |||
| b4f08f61a6 | |||
| 861a25be41 | |||
| 3e175109a6 | |||
| fb2210f2fd | |||
| e928918201 | |||
| df607e5772 | |||
| a7cc470645 | |||
| 4e6421b928 | |||
| df48e8fccd | |||
| 58111bf204 | |||
| 8c02e75fed | |||
| 6f3f1cb4b5 | |||
| dd27997deb | |||
| a1f70d1d4d | |||
| 7da0bac643 | |||
| b3ab2a451c | |||
| 850f836ebd | |||
| d9fa9d03da | |||
| 76c20dc3d7 | |||
| 4568e222d1 | |||
| 344025226a | |||
| f546fcffce | |||
| 068c2d4d00 | |||
| ccf5a902e5 | |||
| 8c72cf9057 | |||
| 1ce14aa231 | |||
| 643485b879 | |||
| 5b3d9f26be | |||
| 70674fbce7 | |||
| f48c0799c0 | |||
| 3bc646187f | |||
| b692ebde6f | |||
| d2a665624a | |||
| 10c8b8ceff | |||
| 92edfc7312 | |||
| eeaeb80f0f | |||
| 6204572ddc | |||
| 14f2f45f20 | |||
| 8ac6c9d7a7 | |||
| 237a47b8ed | |||
| 96bdf606e2 | |||
| 5cc4dcf10b | |||
| a0a3c39606 | |||
| b4cc35b109 | |||
| 72eeb7eb35 | |||
| 44f97411a8 | |||
| ce2296c95d | |||
| 3fe0500746 | |||
| a8daaa787a | |||
| ae9aef9899 | |||
| ad9be91f45 | |||
| 216512788c | |||
| 2483a9c901 | |||
| 9147ed90b7 | |||
| a4ce8c8c52 | |||
| dc83d36193 | |||
| 7dbc466a58 | |||
| 5bb36bc87c | |||
| 7301cd259b | |||
| 5ba5a9dfc4 | |||
| 66e7c60767 | |||
| 22dd7ef093 | |||
| e03bb4f280 | |||
| 4d15283473 | |||
| b1dfbffc4f | |||
| a8de8dcc48 | |||
| e7cdbbcacb | |||
| 060ba2a5be | |||
| 72a21e1ef4 | |||
| cbfb498b0f | |||
| 0ec11c532e | |||
| 65201749f7 | |||
| 8c569183be | |||
| c3665f04d6 | |||
| 2406e0ad49 | |||
| a9490b0838 | |||
| 77e1c69f6b | |||
| f2d8139bef | |||
| 92594dba1d | |||
| 9a28ea7672 | |||
| b87c6c24d8 | |||
| 0c166ff36a | |||
| b7c9530ac0 | |||
| e20f102cb9 | |||
| 7856cc5bb3 | |||
| 663fdf6e2f | |||
| 1867aed769 | |||
| 72aa6150b5 | |||
| 247ae71777 | |||
| bbf5d6712c | |||
| d415f6a9f2 | |||
| 1912881d05 | |||
| a85d9e5442 | |||
| fe2f5b2d8f | |||
| cae64feaf9 | |||
| 6338757a9b | |||
| 60eb08c834 | |||
| cbee0bd6c8 | |||
| db9da54391 | |||
| 5d7f9ced17 | |||
| e3825fbf96 | |||
| 71db2ae1c9 | |||
| b7f12e4291 | |||
| 20f18fc391 | |||
| b6185cc2cc | |||
| 265804a834 | |||
| 9e09de5f1e | |||
| db5f85272f | |||
| cf2f3d0dca | |||
| 0d46b3fa19 | |||
| 768046e889 | |||
| ac940ab25d | |||
| 3cee69d23a | |||
| deaa164f25 | |||
| 68bb7acf95 | |||
| 0eefe3200c | |||
| df65bae58f | |||
| b0443dc812 | |||
| 63545ee732 | |||
| 253d099738 | |||
| 2ea27f2597 | |||
| 2c4b68d719 | |||
| a70f9c0673 | |||
| 540a35cb0e | |||
| 1d9a2ff6fc | |||
| 01a5d71b4a | |||
| 4f11fa53cd | |||
| 8f6540118b | |||
| 089618b8a6 | |||
| 6f87037c78 | |||
| d9b36c0616 | |||
| df2bc9767c | |||
| 508810d5c7 | |||
| dc6770ecaa | |||
| 25b8702a42 | |||
| 5a5aa510c5 | |||
| 5d7777095e | |||
| 95ad5b6fbe | |||
| 9e3227ba0b | |||
| d725c87cae | |||
| 6c3bf03bc3 | |||
| 20c04f32be | |||
| 29bafc6215 | |||
| d3279ef923 | |||
| 291e210e63 | |||
| 535b1aaba9 | |||
| 48cafadbdd | |||
| 39d442c2b3 | |||
| 2bf590b6c4 | |||
| 2e80832154 | |||
| f64e7c1a61 | |||
| 3edf593a18 | |||
| 7ffe41ae9b | |||
| 10a7c327f0 | |||
| a32a2cabd8 | |||
| 7d77870daf | |||
| 45d7233485 | |||
| d03afb5d47 | |||
| d5da52d0fb | |||
| 25714acd38 | |||
| afe05779cd | |||
| 8da007f4eb | |||
| 58b2630968 | |||
| 05e03b3ea4 | |||
| c2781e834f | |||
| 557348831d | |||
| cbf03250f9 | |||
| 7a3bc7086e | |||
| 8047c66869 | |||
| 9d17e2ce9a | |||
| 1f855a7fd7 | |||
| 6310b8f4aa | |||
| 839146b8c7 | |||
| 817a6300ea | |||
| 3a5effaa52 | |||
| 6e8ce9d23d | |||
| cae120cfd4 | |||
| 734e0f7128 | |||
| 4c76439f4e | |||
| 2488d4db53 | |||
| 565987faff | |||
| e14402c6a0 | |||
| ccfc40f6fc | |||
| d69a331b87 | |||
| 01fd66c35a | |||
| 73b33fe697 | |||
| 9e730a2b85 | |||
| 6395b0e945 | |||
| 79d16b98f1 | |||
| eb4fa8d85d | |||
| 7e2d5dfa5d | |||
| f785ba8932 | |||
| 63cf4a2d67 | |||
| 9e270bb53f | |||
| 3bafcb6b4e | |||
| b770a40150 | |||
| 3b50b58aac | |||
| 61ad27845b | |||
| 47bb8563ca | |||
| c2c0df0e88 | |||
| 22f0f8cd60 | |||
| 5af10f1c6b | |||
| 67b322025d | |||
| c58a438ad2 | |||
| c3f5a6f9e2 | |||
| 90422448aa | |||
| 66f7019bf3 | |||
| e0e5e29ba1 | |||
| 5f9010e4b9 | |||
| dd40f272cb | |||
| 9abb018fe8 | |||
| d38a22901c | |||
| 0b8eace5bb | |||
| 16b69ef3cc | |||
| acd556d6f1 | |||
| 2a5db95ef2 | |||
| 3c2c71a7da | |||
| 838ef0cdc7 | |||
| 68229fdd72 | |||
| 20952b5c26 |
+155
@@ -1,4 +1,159 @@
|
||||
|
||||
2.6.5.3041
|
||||
|
||||
Changelog
|
||||
- core: only reference guessed title if there actually is one
|
||||
- core: cf: optimize
|
||||
- core/config: add setting for one existing language to be enough, fixes #491
|
||||
- core/compat: dns: support nameservers via ENV[dns_resolvers]; don't fall back to default DNS when configured custom DNS failed
|
||||
- providers: titlovi: prevent repeated captcha solving for CF
|
||||
|
||||
|
||||
2.6.5.3017
|
||||
|
||||
Changelog
|
||||
- core: SRT parsing: handle (bad) ASS color tag in SRT
|
||||
- core: auto extract embedded: only use one unknown sub for first language
|
||||
- core: better embedded streams language detection
|
||||
- core: optimizations
|
||||
- core: extract embedded: fix is_unknown check
|
||||
- core: don't raise exception when subtitle not found inside archive
|
||||
- core: search external subtitles: fix condition
|
||||
- core: better plex transcoder path detection
|
||||
- core: use Log.Warn instead of Log.Warning (#619, #629, #633)
|
||||
- core: also check for "plex transcoder.exe" in case of windows (fixes #619)
|
||||
- core: auto extract: use mbcs encoding for paths on windows
|
||||
- core: Fix issue scandir not returning the name of the file inside Docker images on ARM systems. (thanks @giejay)
|
||||
- core: also clean PYTHONHOME when calling external notification app
|
||||
- core: update certifi to 2019.3.9
|
||||
- core: scan_video: add series/title as alternative by scanning filename itself without parent folders
|
||||
- core: add generic solution for solving captchas using anti captcha services
|
||||
- core: increase cache time to 180d (was: 30d)
|
||||
- core: guess_matches: handle multiple title matches; fixes bazarr#403
|
||||
- windows: fix compatibility issues with plex transcoder
|
||||
- compat: use lowercase paths on subtitle detection
|
||||
- providers: addic7ed: re-enable (using paid anti captch service)
|
||||
- providers: assrt: assume undefined Chinese flavor as Simplified (chs/zho-Hans)
|
||||
- providers: subscene: make it work again by bypassing cf
|
||||
- providers: subscene: don't fail on missing cover
|
||||
- providers: titlovi: re-enable (might need paid anti captch service)
|
||||
- providers: opensubtitles: fix only_foreign handling
|
||||
- providers: opensubtitles: show subtitles with possibly mismatched series when manually listing subs
|
||||
- menu: list subtitles: show subtitles with bad season/episode values as well
|
||||
- refiners: omdb: fix imdb ids with spaces
|
||||
|
||||
|
||||
2.6.4.2911
|
||||
- core: improve file cache (windows especially); use fixed-length cache filenames; fixes #600
|
||||
- core: don't log "Checking connections ..." when sonarr/radarr not activated
|
||||
- core: make logging for scanning/parsing/preparing videos more clear
|
||||
- core: extract embedded: continue searching for embbedded subs after undefined language is found (thanks @jippo015)
|
||||
- compat: core: don't assume hints["title"] exists
|
||||
- compat: providers: podnapisi: loosen lxml requirement
|
||||
- providers: addic7ed: fix not using user credentials; fixes #605
|
||||
- providers: titlovi: fix provider (thanks @viking1304)
|
||||
- providers: subscene: fix provider
|
||||
- providers: opensubtitles: improve token logging
|
||||
- providers: podnapisi: fix searching for Marvel series
|
||||
- submod: HI: correctly remove uppercase at start of a sentence when lead by a crocodile (>>)
|
||||
- submod: HI: correctly remove lowercase inside brackets when HI matched as well
|
||||
- submod: HI: remove multiple HI_before_colon_caps before one colon
|
||||
- submod: common: also match music symbols after a crocodile; move crocodile removal up the chain
|
||||
|
||||
|
||||
2.6.4.2881
|
||||
- core: extract embedded: fix automatic extraction not actually writing the subtitles to disk under certain circumstances; fixes #598
|
||||
- core: sonarr/radarr: don't block the main thread while checking connectivity; fixes #597
|
||||
- providers: hosszupuska: fix inconsistent series naming (thanks @morpheus133)
|
||||
- providers: opensubtitles: add advanced setting to optionally not skip subtitles with wrong FPS; fixes #578
|
||||
|
||||
|
||||
2.6.4.2864
|
||||
- core: scanning: don't fail on metadata subtitles with bad language code; fixes #596
|
||||
- providers: legendastv, napiprojekt, subscenter, tvsubtitles: fix "No language to search for" issue; fixes #596
|
||||
- menu: fix "ignore list list"
|
||||
- menu: advanced: add skip next search all recently missing subtitles entry
|
||||
|
||||
|
||||
2.6.4.2859
|
||||
- core: fix thread.lock error (only affected the history menu, not the actual functionality)
|
||||
- core: fix audio-based conditional subtitle decision making; fixes #592
|
||||
- core: massively improve metadata subtitle storage
|
||||
- providers: opensubtitles: skip non-forced results when searching for forced
|
||||
- providers: podnapisi: skip non-forced results when searching for forced
|
||||
- submod: common: correctly pad music symbols on either side
|
||||
|
||||
|
||||
|
||||
2.6.4.2834
|
||||
- core: add option to use custom (Google, Cloudflare) DNS to resolve provider hosts in problematic countries; fixes #547
|
||||
- core: add support for downloading subtitles only when the audio streams don't match (any?) configured languages; fixes #519
|
||||
- core: add support for an include list instead of an ignore list; add the option to disable SZ by default, then enable it per item/series/section (inverse ignore list)
|
||||
- core/menu/config: support forced/foreign subtitles independently
|
||||
- core: fallback for OSError on scandir, should fix #532
|
||||
- core: add config versioning/migration system
|
||||
- core: correctly force non-foreign-only-capable providers off; remove subscene from foreign-only capable providers
|
||||
- core: scanning: collect information about audio streams
|
||||
- core: use correct storage path when storing subtitle info, when only VTT is used
|
||||
- core: fix disabled channel mode
|
||||
- core/menu: extract embedded: add extracted embedded subtitles to history
|
||||
- core: embedded subtitle streams: don't try parsing the language if inexistant
|
||||
- core: subtitle: fix log call, fixes #569
|
||||
- core: download best subtitles: only use actually languages searched for
|
||||
- core: refiners: tvdb: warn instead of error when no matching series was found
|
||||
- core: scanning: re-add expected title to guessit for narrowing down the video title
|
||||
- core: resolve #583
|
||||
- core: archives: explicitly skip forced subtitles if not searched for, when picking from an archive
|
||||
- core: activities/auto-refresh: fix hybrid-plus for movies
|
||||
- core: don't disable plugin if all providers throttled; fix #585 #574
|
||||
- core: skip cleanup for ignored paths
|
||||
- core: update requests to 2.20.0 (fixes security issue)
|
||||
- core: update certifi to 2018.10.15
|
||||
- core: auto extract: don't overwrite local sub even if unknown to SZ
|
||||
- config: set autoclean leftover/unused to off by default
|
||||
- providers: opensubtitles: respect rate limit (40 hits/10s); should fix long throttling behaviour
|
||||
- providers: opensubtitles: handle bad/inexistant responses
|
||||
- providers: opensubtitles: log bad response data
|
||||
- providers: opensubtitles: treat empty response as ServiceUnavailable for now
|
||||
- providers: opensubtitles: log reason for ServiceUnavailable
|
||||
- providers: legendastv: match second title and imdb id
|
||||
- providers: titlovi: fix language handling (thanks @viking1304)
|
||||
- providers: titlovi: proper handling of archives with both cyrlic and latin subtitles (thanks @viking1304)
|
||||
- providers: titlovi: allow direct subtitle downloads as fallback (when a subtitle, not an archive was returned)
|
||||
- providers: hosszupuska: implement site change (thanks @morpheus133)
|
||||
- providers: supersubtitles: add base properties to subtitle
|
||||
- providers: opensubtitles, podnapisi: fix foreign/forced handling
|
||||
- providers: subscene: use original/sceneName if possible
|
||||
- menu: fix plugin not responding when ignoring an item in certain menus; fixes #535
|
||||
- menu: select active subtitle: return to item details afterwards; correctly set current
|
||||
- menu: add item thumbnails to history and a couple of submenus
|
||||
- menu: history: use series thumbnail instead of episode screenshot
|
||||
- menu: add full soft include/exclude menu handling
|
||||
- menu: add support for separate forced and not-forced subtitles
|
||||
- menu: fix order of embedded subtitle streams in item detail
|
||||
- menu: support S00E00 and equivalent
|
||||
- submod: add option to fix only-uppercase subtitles and make them readable
|
||||
- submod: keep track of actually applied mods
|
||||
- submod: correctly merge mods of the same kind (offset)
|
||||
- submod: OCR: add dictionaries for bosnian and norwegian bokmal; update dicts for dan, eng, hrv, spa, srp, swe
|
||||
- submod: OCR/HI: skip certain processors for all-caps subs
|
||||
- submod: HI: only remove caps before colon if the colon is followed by whitespace or EOL; fixes #542
|
||||
- submod: HI: remove MAN:
|
||||
- submod: common: improve detection and normalization of quotes, apostrophes
|
||||
- submod: common: fix double quotes that are meant to be single quotes inside words
|
||||
- submod: common: normalize small hyphens to dash
|
||||
- submod: common: remove line only consisting of colon; remove empty colon at start of line
|
||||
- submod: common: add space after punctuation
|
||||
- submod: common: fix lowercase i for english language
|
||||
- submod: common: better fix for music symbols
|
||||
- submod: reverse_RTL: also reverse ":,'-" chars in CM_RTL_reverse (thanks @doopler)
|
||||
- submod: reverse_RTL: enable mod for arabic, farsi and persian besides hebrew
|
||||
- i18n: fix not used translation for recently added missing subtitles menu
|
||||
- i18n: fix spanish translation, fixes #543
|
||||
- i18n: Hungarian translation is incomplete
|
||||
|
||||
|
||||
|
||||
2.5.7.2663
|
||||
- implement translations for the channel and the settings
|
||||
- i18n: German (myself)
|
||||
|
||||
+13
-11
@@ -99,7 +99,7 @@ def update_local_media(videos, ignore_parts_cleanup=None):
|
||||
support.localmedia.find_subtitles(video["plex_part"], ignore_parts_cleanup=ignore_parts_cleanup)
|
||||
|
||||
|
||||
def agent_extract_embedded(video_part_map, history_storage=None):
|
||||
def agent_extract_embedded(video_part_map):
|
||||
try:
|
||||
subtitle_storage = get_subtitle_storage()
|
||||
|
||||
@@ -109,24 +109,29 @@ def agent_extract_embedded(video_part_map, history_storage=None):
|
||||
for scanned_video, part_info in video_part_map.iteritems():
|
||||
plexapi_item = scanned_video.plexapi_metadata["item"]
|
||||
stored_subs = subtitle_storage.load_or_new(plexapi_item)
|
||||
valid_langs_in_media = audio_streams_match_languages(scanned_video, config.get_lang_list(ordered=True))
|
||||
|
||||
if audio_streams_match_languages(scanned_video, list(config.lang_list)):
|
||||
if not config.lang_list.difference(valid_langs_in_media):
|
||||
Log.Debug("Skipping embedded subtitle extraction for %s, audio streams are in correct language(s)",
|
||||
plexapi_item.rating_key)
|
||||
continue
|
||||
|
||||
for plexapi_part in get_all_parts(plexapi_item):
|
||||
item_count = item_count + 1
|
||||
used_one_unknown_stream = False
|
||||
for requested_language in config.lang_list:
|
||||
embedded_subs = stored_subs.get_by_provider(plexapi_part.id, requested_language, "embedded")
|
||||
current = stored_subs.get_any(plexapi_part.id, requested_language) or \
|
||||
requested_language in scanned_video.subtitle_languages
|
||||
requested_language in scanned_video.external_subtitle_languages
|
||||
|
||||
if not embedded_subs:
|
||||
stream_data = get_embedded_subtitle_streams(plexapi_part, requested_language=requested_language)
|
||||
stream_data = get_embedded_subtitle_streams(plexapi_part, requested_language=requested_language,
|
||||
skip_unknown=used_one_unknown_stream)
|
||||
|
||||
if stream_data:
|
||||
stream = stream_data[0]["stream"]
|
||||
if stream_data[0]["is_unknown"]:
|
||||
used_one_unknown_stream = True
|
||||
|
||||
to_extract.append(({scanned_video: part_info}, plexapi_part, str(stream.index),
|
||||
str(requested_language), not current))
|
||||
@@ -139,7 +144,7 @@ def agent_extract_embedded(video_part_map, history_storage=None):
|
||||
if to_extract:
|
||||
Log.Info("Triggering extraction of %d embedded subtitles of %d items", len(to_extract), item_count)
|
||||
Thread.Create(multi_extract_embedded, stream_list=to_extract, refresh=True, with_mods=True,
|
||||
single_thread=not config.advanced.auto_extract_multithread, history_storage=history_storage)
|
||||
single_thread=not config.advanced.auto_extract_multithread)
|
||||
except:
|
||||
Log.Error("Something went wrong when auto-extracting subtitles, continuing: %s", traceback.format_exc())
|
||||
|
||||
@@ -176,7 +181,6 @@ class SubZeroAgent(object):
|
||||
return
|
||||
|
||||
intent = get_intent()
|
||||
history = get_history()
|
||||
|
||||
item_ids = []
|
||||
try:
|
||||
@@ -222,9 +226,9 @@ class SubZeroAgent(object):
|
||||
# auto extract embedded
|
||||
if config.embedded_auto_extract:
|
||||
if config.plex_transcoder:
|
||||
agent_extract_embedded(scanned_video_part_map, history_storage=history)
|
||||
agent_extract_embedded(scanned_video_part_map)
|
||||
else:
|
||||
Log.Warning("Plex Transcoder not found, can't auto extract")
|
||||
Log.Warn("Plex Transcoder not found, can't auto extract")
|
||||
|
||||
# clear missing subtitles menu data
|
||||
if not scheduler.is_task_running("MissingSubtitles"):
|
||||
@@ -276,6 +280,7 @@ class SubZeroAgent(object):
|
||||
for video, video_subtitles in downloaded_subtitles.items():
|
||||
# store item(s) in history
|
||||
for subtitle in video_subtitles:
|
||||
history = get_history()
|
||||
item_title = get_title_for_video_metadata(video.plexapi_metadata, add_section_title=False)
|
||||
history.add(item_title, video.id, section_title=video.plexapi_metadata["section"],
|
||||
thumb=video.plexapi_metadata["super_thumb"],
|
||||
@@ -291,9 +296,6 @@ class SubZeroAgent(object):
|
||||
# update the menu state
|
||||
set_refresh_menu_state(None)
|
||||
|
||||
history.destroy()
|
||||
history = None
|
||||
|
||||
# notify any running tasks about our finished update
|
||||
for item_id in item_ids:
|
||||
#scheduler.signal("updated_metadata", item_id)
|
||||
|
||||
@@ -61,6 +61,10 @@ def AdvancedMenu(randomize=None, header=None, message=None):
|
||||
key=Callback(SkipFindBetterSubtitles, randomize=timestamp()),
|
||||
title=pad_title(_("Skip next find better subtitles (sets last run to now)")),
|
||||
))
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(SkipRecentlyAddedMissing, randomize=timestamp()),
|
||||
title=pad_title(_("Skip next find recently added with missing subtitles (sets last run to now)")),
|
||||
))
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(TriggerStorageMaintenance, randomize=timestamp()),
|
||||
title=pad_title(_("Trigger subtitle storage maintenance")),
|
||||
@@ -207,6 +211,19 @@ def SkipFindBetterSubtitles(randomize=None):
|
||||
)
|
||||
|
||||
|
||||
@route(PREFIX + '/skipram')
|
||||
@debounce
|
||||
def SkipRecentlyAddedMissing(randomize=None):
|
||||
task = scheduler.task("SearchAllRecentlyAddedMissing")
|
||||
task.last_run = datetime.datetime.now()
|
||||
|
||||
return AdvancedMenu(
|
||||
randomize=timestamp(),
|
||||
header=_("Success"),
|
||||
message=_("SearchAllRecentlyAddedMissing skipped")
|
||||
)
|
||||
|
||||
|
||||
@route(PREFIX + '/triggermaintenance')
|
||||
@debounce
|
||||
def TriggerStorageMaintenance(randomize=None):
|
||||
|
||||
@@ -580,6 +580,8 @@ def ListAvailableSubsForItemMenu(rating_key=None, part_id=None, title=None, item
|
||||
bl_addon = "Blacklisted "
|
||||
|
||||
wrong_fps_addon = ""
|
||||
wrong_series_addon = ""
|
||||
wrong_season_ep_addon = ""
|
||||
if subtitle.wrong_fps:
|
||||
if plex_part:
|
||||
wrong_fps_addon = _(" (wrong FPS, sub: %(subtitle_fps)s, media: %(media_fps)s)",
|
||||
@@ -589,15 +591,24 @@ def ListAvailableSubsForItemMenu(rating_key=None, part_id=None, title=None, item
|
||||
wrong_fps_addon = _(" (wrong FPS, sub: %(subtitle_fps)s, media: unknown, low impact mode)",
|
||||
subtitle_fps=subtitle.fps)
|
||||
|
||||
if subtitle.wrong_series:
|
||||
wrong_series_addon = _(" (possibly wrong series)")
|
||||
|
||||
if subtitle.wrong_season_ep:
|
||||
wrong_season_ep_addon = _(" (possibly wrong season/episode)")
|
||||
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(TriggerDownloadSubtitle, rating_key=rating_key, randomize=timestamp(), item_title=item_title,
|
||||
subtitle_id=str(subtitle.id), language=language),
|
||||
title=_(u"%(blacklisted_state)s%(current_state)s: %(provider_name)s, score: %(score)s%(wrong_fps_state)s",
|
||||
title=_(u"%(blacklisted_state)s%(current_state)s: %(provider_name)s, score: %(score)s%(wrong_fps_state)s"
|
||||
u"%(wrong_series_state)s%(wrong_season_ep_state)s",
|
||||
blacklisted_state=bl_addon,
|
||||
current_state=_("Available") if current_id != subtitle.id else _("Current"),
|
||||
provider_name=_(subtitle.provider_name),
|
||||
score=subtitle.score,
|
||||
wrong_fps_state=wrong_fps_addon),
|
||||
wrong_fps_state=wrong_fps_addon,
|
||||
wrong_series_state=wrong_series_addon,
|
||||
wrong_season_ep_state=wrong_season_ep_addon),
|
||||
summary=_(u"Release: %(release_info)s, Matches: %(matches)s",
|
||||
release_info=subtitle.release_info,
|
||||
matches=", ".join(subtitle.matches)),
|
||||
|
||||
@@ -276,6 +276,40 @@ def replace_item(obj, key, replace_value):
|
||||
return obj
|
||||
|
||||
|
||||
def check_connections():
|
||||
# debug drone
|
||||
Log.Debug("Checking connections ...")
|
||||
log_buffer = []
|
||||
try:
|
||||
from subliminal_patch.refiners.drone import SonarrClient, RadarrClient
|
||||
log_buffer.append(["----- Connections -----"])
|
||||
for key, cls in [("sonarr", SonarrClient), ("radarr", RadarrClient)]:
|
||||
if key in config.refiner_settings:
|
||||
cname = key.capitalize()
|
||||
try:
|
||||
status = cls(**config.refiner_settings[key]).status(timeout=5)
|
||||
except HTTPError, e:
|
||||
if e.response.status_code == 401:
|
||||
log_buffer.append(("%s: NOT WORKING - BAD API KEY", cname))
|
||||
else:
|
||||
log_buffer.append(("%s: NOT WORKING - %s", cname, traceback.format_exc()))
|
||||
except:
|
||||
log_buffer.append(("%s: NOT WORKING - %s", cname, traceback.format_exc()))
|
||||
else:
|
||||
if status and status["version"]:
|
||||
log_buffer.append(("%s: OK - %s", cname, status["version"]))
|
||||
else:
|
||||
log_buffer.append(("%s: NOT WORKING - %s", cname))
|
||||
except:
|
||||
log_buffer.append(("Something went really wrong when evaluating Sonarr/Radarr: %s", traceback.format_exc()))
|
||||
finally:
|
||||
Core.log.setLevel(logging.DEBUG)
|
||||
for entry in log_buffer:
|
||||
Log.Debug(*entry)
|
||||
|
||||
Core.log.setLevel(logging.getLevelName(Prefs["log_level"]))
|
||||
|
||||
|
||||
@route(PREFIX + '/ValidatePrefs', enforce_route=True)
|
||||
def ValidatePrefs():
|
||||
Core.log.setLevel(logging.DEBUG)
|
||||
@@ -333,7 +367,8 @@ def ValidatePrefs():
|
||||
"enable_channel", "permissions_ok", "missing_permissions", "fs_encoding",
|
||||
"subtitle_destination_folder", "include", "include_exclude_paths", "include_exclude_sz_files",
|
||||
"new_style_cache", "dbm_supported", "lang_list", "providers", "normal_subs", "forced_only", "forced_also",
|
||||
"plex_transcoder", "refiner_settings", "unrar", "adv_cfg_path", "use_custom_dns"]:
|
||||
"plex_transcoder", "refiner_settings", "unrar", "adv_cfg_path", "use_custom_dns",
|
||||
"has_anticaptcha", "anticaptcha_cls"]:
|
||||
|
||||
value = getattr(config, attr)
|
||||
if isinstance(value, dict):
|
||||
@@ -341,6 +376,9 @@ def ValidatePrefs():
|
||||
Log.Debug("config.%s: %s", attr, d)
|
||||
continue
|
||||
|
||||
if attr in ("api_key",):
|
||||
value = "xxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
Log.Debug("config.%s: %s", attr, value)
|
||||
|
||||
for attr in ["plugin_log_path", "server_log_path"]:
|
||||
@@ -362,30 +400,8 @@ def ValidatePrefs():
|
||||
"subtitles.save.filesystem", ]:
|
||||
Log.Debug("Pref.%s: %s", attr, Prefs[attr])
|
||||
|
||||
# debug drone
|
||||
if "sonarr" in config.refiner_settings or "radarr" in config.refiner_settings:
|
||||
Log.Debug("----- Connections -----")
|
||||
try:
|
||||
from subliminal_patch.refiners.drone import SonarrClient, RadarrClient
|
||||
for key, cls in [("sonarr", SonarrClient), ("radarr", RadarrClient)]:
|
||||
if key in config.refiner_settings:
|
||||
cname = key.capitalize()
|
||||
try:
|
||||
status = cls(**config.refiner_settings[key]).status()
|
||||
except HTTPError, e:
|
||||
if e.response.status_code == 401:
|
||||
Log.Debug("%s: NOT WORKING - BAD API KEY", cname)
|
||||
else:
|
||||
Log.Debug("%s: NOT WORKING - %s", cname, traceback.format_exc())
|
||||
except:
|
||||
Log.Debug("%s: NOT WORKING - %s", cname, traceback.format_exc())
|
||||
else:
|
||||
if status and status["version"]:
|
||||
Log.Debug("%s: OK - %s", cname, status["version"])
|
||||
else:
|
||||
Log.Debug("%s: NOT WORKING - %s", cname)
|
||||
except:
|
||||
Log.Debug("Something went really wrong when evaluating Sonarr/Radarr: %s", traceback.format_exc())
|
||||
Thread.Create(check_connections)
|
||||
|
||||
# fixme: check existance of and os access of logs
|
||||
Log.Debug("----- Environment -----")
|
||||
|
||||
@@ -7,11 +7,12 @@ import os
|
||||
import operator
|
||||
|
||||
from func import enable_channel_wrapper, route_wrapper, register_route_function
|
||||
from subzero.lib.io import get_viable_encoding
|
||||
from subzero.language import Language
|
||||
from support.i18n import is_localized_string, _
|
||||
from support.items import get_kind, get_item_thumb, get_item, get_item_kind_from_item, refresh_item
|
||||
from support.helpers import get_video_display_title, pad_title, display_language, quote_args, is_stream_forced, \
|
||||
get_title_for_video_metadata
|
||||
get_title_for_video_metadata, mswindows
|
||||
from support.history import get_history
|
||||
from support.ignore import get_decision_list
|
||||
from support.lib import get_intent
|
||||
@@ -169,7 +170,6 @@ def extract_embedded_sub(**kwargs):
|
||||
part = kwargs.pop("part", get_part(plex_item, part_id))
|
||||
scanned_videos = kwargs.pop("scanned_videos", None)
|
||||
extract_mode = kwargs.pop("extract_mode", "a")
|
||||
history_storage = kwargs.pop("history_storage", None)
|
||||
|
||||
any_successful = False
|
||||
|
||||
@@ -194,9 +194,16 @@ def extract_embedded_sub(**kwargs):
|
||||
args = [
|
||||
config.plex_transcoder, "-i", part.file, "-map", "0:%s" % stream_index, "-f", out_codec, "-"
|
||||
]
|
||||
|
||||
cmdline = quote_args(args)
|
||||
Log.Debug(u"Calling: %s", cmdline)
|
||||
if mswindows:
|
||||
Log.Debug("MSWindows: Fixing encoding")
|
||||
cmdline = cmdline.encode("mbcs")
|
||||
|
||||
output = None
|
||||
try:
|
||||
output = subprocess.check_output(quote_args(args), stderr=subprocess.PIPE, shell=True)
|
||||
output = subprocess.check_output(cmdline, stderr=subprocess.PIPE, shell=True)
|
||||
except:
|
||||
Log.Error("Extraction failed: %s", traceback.format_exc())
|
||||
|
||||
@@ -220,17 +227,12 @@ def extract_embedded_sub(**kwargs):
|
||||
# add item to history
|
||||
item_title = get_title_for_video_metadata(video.plexapi_metadata,
|
||||
add_section_title=False, add_episode_title=True)
|
||||
if history_storage:
|
||||
history = history_storage
|
||||
else:
|
||||
history = get_history()
|
||||
|
||||
history = get_history()
|
||||
history.add(item_title, video.id, section_title=video.plexapi_metadata["section"],
|
||||
thumb=video.plexapi_metadata["super_thumb"],
|
||||
subtitle=subtitle, mode=extract_mode)
|
||||
|
||||
if not history_storage:
|
||||
history.destroy()
|
||||
history.destroy()
|
||||
|
||||
any_successful = True
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# coding=utf-8
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import inspect
|
||||
@@ -79,7 +80,7 @@ PROVIDER_THROTTLE_MAP = {
|
||||
|
||||
|
||||
class Config(object):
|
||||
config_version = 2
|
||||
config_version = 3
|
||||
libraries_root = None
|
||||
plugin_info = ""
|
||||
version = None
|
||||
@@ -143,11 +144,15 @@ class Config(object):
|
||||
refiner_settings = None
|
||||
exact_filenames = False
|
||||
only_one = False
|
||||
any_language_is_enough = False
|
||||
embedded_auto_extract = False
|
||||
ietf_as_alpha3 = False
|
||||
unrar = None
|
||||
adv_cfg_path = None
|
||||
use_custom_dns = False
|
||||
use_custom_dns = None
|
||||
anticaptcha_token = None
|
||||
anticaptcha_cls = None
|
||||
has_anticaptcha = False
|
||||
|
||||
store_recently_played_amount = 40
|
||||
|
||||
@@ -172,6 +177,8 @@ class Config(object):
|
||||
self.data_items_path = os.path.join(self.data_path, "DataItems")
|
||||
self.universal_plex_token = self.get_universal_plex_token()
|
||||
self.plex_token = os.environ.get("PLEXTOKEN", self.universal_plex_token)
|
||||
self.new_style_cache = cast_bool(Prefs['new_style_cache'])
|
||||
self.pack_cache_dir = self.get_pack_cache_dir()
|
||||
try:
|
||||
self.migrate_prefs()
|
||||
except:
|
||||
@@ -180,12 +187,15 @@ class Config(object):
|
||||
subzero.constants.DEFAULT_TIMEOUT = lib.DEFAULT_TIMEOUT = self.pms_request_timeout = \
|
||||
min(cast_int(Prefs['pms_request_timeout'], 15), 45)
|
||||
self.low_impact_mode = cast_bool(Prefs['low_impact_mode'])
|
||||
self.new_style_cache = cast_bool(Prefs['new_style_cache'])
|
||||
self.pack_cache_dir = self.get_pack_cache_dir()
|
||||
self.advanced = self.get_advanced_config()
|
||||
self.debug_i18n = self.advanced.debug_i18n
|
||||
|
||||
os.environ["SZ_USER_AGENT"] = self.get_user_agent()
|
||||
os.environ["ANTICAPTCHA_ACCOUNT_KEY"] = self.anticaptcha_token = str(Prefs["anticaptcha.api_key"]) or ""
|
||||
acs = str(Prefs["anticaptcha.service"])
|
||||
if acs and acs != "none":
|
||||
os.environ["ANTICAPTCHA_CLASS"] = self.anticaptcha_cls = acs
|
||||
self.has_anticaptcha = self.anticaptcha_token and self.anticaptcha_cls
|
||||
|
||||
self.setup_proxies()
|
||||
self.set_plugin_mode()
|
||||
@@ -225,9 +235,10 @@ class Config(object):
|
||||
self.no_refresh = os.environ.get("SZ_NO_REFRESH", False)
|
||||
self.plex_transcoder = self.get_plex_transcoder()
|
||||
self.only_one = cast_bool(Prefs['subtitles.only_one'])
|
||||
self.any_language_is_enough = Prefs['subtitles.any_language_is_enough']
|
||||
self.embedded_auto_extract = cast_bool(Prefs["subtitles.embedded.autoextract"])
|
||||
self.ietf_as_alpha3 = cast_bool(Prefs["subtitles.language.ietf_normalize"])
|
||||
self.use_custom_dns = cast_bool(Prefs['use_custom_dns'])
|
||||
self.use_custom_dns = self.parse_custom_dns()
|
||||
self.initialized = True
|
||||
|
||||
def migrate_prefs(self):
|
||||
@@ -256,6 +267,9 @@ class Config(object):
|
||||
|
||||
if update_prefs:
|
||||
update_user_prefs(update_prefs, Prefs, Log)
|
||||
else:
|
||||
Dict["config_version"] = self.config_version
|
||||
Dict.Save()
|
||||
|
||||
def migrate_prefs_to_1(self, user_prefs, **kwargs):
|
||||
update_prefs = {}
|
||||
@@ -275,6 +289,15 @@ class Config(object):
|
||||
|
||||
return update_prefs
|
||||
|
||||
def migrate_prefs_to_3(self, user_prefs, **kwargs):
|
||||
if config.new_style_cache:
|
||||
self.init_cache()
|
||||
try:
|
||||
subliminal.region.backend.clear()
|
||||
except:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def init_libraries(self):
|
||||
try_executables = []
|
||||
custom_unrar = os.environ.get("SZ_UNRAR_TOOL")
|
||||
@@ -317,9 +340,10 @@ class Config(object):
|
||||
|
||||
def init_cache(self):
|
||||
if self.new_style_cache:
|
||||
subliminal.region.configure('subzero.cache.file', expiration_time=datetime.timedelta(days=30),
|
||||
subliminal.region.configure('subzero.cache.file', expiration_time=datetime.timedelta(days=180),
|
||||
arguments={'appname': "sz_cache",
|
||||
'app_cache_dir': self.data_path})
|
||||
'app_cache_dir': self.data_path},
|
||||
replace_existing_backend=True)
|
||||
Log.Info("Using new style file based cache!")
|
||||
return
|
||||
|
||||
@@ -359,14 +383,15 @@ class Config(object):
|
||||
try:
|
||||
subliminal.region.configure('dogpile.cache.dbm', expiration_time=datetime.timedelta(days=30),
|
||||
arguments={'filename': dbfn,
|
||||
'lock_factory': MutexLock})
|
||||
'lock_factory': MutexLock},
|
||||
replace_existing_backend=True)
|
||||
Log.Info("Using file based cache!")
|
||||
return
|
||||
except:
|
||||
self.dbm_supported = False
|
||||
|
||||
Log.Warn("Not using file based cache!")
|
||||
subliminal.region.configure('dogpile.cache.memory')
|
||||
subliminal.region.configure('dogpile.cache.memory', replace_existing_backend=True)
|
||||
|
||||
def sync_cache(self):
|
||||
if not self.new_style_cache:
|
||||
@@ -633,7 +658,7 @@ class Config(object):
|
||||
return enabled_sections
|
||||
|
||||
# Prepare a list of languages we want subs for
|
||||
def get_lang_list(self, provider=None):
|
||||
def get_lang_list(self, provider=None, ordered=False):
|
||||
# advanced settings
|
||||
if provider and self.advanced.providers and provider in self.advanced.providers:
|
||||
adv_languages = self.advanced.providers[provider].get("languages", None)
|
||||
@@ -654,21 +679,21 @@ class Config(object):
|
||||
if adv_out:
|
||||
return adv_out
|
||||
|
||||
l = {Language.fromietf(Prefs["langPref1a"])}
|
||||
l = [Language.fromietf(Prefs["langPref1a"])]
|
||||
lang_custom = Prefs["langPrefCustom"].strip()
|
||||
|
||||
if Prefs['subtitles.only_one']:
|
||||
return l
|
||||
return set(l) if not ordered else l
|
||||
|
||||
if Prefs["langPref2a"] != "None":
|
||||
try:
|
||||
l.update({Language.fromietf(Prefs["langPref2a"])})
|
||||
l.append(Language.fromietf(Prefs["langPref2a"]))
|
||||
except:
|
||||
pass
|
||||
|
||||
if Prefs["langPref3a"] != "None":
|
||||
try:
|
||||
l.update({Language.fromietf(Prefs["langPref3a"])})
|
||||
l.append(Language.fromietf(Prefs["langPref3a"]))
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -682,12 +707,12 @@ class Config(object):
|
||||
real_lang = Language.fromname(lang)
|
||||
except:
|
||||
continue
|
||||
l.update({real_lang})
|
||||
l.append(real_lang)
|
||||
|
||||
if self.forced_also:
|
||||
langs_to_force = []
|
||||
if Prefs["subtitles.when_forced"] == "Always":
|
||||
langs_to_force = list(l)
|
||||
for lang in list(l):
|
||||
l.append(Language.rebuild(lang, forced=True))
|
||||
|
||||
else:
|
||||
for (setting, index) in (("Only for Subtitle Language (1)", 0),
|
||||
@@ -695,19 +720,21 @@ class Config(object):
|
||||
("Only for Subtitle Language (3)", 2)):
|
||||
if Prefs["subtitles.when_forced"] == setting:
|
||||
try:
|
||||
langs_to_force.append(list(l)[index])
|
||||
l.append(Language.rebuild(list(l)[index], forced=True))
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
for lang in langs_to_force:
|
||||
l.add(Language.rebuild(lang, forced=True))
|
||||
|
||||
elif self.forced_only:
|
||||
for lang in l:
|
||||
lang.forced = True
|
||||
|
||||
return l
|
||||
if not self.normal_subs:
|
||||
for lang in l[:]:
|
||||
if not lang.forced:
|
||||
l.remove(lang)
|
||||
|
||||
return set(l) if not ordered else l
|
||||
|
||||
lang_list = property(get_lang_list)
|
||||
|
||||
@@ -735,7 +762,7 @@ class Config(object):
|
||||
# 'thesubdb': Prefs['provider.thesubdb.enabled'],
|
||||
'podnapisi': cast_bool(Prefs['provider.podnapisi.enabled']),
|
||||
'titlovi': cast_bool(Prefs['provider.titlovi.enabled']),
|
||||
'addic7ed': cast_bool(Prefs['provider.addic7ed.enabled']),
|
||||
'addic7ed': cast_bool(Prefs['provider.addic7ed.enabled']) and self.has_anticaptcha,
|
||||
'tvsubtitles': cast_bool(Prefs['provider.tvsubtitles.enabled']),
|
||||
'legendastv': cast_bool(Prefs['provider.legendastv.enabled']),
|
||||
'napiprojekt': cast_bool(Prefs['provider.napiprojekt.enabled']),
|
||||
@@ -815,11 +842,13 @@ class Config(object):
|
||||
|
||||
def get_provider_settings(self):
|
||||
os_use_https = self.advanced.providers.opensubtitles.use_https \
|
||||
if self.advanced.providers.opensubtitles.use_https != None else True
|
||||
if self.advanced.providers.opensubtitles.use_https is not None else True
|
||||
|
||||
os_skip_wrong_fps = self.advanced.providers.opensubtitles.skip_wrong_fps \
|
||||
if self.advanced.providers.opensubtitles.skip_wrong_fps is not None else True
|
||||
|
||||
provider_settings = {'addic7ed': {'username': Prefs['provider.addic7ed.username'],
|
||||
'password': Prefs['provider.addic7ed.password'],
|
||||
'use_random_agents': cast_bool(Prefs['provider.addic7ed.use_random_agents1']),
|
||||
},
|
||||
'opensubtitles': {'username': Prefs['provider.opensubtitles.username'],
|
||||
'password': Prefs['provider.opensubtitles.password'],
|
||||
@@ -829,6 +858,7 @@ class Config(object):
|
||||
'is_vip': cast_bool(Prefs['provider.opensubtitles.is_vip']),
|
||||
'use_ssl': os_use_https,
|
||||
'timeout': self.advanced.providers.opensubtitles.timeout or 15,
|
||||
'skip_wrong_fps': os_skip_wrong_fps,
|
||||
},
|
||||
'podnapisi': {
|
||||
'only_foreign': self.forced_only,
|
||||
@@ -968,27 +998,37 @@ class Config(object):
|
||||
self.activity_mode = "next_episode"
|
||||
|
||||
def get_plex_transcoder(self):
|
||||
paths = []
|
||||
base_path = os.environ.get("PLEX_MEDIA_SERVER_HOME", None)
|
||||
if not base_path:
|
||||
# fall back to bundled plugins path
|
||||
bundle_path = os.environ.get("PLEXBUNDLEDPLUGINSPATH", None)
|
||||
if bundle_path:
|
||||
base_path = os.path.normpath(os.path.join(bundle_path, "..", ".."))
|
||||
if base_path:
|
||||
paths.append(base_path)
|
||||
|
||||
bundle_path = os.environ.get("PLEXBUNDLEDPLUGINSPATH", None)
|
||||
if bundle_path:
|
||||
paths.append(os.path.normpath(os.path.join(bundle_path, "..", "..")))
|
||||
|
||||
paths.append(self.app_support_path)
|
||||
|
||||
bns = []
|
||||
if sys.platform == "darwin":
|
||||
fn = os.path.join(base_path, "MacOS", "Plex Transcoder")
|
||||
bns.append(("MacOS", "Plex Transcoder"))
|
||||
elif mswindows:
|
||||
fn = os.path.join(base_path, "plextranscoder.exe")
|
||||
bns = [("plextranscoder.exe",), ("plex transcoder.exe",)]
|
||||
else:
|
||||
fn = os.path.join(base_path, "Plex Transcoder")
|
||||
bns.append(("Plex Transcoder",))
|
||||
|
||||
if os.path.isfile(fn):
|
||||
return fn
|
||||
for path in paths:
|
||||
for bn in bns:
|
||||
fn = os.path.join(path, *bn)
|
||||
|
||||
# look inside Resources folder as fallback, as well
|
||||
fn = os.path.join(base_path, "Resources", "Plex Transcoder")
|
||||
if os.path.isfile(fn):
|
||||
return fn
|
||||
if os.path.isfile(fn):
|
||||
return fn
|
||||
|
||||
# look inside Resources folder as fallback, as well
|
||||
for vbn in ("Plex Transcoder", "plextranscoder.exe", "plex transcoder.exe"):
|
||||
fn = os.path.join(path, "Resources", vbn)
|
||||
if os.path.isfile(fn):
|
||||
return fn
|
||||
|
||||
def parse_rename_mode(self):
|
||||
# fixme: exact_filenames should be determined via callback combined with info about the current video
|
||||
@@ -1041,6 +1081,15 @@ class Config(object):
|
||||
def text_based_formats(self):
|
||||
return self.advanced.text_subtitle_formats or TEXT_SUBTITLE_EXTS
|
||||
|
||||
def parse_custom_dns(self):
|
||||
custom_dns = Prefs['use_custom_dns2'].strip()
|
||||
os.environ["dns_resolvers"] = ""
|
||||
if custom_dns:
|
||||
ips = filter(lambda x: x, [d.strip() for d in custom_dns.split(",")])
|
||||
if ips:
|
||||
os.environ["dns_resolvers"] = json.dumps(ips)
|
||||
return os.environ["dns_resolvers"]
|
||||
|
||||
def init_subliminal_patches(self):
|
||||
# configure custom subtitle destination folders for scanning pre-existing subs
|
||||
Log.Debug("Patching subliminal ...")
|
||||
@@ -1051,9 +1100,6 @@ class Config(object):
|
||||
subliminal_patch.core.DOWNLOAD_TRIES = int(Prefs['subtitles.try_downloads'])
|
||||
subliminal.score.episode_scores["addic7ed_boost"] = int(Prefs['provider.addic7ed.boost_by2'])
|
||||
|
||||
if self.use_custom_dns:
|
||||
subliminal_patch.http.set_custom_resolver()
|
||||
|
||||
|
||||
config = Config()
|
||||
config.initialize()
|
||||
|
||||
@@ -6,8 +6,7 @@ from subzero.language import Language
|
||||
import subliminal_patch as subliminal
|
||||
|
||||
from support.config import config
|
||||
from support.helpers import cast_bool, audio_streams_match_languages
|
||||
from subtitlehelpers import get_subtitles_from_metadata
|
||||
from support.helpers import audio_streams_match_languages
|
||||
from subliminal_patch import compute_score
|
||||
from support.plex_media import get_blacklist_from_part_map
|
||||
from subzero.video import refine_video
|
||||
@@ -15,9 +14,17 @@ from support.storage import get_pack_data, store_pack_data
|
||||
|
||||
|
||||
def get_missing_languages(video, part):
|
||||
languages = set([Language.rebuild(l) for l in config.lang_list])
|
||||
languages_list = config.get_lang_list(ordered=True)
|
||||
languages = set(languages_list)
|
||||
valid_langs_in_media = set()
|
||||
|
||||
if audio_streams_match_languages(video, list(config.lang_list)):
|
||||
if Prefs["subtitles.when"] != "Always":
|
||||
valid_langs_in_media = audio_streams_match_languages(video, languages_list)
|
||||
languages = languages.difference(valid_langs_in_media)
|
||||
if languages:
|
||||
Log.Debug("Languages missing after taking the audio streams into account: %s" % languages)
|
||||
|
||||
if valid_langs_in_media and not languages:
|
||||
Log.Debug("Skipping subtitle search for %s, audio streams are in correct language(s)",
|
||||
video)
|
||||
return set()
|
||||
@@ -30,14 +37,6 @@ def get_missing_languages(video, part):
|
||||
alpha3_map[language.alpha3] = language.country
|
||||
language.country = None
|
||||
|
||||
if not Prefs['subtitles.save.filesystem']:
|
||||
# scan for existing metadata subtitles
|
||||
meta_subs = get_subtitles_from_metadata(part)
|
||||
for language, subList in meta_subs.iteritems():
|
||||
if subList:
|
||||
video.subtitle_languages.add(language)
|
||||
Log.Debug("Found metadata subtitle %s for %s", language, video)
|
||||
|
||||
have_languages = video.subtitle_languages.copy()
|
||||
if config.ietf_as_alpha3:
|
||||
for language in have_languages:
|
||||
@@ -47,6 +46,22 @@ def get_missing_languages(video, part):
|
||||
|
||||
missing_languages = (languages - have_languages)
|
||||
|
||||
if config.any_language_is_enough != "Always search for all configured languages":
|
||||
not_in_forced = "foreign" in config.any_language_is_enough
|
||||
if "External or embedded subtitle" in config.any_language_is_enough:
|
||||
langs = video.subtitle_languages if not not_in_forced else \
|
||||
filter(lambda l: not l.forced, video.subtitle_languages)
|
||||
if langs:
|
||||
Log.Debug("We have at least one subtitle for any configured language.")
|
||||
return False
|
||||
|
||||
elif "External subtitle" in config.any_language_is_enough:
|
||||
langs = video.subtitle_languages if not not_in_forced else \
|
||||
filter(lambda l: not l.forced, video.external_subtitle_languages)
|
||||
if langs:
|
||||
Log.Debug("We have at least one external subtitle for any configured language.")
|
||||
return False
|
||||
|
||||
# all languages are found if we either really have subs for all languages or we only want to have exactly one language
|
||||
# and we've only found one (the case for a selected language, Prefs['subtitles.only_one'] (one found sub matches any language))
|
||||
found_one_which_is_enough = len(video.subtitle_languages) >= 1 and Prefs['subtitles.only_one']
|
||||
|
||||
@@ -12,10 +12,12 @@ import subprocess
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
from babelfish.exceptions import LanguageError
|
||||
|
||||
import chardet
|
||||
|
||||
from bs4 import UnicodeDammit
|
||||
from subzero.language import Language
|
||||
from subzero.language import Language, language_from_stream
|
||||
from subzero.analytics import track_event
|
||||
|
||||
mswindows = (sys.platform == "win32")
|
||||
@@ -274,6 +276,8 @@ def get_item_hints(data):
|
||||
"title": data["original_title"] or data["series"],
|
||||
}
|
||||
)
|
||||
if hints["title"]:
|
||||
hints["title"] = hints["title"].replace(":", "")
|
||||
return hints
|
||||
|
||||
|
||||
@@ -282,6 +286,7 @@ def notify_executable(exe_info, videos, subtitles, storage):
|
||||
"subtitle_language", "subtitle_path", "subtitle_filename", "provider", "score", "storage", "series_id",
|
||||
"series", "title", "section", "filename", "path", "folder", "season_id", "type", "id", "season"
|
||||
)
|
||||
to_clean = ("PYTHONPATH", "PYTHONHOME")
|
||||
exe, arguments = exe_info
|
||||
for video, video_subtitles in subtitles.items():
|
||||
for subtitle in video_subtitles:
|
||||
@@ -317,8 +322,9 @@ def notify_executable(exe_info, videos, subtitles, storage):
|
||||
env = dict(os.environ)
|
||||
|
||||
# clean out any Plex-PYTHONPATH that may bleed through the spawned process
|
||||
if "PYTHONPATH" in env and "plex" in env["PYTHONPATH"].lower():
|
||||
del env["PYTHONPATH"]
|
||||
for v in to_clean:
|
||||
if v in env and "plex" in env[v].lower():
|
||||
del env[v]
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(quote_args([exe] + prepared_arguments), stdout=subprocess.PIPE,
|
||||
@@ -386,42 +392,44 @@ def get_language_from_stream(lang_code):
|
||||
if lang and lang != "xx":
|
||||
# Log.Debug("Found language: %r", lang)
|
||||
return Language.fromietf(lang)
|
||||
elif lang:
|
||||
try:
|
||||
return language_from_stream(lang)
|
||||
except LanguageError:
|
||||
pass
|
||||
|
||||
|
||||
def audio_streams_match_languages(video, languages):
|
||||
if video.audio_languages:
|
||||
decision = []
|
||||
|
||||
without_forced = filter(lambda x: not x.forced, languages)
|
||||
if video.audio_languages and without_forced:
|
||||
if Prefs["subtitles.when"] == "Always":
|
||||
decision.append(False)
|
||||
return set()
|
||||
|
||||
elif Prefs["subtitles.when"] == "When main audio stream is not Subtitle Language (1)":
|
||||
if video.audio_languages[0] == languages[0]:
|
||||
decision.append(True)
|
||||
if video.audio_languages[0] == without_forced[0]:
|
||||
return set(without_forced)
|
||||
|
||||
elif Prefs["subtitles.when"] == "When any audio stream is not Subtitle Language (1)":
|
||||
if languages[0] in video.audio_languages:
|
||||
decision.append(True)
|
||||
if without_forced[0] in video.audio_languages:
|
||||
return set(without_forced)
|
||||
|
||||
elif Prefs["subtitles.when"] == "When main audio stream is not any configured language":
|
||||
if video.audio_languages[0] in languages:
|
||||
decision.append(True)
|
||||
if video.audio_languages[0] in without_forced:
|
||||
return set(without_forced)
|
||||
|
||||
elif Prefs["subtitles.when"] == "When any audio stream is not any configured language":
|
||||
if set(video.audio_languages).intersection(set(languages)):
|
||||
decision.append(True)
|
||||
matching = set(video.audio_languages).intersection(set(without_forced))
|
||||
if matching:
|
||||
return set(without_forced)
|
||||
|
||||
if Prefs["subtitles.when_forced"] in [
|
||||
"Always",
|
||||
"Only for Subtitle Language (1)",
|
||||
"Only for Subtitle Language (2)",
|
||||
"Only for Subtitle Language (3)"
|
||||
]:
|
||||
decision.append(False)
|
||||
# if Prefs["subtitles.when_forced"] in [
|
||||
# "Always",
|
||||
# "Only for Subtitle Language (1)",
|
||||
# "Only for Subtitle Language (2)",
|
||||
# "Only for Subtitle Language (3)"
|
||||
# ]:
|
||||
|
||||
return all(decision)
|
||||
|
||||
return False
|
||||
return set()
|
||||
|
||||
|
||||
def get_language(lang_short):
|
||||
|
||||
@@ -174,9 +174,11 @@ def get_all_parts(plex_item):
|
||||
return parts
|
||||
|
||||
|
||||
def get_embedded_subtitle_streams(part, requested_language=None, skip_duplicate_unknown=True):
|
||||
def get_embedded_subtitle_streams(part, requested_language=None, skip_duplicate_unknown=True, skip_unknown=False):
|
||||
streams = []
|
||||
streams_unknown = []
|
||||
has_unknown = False
|
||||
found_requested_language = False
|
||||
for stream in part.streams:
|
||||
# subtitle stream
|
||||
if stream.stream_type == 3 and not stream.stream_key and stream.codec in TEXT_SUBTITLE_EXTS:
|
||||
@@ -196,14 +198,19 @@ def get_embedded_subtitle_streams(part, requested_language=None, skip_duplicate_
|
||||
language = Language.rebuild(list(config.lang_list)[0], forced=is_forced)
|
||||
is_unknown = True
|
||||
has_unknown = True
|
||||
streams_unknown.append({"stream": stream, "is_unknown": is_unknown, "language": language,
|
||||
"is_forced": is_forced})
|
||||
|
||||
if not requested_language or found_requested_language or has_unknown:
|
||||
if not requested_language or found_requested_language:
|
||||
streams.append({"stream": stream, "is_unknown": is_unknown, "language": language,
|
||||
"is_forced": is_forced})
|
||||
|
||||
if found_requested_language:
|
||||
break
|
||||
|
||||
if streams_unknown and not found_requested_language and not skip_unknown:
|
||||
streams = streams_unknown
|
||||
|
||||
return streams
|
||||
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ from support.lib import Plex, get_intent
|
||||
from support.plex_media import get_stream_fps
|
||||
from support.storage import get_subtitle_storage
|
||||
from support.config import config, TEXT_SUBTITLE_EXTS
|
||||
|
||||
from support.subtitlehelpers import get_subtitles_from_metadata
|
||||
from subzero.video import parse_video, set_existing_languages
|
||||
from subzero.language import language_from_stream, Language
|
||||
|
||||
|
||||
def scan_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, providers=None, skip_hashing=False):
|
||||
def prepare_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, providers=None, skip_hashing=False):
|
||||
"""
|
||||
returnes a subliminal/guessit-refined parsed video
|
||||
:param pms_video_info:
|
||||
@@ -29,7 +29,7 @@ def scan_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, pr
|
||||
if ignore_all:
|
||||
Log.Debug("Force refresh intended.")
|
||||
|
||||
Log.Debug("Scanning video: %s, external_subtitles=%s, embedded_subtitles=%s" % (
|
||||
Log.Debug("Detecting streams: %s, external_subtitles=%s, embedded_subtitles=%s" % (
|
||||
plex_part.file, external_subtitles, embedded_subtitles))
|
||||
|
||||
known_embedded = []
|
||||
@@ -86,7 +86,28 @@ def scan_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, pr
|
||||
else:
|
||||
Log.Warn("Part %s missing of %s, not able to scan internal streams", plex_part.id, rating_key)
|
||||
|
||||
Log.Debug("Known embedded: %r", known_embedded)
|
||||
# metadata subtitles
|
||||
known_metadata_subs = set()
|
||||
meta_subs = get_subtitles_from_metadata(plex_part)
|
||||
for language, subList in meta_subs.iteritems():
|
||||
try:
|
||||
lang = Language.fromietf(Locale.Language.Match(language))
|
||||
except LanguageError:
|
||||
if config.treat_und_as_first:
|
||||
lang = Language.rebuild(list(config.lang_list)[0])
|
||||
else:
|
||||
continue
|
||||
|
||||
if subList:
|
||||
for key in subList:
|
||||
if key.startswith("subzero_md_forced"):
|
||||
lang = Language.rebuild(lang, forced=True)
|
||||
|
||||
known_metadata_subs.add(lang)
|
||||
Log.Debug("Found metadata subtitle %r:%s for %s", lang, key, plex_part.file)
|
||||
|
||||
Log.Debug("Known metadata subtitles: %r", known_metadata_subs)
|
||||
Log.Debug("Known embedded subtitles: %r", known_embedded)
|
||||
|
||||
subtitle_storage = get_subtitle_storage()
|
||||
stored_subs = subtitle_storage.load(rating_key)
|
||||
@@ -106,7 +127,7 @@ def scan_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, pr
|
||||
set_existing_languages(video, pms_video_info, external_subtitles=external_subtitles,
|
||||
embedded_subtitles=embedded_subtitles, known_embedded=known_embedded,
|
||||
stored_subs=stored_subs, languages=config.lang_list,
|
||||
only_one=config.only_one)
|
||||
only_one=config.only_one, known_metadata_subs=known_metadata_subs)
|
||||
|
||||
# add video fps info
|
||||
video.fps = plex_part.fps
|
||||
@@ -133,9 +154,9 @@ def scan_videos(videos, ignore_all=False, providers=None, skip_hashing=False):
|
||||
hints = helpers.get_item_hints(video)
|
||||
video["plex_part"].fps = get_stream_fps(video["plex_part"].streams)
|
||||
p = providers or config.get_providers(media_type="series" if video["type"] == "episode" else "movies")
|
||||
scanned_video = scan_video(video, ignore_all=force_refresh or ignore_all, hints=hints,
|
||||
rating_key=video["id"], providers=p,
|
||||
skip_hashing=skip_hashing)
|
||||
scanned_video = prepare_video(video, ignore_all=force_refresh or ignore_all, hints=hints,
|
||||
rating_key=video["id"], providers=p,
|
||||
skip_hashing=skip_hashing)
|
||||
|
||||
if not scanned_video:
|
||||
continue
|
||||
|
||||
@@ -33,7 +33,7 @@ def store_subtitle_info(scanned_video_part_map, downloaded_subtitles, storage_ty
|
||||
video_id = str(video.id)
|
||||
plex_item = get_item(video_id)
|
||||
if not plex_item:
|
||||
Log.Warning("Plex item not found: %s", video_id)
|
||||
Log.Warn("Plex item not found: %s", video_id)
|
||||
continue
|
||||
|
||||
metadata = video.plexapi_metadata
|
||||
@@ -143,9 +143,22 @@ def save_subtitles_to_metadata(videos, subtitles):
|
||||
else:
|
||||
mp = mediaPart
|
||||
pm = Proxy.Media(content, ext="srt", forced="1" if subtitle.language.forced else None)
|
||||
new_key = "subzero_md" + ("_forced" if subtitle.language.forced else "")
|
||||
lang = Locale.Language.Match(subtitle.language.alpha2)
|
||||
mp.subtitles[lang].validate_keys({})
|
||||
mp.subtitles[lang]["subzero"] = pm
|
||||
|
||||
for key, proxy in getattr(mp.subtitles[lang], "_proxies").iteritems():
|
||||
if not proxy or not len(proxy) >= 5:
|
||||
Log.Debug("Can't parse metadata: %s" % repr(proxy))
|
||||
continue
|
||||
if proxy[0] == "Media":
|
||||
if not key.startswith("subzero_"):
|
||||
if key == "subzero":
|
||||
Log.Debug("Removing legacy metadata subtitle for %s", lang)
|
||||
del mp.subtitles[lang][key]
|
||||
Log.Debug("Existing metadata subtitle for %s: %s", lang, key)
|
||||
|
||||
Log.Debug("Adding metadata sub for %s: %s", lang, subtitle)
|
||||
mp.subtitles[lang][new_key] = pm
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -174,10 +174,11 @@ class DefaultSubtitleHelper(SubtitleHelper):
|
||||
|
||||
Log('Found subtitle file: ' + self.filename + ' language: ' + language + ' codec: ' + str(
|
||||
codec) + ' format: ' + str(format) + ' default: ' + default + ' forced: ' + forced)
|
||||
part.subtitles[language][basename] = Proxy.LocalFile(self.filename, codec=codec, format=format, default=default,
|
||||
key = ("subzero_ex" + "_forced" if forced else "") + basename
|
||||
part.subtitles[language][key] = Proxy.LocalFile(self.filename, codec=codec, format=format, default=default,
|
||||
forced=forced)
|
||||
|
||||
lang_sub_map[language] = [basename]
|
||||
lang_sub_map[language] = [key]
|
||||
return lang_sub_map
|
||||
|
||||
|
||||
@@ -194,9 +195,12 @@ def get_subtitles_from_metadata(part):
|
||||
p_type = proxy[0]
|
||||
|
||||
if p_type == "Media":
|
||||
if not key.startswith("subzero"):
|
||||
continue
|
||||
|
||||
# metadata subtitle
|
||||
Log.Debug(u"Found metadata subtitle: %s, %s" % (language, repr(proxy)))
|
||||
subs[language] = [key]
|
||||
#Log.Debug(u"Found metadata subtitle: %s, %s, %s" % (language, key, repr(proxy)))
|
||||
subs[language].append(key)
|
||||
return subs
|
||||
|
||||
|
||||
|
||||
@@ -165,8 +165,10 @@ class SubtitleListingMixin(object):
|
||||
can_verify_series = False
|
||||
|
||||
if can_verify_series and not {"series", "season", "episode"}.issubset(matches):
|
||||
Log.Debug(u"%s: Skipping %s, because it doesn't match our series/episode", self.name, s)
|
||||
continue
|
||||
if "series" not in matches:
|
||||
s.wrong_series = True
|
||||
else:
|
||||
s.wrong_season_ep = True
|
||||
|
||||
unsorted_subtitles.append(
|
||||
(s, compute_score(matches, s, video, hearing_impaired=use_hearing_impaired), matches))
|
||||
@@ -175,7 +177,7 @@ class SubtitleListingMixin(object):
|
||||
subtitles = []
|
||||
for subtitle, score, matches in scored_subtitles:
|
||||
# check score
|
||||
if score < min_score:
|
||||
if score < min_score and not subtitle.wrong_series:
|
||||
Log.Info(u'%s: Score %d is below min_score (%d)', self.name, score, min_score)
|
||||
continue
|
||||
subtitle.score = score
|
||||
|
||||
+37
-13
@@ -205,6 +205,19 @@
|
||||
],
|
||||
"default": "Never"
|
||||
},
|
||||
{
|
||||
"id": "subtitles.any_language_is_enough",
|
||||
"label": "Don't search for subtitles if a subtitle in any configured language exists as",
|
||||
"type": "enum",
|
||||
"values": [
|
||||
"External or embedded subtitle",
|
||||
"External or embedded subtitle (not foreign/forced)",
|
||||
"External subtitle",
|
||||
"External subtitle (not foreign/forced)",
|
||||
"Always search for all configured languages"
|
||||
],
|
||||
"default": "Always search for all configured languages"
|
||||
},
|
||||
{
|
||||
"id": "subtitles.language.ietf_display",
|
||||
"label": "Display languages with country attribute as ISO 639-1 (e.g. pt-BR = pt)",
|
||||
@@ -273,6 +286,23 @@
|
||||
"type": "text",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"id": "anticaptcha.service",
|
||||
"label": "AntiCaptcha-Service (needs paid account; enables Addic7ed, titlovi)",
|
||||
"type": "enum",
|
||||
"values": [
|
||||
"none",
|
||||
"anti-captcha.com",
|
||||
"deathbycaptcha.com"
|
||||
],
|
||||
"default": "none"
|
||||
},
|
||||
{
|
||||
"id": "anticaptcha.api_key",
|
||||
"label": "AntiCaptcha-Service key (anti-captcha.com: account_key; deathbycaptcha.com: username:password)",
|
||||
"type": "text",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"id": "provider.opensubtitles.enabled",
|
||||
"label": "Provider: Enable OpenSubtitles",
|
||||
@@ -305,15 +335,9 @@
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"id": "provider.titlovi.enabled",
|
||||
"label": "Provider: Enable Titlovi.com",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"id": "provider.addic7ed.enabled",
|
||||
"label": "Provider: Enable Addic7ed",
|
||||
"label": "Provider: Enable Addic7ed (needs AntiCaptcha)",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
@@ -364,8 +388,8 @@
|
||||
"default": "19"
|
||||
},
|
||||
{
|
||||
"id": "provider.addic7ed.use_random_agents1",
|
||||
"label": "Addic7ed: Use random user agents",
|
||||
"id": "provider.titlovi.enabled",
|
||||
"label": "Provider: Enable Titlovi.com (might need AntiCaptcha)",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
@@ -836,10 +860,10 @@
|
||||
"default": "15"
|
||||
},
|
||||
{
|
||||
"id": "use_custom_dns",
|
||||
"label": "Use Google DNS (for \"problematic\" countries)",
|
||||
"type": "bool",
|
||||
"default": "false"
|
||||
"id": "use_custom_dns2",
|
||||
"label": "Use custom DNS (IPs, comma-separated, leave empty for system DNS. Default: Google/CF)",
|
||||
"type": "text",
|
||||
"default": "1.1.1.1, 8.8.8.8"
|
||||
},
|
||||
{
|
||||
"id": "proxy",
|
||||
|
||||
+5
-5
@@ -9,11 +9,11 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.6.4</string>
|
||||
<string>2.6.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.6.4.2834</string>
|
||||
<string>2.6.5.3074</string>
|
||||
<key>PlexFrameworkVersion</key>
|
||||
<string>2</string>
|
||||
<key>PlexPluginClass</key>
|
||||
@@ -23,7 +23,7 @@
|
||||
<key>PlexPluginConsoleLogging</key>
|
||||
<string>0</string>
|
||||
<key>PlexPluginDevMode</key>
|
||||
<string>0</string>
|
||||
<string>1</string>
|
||||
<key>PlexPluginCodePolicy</key>
|
||||
<!-- this allows channels to access some python methods which are otherwise blocked, as well as import external code libraries, and interact with the PMS HTTP API -->
|
||||
<string>Elevated</string>
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<h1>Sub-Zero for Plex</h1><i>Subtitles done right</i>
|
||||
|
||||
Version 2.6.4.2834
|
||||
Version 2.6.5.3074 DEV
|
||||
|
||||
Originally based on @bramwalet's awesome <a href="https://github.com/bramwalet/Subliminal.bundle">Subliminal.bundle</a>
|
||||
|
||||
@@ -46,7 +46,7 @@ Github: <a href="https://github.com/pannal/Sub-Zero.bundle">http
|
||||
|
||||
3rd party licenses: <a href="https://github.com/pannal/Sub-Zero.bundle/tree/master/Licenses">https://github.com/pannal/Sub-Zero.bundle/tree/master/Licenses</a>
|
||||
|
||||
panni, 2018
|
||||
panni, 2019
|
||||
</div>
|
||||
</string>
|
||||
</dict>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from .core import where, old_where
|
||||
from .core import where
|
||||
|
||||
__version__ = "2018.10.15"
|
||||
__version__ = "2019.03.09"
|
||||
|
||||
@@ -4268,3 +4268,391 @@ rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
|
||||
57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
|
||||
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
|
||||
# Subject: CN=GTS Root R1 O=Google Trust Services LLC
|
||||
# Label: "GTS Root R1"
|
||||
# Serial: 146587175971765017618439757810265552097
|
||||
# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85
|
||||
# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8
|
||||
# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
|
||||
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
|
||||
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
|
||||
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
|
||||
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
|
||||
AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
|
||||
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
|
||||
mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
|
||||
zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
|
||||
fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
|
||||
vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
|
||||
Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
|
||||
zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
|
||||
Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
|
||||
k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
|
||||
DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
|
||||
lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
|
||||
HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
|
||||
Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
|
||||
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
|
||||
XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
|
||||
gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
|
||||
d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
|
||||
J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
|
||||
DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
|
||||
+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
|
||||
F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
|
||||
SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
|
||||
E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
|
||||
# Subject: CN=GTS Root R2 O=Google Trust Services LLC
|
||||
# Label: "GTS Root R2"
|
||||
# Serial: 146587176055767053814479386953112547951
|
||||
# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b
|
||||
# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d
|
||||
# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH
|
||||
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
|
||||
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
|
||||
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
|
||||
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB
|
||||
AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
|
||||
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg
|
||||
GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu
|
||||
XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd
|
||||
re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu
|
||||
PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1
|
||||
mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K
|
||||
8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj
|
||||
x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR
|
||||
nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0
|
||||
kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok
|
||||
twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
|
||||
HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp
|
||||
8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
|
||||
vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT
|
||||
z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA
|
||||
pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb
|
||||
pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB
|
||||
R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R
|
||||
RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk
|
||||
0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC
|
||||
5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF
|
||||
izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn
|
||||
yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
|
||||
# Subject: CN=GTS Root R3 O=Google Trust Services LLC
|
||||
# Label: "GTS Root R3"
|
||||
# Serial: 146587176140553309517047991083707763997
|
||||
# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25
|
||||
# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5
|
||||
# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
|
||||
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA
|
||||
IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
|
||||
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A
|
||||
DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
|
||||
DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk
|
||||
fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA
|
||||
njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
|
||||
# Subject: CN=GTS Root R4 O=Google Trust Services LLC
|
||||
# Label: "GTS Root R4"
|
||||
# Serial: 146587176229350439916519468929765261721
|
||||
# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26
|
||||
# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb
|
||||
# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
|
||||
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA
|
||||
IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
|
||||
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l
|
||||
xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
|
||||
DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0
|
||||
CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx
|
||||
sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=UCA Global G2 Root O=UniTrust
|
||||
# Subject: CN=UCA Global G2 Root O=UniTrust
|
||||
# Label: "UCA Global G2 Root"
|
||||
# Serial: 124779693093741543919145257850076631279
|
||||
# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8
|
||||
# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a
|
||||
# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9
|
||||
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH
|
||||
bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x
|
||||
CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds
|
||||
b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr
|
||||
b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9
|
||||
kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm
|
||||
VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R
|
||||
VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc
|
||||
C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj
|
||||
tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY
|
||||
D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv
|
||||
j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl
|
||||
NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6
|
||||
iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP
|
||||
O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/
|
||||
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV
|
||||
ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj
|
||||
L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
|
||||
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl
|
||||
1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU
|
||||
b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV
|
||||
PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj
|
||||
y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb
|
||||
EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg
|
||||
DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI
|
||||
+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy
|
||||
YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX
|
||||
UB+K+wb1whnw0A==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=UCA Extended Validation Root O=UniTrust
|
||||
# Subject: CN=UCA Extended Validation Root O=UniTrust
|
||||
# Label: "UCA Extended Validation Root"
|
||||
# Serial: 106100277556486529736699587978573607008
|
||||
# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2
|
||||
# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a
|
||||
# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH
|
||||
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF
|
||||
eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx
|
||||
MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV
|
||||
BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB
|
||||
AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog
|
||||
D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS
|
||||
sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop
|
||||
O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk
|
||||
sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi
|
||||
c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj
|
||||
VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz
|
||||
KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/
|
||||
TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G
|
||||
sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs
|
||||
1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD
|
||||
fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T
|
||||
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN
|
||||
l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
|
||||
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ
|
||||
VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5
|
||||
c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp
|
||||
4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s
|
||||
t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj
|
||||
2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO
|
||||
vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C
|
||||
xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx
|
||||
cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM
|
||||
fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
|
||||
# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
|
||||
# Label: "Certigna Root CA"
|
||||
# Serial: 269714418870597844693661054334862075617
|
||||
# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77
|
||||
# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43
|
||||
# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
|
||||
WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
|
||||
MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
|
||||
MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD
|
||||
VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX
|
||||
BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
|
||||
ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO
|
||||
ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M
|
||||
CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu
|
||||
I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm
|
||||
TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh
|
||||
C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf
|
||||
ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz
|
||||
IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT
|
||||
Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k
|
||||
JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5
|
||||
hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB
|
||||
GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
|
||||
FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
|
||||
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov
|
||||
L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo
|
||||
dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr
|
||||
aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L
|
||||
6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG
|
||||
HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6
|
||||
0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB
|
||||
lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi
|
||||
o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1
|
||||
gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v
|
||||
faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63
|
||||
Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
|
||||
jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
|
||||
3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
|
||||
# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
|
||||
# Label: "emSign Root CA - G1"
|
||||
# Serial: 235931866688319308814040
|
||||
# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac
|
||||
# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c
|
||||
# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
|
||||
VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
|
||||
ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
|
||||
MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
|
||||
MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
|
||||
Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
|
||||
f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
|
||||
8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
|
||||
d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
|
||||
tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
|
||||
Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
|
||||
o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
|
||||
AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
|
||||
PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
|
||||
wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
|
||||
GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
|
||||
6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
|
||||
RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
|
||||
iN66zB+Afko=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
|
||||
# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
|
||||
# Label: "emSign ECC Root CA - G3"
|
||||
# Serial: 287880440101571086945156
|
||||
# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40
|
||||
# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1
|
||||
# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
|
||||
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
|
||||
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
|
||||
RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
|
||||
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
|
||||
b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
|
||||
djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
|
||||
WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
|
||||
fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
|
||||
zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
|
||||
hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
|
||||
CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
|
||||
+JbNR6iC8hZVdyR+EhCVBCyj
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
|
||||
# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
|
||||
# Label: "emSign Root CA - C1"
|
||||
# Serial: 825510296613316004955058
|
||||
# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68
|
||||
# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01
|
||||
# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
|
||||
A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
|
||||
SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
|
||||
MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
|
||||
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
|
||||
dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
|
||||
BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
|
||||
HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
|
||||
3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
|
||||
GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
|
||||
xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
|
||||
aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
|
||||
TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
|
||||
BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
|
||||
/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
|
||||
kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
|
||||
YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
|
||||
+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
|
||||
WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
|
||||
# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
|
||||
# Label: "emSign ECC Root CA - C3"
|
||||
# Serial: 582948710642506000014504
|
||||
# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5
|
||||
# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66
|
||||
# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
|
||||
EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
|
||||
IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
|
||||
MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
|
||||
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
|
||||
IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
|
||||
MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
|
||||
sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
|
||||
BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
|
||||
Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
|
||||
3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
|
||||
0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post
|
||||
# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post
|
||||
# Label: "Hongkong Post Root CA 3"
|
||||
# Serial: 46170865288971385588281144162979347873371282084
|
||||
# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0
|
||||
# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02
|
||||
# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
|
||||
BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
|
||||
SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n
|
||||
a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5
|
||||
NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT
|
||||
CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u
|
||||
Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||
AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO
|
||||
dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI
|
||||
VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV
|
||||
9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY
|
||||
2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY
|
||||
vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt
|
||||
bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb
|
||||
x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+
|
||||
l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK
|
||||
TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj
|
||||
Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP
|
||||
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
|
||||
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw
|
||||
DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG
|
||||
7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk
|
||||
MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr
|
||||
gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk
|
||||
GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS
|
||||
3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm
|
||||
Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+
|
||||
l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c
|
||||
JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP
|
||||
L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
|
||||
LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
|
||||
mpv0
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@@ -8,30 +7,9 @@ certifi.py
|
||||
This module returns the installation location of cacert.pem.
|
||||
"""
|
||||
import os
|
||||
import warnings
|
||||
|
||||
|
||||
class DeprecatedBundleWarning(DeprecationWarning):
|
||||
"""
|
||||
The weak security bundle is being deprecated. Please bother your service
|
||||
provider to get them to stop using cross-signed roots.
|
||||
"""
|
||||
|
||||
|
||||
def where():
|
||||
f = os.path.dirname(__file__)
|
||||
|
||||
return os.path.join(f, 'cacert.pem')
|
||||
|
||||
|
||||
def old_where():
|
||||
warnings.warn(
|
||||
"The weak security bundle has been removed. certifi.old_where() is now an alias "
|
||||
"of certifi.where(). Please update your code to use certifi.where() instead. "
|
||||
"certifi.old_where() will be removed in 2018.",
|
||||
DeprecatedBundleWarning
|
||||
)
|
||||
return where()
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(where())
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
import ssl
|
||||
|
||||
from copy import deepcopy
|
||||
from time import sleep
|
||||
from collections import OrderedDict
|
||||
|
||||
from requests.sessions import Session
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.packages.urllib3.util.ssl_ import create_urllib3_context
|
||||
|
||||
from .interpreters import JavaScriptInterpreter
|
||||
from .user_agent import User_Agent
|
||||
|
||||
try:
|
||||
from requests_toolbelt.utils import dump
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import brotli
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
from urlparse import urlunparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import urlunparse
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
__version__ = '1.1.9'
|
||||
|
||||
BUG_REPORT = 'Cloudflare may have changed their technique, or there may be a bug in the script.'
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
|
||||
class CipherSuiteAdapter(HTTPAdapter):
|
||||
|
||||
def __init__(self, cipherSuite=None, **kwargs):
|
||||
self.cipherSuite = cipherSuite
|
||||
|
||||
if hasattr(ssl, 'PROTOCOL_TLS'):
|
||||
self.ssl_context = create_urllib3_context(
|
||||
ssl_version=getattr(ssl, 'PROTOCOL_TLSv1_3', ssl.PROTOCOL_TLSv1_2),
|
||||
ciphers=self.cipherSuite
|
||||
)
|
||||
else:
|
||||
self.ssl_context = create_urllib3_context(ssl_version=ssl.PROTOCOL_TLSv1)
|
||||
|
||||
super(CipherSuiteAdapter, self).__init__(**kwargs)
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
def init_poolmanager(self, *args, **kwargs):
|
||||
kwargs['ssl_context'] = self.ssl_context
|
||||
return super(CipherSuiteAdapter, self).init_poolmanager(*args, **kwargs)
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
def proxy_manager_for(self, *args, **kwargs):
|
||||
kwargs['ssl_context'] = self.ssl_context
|
||||
return super(CipherSuiteAdapter, self).proxy_manager_for(*args, **kwargs)
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
|
||||
class CloudScraper(Session):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.debug = kwargs.pop('debug', False)
|
||||
self.delay = kwargs.pop('delay', None)
|
||||
self.interpreter = kwargs.pop('interpreter', 'js2py')
|
||||
self.allow_brotli = kwargs.pop('allow_brotli', True if 'brotli' in sys.modules.keys() else False)
|
||||
self.cipherSuite = None
|
||||
|
||||
super(CloudScraper, self).__init__(*args, **kwargs)
|
||||
|
||||
if 'requests' in self.headers['User-Agent']:
|
||||
# Set a random User-Agent if no custom User-Agent has been set
|
||||
self.headers = User_Agent(allow_brotli=self.allow_brotli).headers
|
||||
|
||||
self.mount('https://', CipherSuiteAdapter(self.loadCipherSuite()))
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
@staticmethod
|
||||
def debugRequest(req):
|
||||
try:
|
||||
print(dump.dump_all(req).decode('utf-8'))
|
||||
except: # noqa
|
||||
pass
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
def loadCipherSuite(self):
|
||||
if self.cipherSuite:
|
||||
return self.cipherSuite
|
||||
|
||||
self.cipherSuite = ''
|
||||
|
||||
if hasattr(ssl, 'PROTOCOL_TLS'):
|
||||
ciphers = [
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-CHACHA20-POLY1305-SHA256', 'ECDHE-RSA-CHACHA20-POLY1305-SHA256',
|
||||
'ECDHE-RSA-AES128-CBC-SHA', 'ECDHE-RSA-AES256-CBC-SHA', 'RSA-AES128-GCM-SHA256', 'RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-RSA-AES128-GCM-SHA256', 'RSA-AES256-SHA', '3DES-EDE-CBC'
|
||||
]
|
||||
|
||||
if hasattr(ssl, 'PROTOCOL_TLSv1_3'):
|
||||
ciphers.insert(0, ['GREASE_3A', 'GREASE_6A', 'AES128-GCM-SHA256', 'AES256-GCM-SHA256', 'AES256-GCM-SHA384', 'CHACHA20-POLY1305-SHA256'])
|
||||
|
||||
ctx = ssl.SSLContext(getattr(ssl, 'PROTOCOL_TLSv1_3', ssl.PROTOCOL_TLSv1_2))
|
||||
|
||||
for cipher in ciphers:
|
||||
try:
|
||||
ctx.set_ciphers(cipher)
|
||||
self.cipherSuite = '{}:{}'.format(self.cipherSuite, cipher).rstrip(':')
|
||||
except ssl.SSLError:
|
||||
pass
|
||||
|
||||
return self.cipherSuite
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
def request(self, method, url, *args, **kwargs):
|
||||
ourSuper = super(CloudScraper, self)
|
||||
resp = ourSuper.request(method, url, *args, **kwargs)
|
||||
|
||||
if resp.headers.get('Content-Encoding') == 'br':
|
||||
if self.allow_brotli and resp._content:
|
||||
resp._content = brotli.decompress(resp.content)
|
||||
else:
|
||||
logging.warning('Brotli content detected, But option is disabled, we will not continue.')
|
||||
return resp
|
||||
|
||||
# Debug request
|
||||
if self.debug:
|
||||
self.debugRequest(resp)
|
||||
|
||||
# Check if Cloudflare anti-bot is on
|
||||
if self.isChallengeRequest(resp):
|
||||
if resp.request.method != 'GET':
|
||||
# Work around if the initial request is not a GET,
|
||||
# Supersede with a GET then re-request the original METHOD.
|
||||
self.request('GET', resp.url)
|
||||
resp = ourSuper.request(method, url, *args, **kwargs)
|
||||
else:
|
||||
# Solve Challenge
|
||||
resp = self.sendChallengeResponse(resp, **kwargs)
|
||||
|
||||
return resp
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
@staticmethod
|
||||
def isChallengeRequest(resp):
|
||||
if resp.headers.get('Server', '').startswith('cloudflare'):
|
||||
if b'why_captcha' in resp.content or b'/cdn-cgi/l/chk_captcha' in resp.content:
|
||||
raise ValueError('Captcha')
|
||||
|
||||
return (
|
||||
resp.status_code in [429, 503]
|
||||
and all(s in resp.content for s in [b'jschl_vc', b'jschl_answer'])
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
def sendChallengeResponse(self, resp, **original_kwargs):
|
||||
body = resp.text
|
||||
|
||||
# Cloudflare requires a delay before solving the challenge
|
||||
if not self.delay:
|
||||
try:
|
||||
delay = float(re.search(r'submit\(\);\r?\n\s*},\s*([0-9]+)', body).group(1)) / float(1000)
|
||||
if isinstance(delay, (int, float)):
|
||||
self.delay = delay
|
||||
except: # noqa
|
||||
pass
|
||||
|
||||
sleep(self.delay)
|
||||
|
||||
parsed_url = urlparse(resp.url)
|
||||
domain = parsed_url.netloc
|
||||
submit_url = '{}://{}/cdn-cgi/l/chk_jschl'.format(parsed_url.scheme, domain)
|
||||
|
||||
cloudflare_kwargs = deepcopy(original_kwargs)
|
||||
|
||||
try:
|
||||
params = OrderedDict()
|
||||
|
||||
s = re.search(r'name="s"\svalue="(?P<s_value>[^"]+)', body)
|
||||
if s:
|
||||
params['s'] = s.group('s_value')
|
||||
|
||||
params.update(
|
||||
[
|
||||
('jschl_vc', re.search(r'name="jschl_vc" value="(\w+)"', body).group(1)),
|
||||
('pass', re.search(r'name="pass" value="(.+?)"', body).group(1))
|
||||
]
|
||||
)
|
||||
|
||||
params = cloudflare_kwargs.setdefault('params', params)
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError('Unable to parse Cloudflare anti-bots page: {} {}'.format(e.message, BUG_REPORT))
|
||||
|
||||
# Solve the Javascript challenge
|
||||
params['jschl_answer'] = JavaScriptInterpreter.dynamicImport(self.interpreter).solveChallenge(body, 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.
|
||||
|
||||
cloudflare_kwargs['allow_redirects'] = False
|
||||
|
||||
redirect = self.request(resp.request.method, submit_url, **cloudflare_kwargs)
|
||||
redirect_location = urlparse(redirect.headers['Location'])
|
||||
if not redirect_location.netloc:
|
||||
redirect_url = urlunparse(
|
||||
(
|
||||
parsed_url.scheme,
|
||||
domain,
|
||||
redirect_location.path,
|
||||
redirect_location.params,
|
||||
redirect_location.query,
|
||||
redirect_location.fragment
|
||||
)
|
||||
)
|
||||
return self.request(resp.request.method, redirect_url, **original_kwargs)
|
||||
|
||||
return self.request(resp.request.method, redirect.headers['Location'], **original_kwargs)
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
@classmethod
|
||||
def create_scraper(cls, sess=None, **kwargs):
|
||||
"""
|
||||
Convenience function for creating a ready-to-go CloudScraper object.
|
||||
"""
|
||||
scraper = cls(**kwargs)
|
||||
|
||||
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 cloudscraper with other applications and scripts
|
||||
@classmethod
|
||||
def get_tokens(cls, url, **kwargs):
|
||||
scraper = cls.create_scraper(
|
||||
debug=kwargs.pop('debug', False),
|
||||
delay=kwargs.pop('delay', None),
|
||||
interpreter=kwargs.pop('interpreter', 'js2py'),
|
||||
allow_brotli=kwargs.pop('allow_brotli', True),
|
||||
)
|
||||
|
||||
try:
|
||||
resp = scraper.get(url, **kwargs)
|
||||
resp.raise_for_status()
|
||||
except Exception:
|
||||
logging.error('"{}" returned an error. Could not collect tokens.'.format(url))
|
||||
raise
|
||||
|
||||
domain = urlparse(resp.url).netloc
|
||||
# noinspection PyUnusedLocal
|
||||
cookie_domain = None
|
||||
|
||||
for d in scraper.cookies.list_domains():
|
||||
if d.startswith('.') and d in ('.{}'.format(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, **kwargs):
|
||||
"""
|
||||
Convenience function for building a Cookie HTTP header value.
|
||||
"""
|
||||
tokens, user_agent = cls.get_tokens(url, **kwargs)
|
||||
return '; '.join('='.join(pair) for pair in tokens.items()), user_agent
|
||||
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
create_scraper = CloudScraper.create_scraper
|
||||
get_tokens = CloudScraper.get_tokens
|
||||
get_cookie_string = CloudScraper.get_cookie_string
|
||||
@@ -0,0 +1,89 @@
|
||||
import re
|
||||
import sys
|
||||
import logging
|
||||
import abc
|
||||
|
||||
if sys.version_info >= (3, 4):
|
||||
ABC = abc.ABC # noqa
|
||||
else:
|
||||
ABC = abc.ABCMeta('ABC', (), {})
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
BUG_REPORT = 'Cloudflare may have changed their technique, or there may be a bug in the script.'
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
interpreters = {}
|
||||
|
||||
|
||||
class JavaScriptInterpreter(ABC):
|
||||
@abc.abstractmethod
|
||||
def __init__(self, name):
|
||||
interpreters[name] = self
|
||||
|
||||
@classmethod
|
||||
def dynamicImport(cls, name):
|
||||
if name not in interpreters:
|
||||
try:
|
||||
__import__('{}.{}'.format(cls.__module__, name))
|
||||
if not isinstance(interpreters.get(name), JavaScriptInterpreter):
|
||||
raise ImportError('The interpreter was not initialized.')
|
||||
except ImportError:
|
||||
logging.error('Unable to load {} interpreter'.format(name))
|
||||
raise
|
||||
|
||||
return interpreters[name]
|
||||
|
||||
@abc.abstractmethod
|
||||
def eval(self, jsEnv, js):
|
||||
pass
|
||||
|
||||
def solveChallenge(self, body, domain):
|
||||
try:
|
||||
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)
|
||||
except Exception:
|
||||
raise ValueError('Unable to identify Cloudflare IUAM Javascript on website. {}'.format(BUG_REPORT))
|
||||
|
||||
js = re.sub(r'\s{2,}', ' ', js, flags=re.MULTILINE | re.DOTALL).replace('\'; 121\'', '')
|
||||
js += '\na.value;'
|
||||
|
||||
jsEnv = '''
|
||||
String.prototype.italics=function(str) {{return "<i>" + this + "</i>";}};
|
||||
var document = {{
|
||||
createElement: function () {{
|
||||
return {{ firstChild: {{ href: "https://{domain}/" }} }}
|
||||
}},
|
||||
getElementById: function () {{
|
||||
return {{"innerHTML": "{innerHTML}"}};
|
||||
}}
|
||||
}};
|
||||
'''
|
||||
|
||||
try:
|
||||
innerHTML = re.search(
|
||||
r'<div(?: [^<>]*)? id="([^<>]*?)">([^<>]*?)</div>',
|
||||
body,
|
||||
re.MULTILINE | re.DOTALL
|
||||
)
|
||||
innerHTML = innerHTML.group(2) if innerHTML else ''
|
||||
|
||||
except: # noqa
|
||||
logging.error('Error extracting Cloudflare IUAM Javascript. {}'.format(BUG_REPORT))
|
||||
raise
|
||||
|
||||
try:
|
||||
result = self.eval(
|
||||
re.sub(r'\s{2,}', ' ', jsEnv.format(domain=domain, innerHTML=innerHTML), flags=re.MULTILINE | re.DOTALL),
|
||||
js
|
||||
)
|
||||
|
||||
float(result)
|
||||
except Exception:
|
||||
logging.error('Error executing Cloudflare IUAM Javascript. {}'.format(BUG_REPORT))
|
||||
raise
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import js2py
|
||||
import logging
|
||||
import base64
|
||||
|
||||
from . import JavaScriptInterpreter
|
||||
|
||||
from .jsunfuck import jsunfuck
|
||||
|
||||
|
||||
class ChallengeInterpreter(JavaScriptInterpreter):
|
||||
|
||||
def __init__(self):
|
||||
super(ChallengeInterpreter, self).__init__('js2py')
|
||||
|
||||
def eval(self, jsEnv, js):
|
||||
if js2py.eval_js('(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]') == '1':
|
||||
logging.warning('WARNING - Please upgrade your js2py https://github.com/PiotrDabkowski/Js2Py, applying work around for the meantime.')
|
||||
js = jsunfuck(js)
|
||||
|
||||
def atob(s):
|
||||
return base64.b64decode('{}'.format(s)).decode('utf-8')
|
||||
|
||||
js2py.disable_pyimport()
|
||||
context = js2py.EvalJs({'atob': atob})
|
||||
result = context.eval('{}{}'.format(jsEnv, js))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
ChallengeInterpreter()
|
||||
@@ -0,0 +1,97 @@
|
||||
MAPPING = {
|
||||
'a': '(false+"")[1]',
|
||||
'b': '([]["entries"]()+"")[2]',
|
||||
'c': '([]["fill"]+"")[3]',
|
||||
'd': '(undefined+"")[2]',
|
||||
'e': '(true+"")[3]',
|
||||
'f': '(false+"")[0]',
|
||||
'g': '(false+[0]+String)[20]',
|
||||
'h': '(+(101))["to"+String["name"]](21)[1]',
|
||||
'i': '([false]+undefined)[10]',
|
||||
'j': '([]["entries"]()+"")[3]',
|
||||
'k': '(+(20))["to"+String["name"]](21)',
|
||||
'l': '(false+"")[2]',
|
||||
'm': '(Number+"")[11]',
|
||||
'n': '(undefined+"")[1]',
|
||||
'o': '(true+[]["fill"])[10]',
|
||||
'p': '(+(211))["to"+String["name"]](31)[1]',
|
||||
'q': '(+(212))["to"+String["name"]](31)[1]',
|
||||
'r': '(true+"")[1]',
|
||||
's': '(false+"")[3]',
|
||||
't': '(true+"")[0]',
|
||||
'u': '(undefined+"")[0]',
|
||||
'v': '(+(31))["to"+String["name"]](32)',
|
||||
'w': '(+(32))["to"+String["name"]](33)',
|
||||
'x': '(+(101))["to"+String["name"]](34)[1]',
|
||||
'y': '(NaN+[Infinity])[10]',
|
||||
'z': '(+(35))["to"+String["name"]](36)',
|
||||
'A': '(+[]+Array)[10]',
|
||||
'B': '(+[]+Boolean)[10]',
|
||||
'C': 'Function("return escape")()(("")["italics"]())[2]',
|
||||
'D': 'Function("return escape")()([]["fill"])["slice"]("-1")',
|
||||
'E': '(RegExp+"")[12]',
|
||||
'F': '(+[]+Function)[10]',
|
||||
'G': '(false+Function("return Date")()())[30]',
|
||||
'I': '(Infinity+"")[0]',
|
||||
'M': '(true+Function("return Date")()())[30]',
|
||||
'N': '(NaN+"")[0]',
|
||||
'O': '(NaN+Function("return{}")())[11]',
|
||||
'R': '(+[]+RegExp)[10]',
|
||||
'S': '(+[]+String)[10]',
|
||||
'T': '(NaN+Function("return Date")()())[30]',
|
||||
'U': '(NaN+Function("return{}")()["to"+String["name"]]["call"]())[11]',
|
||||
' ': '(NaN+[]["fill"])[11]',
|
||||
'"': '("")["fontcolor"]()[12]',
|
||||
'%': 'Function("return escape")()([]["fill"])[21]',
|
||||
'&': '("")["link"](0+")[10]',
|
||||
'(': '(undefined+[]["fill"])[22]',
|
||||
')': '([0]+false+[]["fill"])[20]',
|
||||
'+': '(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[2]',
|
||||
',': '([]["slice"]["call"](false+"")+"")[1]',
|
||||
'-': '(+(.+[0000000001])+"")[2]',
|
||||
'.': '(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]',
|
||||
'/': '(false+[0])["italics"]()[10]',
|
||||
':': '(RegExp()+"")[3]',
|
||||
';': '("")["link"](")[14]',
|
||||
'<': '("")["italics"]()[0]',
|
||||
'=': '("")["fontcolor"]()[11]',
|
||||
'>': '("")["italics"]()[2]',
|
||||
'?': '(RegExp()+"")[2]',
|
||||
'[': '([]["entries"]()+"")[0]',
|
||||
']': '([]["entries"]()+"")[22]',
|
||||
'{': '(true+[]["fill"])[20]',
|
||||
'}': '([]["fill"]+"")["slice"]("-1")'
|
||||
}
|
||||
|
||||
SIMPLE = {
|
||||
'false': '![]',
|
||||
'true': '!![]',
|
||||
'undefined': '[][[]]',
|
||||
'NaN': '+[![]]',
|
||||
'Infinity': '+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])' # +"1e1000"
|
||||
}
|
||||
|
||||
CONSTRUCTORS = {
|
||||
'Array': '[]',
|
||||
'Number': '(+[])',
|
||||
'String': '([]+[])',
|
||||
'Boolean': '(![])',
|
||||
'Function': '[]["fill"]',
|
||||
'RegExp': 'Function("return/"+false+"/")()'
|
||||
}
|
||||
|
||||
|
||||
def jsunfuck(jsfuckString):
|
||||
for key in sorted(MAPPING, key=lambda k: len(MAPPING[k]), reverse=True):
|
||||
if MAPPING.get(key) in jsfuckString:
|
||||
jsfuckString = jsfuckString.replace(MAPPING.get(key), '"{}"'.format(key))
|
||||
|
||||
for key in sorted(SIMPLE, key=lambda k: len(SIMPLE[k]), reverse=True):
|
||||
if SIMPLE.get(key) in jsfuckString:
|
||||
jsfuckString = jsfuckString.replace(SIMPLE.get(key), '{}'.format(key))
|
||||
|
||||
# for key in sorted(CONSTRUCTORS, key=lambda k: len(CONSTRUCTORS[k]), reverse=True):
|
||||
# if CONSTRUCTORS.get(key) in jsfuckString:
|
||||
# jsfuckString = jsfuckString.replace(CONSTRUCTORS.get(key), '{}'.format(key))
|
||||
|
||||
return jsfuckString
|
||||
@@ -0,0 +1,46 @@
|
||||
import base64
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
from . import JavaScriptInterpreter
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
BUG_REPORT = 'Cloudflare may have changed their technique, or there may be a bug in the script.'
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
|
||||
class ChallengeInterpreter(JavaScriptInterpreter):
|
||||
|
||||
def __init__(self):
|
||||
super(ChallengeInterpreter, self).__init__('nodejs')
|
||||
|
||||
def eval(self, jsEnv, js):
|
||||
try:
|
||||
js = 'var atob = function(str) {return Buffer.from(str, "base64").toString("binary");};' \
|
||||
'var challenge = atob("%s");' \
|
||||
'var context = {atob: atob};' \
|
||||
'var options = {filename: "iuam-challenge.js", timeout: 4000};' \
|
||||
'var answer = require("vm").runInNewContext(challenge, context, options);' \
|
||||
'process.stdout.write(String(answer));' \
|
||||
% base64.b64encode('{}{}'.format(jsEnv, js).encode('UTF-8')).decode('ascii')
|
||||
|
||||
return subprocess.check_output(['node', '-e', js])
|
||||
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
raise EnvironmentError(
|
||||
'Missing Node.js runtime. Node is required and must be in the PATH (check with `node -v`). Your Node binary may be called `nodejs` rather than `node`, '
|
||||
'in which case you may need to run `apt-get install nodejs-legacy` on some Debian-based systems. (Please read the cloudscraper'
|
||||
' README\'s Dependencies section: https://github.com/VeNoMouS/cloudscraper#dependencies.'
|
||||
)
|
||||
raise
|
||||
except Exception:
|
||||
logging.error('Error executing Cloudflare IUAM Javascript. %s' % BUG_REPORT)
|
||||
raise
|
||||
|
||||
pass
|
||||
|
||||
|
||||
ChallengeInterpreter()
|
||||
@@ -0,0 +1,40 @@
|
||||
import os
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
|
||||
class User_Agent():
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.headers = None
|
||||
self.loadUserAgent(*args, **kwargs)
|
||||
|
||||
##########################################################################################################################################################
|
||||
|
||||
def loadUserAgent(self, *args, **kwargs):
|
||||
browser = kwargs.pop('browser', 'chrome')
|
||||
|
||||
user_agents = json.load(
|
||||
open(os.path.join(os.path.dirname(__file__), 'browsers.json'), 'r'),
|
||||
object_pairs_hook=OrderedDict
|
||||
)
|
||||
|
||||
if not user_agents.get(browser):
|
||||
logging.error('Sorry "{}" browser User-Agent was not found.'.format(browser))
|
||||
raise
|
||||
|
||||
user_agent = random.choice(user_agents.get(browser))
|
||||
|
||||
self.headers = user_agent.get('headers')
|
||||
self.headers['User-Agent'] = random.choice(user_agent.get('User-Agent'))
|
||||
|
||||
if not kwargs.get('allow_brotli', False):
|
||||
if 'br' in self.headers['Accept-Encoding']:
|
||||
self.headers['Accept-Encoding'] = ','.join([encoding for encoding in self.headers['Accept-Encoding'].split(',') if encoding.strip() != 'br']).strip()
|
||||
@@ -0,0 +1,336 @@
|
||||
{
|
||||
"chrome": [
|
||||
{
|
||||
"User-Agent": [
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36"
|
||||
],
|
||||
"headers": {
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": null,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.8",
|
||||
"Accept-Encoding": "gzip, deflate, , br"
|
||||
}
|
||||
},
|
||||
{
|
||||
"User-Agent": [
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36"
|
||||
],
|
||||
"headers": {
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": null,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.8",
|
||||
"Accept-Encoding": "gzip, deflate, br"
|
||||
}
|
||||
},
|
||||
{
|
||||
"User-Agent": [
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36"
|
||||
],
|
||||
"headers": {
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": null,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept-Encoding": "gzip, deflate, br"
|
||||
}
|
||||
},
|
||||
{
|
||||
"User-Agent": [
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
|
||||
],
|
||||
"headers": {
|
||||
"Connection": "keep-alive",
|
||||
"User-Agent": null,
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept-Encoding": "gzip, deflate, br"
|
||||
}
|
||||
},
|
||||
{
|
||||
"User-Agent": [
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.40 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.40 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36"
|
||||
],
|
||||
"headers": {
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": null,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept-Encoding": "gzip, deflate, br"
|
||||
}
|
||||
},
|
||||
{
|
||||
"User-Agent": [
|
||||
"Mozilla/5.0 (Linux; Android 8.1.0; SM-N960F Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; SM-G965F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 Build/OPD1.170816.010) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; Pixel Build/OPR6.170623.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.1; SM-A530F Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; Pixel Build/NDE63H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G955F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G950F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-T825 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G930F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; Nexus 6 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; XT1092 Build/MPE24.49-18) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-N910C Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.0.2; SM-G920F Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.0; Nexus 6 Build/LRX21O) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 9; Pixel 3 XL Build/PD1A.180720.030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PD1A.180720.030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 9; Pixel 2 Build/PPR1.180610.009) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/KRT16M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.2; SM-T530 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.4.4; SM-N910C Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; Nexus 9 Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.1; SM-N950F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36"
|
||||
],
|
||||
"headers": {
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": null,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-US,en;q=0.9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"User-Agent": [
|
||||
"Mozilla/5.0 (Linux; Android 8.1.0; SM-T835 Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.0; XT1092 Build/LXE22.46-19) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36"
|
||||
],
|
||||
"headers": {
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": null,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""Death by Captcha HTTP and socket API clients.
|
||||
|
||||
There are two types of Death by Captcha (DBC hereinafter) API: HTTP and
|
||||
socket ones. Both offer the same functionalily, with the socket API
|
||||
sporting faster responses and using way less connections.
|
||||
|
||||
To access the socket API, use SocketClient class; for the HTTP API, use
|
||||
HttpClient class. Both are thread-safe. SocketClient keeps a persistent
|
||||
connection opened and serializes all API requests sent through it, thus
|
||||
it is advised to keep a pool of them if you're script is heavily
|
||||
multithreaded.
|
||||
|
||||
Both SocketClient and HttpClient give you the following methods:
|
||||
|
||||
get_user()
|
||||
Returns your DBC account details as a dict with the following keys:
|
||||
|
||||
"user": your account numeric ID; if login fails, it will be the only
|
||||
item with the value of 0;
|
||||
"rate": your CAPTCHA rate, i.e. how much you will be charged for one
|
||||
solved CAPTCHA in US cents;
|
||||
"balance": your DBC account balance in US cents;
|
||||
"is_banned": flag indicating whether your account is suspended or not.
|
||||
|
||||
get_balance()
|
||||
Returns your DBC account balance in US cents.
|
||||
|
||||
get_captcha(cid)
|
||||
Returns an uploaded CAPTCHA details as a dict with the following keys:
|
||||
|
||||
"captcha": the CAPTCHA numeric ID; if no such CAPTCHAs found, it will
|
||||
be the only item with the value of 0;
|
||||
"text": the CAPTCHA text, if solved, otherwise None;
|
||||
"is_correct": flag indicating whether the CAPTCHA was solved correctly
|
||||
(DBC can detect that in rare cases).
|
||||
|
||||
The only argument `cid` is the CAPTCHA numeric ID.
|
||||
|
||||
get_text(cid)
|
||||
Returns an uploaded CAPTCHA text (None if not solved). The only argument
|
||||
`cid` is the CAPTCHA numeric ID.
|
||||
|
||||
report(cid)
|
||||
Reports an incorrectly solved CAPTCHA. The only argument `cid` is the
|
||||
CAPTCHA numeric ID. Returns True on success, False otherwise.
|
||||
|
||||
upload(captcha)
|
||||
Uploads a CAPTCHA. The only argument `captcha` can be either file-like
|
||||
object (any object with `read` method defined, actually, so StringIO
|
||||
will do), or CAPTCHA image file name. On successul upload you'll get
|
||||
the CAPTCHA details dict (see get_captcha() method).
|
||||
|
||||
NOTE: AT THIS POINT THE UPLOADED CAPTCHA IS NOT SOLVED YET! You have
|
||||
to poll for its status periodically using get_captcha() or get_text()
|
||||
method until the CAPTCHA is solved and you get the text.
|
||||
|
||||
decode(captcha, timeout=DEFAULT_TIMEOUT)
|
||||
A convenient method that uploads a CAPTCHA and polls for its status
|
||||
periodically, but no longer than `timeout` (defaults to 60 seconds).
|
||||
If solved, you'll get the CAPTCHA details dict (see get_captcha()
|
||||
method for details). See upload() method for details on `captcha`
|
||||
argument.
|
||||
|
||||
Visit http://www.deathbycaptcha.com/user/api for updates.
|
||||
|
||||
"""
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import errno
|
||||
import imghdr
|
||||
import random
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib
|
||||
import urllib2
|
||||
try:
|
||||
from json import read as json_decode, write as json_encode
|
||||
except ImportError:
|
||||
try:
|
||||
from json import loads as json_decode, dumps as json_encode
|
||||
except ImportError:
|
||||
from simplejson import loads as json_decode, dumps as json_encode
|
||||
|
||||
|
||||
# API version and unique software ID
|
||||
API_VERSION = 'DBC/Python v4.6'
|
||||
|
||||
# Default CAPTCHA timeout and decode() polling interval
|
||||
DEFAULT_TIMEOUT = 60
|
||||
DEFAULT_TOKEN_TIMEOUT = 120
|
||||
POLLS_INTERVAL = [1, 1, 2, 3, 2, 2, 3, 2, 2]
|
||||
DFLT_POLL_INTERVAL = 3
|
||||
|
||||
# Base HTTP API url
|
||||
HTTP_BASE_URL = 'http://api.dbcapi.me/api'
|
||||
|
||||
# Preferred HTTP API server's response content type, do not change
|
||||
HTTP_RESPONSE_TYPE = 'application/json'
|
||||
|
||||
# Socket API server's host & ports range
|
||||
SOCKET_HOST = 'api.dbcapi.me'
|
||||
SOCKET_PORTS = range(8123, 8131)
|
||||
|
||||
|
||||
def _load_image(captcha):
|
||||
if hasattr(captcha, 'read'):
|
||||
img = captcha.read()
|
||||
elif type(captcha) == bytearray:
|
||||
img = captcha
|
||||
else:
|
||||
img = ''
|
||||
try:
|
||||
captcha_file = open(captcha, 'rb')
|
||||
except Exception:
|
||||
raise
|
||||
else:
|
||||
img = captcha_file.read()
|
||||
captcha_file.close()
|
||||
if not len(img):
|
||||
raise ValueError('CAPTCHA image is empty')
|
||||
elif imghdr.what(None, img) is None:
|
||||
raise TypeError('Unknown CAPTCHA image type')
|
||||
else:
|
||||
return img
|
||||
|
||||
|
||||
class AccessDeniedException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
||||
"""Death by Captcha API Client."""
|
||||
|
||||
def __init__(self, username, password):
|
||||
self.is_verbose = False
|
||||
self.userpwd = {'username': username, 'password': password}
|
||||
|
||||
def _log(self, cmd, msg=''):
|
||||
if self.is_verbose:
|
||||
print '%d %s %s' % (time.time(), cmd, msg.rstrip())
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def connect(self):
|
||||
pass
|
||||
|
||||
def get_user(self):
|
||||
"""Fetch user details -- ID, balance, rate and banned status."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_balance(self):
|
||||
"""Fetch user balance (in US cents)."""
|
||||
return self.get_user().get('balance')
|
||||
|
||||
def get_captcha(self, cid):
|
||||
"""Fetch a CAPTCHA details -- ID, text and correctness flag."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_text(self, cid):
|
||||
"""Fetch a CAPTCHA text."""
|
||||
return self.get_captcha(cid).get('text') or None
|
||||
|
||||
def report(self, cid):
|
||||
"""Report a CAPTCHA as incorrectly solved."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def upload(self, captcha):
|
||||
"""Upload a CAPTCHA.
|
||||
|
||||
Accepts file names and file-like objects. Returns CAPTCHA details
|
||||
dict on success.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def decode(self, captcha=None, timeout=None, **kwargs):
|
||||
"""
|
||||
Try to solve a CAPTCHA.
|
||||
|
||||
See Client.upload() for arguments details.
|
||||
|
||||
Uploads a CAPTCHA, polls for its status periodically with arbitrary
|
||||
timeout (in seconds), returns CAPTCHA details if (correctly) solved.
|
||||
"""
|
||||
if not timeout:
|
||||
if not captcha:
|
||||
timeout = DEFAULT_TOKEN_TIMEOUT
|
||||
else:
|
||||
timeout = DEFAULT_TIMEOUT
|
||||
|
||||
deadline = time.time() + (max(0, timeout) or DEFAULT_TIMEOUT)
|
||||
uploaded_captcha = self.upload(captcha, **kwargs)
|
||||
if uploaded_captcha:
|
||||
intvl_idx = 0 # POLL_INTERVAL index
|
||||
while deadline > time.time() and not uploaded_captcha.get('text'):
|
||||
intvl, intvl_idx = self._get_poll_interval(intvl_idx)
|
||||
time.sleep(intvl)
|
||||
pulled = self.get_captcha(uploaded_captcha['captcha'])
|
||||
if pulled['captcha'] == uploaded_captcha['captcha']:
|
||||
uploaded_captcha = pulled
|
||||
if uploaded_captcha.get('text') and \
|
||||
uploaded_captcha.get('is_correct'):
|
||||
return uploaded_captcha
|
||||
|
||||
def _get_poll_interval(self, idx):
|
||||
"""Returns poll interval and next index depending on index provided"""
|
||||
|
||||
if len(POLLS_INTERVAL) > idx:
|
||||
intvl = POLLS_INTERVAL[idx]
|
||||
else:
|
||||
intvl = DFLT_POLL_INTERVAL
|
||||
idx += 1
|
||||
|
||||
return intvl, idx
|
||||
|
||||
|
||||
class HttpClient(Client):
|
||||
|
||||
"""Death by Captcha HTTP API client."""
|
||||
|
||||
def __init__(self, *args):
|
||||
Client.__init__(self, *args)
|
||||
self.opener = urllib2.build_opener(urllib2.HTTPRedirectHandler())
|
||||
|
||||
def _call(self, cmd, payload=None, headers=None):
|
||||
if headers is None:
|
||||
headers = {}
|
||||
headers['Accept'] = HTTP_RESPONSE_TYPE
|
||||
headers['User-Agent'] = API_VERSION
|
||||
if hasattr(payload, 'items'):
|
||||
payload = urllib.urlencode(payload)
|
||||
self._log('SEND', '%s %d %s' % (cmd, len(payload), payload))
|
||||
else:
|
||||
self._log('SEND', '%s' % cmd)
|
||||
if payload is not None:
|
||||
headers['Content-Length'] = len(payload)
|
||||
try:
|
||||
response = self.opener.open(urllib2.Request(
|
||||
HTTP_BASE_URL + '/' + cmd.strip('/'),
|
||||
data=payload,
|
||||
headers=headers
|
||||
)).read()
|
||||
except urllib2.HTTPError, err:
|
||||
if 403 == err.code:
|
||||
raise AccessDeniedException('Access denied, please check'
|
||||
' your credentials and/or balance')
|
||||
elif 400 == err.code or 413 == err.code:
|
||||
raise ValueError("CAPTCHA was rejected by the service, check"
|
||||
" if it's a valid image")
|
||||
elif 503 == err.code:
|
||||
raise OverflowError("CAPTCHA was rejected due to service"
|
||||
" overload, try again later")
|
||||
else:
|
||||
raise err
|
||||
else:
|
||||
self._log('RECV', '%d %s' % (len(response), response))
|
||||
try:
|
||||
return json_decode(response)
|
||||
except Exception:
|
||||
raise RuntimeError('Invalid API response')
|
||||
return {}
|
||||
|
||||
def get_user(self):
|
||||
return self._call('user', self.userpwd.copy()) or {'user': 0}
|
||||
|
||||
def get_captcha(self, cid):
|
||||
return self._call('captcha/%d' % cid) or {'captcha': 0}
|
||||
|
||||
def report(self, cid):
|
||||
return not self._call('captcha/%d/report' % cid,
|
||||
self.userpwd.copy()).get('is_correct')
|
||||
|
||||
def upload(self, captcha=None, **kwargs):
|
||||
boundary = binascii.hexlify(os.urandom(16))
|
||||
banner = kwargs.get('banner', '')
|
||||
if banner:
|
||||
kwargs['banner'] = 'base64:' + base64.b64encode(_load_image(banner))
|
||||
body = '\r\n'.join(('\r\n'.join((
|
||||
'--%s' % boundary,
|
||||
'Content-Disposition: form-data; name="%s"' % k,
|
||||
'Content-Type: text/plain',
|
||||
'Content-Length: %d' % len(str(v)),
|
||||
'',
|
||||
str(v)
|
||||
))) for k, v in self.userpwd.items())
|
||||
|
||||
body += '\r\n'.join(('\r\n'.join((
|
||||
'--%s' % boundary,
|
||||
'Content-Disposition: form-data; name="%s"' % k,
|
||||
'Content-Type: text/plain',
|
||||
'Content-Length: %d' % len(str(v)),
|
||||
'',
|
||||
str(v)
|
||||
))) for k, v in kwargs.items())
|
||||
|
||||
if captcha:
|
||||
img = _load_image(captcha)
|
||||
body += '\r\n'.join((
|
||||
'',
|
||||
'--%s' % boundary,
|
||||
'Content-Disposition: form-data; name="captchafile"; '
|
||||
'filename="captcha"',
|
||||
'Content-Type: application/octet-stream',
|
||||
'Content-Length: %d' % len(img),
|
||||
'',
|
||||
img,
|
||||
'--%s--' % boundary,
|
||||
''
|
||||
))
|
||||
|
||||
response = self._call('captcha', body, {
|
||||
'Content-Type': 'multipart/form-data; boundary="%s"' % boundary
|
||||
}) or {}
|
||||
if response.get('captcha'):
|
||||
return response
|
||||
|
||||
|
||||
class SocketClient(Client):
|
||||
|
||||
"""Death by Captcha socket API client."""
|
||||
|
||||
TERMINATOR = '\r\n'
|
||||
|
||||
def __init__(self, *args):
|
||||
Client.__init__(self, *args)
|
||||
self.socket_lock = threading.Lock()
|
||||
self.socket = None
|
||||
|
||||
def close(self):
|
||||
if self.socket:
|
||||
self._log('CLOSE')
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except socket.error:
|
||||
pass
|
||||
finally:
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
|
||||
def connect(self):
|
||||
if not self.socket:
|
||||
self._log('CONN')
|
||||
host = (socket.gethostbyname(SOCKET_HOST),
|
||||
random.choice(SOCKET_PORTS))
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.settimeout(0)
|
||||
try:
|
||||
self.socket.connect(host)
|
||||
except socket.error, err:
|
||||
if (err.args[0] not in
|
||||
(errno.EAGAIN, errno.EWOULDBLOCK, errno.EINPROGRESS)):
|
||||
self.close()
|
||||
raise err
|
||||
return self.socket
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def _sendrecv(self, sock, buf):
|
||||
self._log('SEND', buf)
|
||||
fds = [sock]
|
||||
buf += self.TERMINATOR
|
||||
response = ''
|
||||
intvl_idx = 0
|
||||
while True:
|
||||
intvl, intvl_idx = self._get_poll_interval(intvl_idx)
|
||||
rds, wrs, exs = select.select((not buf and fds) or [],
|
||||
(buf and fds) or [],
|
||||
fds,
|
||||
intvl)
|
||||
if exs:
|
||||
raise IOError('select() failed')
|
||||
try:
|
||||
if wrs:
|
||||
while buf:
|
||||
buf = buf[wrs[0].send(buf):]
|
||||
elif rds:
|
||||
while True:
|
||||
s = rds[0].recv(256)
|
||||
if not s:
|
||||
raise IOError('recv(): connection lost')
|
||||
else:
|
||||
response += s
|
||||
except socket.error, err:
|
||||
if (err.args[0] not in
|
||||
(errno.EAGAIN, errno.EWOULDBLOCK, errno.EINPROGRESS)):
|
||||
raise err
|
||||
if response.endswith(self.TERMINATOR):
|
||||
self._log('RECV', response)
|
||||
return response.rstrip(self.TERMINATOR)
|
||||
raise IOError('send/recv timed out')
|
||||
|
||||
def _call(self, cmd, data=None):
|
||||
if data is None:
|
||||
data = {}
|
||||
data['cmd'] = cmd
|
||||
data['version'] = API_VERSION
|
||||
request = json_encode(data)
|
||||
|
||||
response = None
|
||||
for _ in range(2):
|
||||
if not self.socket and cmd != 'login':
|
||||
self._call('login', self.userpwd.copy())
|
||||
self.socket_lock.acquire()
|
||||
try:
|
||||
sock = self.connect()
|
||||
response = self._sendrecv(sock, request)
|
||||
except IOError, err:
|
||||
sys.stderr.write(str(err) + "\n")
|
||||
self.close()
|
||||
except socket.error, err:
|
||||
sys.stderr.write(str(err) + "\n")
|
||||
self.close()
|
||||
raise IOError('Connection refused')
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
self.socket_lock.release()
|
||||
|
||||
if response is None:
|
||||
raise IOError('Connection lost or timed out during API request')
|
||||
|
||||
try:
|
||||
response = json_decode(response)
|
||||
except Exception:
|
||||
raise RuntimeError('Invalid API response')
|
||||
|
||||
if not response.get('error'):
|
||||
return response
|
||||
|
||||
error = response['error']
|
||||
if error in ('not-logged-in', 'invalid-credentials'):
|
||||
raise AccessDeniedException('Access denied, check your credentials')
|
||||
elif 'banned' == error:
|
||||
raise AccessDeniedException('Access denied, account is suspended')
|
||||
elif 'insufficient-funds' == error:
|
||||
raise AccessDeniedException(
|
||||
'CAPTCHA was rejected due to low balance')
|
||||
elif 'invalid-captcha' == error:
|
||||
raise ValueError('CAPTCHA is not a valid image')
|
||||
elif 'service-overload' == error:
|
||||
raise OverflowError(
|
||||
'CAPTCHA was rejected due to service overload, try again later')
|
||||
else:
|
||||
self.socket_lock.acquire()
|
||||
self.close()
|
||||
self.socket_lock.release()
|
||||
raise RuntimeError('API server error occured: %s' % error)
|
||||
|
||||
def get_user(self):
|
||||
return self._call('user') or {'user': 0}
|
||||
|
||||
def get_captcha(self, cid):
|
||||
return self._call('captcha', {'captcha': cid}) or {'captcha': 0}
|
||||
|
||||
def upload(self, captcha=None, **kwargs):
|
||||
data = {}
|
||||
if captcha:
|
||||
data['captcha'] = base64.b64encode(_load_image(captcha))
|
||||
if kwargs:
|
||||
banner = kwargs.get('banner', '')
|
||||
if banner:
|
||||
kwargs['banner'] = base64.b64encode(_load_image(banner))
|
||||
data.update(kwargs)
|
||||
response = self._call('upload', data)
|
||||
if response.get('captcha'):
|
||||
uploaded_captcha = dict(
|
||||
(k, response.get(k))
|
||||
for k in ('captcha', 'text', 'is_correct')
|
||||
)
|
||||
if not uploaded_captcha['text']:
|
||||
uploaded_captcha['text'] = None
|
||||
return uploaded_captcha
|
||||
|
||||
def report(self, cid):
|
||||
return not self._call('report', {'captcha': cid}).get('is_correct')
|
||||
|
||||
|
||||
if '__main__' == __name__:
|
||||
# Put your DBC username & password here:
|
||||
# client = HttpClient(sys.argv[1], sys.argv[2])
|
||||
client = SocketClient(sys.argv[1], sys.argv[2])
|
||||
client.is_verbose = True
|
||||
|
||||
print 'Your balance is %s US cents' % client.get_balance()
|
||||
|
||||
for fn in sys.argv[3:]:
|
||||
try:
|
||||
# Put your CAPTCHA image file name or file-like object, and optional
|
||||
# solving timeout (in seconds) here:
|
||||
captcha = client.decode(fn, DEFAULT_TIMEOUT)
|
||||
except Exception, e:
|
||||
sys.stderr.write('Failed uploading CAPTCHA: %s\n' % (e, ))
|
||||
captcha = None
|
||||
|
||||
if captcha:
|
||||
print 'CAPTCHA %d solved: %s' % \
|
||||
(captcha['captcha'], captcha['text'])
|
||||
|
||||
# Report as incorrectly solved if needed. Make sure the CAPTCHA was
|
||||
# in fact incorrectly solved!
|
||||
# try:
|
||||
# client.report(captcha['captcha'])
|
||||
# except Exception, e:
|
||||
# sys.stderr.write('Failed reporting CAPTCHA: %s\n' % (e, ))
|
||||
@@ -5,6 +5,7 @@ import pickle
|
||||
import shutil
|
||||
import tempfile
|
||||
import traceback
|
||||
import hashlib
|
||||
|
||||
import appdirs
|
||||
|
||||
@@ -89,7 +90,7 @@ class FileCache(MutableMapping):
|
||||
"""
|
||||
|
||||
def __init__(self, appname, flag='c', mode=0o666, keyencoding='utf-8',
|
||||
serialize=True, app_cache_dir=None):
|
||||
serialize=True, app_cache_dir=None, key_file_ext=".txt"):
|
||||
"""Initialize a :class:`FileCache` object."""
|
||||
if not isinstance(flag, str):
|
||||
raise TypeError("flag must be str not '{}'".format(type(flag)))
|
||||
@@ -130,6 +131,7 @@ class FileCache(MutableMapping):
|
||||
self._mode = mode
|
||||
self._keyencoding = keyencoding
|
||||
self._serialize = serialize
|
||||
self.key_file_ext = key_file_ext
|
||||
|
||||
def _parse_appname(self, appname):
|
||||
"""Splits an appname into the appname and subcache components."""
|
||||
@@ -188,6 +190,11 @@ class FileCache(MutableMapping):
|
||||
except:
|
||||
logger.error("Couldn't write content from %r to cache file: %r: %s", ekey, filename,
|
||||
traceback.format_exc())
|
||||
try:
|
||||
self.__write_to_file(filename + self.key_file_ext, ekey)
|
||||
except:
|
||||
logger.error("Couldn't write content from %r to cache file: %r: %s", ekey, filename,
|
||||
traceback.format_exc())
|
||||
self._buffer.clear()
|
||||
self._sync = False
|
||||
|
||||
@@ -196,8 +203,7 @@ class FileCache(MutableMapping):
|
||||
raise ValueError("invalid operation on closed cache")
|
||||
|
||||
def _encode_key(self, key):
|
||||
"""Encode key using *hex_codec* for constructing a cache filename.
|
||||
|
||||
"""
|
||||
Keys are implicitly converted to :class:`bytes` if passed as
|
||||
:class:`str`.
|
||||
|
||||
@@ -206,16 +212,15 @@ class FileCache(MutableMapping):
|
||||
key = key.encode(self._keyencoding)
|
||||
elif not isinstance(key, bytes):
|
||||
raise TypeError("key must be bytes or str")
|
||||
return codecs.encode(key, 'hex_codec').decode(self._keyencoding)
|
||||
return key.decode(self._keyencoding)
|
||||
|
||||
def _decode_key(self, key):
|
||||
"""Decode key using hex_codec to retrieve the original key.
|
||||
|
||||
"""
|
||||
Keys are returned as :class:`str` if serialization is enabled.
|
||||
Keys are returned as :class:`bytes` if serialization is disabled.
|
||||
|
||||
"""
|
||||
bkey = codecs.decode(key.encode(self._keyencoding), 'hex_codec')
|
||||
bkey = key.encode(self._keyencoding)
|
||||
return bkey.decode(self._keyencoding) if self._serialize else bkey
|
||||
|
||||
def _dumps(self, value):
|
||||
@@ -226,18 +231,24 @@ class FileCache(MutableMapping):
|
||||
|
||||
def _key_to_filename(self, key):
|
||||
"""Convert an encoded key to an absolute cache filename."""
|
||||
return os.path.join(self.cache_dir, key)
|
||||
if isinstance(key, unicode):
|
||||
key = key.encode(self._keyencoding)
|
||||
return os.path.join(self.cache_dir, hashlib.md5(key).hexdigest())
|
||||
|
||||
def _filename_to_key(self, absfilename):
|
||||
"""Convert an absolute cache filename to a key name."""
|
||||
return os.path.split(absfilename)[1]
|
||||
hkey_hdr_fn = absfilename + self.key_file_ext
|
||||
if os.path.isfile(hkey_hdr_fn):
|
||||
with open(hkey_hdr_fn, 'rb') as f:
|
||||
key = f.read()
|
||||
return key.decode(self._keyencoding) if self._serialize else key
|
||||
|
||||
def _all_filenames(self, scandir_generic=True):
|
||||
"""Return a list of absolute cache filenames"""
|
||||
_scandir = _scandir_generic if scandir_generic else scandir
|
||||
try:
|
||||
for entry in _scandir(self.cache_dir):
|
||||
if entry.is_file(follow_symlinks=False):
|
||||
if entry.is_file(follow_symlinks=False) and not entry.name.endswith(self.key_file_ext):
|
||||
yield os.path.join(self.cache_dir, entry.name)
|
||||
except (FileNotFoundError, OSError):
|
||||
raise StopIteration
|
||||
@@ -250,14 +261,17 @@ class FileCache(MutableMapping):
|
||||
else:
|
||||
return set(file_keys + list(self._buffer))
|
||||
|
||||
def _write_to_file(self, filename, bytesvalue):
|
||||
def __write_to_file(self, filename, value):
|
||||
"""Write bytesvalue to filename."""
|
||||
fh, tmp = tempfile.mkstemp()
|
||||
with os.fdopen(fh, self._flag) as f:
|
||||
f.write(self._dumps(bytesvalue))
|
||||
f.write(value)
|
||||
rename(tmp, filename)
|
||||
os.chmod(filename, self._mode)
|
||||
|
||||
def _write_to_file(self, filename, bytesvalue):
|
||||
self.__write_to_file(filename, self._dumps(bytesvalue))
|
||||
|
||||
def _read_from_file(self, filename):
|
||||
"""Read data from filename."""
|
||||
try:
|
||||
@@ -274,6 +288,7 @@ class FileCache(MutableMapping):
|
||||
else:
|
||||
filename = self._key_to_filename(ekey)
|
||||
self._write_to_file(filename, value)
|
||||
self.__write_to_file(filename + self.key_file_ext, ekey)
|
||||
|
||||
def __getitem__(self, key):
|
||||
ekey = self._encode_key(key)
|
||||
@@ -283,8 +298,9 @@ class FileCache(MutableMapping):
|
||||
except KeyError:
|
||||
pass
|
||||
filename = self._key_to_filename(ekey)
|
||||
if filename not in self._all_filenames():
|
||||
if not os.path.isfile(filename):
|
||||
raise KeyError(key)
|
||||
|
||||
return self._read_from_file(filename)
|
||||
|
||||
def __delitem__(self, key):
|
||||
@@ -301,6 +317,11 @@ class FileCache(MutableMapping):
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
try:
|
||||
os.remove(filename + self.key_file_ext)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
def __iter__(self):
|
||||
for key in self._all_keys():
|
||||
yield self._decode_key(key)
|
||||
@@ -310,4 +331,10 @@ class FileCache(MutableMapping):
|
||||
|
||||
def __contains__(self, key):
|
||||
ekey = self._encode_key(key)
|
||||
return ekey in self._all_keys()
|
||||
if not self._sync:
|
||||
try:
|
||||
return ekey in self._buffer
|
||||
except KeyError:
|
||||
pass
|
||||
filename = self._key_to_filename(ekey)
|
||||
return os.path.isfile(filename)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
# 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', 'require'
|
||||
]
|
||||
|
||||
from .base import PyJsException
|
||||
from .evaljs import *
|
||||
from .translators import parse as parse_js
|
||||
from .node_import import require
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
__author__ = 'Piotr Dabkowski'
|
||||
@@ -0,0 +1,48 @@
|
||||
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
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
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
|
||||
})
|
||||
@@ -0,0 +1,405 @@
|
||||
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:
|
||||
try:
|
||||
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
except:
|
||||
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ")
|
||||
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
|
||||
})
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
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)
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,157 @@
|
||||
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)
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
})
|
||||
@@ -0,0 +1,198 @@
|
||||
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
|
||||
@@ -0,0 +1,16 @@
|
||||
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
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
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
|
||||
})
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,207 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,41 @@
|
||||
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;'))
|
||||
@@ -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;
|
||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
# 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):
|
||||
"""Just like eval_js but with experimental support for js6 via babel."""
|
||||
return eval_js(js6_to_js5(js))
|
||||
|
||||
|
||||
def translate_js6(js):
|
||||
"""Just like translate_js but with experimental support for js6 via babel."""
|
||||
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.encode("utf-8")).hexdigest() + '.py'
|
||||
try:
|
||||
with open(filename, mode='w') as f:
|
||||
f.write(code)
|
||||
with open(filename, "r") as f:
|
||||
pyCode = compile(f.read(), filename, 'exec')
|
||||
exec(pyCode, 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,0 +1,15 @@
|
||||
from ..base import *
|
||||
|
||||
@Js
|
||||
def console():
|
||||
pass
|
||||
|
||||
@Js
|
||||
def log():
|
||||
print(arguments[0])
|
||||
|
||||
console.put('log', log)
|
||||
console.put('debug', log)
|
||||
console.put('info', log)
|
||||
console.put('warn', log)
|
||||
console.put('error', log)
|
||||
@@ -0,0 +1,51 @@
|
||||
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())
|
||||
@@ -0,0 +1,176 @@
|
||||
from ..base import *
|
||||
from six.moves.urllib.parse import quote, unquote
|
||||
|
||||
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 test them properly
|
||||
|
||||
|
||||
@Js
|
||||
def escape(text):
|
||||
return quote(text.to_string().value)
|
||||
|
||||
|
||||
@Js
|
||||
def unescape(text):
|
||||
return unquote(text.to_string().value)
|
||||
|
||||
|
||||
@Js
|
||||
def encodeURI(text):
|
||||
return quote(text.to_string().value, safe='~@#$&()*!+=:;,.?/\'')
|
||||
|
||||
|
||||
@Js
|
||||
def decodeURI(text):
|
||||
return unquote(text.to_string().value)
|
||||
|
||||
|
||||
@Js
|
||||
def encodeURIComponent(text):
|
||||
return quote(text.to_string().value, safe='~()*!.\'')
|
||||
|
||||
|
||||
@Js
|
||||
def decodeURIComponent(text):
|
||||
return unquote(text.to_string().value)
|
||||
@@ -0,0 +1,929 @@
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
|
||||
import datetime
|
||||
|
||||
from .desc import *
|
||||
from .simplex import *
|
||||
from .conversions import *
|
||||
|
||||
from pyjsparser import PyJsParser
|
||||
|
||||
import six
|
||||
if six.PY2:
|
||||
from itertools import izip
|
||||
else:
|
||||
izip = zip
|
||||
|
||||
|
||||
|
||||
|
||||
def Type(obj):
|
||||
return obj.TYPE
|
||||
|
||||
|
||||
# 8.6.2
|
||||
class PyJs(object):
|
||||
TYPE = 'Object'
|
||||
IS_CONSTRUCTOR = False
|
||||
|
||||
prototype = None
|
||||
Class = None
|
||||
extensible = True
|
||||
value = None
|
||||
|
||||
own = {}
|
||||
|
||||
def get_member(self, unconverted_prop):
|
||||
return self.get(to_string(unconverted_prop))
|
||||
|
||||
def put_member(self, unconverted_prop, val):
|
||||
return self.put(to_string(unconverted_prop), val)
|
||||
|
||||
def get(self, prop):
|
||||
assert type(prop) == unicode
|
||||
cand = self.get_property(prop)
|
||||
if cand is None:
|
||||
return undefined
|
||||
if is_data_descriptor(cand):
|
||||
return cand['value']
|
||||
if is_undefined(cand['get']):
|
||||
return undefined
|
||||
return cand['get'].call(self)
|
||||
|
||||
def get_own_property(self, prop):
|
||||
assert type(prop) == unicode
|
||||
# takes py returns py
|
||||
return self.own.get(prop)
|
||||
|
||||
def get_property(self, prop):
|
||||
assert type(prop) == unicode
|
||||
# take py returns py
|
||||
cand = self.get_own_property(prop)
|
||||
if cand:
|
||||
return cand
|
||||
if self.prototype is not None:
|
||||
return self.prototype.get_property(prop)
|
||||
|
||||
def put(self, prop, val, throw=False):
|
||||
assert type(prop) == unicode
|
||||
# takes py, returns none
|
||||
if not self.can_put(prop):
|
||||
if throw:
|
||||
raise MakeError('TypeError', 'Could not define own property')
|
||||
return
|
||||
own_desc = self.get_own_property(prop)
|
||||
if is_data_descriptor(own_desc):
|
||||
self.own[prop]['value'] = val
|
||||
return
|
||||
desc = self.get_property(prop)
|
||||
if is_accessor_descriptor(desc):
|
||||
desc['set'].call(
|
||||
self, (val, )) # calling setter on own or inherited element
|
||||
else: # new property
|
||||
self.own[prop] = {
|
||||
'value': val,
|
||||
'writable': True,
|
||||
'configurable': True,
|
||||
'enumerable': True
|
||||
}
|
||||
|
||||
def can_put(self, prop): # to check
|
||||
assert type(prop) == unicode, type(prop)
|
||||
# takes py returns py
|
||||
desc = self.get_own_property(prop)
|
||||
if desc: # if we have this property
|
||||
if is_accessor_descriptor(desc):
|
||||
return is_callable(
|
||||
desc['set']) # Check if setter method is defined
|
||||
else: # data desc
|
||||
return desc['writable']
|
||||
if self.prototype is None:
|
||||
return self.extensible
|
||||
inherited = self.prototype.get_property(prop)
|
||||
if inherited is None:
|
||||
return self.extensible
|
||||
if is_accessor_descriptor(inherited):
|
||||
return not is_undefined(inherited['set'])
|
||||
elif self.extensible:
|
||||
return inherited['writable'] # weird...
|
||||
return False
|
||||
|
||||
def has_property(self, prop):
|
||||
assert type(prop) == unicode
|
||||
# takes py returns Py
|
||||
return self.get_property(prop) is not None
|
||||
|
||||
def delete(self, prop, throw=False):
|
||||
assert type(prop) == unicode
|
||||
# takes py, returns py
|
||||
desc = self.get_own_property(prop)
|
||||
if desc is None:
|
||||
return True
|
||||
if desc['configurable']:
|
||||
del self.own[prop]
|
||||
return True
|
||||
if throw:
|
||||
raise MakeError('TypeError', 'Could not define own property')
|
||||
return False
|
||||
|
||||
def default_value(self, hint=None):
|
||||
order = ('valueOf', 'toString')
|
||||
if hint == 'String' or (hint is None and self.Class == 'Date'):
|
||||
order = ('toString', 'valueOf')
|
||||
for meth_name in order:
|
||||
method = self.get(meth_name)
|
||||
if method is not None and is_callable(method):
|
||||
cand = method.call(self, ())
|
||||
if is_primitive(cand):
|
||||
return cand
|
||||
raise MakeError('TypeError',
|
||||
'Cannot convert object to primitive value')
|
||||
|
||||
def define_own_property(
|
||||
self, prop, desc,
|
||||
throw): # Internal use only. External through Object
|
||||
assert type(prop) == unicode
|
||||
# takes Py, returns Py
|
||||
# prop must be a Py string. Desc is either a descriptor or accessor.
|
||||
# Messy method - raw translation from Ecma spec to prevent any bugs. # todo check this
|
||||
current = self.get_own_property(prop)
|
||||
|
||||
extensible = self.extensible
|
||||
if not current: # We are creating a new OWN property
|
||||
if not extensible:
|
||||
if throw:
|
||||
raise MakeError('TypeError',
|
||||
'Could not define own property')
|
||||
return False
|
||||
# extensible must be True
|
||||
if is_data_descriptor(desc) or is_generic_descriptor(desc):
|
||||
DEFAULT_DATA_DESC = {
|
||||
'value': undefined, # undefined
|
||||
'writable': False,
|
||||
'enumerable': False,
|
||||
'configurable': False
|
||||
}
|
||||
DEFAULT_DATA_DESC.update(desc)
|
||||
self.own[prop] = DEFAULT_DATA_DESC
|
||||
else:
|
||||
DEFAULT_ACCESSOR_DESC = {
|
||||
'get': undefined, # undefined
|
||||
'set': undefined, # undefined
|
||||
'enumerable': False,
|
||||
'configurable': False
|
||||
}
|
||||
DEFAULT_ACCESSOR_DESC.update(desc)
|
||||
self.own[prop] = DEFAULT_ACCESSOR_DESC
|
||||
return True
|
||||
# therefore current exists!
|
||||
if not desc or desc == current: # We don't need to change anything.
|
||||
return True
|
||||
configurable = current['configurable']
|
||||
if not configurable: # Prevent changing params
|
||||
if desc.get('configurable'):
|
||||
if throw:
|
||||
raise MakeError('TypeError',
|
||||
'Could not define own property')
|
||||
return False
|
||||
if 'enumerable' in desc and desc['enumerable'] != current[
|
||||
'enumerable']:
|
||||
if throw:
|
||||
raise MakeError('TypeError',
|
||||
'Could not define own property')
|
||||
return False
|
||||
if is_generic_descriptor(desc):
|
||||
pass
|
||||
elif is_data_descriptor(current) != is_data_descriptor(desc):
|
||||
# we want to change the current type of property
|
||||
if not configurable:
|
||||
if throw:
|
||||
raise MakeError('TypeError',
|
||||
'Could not define own property')
|
||||
return False
|
||||
if is_data_descriptor(current): # from data to setter
|
||||
del current['value']
|
||||
del current['writable']
|
||||
current['set'] = undefined # undefined
|
||||
current['get'] = undefined # undefined
|
||||
else: # from setter to data
|
||||
del current['set']
|
||||
del current['get']
|
||||
current['value'] = undefined # undefined
|
||||
current['writable'] = False
|
||||
elif is_data_descriptor(current) and is_data_descriptor(desc):
|
||||
if not configurable:
|
||||
if not current['writable'] and desc.get('writable'):
|
||||
if throw:
|
||||
raise MakeError('TypeError',
|
||||
'Could not define own property')
|
||||
return False
|
||||
if not current['writable'] and 'value' in desc and current[
|
||||
'value'] != desc['value']:
|
||||
if throw:
|
||||
raise MakeError('TypeError',
|
||||
'Could not define own property')
|
||||
return False
|
||||
elif is_accessor_descriptor(current) and is_accessor_descriptor(desc):
|
||||
if not configurable:
|
||||
if 'set' in desc and desc['set'] != current['set']:
|
||||
if throw:
|
||||
raise MakeError('TypeError',
|
||||
'Could not define own property')
|
||||
return False
|
||||
if 'get' in desc and desc['get'] != current['get']:
|
||||
if throw:
|
||||
raise MakeError('TypeError',
|
||||
'Could not define own property')
|
||||
return False
|
||||
current.update(desc)
|
||||
return True
|
||||
|
||||
def create(self, args, space):
|
||||
'''Generally not a constructor, raise an error'''
|
||||
raise MakeError('TypeError', '%s is not a constructor' % self.Class)
|
||||
|
||||
|
||||
def get_member(
|
||||
self, prop, space
|
||||
): # general member getter, prop has to be unconverted prop. it is it can be any value
|
||||
typ = type(self)
|
||||
if typ not in PRIMITIVES: # most likely getter for object
|
||||
return self.get_member(
|
||||
prop
|
||||
) # <- object can implement this to support faster prop getting. ex array.
|
||||
elif typ == unicode: # then probably a String
|
||||
if type(prop) == float and is_finite(prop):
|
||||
index = int(prop)
|
||||
if index == prop and 0 <= index < len(self):
|
||||
return self[index]
|
||||
s_prop = to_string(prop)
|
||||
if s_prop == 'length':
|
||||
return float(len(self))
|
||||
elif s_prop.isdigit():
|
||||
index = int(s_prop)
|
||||
if 0 <= index < len(self):
|
||||
return self[index]
|
||||
# use standard string prototype
|
||||
return space.StringPrototype.get(s_prop)
|
||||
# maybe an index
|
||||
elif typ == float:
|
||||
# use standard number prototype
|
||||
return space.NumberPrototype.get(to_string(prop))
|
||||
elif typ == bool:
|
||||
return space.BooleanPrototype.get(to_string(prop))
|
||||
elif typ is UNDEFINED_TYPE:
|
||||
raise MakeError('TypeError',
|
||||
"Cannot read property '%s' of undefined" % prop)
|
||||
elif typ is NULL_TYPE:
|
||||
raise MakeError('TypeError',
|
||||
"Cannot read property '%s' of null" % prop)
|
||||
else:
|
||||
raise RuntimeError('Unknown type! - ' + repr(typ))
|
||||
|
||||
|
||||
def get_member_dot(self, prop, space):
|
||||
# dot member getter, prop has to be unicode
|
||||
typ = type(self)
|
||||
if typ not in PRIMITIVES: # most likely getter for object
|
||||
return self.get(prop)
|
||||
elif typ == unicode: # then probably a String
|
||||
if prop == 'length':
|
||||
return float(len(self))
|
||||
elif prop.isdigit():
|
||||
index = int(prop)
|
||||
if 0 <= index < len(self):
|
||||
return self[index]
|
||||
else:
|
||||
# use standard string prototype
|
||||
return space.StringPrototype.get(prop)
|
||||
# maybe an index
|
||||
elif typ == float:
|
||||
# use standard number prototype
|
||||
return space.NumberPrototype.get(prop)
|
||||
elif typ == bool:
|
||||
return space.BooleanPrototype.get(prop)
|
||||
elif typ in (UNDEFINED_TYPE, NULL_TYPE):
|
||||
raise MakeError('TypeError',
|
||||
"Cannot read property '%s' of undefined" % prop)
|
||||
else:
|
||||
raise RuntimeError('Unknown type! - ' + repr(typ))
|
||||
|
||||
|
||||
# Object
|
||||
|
||||
|
||||
class PyJsObject(PyJs):
|
||||
TYPE = 'Object'
|
||||
Class = 'Object'
|
||||
|
||||
def __init__(self, prototype=None):
|
||||
self.prototype = prototype
|
||||
self.own = {}
|
||||
|
||||
def _init(self, props, vals):
|
||||
i = 0
|
||||
for prop, kind in props:
|
||||
if prop in self.own: # just check... probably will not happen very often.
|
||||
if is_data_descriptor(self.own[prop]):
|
||||
if kind != 'i':
|
||||
raise MakeError(
|
||||
'SyntaxError',
|
||||
'Invalid object initializer! Duplicate property name "%s"'
|
||||
% prop)
|
||||
else:
|
||||
if kind == 'i' or (kind == 'g' and 'get' in self.own[prop]
|
||||
) or (kind == 's'
|
||||
and 'set' in self.own[prop]):
|
||||
raise MakeError(
|
||||
'SyntaxError',
|
||||
'Invalid object initializer! Duplicate setter/getter of prop: "%s"'
|
||||
% prop)
|
||||
|
||||
if kind == 'i': # init
|
||||
self.own[prop] = {
|
||||
'value': vals[i],
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}
|
||||
elif kind == 'g': # get
|
||||
self.define_own_property(prop, {
|
||||
'get': vals[i],
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
elif kind == 's': # get
|
||||
self.define_own_property(prop, {
|
||||
'get': vals[i],
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
'Invalid property kind - %s. Expected one of i, g, s.' %
|
||||
repr(kind))
|
||||
i += 1
|
||||
|
||||
def _set_props(self, prop_descs):
|
||||
for prop, desc in six.iteritems(prop_descs):
|
||||
self.define_own_property(prop, desc)
|
||||
|
||||
|
||||
# Array
|
||||
|
||||
|
||||
# todo Optimise Array - extremely slow due to index conversions from str to int and back etc.
|
||||
# solution - make get and put methods callable with any type of prop and handle conversions from inside
|
||||
# if not array then use to_string(prop). In array if prop is integer then just use it
|
||||
# also consider removal of these stupid writable, enumerable etc for ints.
|
||||
class PyJsArray(PyJs):
|
||||
Class = 'Array'
|
||||
|
||||
def __init__(self, length, prototype=None):
|
||||
self.prototype = prototype
|
||||
self.own = {
|
||||
'length': {
|
||||
'value': float(length),
|
||||
'writable': True,
|
||||
'enumerable': False,
|
||||
'configurable': False
|
||||
}
|
||||
}
|
||||
|
||||
def _init(self, elements):
|
||||
for i, ele in enumerate(elements):
|
||||
if ele is None: continue
|
||||
self.own[unicode(i)] = {
|
||||
'value': ele,
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}
|
||||
|
||||
def put(self, prop, val, throw=False):
|
||||
assert type(val) != int
|
||||
# takes py, returns none
|
||||
if not self.can_put(prop):
|
||||
if throw:
|
||||
raise MakeError('TypeError', 'Could not define own property')
|
||||
return
|
||||
own_desc = self.get_own_property(prop)
|
||||
if is_data_descriptor(own_desc):
|
||||
self.define_own_property(prop, {'value': val}, False)
|
||||
return
|
||||
desc = self.get_property(prop)
|
||||
if is_accessor_descriptor(desc):
|
||||
desc['set'].call(
|
||||
self, (val, )) # calling setter on own or inherited element
|
||||
else: # new property
|
||||
self.define_own_property(
|
||||
prop, {
|
||||
'value': val,
|
||||
'writable': True,
|
||||
'configurable': True,
|
||||
'enumerable': True
|
||||
}, False)
|
||||
|
||||
def define_own_property(self, prop, desc, throw):
|
||||
assert type(desc.get('value')) != int
|
||||
old_len_desc = self.get_own_property('length')
|
||||
old_len = old_len_desc['value'] # value is js type so convert to py.
|
||||
if prop == 'length':
|
||||
if 'value' not in desc:
|
||||
return PyJs.define_own_property(self, prop, desc, False)
|
||||
new_len = to_uint32(desc['value'])
|
||||
if new_len != to_number(desc['value']):
|
||||
raise MakeError('RangeError', 'Invalid range!')
|
||||
new_desc = dict((k, v) for k, v in six.iteritems(desc))
|
||||
new_desc['value'] = float(new_len)
|
||||
if new_len >= old_len:
|
||||
return PyJs.define_own_property(self, prop, new_desc, False)
|
||||
if not old_len_desc['writable']:
|
||||
return False
|
||||
if 'writable' not in new_desc or new_desc['writable'] == True:
|
||||
new_writable = True
|
||||
else:
|
||||
new_writable = False
|
||||
new_desc['writable'] = True
|
||||
if not PyJs.define_own_property(self, prop, new_desc, False):
|
||||
return False
|
||||
if new_len < old_len:
|
||||
# not very efficient for sparse arrays, so using different method for sparse:
|
||||
if old_len > 30 * len(self.own):
|
||||
for ele in self.own.keys():
|
||||
if ele.isdigit() and int(ele) >= new_len:
|
||||
if not self.delete(
|
||||
ele
|
||||
): # if failed to delete set len to current len and reject.
|
||||
new_desc['value'] = old_len + 1.
|
||||
if not new_writable:
|
||||
new_desc['writable'] = False
|
||||
PyJs.define_own_property(
|
||||
self, prop, new_desc, False)
|
||||
return False
|
||||
old_len = new_len
|
||||
else: # standard method
|
||||
while new_len < old_len:
|
||||
old_len -= 1
|
||||
if not self.delete(
|
||||
unicode(int(old_len))
|
||||
): # if failed to delete set len to current len and reject.
|
||||
new_desc['value'] = old_len + 1.
|
||||
if not new_writable:
|
||||
new_desc['writable'] = False
|
||||
PyJs.define_own_property(self, prop, new_desc,
|
||||
False)
|
||||
return False
|
||||
if not new_writable:
|
||||
self.own['length']['writable'] = False
|
||||
return True
|
||||
|
||||
elif prop.isdigit():
|
||||
index = to_uint32(prop)
|
||||
if index >= old_len and not old_len_desc['writable']:
|
||||
return False
|
||||
if not PyJs.define_own_property(self, prop, desc, False):
|
||||
return False
|
||||
if index >= old_len:
|
||||
old_len_desc['value'] = index + 1.
|
||||
return True
|
||||
else:
|
||||
return PyJs.define_own_property(self, prop, desc, False)
|
||||
|
||||
def to_list(self):
|
||||
return [
|
||||
self.get(str(e)) for e in xrange(self.get('length').to_uint32())
|
||||
]
|
||||
|
||||
|
||||
# database with compiled patterns. Js pattern -> Py pattern.
|
||||
REGEXP_DB = {}
|
||||
|
||||
|
||||
class PyJsRegExp(PyJs):
|
||||
Class = 'RegExp'
|
||||
|
||||
def __init__(self, body, flags, prototype=None):
|
||||
self.prototype = prototype
|
||||
self.glob = True if 'g' in flags else False
|
||||
self.ignore_case = re.IGNORECASE if 'i' in flags else 0
|
||||
self.multiline = re.MULTILINE if 'm' in flags else 0
|
||||
self.value = body
|
||||
|
||||
if (body, flags) in REGEXP_DB:
|
||||
self.pat = REGEXP_DB[body, flags]
|
||||
else:
|
||||
comp = None
|
||||
try:
|
||||
# converting JS regexp pattern to Py pattern.
|
||||
possible_fixes = [(u'[]', u'[\0]'), (u'[^]', u'[^\0]'),
|
||||
(u'nofix1791', u'nofix1791')]
|
||||
reg = self.value
|
||||
for fix, rep in possible_fixes:
|
||||
comp = PyJsParser()._interpret_regexp(reg, flags)
|
||||
#print 'reg -> comp', reg, '->', comp
|
||||
try:
|
||||
self.pat = re.compile(
|
||||
comp, self.ignore_case | self.multiline)
|
||||
#print reg, '->', comp
|
||||
break
|
||||
except:
|
||||
reg = reg.replace(fix, rep)
|
||||
# print 'Fix', fix, '->', rep, '=', reg
|
||||
else:
|
||||
raise Exception()
|
||||
REGEXP_DB[body, flags] = self.pat
|
||||
except:
|
||||
#print 'Invalid pattern...', self.value, comp
|
||||
raise MakeError(
|
||||
'SyntaxError',
|
||||
'Invalid RegExp pattern: %s -> %s' % (repr(self.value),
|
||||
repr(comp)))
|
||||
# now set own properties:
|
||||
self.own = {
|
||||
'source': {
|
||||
'value': self.value,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False
|
||||
},
|
||||
'global': {
|
||||
'value': self.glob,
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False
|
||||
},
|
||||
'ignoreCase': {
|
||||
'value': bool(self.ignore_case),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False
|
||||
},
|
||||
'multiline': {
|
||||
'value': bool(self.multiline),
|
||||
'enumerable': False,
|
||||
'writable': False,
|
||||
'configurable': False
|
||||
},
|
||||
'lastIndex': {
|
||||
'value': 0.,
|
||||
'enumerable': False,
|
||||
'writable': True,
|
||||
'configurable': False
|
||||
}
|
||||
}
|
||||
|
||||
def match(self, string, pos):
|
||||
'''string is of course a py string'''
|
||||
return self.pat.match(string, int(pos))
|
||||
|
||||
|
||||
class PyJsError(PyJs):
|
||||
Class = 'Error'
|
||||
extensible = True
|
||||
|
||||
def __init__(self, message=None, prototype=None):
|
||||
self.prototype = prototype
|
||||
self.own = {}
|
||||
if message is not None:
|
||||
self.put('message', to_string(message))
|
||||
self.own['message']['enumerable'] = False
|
||||
|
||||
|
||||
class PyJsDate(PyJs):
|
||||
Class = 'Date'
|
||||
UTCToLocal = None # todo UTC to local should be imported!
|
||||
|
||||
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(
|
||||
self.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)'
|
||||
)
|
||||
|
||||
|
||||
# Scope class it will hold all the variables accessible to user
|
||||
class Scope(PyJs):
|
||||
Class = 'Global'
|
||||
extensible = True
|
||||
IS_CHILD_SCOPE = True
|
||||
THIS_BINDING = None
|
||||
space = None
|
||||
exe = None
|
||||
|
||||
# todo speed up!
|
||||
# in order to speed up this very important class the top scope should behave differently than
|
||||
# child scopes, child scope should not have this property descriptor thing because they cant be changed anyway
|
||||
# they are all confugurable= False
|
||||
|
||||
def __init__(self, scope, space, parent=None):
|
||||
"""Doc"""
|
||||
self.space = space
|
||||
self.prototype = parent
|
||||
if type(scope) is not dict:
|
||||
assert parent is not None, 'You initialised the WITH_SCOPE without a parent scope.'
|
||||
self.own = scope
|
||||
self.is_with_scope = True
|
||||
else:
|
||||
self.is_with_scope = False
|
||||
if parent is None:
|
||||
# global, top level scope
|
||||
self.own = {}
|
||||
for k, v in six.iteritems(scope):
|
||||
# set all the global items
|
||||
self.define_own_property(
|
||||
k, {
|
||||
'value': v,
|
||||
'configurable': False,
|
||||
'writable': False,
|
||||
'enumerable': False
|
||||
}, False)
|
||||
else:
|
||||
# not global, less powerful but faster closure.
|
||||
self.own = scope # simple dictionary which maps name directly to js object.
|
||||
|
||||
self.par = super(Scope, self)
|
||||
self.stack = []
|
||||
|
||||
def register(self, var):
|
||||
# registered keeps only global registered variables
|
||||
if self.prototype is None:
|
||||
# define in global scope
|
||||
if var in self.own:
|
||||
self.own[var]['configurable'] = False
|
||||
else:
|
||||
self.define_own_property(
|
||||
var, {
|
||||
'value': undefined,
|
||||
'configurable': False,
|
||||
'writable': True,
|
||||
'enumerable': True
|
||||
}, False)
|
||||
elif var not in self.own:
|
||||
# define in local scope since it has not been defined yet
|
||||
self.own[var] = undefined # default value
|
||||
|
||||
def registers(self, vars):
|
||||
"""register multiple variables"""
|
||||
for var in vars:
|
||||
self.register(var)
|
||||
|
||||
def put(self, var, val, throw=False):
|
||||
if self.prototype is None:
|
||||
desc = self.own.get(var) # global scope
|
||||
if desc is None:
|
||||
self.par.put(var, val, False)
|
||||
else:
|
||||
if desc['writable']: # todo consider getters/setters
|
||||
desc['value'] = val
|
||||
else:
|
||||
if self.is_with_scope:
|
||||
if self.own.has_property(var):
|
||||
return self.own.put(var, val, throw=throw)
|
||||
else:
|
||||
return self.prototype.put(var, val)
|
||||
# trying to put in local scope
|
||||
# we dont know yet in which scope we should place this var
|
||||
elif var in self.own:
|
||||
self.own[var] = val
|
||||
return val
|
||||
else:
|
||||
# try to put in the lower scope since we cant put in this one (var wasn't registered)
|
||||
return self.prototype.put(var, val)
|
||||
|
||||
def get(self, var, throw=False):
|
||||
if self.prototype is not None:
|
||||
if self.is_with_scope:
|
||||
cand = None if not self.own.has_property(
|
||||
var) else self.own.get(var)
|
||||
else:
|
||||
# fast local scope
|
||||
cand = self.own.get(var)
|
||||
if cand is None:
|
||||
return self.prototype.get(var, throw)
|
||||
return cand
|
||||
# slow, global scope
|
||||
if var not in self.own:
|
||||
# try in ObjectPrototype...
|
||||
if var in self.space.ObjectPrototype.own:
|
||||
return self.space.ObjectPrototype.get(var)
|
||||
if throw:
|
||||
raise MakeError('ReferenceError', '%s is not defined' % var)
|
||||
return undefined
|
||||
cand = self.own[var].get('value')
|
||||
return cand if cand is not None else self.own[var]['get'].call(self)
|
||||
|
||||
def delete(self, var, throw=False):
|
||||
if self.prototype is not None:
|
||||
if self.is_with_scope:
|
||||
if self.own.has_property(var):
|
||||
return self.own.delete(var)
|
||||
elif var in self.own:
|
||||
return False
|
||||
return self.prototype.delete(var)
|
||||
# we are in global scope here. Must exist and be configurable to delete
|
||||
if var not in self.own:
|
||||
# this var does not exist, why do you want to delete it???
|
||||
return True
|
||||
if self.own[var]['configurable']:
|
||||
del self.own[var]
|
||||
return True
|
||||
# not configurable, cant delete
|
||||
return False
|
||||
|
||||
|
||||
def get_new_arguments_obj(args, space):
|
||||
obj = space.NewObject()
|
||||
obj.Class = 'Arguments'
|
||||
obj.define_own_property(
|
||||
'length', {
|
||||
'value': float(len(args)),
|
||||
'writable': True,
|
||||
'enumerable': False,
|
||||
'configurable': True
|
||||
}, False)
|
||||
for i, e in enumerate(args):
|
||||
obj.put(unicode(i), e)
|
||||
return obj
|
||||
|
||||
|
||||
#Function
|
||||
class PyJsFunction(PyJs):
|
||||
Class = 'Function'
|
||||
source = '{ [native code] }'
|
||||
IS_CONSTRUCTOR = True
|
||||
|
||||
def __init__(self,
|
||||
code,
|
||||
ctx,
|
||||
params,
|
||||
name,
|
||||
space,
|
||||
is_declaration,
|
||||
definitions,
|
||||
prototype=None):
|
||||
self.prototype = prototype
|
||||
self.own = {}
|
||||
|
||||
self.code = code
|
||||
if type(
|
||||
self.code
|
||||
) == int: # just a label pointing to the beginning of the code.
|
||||
self.is_native = False
|
||||
else:
|
||||
self.is_native = True # python function
|
||||
|
||||
self.ctx = ctx
|
||||
|
||||
self.params = params
|
||||
self.arguments_in_params = 'arguments' in params
|
||||
self.definitions = definitions
|
||||
|
||||
# todo remove this check later
|
||||
for p in params:
|
||||
assert p in self.definitions
|
||||
|
||||
self.name = name
|
||||
self.space = space
|
||||
self.is_declaration = is_declaration
|
||||
|
||||
#set own property length to the number of arguments
|
||||
self.own['length'] = {
|
||||
'value': float(len(params)),
|
||||
'writable': False,
|
||||
'enumerable': False,
|
||||
'configurable': False
|
||||
}
|
||||
|
||||
if name:
|
||||
self.own['name'] = {
|
||||
'value': name,
|
||||
'writable': False,
|
||||
'enumerable': False,
|
||||
'configurable': True
|
||||
}
|
||||
|
||||
if not self.is_native: # set prototype for user defined functions
|
||||
# constructor points to this function
|
||||
proto = space.NewObject()
|
||||
proto.own['constructor'] = {
|
||||
'value': self,
|
||||
'writable': True,
|
||||
'enumerable': False,
|
||||
'configurable': True
|
||||
}
|
||||
self.own['prototype'] = {
|
||||
'value': proto,
|
||||
'writable': True,
|
||||
'enumerable': False,
|
||||
'configurable': False
|
||||
}
|
||||
# todo set up throwers on callee and arguments if in strict mode
|
||||
|
||||
def call(self, this, args=()):
|
||||
''' Dont use this method from inside bytecode to call other bytecode. '''
|
||||
if self.is_native:
|
||||
_args = SpaceTuple(
|
||||
args
|
||||
) # we have to do that unfortunately to pass all the necessary info to the funcs
|
||||
_args.space = self.space
|
||||
return self.code(
|
||||
this, _args
|
||||
) # must return valid js object - undefined, null, float, unicode, bool, or PyJs
|
||||
else:
|
||||
return self.space.exe._call(self, this,
|
||||
args) # will run inside bytecode
|
||||
|
||||
def has_instance(self, other):
|
||||
# I am not sure here so instanceof may not work lol.
|
||||
if not is_object(other):
|
||||
return False
|
||||
proto = self.get('prototype')
|
||||
if not is_object(proto):
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Function has non-object prototype in instanceof check')
|
||||
while True:
|
||||
other = other.prototype
|
||||
if not other: # todo make sure that the condition is not None or null
|
||||
return False
|
||||
if other is proto:
|
||||
return True
|
||||
|
||||
def create(self, args, space):
|
||||
proto = self.get('prototype')
|
||||
if not is_object(proto):
|
||||
proto = space.ObjectPrototype
|
||||
new = PyJsObject(prototype=proto)
|
||||
res = self.call(new, args)
|
||||
if is_object(res):
|
||||
return res
|
||||
return new
|
||||
|
||||
def _generate_my_context(self, this, args):
|
||||
my_ctx = Scope(
|
||||
dict(izip(self.params, args)), self.space, parent=self.ctx)
|
||||
my_ctx.registers(self.definitions)
|
||||
my_ctx.THIS_BINDING = this
|
||||
if not self.arguments_in_params:
|
||||
my_ctx.own['arguments'] = get_new_arguments_obj(args, self.space)
|
||||
if not self.is_declaration and self.name and self.name not in my_ctx.own:
|
||||
my_ctx.own[
|
||||
self.
|
||||
name] = self # this should be immutable binding but come on!
|
||||
return my_ctx
|
||||
|
||||
|
||||
class SpaceTuple:
|
||||
def __init__(self, tup):
|
||||
self.tup = tup
|
||||
|
||||
def __len__(self):
|
||||
return len(self.tup)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.tup[item]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.tup)
|
||||
@@ -0,0 +1,753 @@
|
||||
from .code import Code
|
||||
from .simplex import MakeError
|
||||
from .opcodes import *
|
||||
from .operations import *
|
||||
from .trans_utils import *
|
||||
|
||||
SPECIAL_IDENTIFIERS = {'true', 'false', 'this'}
|
||||
|
||||
|
||||
class ByteCodeGenerator:
|
||||
def __init__(self, exe):
|
||||
self.exe = exe
|
||||
|
||||
self.declared_continue_labels = {}
|
||||
self.declared_break_labels = {}
|
||||
|
||||
self.implicit_breaks = []
|
||||
self.implicit_continues = []
|
||||
|
||||
self.declared_vars = []
|
||||
|
||||
self.function_declaration_tape = []
|
||||
|
||||
self.states = []
|
||||
|
||||
def record_state(self):
|
||||
self.states.append(
|
||||
(self.declared_continue_labels, self.declared_break_labels,
|
||||
self.implicit_breaks, self.implicit_continues, self.declared_vars,
|
||||
self.function_declaration_tape))
|
||||
self.declared_continue_labels, self.declared_break_labels, \
|
||||
self.implicit_breaks, self.implicit_continues, \
|
||||
self.declared_vars, self.function_declaration_tape = {}, {}, [], [], [], []
|
||||
|
||||
def restore_state(self):
|
||||
self.declared_continue_labels, self.declared_break_labels, \
|
||||
self.implicit_breaks, self.implicit_continues, \
|
||||
self.declared_vars, self.function_declaration_tape = self.states.pop()
|
||||
|
||||
def ArrayExpression(self, elements, **kwargs):
|
||||
for e in elements:
|
||||
if e is None:
|
||||
self.emit('LOAD_NONE')
|
||||
else:
|
||||
self.emit(e)
|
||||
self.emit('LOAD_ARRAY', len(elements))
|
||||
|
||||
def AssignmentExpression(self, operator, left, right, **kwargs):
|
||||
operator = operator[:-1]
|
||||
if left['type'] == 'MemberExpression':
|
||||
self.emit(left['object'])
|
||||
if left['computed']:
|
||||
self.emit(left['property'])
|
||||
self.emit(right)
|
||||
if operator:
|
||||
self.emit('STORE_MEMBER_OP', operator)
|
||||
else:
|
||||
self.emit('STORE_MEMBER')
|
||||
else:
|
||||
self.emit(right)
|
||||
if operator:
|
||||
self.emit('STORE_MEMBER_DOT_OP', left['property']['name'],
|
||||
operator)
|
||||
else:
|
||||
self.emit('STORE_MEMBER_DOT', left['property']['name'])
|
||||
elif left['type'] == 'Identifier':
|
||||
if left['name'] in SPECIAL_IDENTIFIERS:
|
||||
raise MakeError('SyntaxError',
|
||||
'Invalid left-hand side in assignment')
|
||||
self.emit(right)
|
||||
if operator:
|
||||
self.emit('STORE_OP', left['name'], operator)
|
||||
else:
|
||||
self.emit('STORE', left['name'])
|
||||
else:
|
||||
raise MakeError('SyntaxError',
|
||||
'Invalid left-hand side in assignment')
|
||||
|
||||
def BinaryExpression(self, operator, left, right, **kwargs):
|
||||
self.emit(left)
|
||||
self.emit(right)
|
||||
self.emit('BINARY_OP', operator)
|
||||
|
||||
def BlockStatement(self, body, **kwargs):
|
||||
self._emit_statement_list(body)
|
||||
|
||||
def BreakStatement(self, label, **kwargs):
|
||||
if label is None:
|
||||
self.emit('JUMP', self.implicit_breaks[-1])
|
||||
else:
|
||||
label = label.get('name')
|
||||
if label not in self.declared_break_labels:
|
||||
raise MakeError('SyntaxError',
|
||||
'Undefined label \'%s\'' % label)
|
||||
else:
|
||||
self.emit('JUMP', self.declared_break_labels[label])
|
||||
|
||||
def CallExpression(self, callee, arguments, **kwargs):
|
||||
if callee['type'] == 'MemberExpression':
|
||||
self.emit(callee['object'])
|
||||
if callee['computed']:
|
||||
self.emit(callee['property'])
|
||||
if arguments:
|
||||
for e in arguments:
|
||||
self.emit(e)
|
||||
self.emit('LOAD_N_TUPLE', len(arguments))
|
||||
self.emit('CALL_METHOD')
|
||||
else:
|
||||
self.emit('CALL_METHOD_NO_ARGS')
|
||||
else:
|
||||
prop_name = to_key(callee['property'])
|
||||
if arguments:
|
||||
for e in arguments:
|
||||
self.emit(e)
|
||||
self.emit('LOAD_N_TUPLE', len(arguments))
|
||||
self.emit('CALL_METHOD_DOT', prop_name)
|
||||
else:
|
||||
self.emit('CALL_METHOD_DOT_NO_ARGS', prop_name)
|
||||
else:
|
||||
self.emit(callee)
|
||||
if arguments:
|
||||
for e in arguments:
|
||||
self.emit(e)
|
||||
self.emit('LOAD_N_TUPLE', len(arguments))
|
||||
self.emit('CALL')
|
||||
else:
|
||||
self.emit('CALL_NO_ARGS')
|
||||
|
||||
def ClassBody(self, body, **kwargs):
|
||||
raise NotImplementedError('Not available in ECMA 5.1')
|
||||
|
||||
def ClassDeclaration(self, id, superClass, body, **kwargs):
|
||||
raise NotImplementedError('Not available in ECMA 5.1')
|
||||
|
||||
def ClassExpression(self, id, superClass, body, **kwargs):
|
||||
raise NotImplementedError('Classes not available in ECMA 5.1')
|
||||
|
||||
def ConditionalExpression(self, test, consequent, alternate, **kwargs):
|
||||
alt = self.exe.get_new_label()
|
||||
end = self.exe.get_new_label()
|
||||
# ?
|
||||
self.emit(test)
|
||||
self.emit('JUMP_IF_FALSE', alt)
|
||||
# first val
|
||||
self.emit(consequent)
|
||||
self.emit('JUMP', end)
|
||||
# second val
|
||||
self.emit('LABEL', alt)
|
||||
self.emit(alternate)
|
||||
# end of ?: statement
|
||||
self.emit('LABEL', end)
|
||||
|
||||
def ContinueStatement(self, label, **kwargs):
|
||||
if label is None:
|
||||
self.emit('JUMP', self.implicit_continues[-1])
|
||||
else:
|
||||
label = label.get('name')
|
||||
if label not in self.declared_continue_labels:
|
||||
raise MakeError('SyntaxError',
|
||||
'Undefined label \'%s\'' % label)
|
||||
else:
|
||||
self.emit('JUMP', self.declared_continue_labels[label])
|
||||
|
||||
def DebuggerStatement(self, **kwargs):
|
||||
self.EmptyStatement(**kwargs)
|
||||
|
||||
def DoWhileStatement(self, body, test, **kwargs):
|
||||
continue_label = self.exe.get_new_label()
|
||||
break_label = self.exe.get_new_label()
|
||||
initial_do = self.exe.get_new_label()
|
||||
|
||||
self.emit('JUMP', initial_do)
|
||||
self.emit('LABEL', continue_label)
|
||||
self.emit(test)
|
||||
self.emit('JUMP_IF_FALSE', break_label)
|
||||
self.emit('LABEL', initial_do)
|
||||
|
||||
# translate the body, remember to add and afterwards remove implicit break/continue labels
|
||||
|
||||
self.implicit_continues.append(continue_label)
|
||||
self.implicit_breaks.append(break_label)
|
||||
self.emit(body)
|
||||
self.implicit_continues.pop()
|
||||
self.implicit_breaks.pop()
|
||||
|
||||
self.emit('JUMP', continue_label) # loop back
|
||||
self.emit('LABEL', break_label)
|
||||
|
||||
def EmptyStatement(self, **kwargs):
|
||||
# do nothing
|
||||
pass
|
||||
|
||||
def ExpressionStatement(self, expression, **kwargs):
|
||||
# change the final stack value
|
||||
# pop the previous value and execute expression
|
||||
self.emit('POP')
|
||||
self.emit(expression)
|
||||
|
||||
def ForStatement(self, init, test, update, body, **kwargs):
|
||||
continue_label = self.exe.get_new_label()
|
||||
break_label = self.exe.get_new_label()
|
||||
first_start = self.exe.get_new_label()
|
||||
|
||||
if init is not None:
|
||||
self.emit(init)
|
||||
if init['type'] != 'VariableDeclaration':
|
||||
self.emit('POP')
|
||||
|
||||
# skip first update and go straight to test
|
||||
self.emit('JUMP', first_start)
|
||||
|
||||
self.emit('LABEL', continue_label)
|
||||
if update:
|
||||
self.emit(update)
|
||||
self.emit('POP')
|
||||
self.emit('LABEL', first_start)
|
||||
if test:
|
||||
self.emit(test)
|
||||
self.emit('JUMP_IF_FALSE', break_label)
|
||||
|
||||
# translate the body, remember to add and afterwards to remove implicit break/continue labels
|
||||
|
||||
self.implicit_continues.append(continue_label)
|
||||
self.implicit_breaks.append(break_label)
|
||||
self.emit(body)
|
||||
self.implicit_continues.pop()
|
||||
self.implicit_breaks.pop()
|
||||
|
||||
self.emit('JUMP', continue_label) # loop back
|
||||
self.emit('LABEL', break_label)
|
||||
|
||||
def ForInStatement(self, left, right, body, **kwargs):
|
||||
# prepare the needed labels
|
||||
body_start_label = self.exe.get_new_label()
|
||||
continue_label = self.exe.get_new_label()
|
||||
break_label = self.exe.get_new_label()
|
||||
|
||||
# prepare the name
|
||||
if left['type'] == 'VariableDeclaration':
|
||||
if len(left['declarations']) != 1:
|
||||
raise MakeError(
|
||||
'SyntaxError',
|
||||
' Invalid left-hand side in for-in loop: Must have a single binding.'
|
||||
)
|
||||
self.emit(left)
|
||||
name = left['declarations'][0]['id']['name']
|
||||
elif left['type'] == 'Identifier':
|
||||
name = left['name']
|
||||
else:
|
||||
raise MakeError('SyntaxError',
|
||||
'Invalid left-hand side in for-loop')
|
||||
|
||||
# prepare the iterable
|
||||
self.emit(right)
|
||||
|
||||
# emit ForIn Opcode
|
||||
self.emit('FOR_IN', name, body_start_label, continue_label,
|
||||
break_label)
|
||||
|
||||
# a special continue position
|
||||
self.emit('LABEL', continue_label)
|
||||
self.emit('NOP')
|
||||
|
||||
self.emit('LABEL', body_start_label)
|
||||
self.implicit_continues.append(continue_label)
|
||||
self.implicit_breaks.append(break_label)
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
self.emit(body)
|
||||
self.implicit_continues.pop()
|
||||
self.implicit_breaks.pop()
|
||||
self.emit('NOP')
|
||||
self.emit('LABEL', break_label)
|
||||
self.emit('NOP')
|
||||
|
||||
def FunctionDeclaration(self, id, params, defaults, body, **kwargs):
|
||||
if defaults:
|
||||
raise NotImplementedError('Defaults not available in ECMA 5.1')
|
||||
|
||||
# compile function
|
||||
self.record_state(
|
||||
) # cleans translator state and appends it to the stack so that it can be later restored
|
||||
function_start = self.exe.get_new_label()
|
||||
function_declarations = self.exe.get_new_label()
|
||||
declarations_done = self.exe.get_new_label(
|
||||
) # put jump to this place at the and of function tape!
|
||||
function_end = self.exe.get_new_label()
|
||||
|
||||
# skip the function if encountered externally
|
||||
self.emit('JUMP', function_end)
|
||||
|
||||
self.emit('LABEL', function_start)
|
||||
# call is made with empty stack so load undefined to fill it
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
# declare all functions
|
||||
self.emit('JUMP', function_declarations)
|
||||
self.emit('LABEL', declarations_done)
|
||||
self.function_declaration_tape.append(LABEL(function_declarations))
|
||||
|
||||
self.emit(body)
|
||||
self.ReturnStatement(None)
|
||||
|
||||
self.function_declaration_tape.append(JUMP(declarations_done))
|
||||
self.exe.tape.extend(self.function_declaration_tape)
|
||||
|
||||
self.emit('LABEL', function_end)
|
||||
declared_vars = self.declared_vars
|
||||
self.restore_state()
|
||||
|
||||
# create function object and append to stack
|
||||
name = id.get('name')
|
||||
assert name is not None
|
||||
self.declared_vars.append(name)
|
||||
self.function_declaration_tape.append(
|
||||
LOAD_FUNCTION(function_start, tuple(p['name'] for p in params),
|
||||
name, True, tuple(declared_vars)))
|
||||
self.function_declaration_tape.append(STORE(name))
|
||||
self.function_declaration_tape.append(POP())
|
||||
|
||||
def FunctionExpression(self, id, params, defaults, body, **kwargs):
|
||||
if defaults:
|
||||
raise NotImplementedError('Defaults not available in ECMA 5.1')
|
||||
|
||||
# compile function
|
||||
self.record_state(
|
||||
) # cleans translator state and appends it to the stack so that it can be later restored
|
||||
function_start = self.exe.get_new_label()
|
||||
function_declarations = self.exe.get_new_label()
|
||||
declarations_done = self.exe.get_new_label(
|
||||
) # put jump to this place at the and of function tape!
|
||||
function_end = self.exe.get_new_label()
|
||||
|
||||
# skip the function if encountered externally
|
||||
self.emit('JUMP', function_end)
|
||||
|
||||
self.emit('LABEL', function_start)
|
||||
# call is made with empty stack so load undefined to fill it
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
# declare all functions
|
||||
self.emit('JUMP', function_declarations)
|
||||
self.emit('LABEL', declarations_done)
|
||||
self.function_declaration_tape.append(LABEL(function_declarations))
|
||||
|
||||
self.emit(body)
|
||||
self.ReturnStatement(None)
|
||||
|
||||
self.function_declaration_tape.append(JUMP(declarations_done))
|
||||
self.exe.tape.extend(self.function_declaration_tape)
|
||||
|
||||
self.emit('LABEL', function_end)
|
||||
declared_vars = self.declared_vars
|
||||
self.restore_state()
|
||||
|
||||
# create function object and append to stack
|
||||
name = id.get('name') if id else None
|
||||
self.emit('LOAD_FUNCTION', function_start,
|
||||
tuple(p['name'] for p in params), name, False,
|
||||
tuple(declared_vars))
|
||||
|
||||
def Identifier(self, name, **kwargs):
|
||||
if name == 'true':
|
||||
self.emit('LOAD_BOOLEAN', 1)
|
||||
elif name == 'false':
|
||||
self.emit('LOAD_BOOLEAN', 0)
|
||||
elif name == 'undefined':
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
else:
|
||||
self.emit('LOAD', name)
|
||||
|
||||
def IfStatement(self, test, consequent, alternate, **kwargs):
|
||||
alt = self.exe.get_new_label()
|
||||
end = self.exe.get_new_label()
|
||||
# if
|
||||
self.emit(test)
|
||||
self.emit('JUMP_IF_FALSE', alt)
|
||||
# consequent
|
||||
self.emit(consequent)
|
||||
self.emit('JUMP', end)
|
||||
# alternate
|
||||
self.emit('LABEL', alt)
|
||||
if alternate is not None:
|
||||
self.emit(alternate)
|
||||
# end of if statement
|
||||
self.emit('LABEL', end)
|
||||
|
||||
def LabeledStatement(self, label, body, **kwargs):
|
||||
label = label['name']
|
||||
if body['type'] in ('WhileStatement', 'DoWhileStatement',
|
||||
'ForStatement', 'ForInStatement'):
|
||||
# Continue label available... Simply take labels defined by the loop.
|
||||
# It is important that they request continue label first
|
||||
self.declared_continue_labels[label] = self.exe._label_count + 1
|
||||
self.declared_break_labels[label] = self.exe._label_count + 2
|
||||
self.emit(body)
|
||||
del self.declared_break_labels[label]
|
||||
del self.declared_continue_labels[label]
|
||||
else:
|
||||
# only break label available
|
||||
lbl = self.exe.get_new_label()
|
||||
self.declared_break_labels[label] = lbl
|
||||
self.emit(body)
|
||||
self.emit('LABEL', lbl)
|
||||
del self.declared_break_labels[label]
|
||||
|
||||
def Literal(self, value, **kwargs):
|
||||
if value is None:
|
||||
self.emit('LOAD_NULL')
|
||||
elif isinstance(value, bool):
|
||||
self.emit('LOAD_BOOLEAN', int(value))
|
||||
elif isinstance(value, basestring):
|
||||
self.emit('LOAD_STRING', unicode(value))
|
||||
elif isinstance(value, (float, int, long)):
|
||||
self.emit('LOAD_NUMBER', float(value))
|
||||
elif isinstance(value, tuple):
|
||||
self.emit('LOAD_REGEXP', *value)
|
||||
else:
|
||||
raise RuntimeError('Unsupported literal')
|
||||
|
||||
def LogicalExpression(self, left, right, operator, **kwargs):
|
||||
end = self.exe.get_new_label()
|
||||
if operator == '&&':
|
||||
# AND
|
||||
self.emit(left)
|
||||
self.emit('JUMP_IF_FALSE_WITHOUT_POP', end)
|
||||
self.emit('POP')
|
||||
self.emit(right)
|
||||
self.emit('LABEL', end)
|
||||
elif operator == '||':
|
||||
# OR
|
||||
self.emit(left)
|
||||
self.emit('JUMP_IF_TRUE_WITHOUT_POP', end)
|
||||
self.emit('POP')
|
||||
self.emit(right)
|
||||
self.emit('LABEL', end)
|
||||
else:
|
||||
raise RuntimeError("Unknown logical expression: %s" % operator)
|
||||
|
||||
def MemberExpression(self, computed, object, property, **kwargs):
|
||||
if computed:
|
||||
self.emit(object)
|
||||
self.emit(property)
|
||||
self.emit('LOAD_MEMBER')
|
||||
else:
|
||||
self.emit(object)
|
||||
self.emit('LOAD_MEMBER_DOT', property['name'])
|
||||
|
||||
def NewExpression(self, callee, arguments, **kwargs):
|
||||
self.emit(callee)
|
||||
if arguments:
|
||||
n = len(arguments)
|
||||
for e in arguments:
|
||||
self.emit(e)
|
||||
self.emit('LOAD_N_TUPLE', n)
|
||||
self.emit('NEW')
|
||||
else:
|
||||
self.emit('NEW_NO_ARGS')
|
||||
|
||||
def ObjectExpression(self, properties, **kwargs):
|
||||
data = []
|
||||
for prop in properties:
|
||||
self.emit(prop['value'])
|
||||
if prop['computed']:
|
||||
raise NotImplementedError(
|
||||
'ECMA 5.1 does not support computed object properties!')
|
||||
data.append((to_key(prop['key']), prop['kind'][0]))
|
||||
self.emit('LOAD_OBJECT', tuple(data))
|
||||
|
||||
def Program(self, body, **kwargs):
|
||||
old_tape_len = len(self.exe.tape)
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
self.emit(body)
|
||||
# add function tape !
|
||||
self.exe.tape = self.exe.tape[:old_tape_len] + self.function_declaration_tape + self.exe.tape[old_tape_len:]
|
||||
|
||||
def Pyimport(self, imp, **kwargs):
|
||||
raise NotImplementedError(
|
||||
'Not available for bytecode interpreter yet, use the Js2Py translator.'
|
||||
)
|
||||
|
||||
def Property(self, kind, key, computed, value, method, shorthand,
|
||||
**kwargs):
|
||||
raise NotImplementedError('Not available in ECMA 5.1')
|
||||
|
||||
def RestElement(self, argument, **kwargs):
|
||||
raise NotImplementedError('Not available in ECMA 5.1')
|
||||
|
||||
def ReturnStatement(self, argument, **kwargs):
|
||||
self.emit('POP') # pop result of expression statements
|
||||
if argument is None:
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
else:
|
||||
self.emit(argument)
|
||||
self.emit('RETURN')
|
||||
|
||||
def SequenceExpression(self, expressions, **kwargs):
|
||||
for e in expressions:
|
||||
self.emit(e)
|
||||
self.emit('POP')
|
||||
del self.exe.tape[-1]
|
||||
|
||||
def SwitchCase(self, test, consequent, **kwargs):
|
||||
raise NotImplementedError('Already implemented in SwitchStatement')
|
||||
|
||||
def SwitchStatement(self, discriminant, cases, **kwargs):
|
||||
self.emit(discriminant)
|
||||
labels = [self.exe.get_new_label() for case in cases]
|
||||
tests = [case['test'] for case in cases]
|
||||
consequents = [case['consequent'] for case in cases]
|
||||
end_of_switch = self.exe.get_new_label()
|
||||
|
||||
# translate test cases
|
||||
for test, label in zip(tests, labels):
|
||||
if test is not None:
|
||||
self.emit(test)
|
||||
self.emit('JUMP_IF_EQ', label)
|
||||
else:
|
||||
self.emit('POP')
|
||||
self.emit('JUMP', label)
|
||||
# this will be executed if none of the cases worked
|
||||
self.emit('POP')
|
||||
self.emit('JUMP', end_of_switch)
|
||||
|
||||
# translate consequents
|
||||
self.implicit_breaks.append(end_of_switch)
|
||||
for consequent, label in zip(consequents, labels):
|
||||
self.emit('LABEL', label)
|
||||
self._emit_statement_list(consequent)
|
||||
self.implicit_breaks.pop()
|
||||
|
||||
self.emit('LABEL', end_of_switch)
|
||||
|
||||
def ThisExpression(self, **kwargs):
|
||||
self.emit('LOAD_THIS')
|
||||
|
||||
def ThrowStatement(self, argument, **kwargs):
|
||||
# throw with the empty stack
|
||||
self.emit('POP')
|
||||
self.emit(argument)
|
||||
self.emit('THROW')
|
||||
|
||||
def TryStatement(self, block, handler, finalizer, **kwargs):
|
||||
try_label = self.exe.get_new_label()
|
||||
catch_label = self.exe.get_new_label()
|
||||
finally_label = self.exe.get_new_label()
|
||||
end_label = self.exe.get_new_label()
|
||||
|
||||
self.emit('JUMP', end_label)
|
||||
|
||||
# try block
|
||||
self.emit('LABEL', try_label)
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
self.emit(block)
|
||||
self.emit(
|
||||
'NOP'
|
||||
) # needed to distinguish from break/continue vs some internal jumps
|
||||
|
||||
# catch block
|
||||
self.emit('LABEL', catch_label)
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
if handler:
|
||||
self.emit(handler['body'])
|
||||
self.emit('NOP')
|
||||
|
||||
# finally block
|
||||
self.emit('LABEL', finally_label)
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
if finalizer:
|
||||
self.emit(finalizer)
|
||||
self.emit('NOP')
|
||||
|
||||
self.emit('LABEL', end_label)
|
||||
|
||||
# give life to the code
|
||||
self.emit('TRY_CATCH_FINALLY', try_label, catch_label,
|
||||
handler['param']['name'] if handler else None, finally_label,
|
||||
bool(finalizer), end_label)
|
||||
|
||||
def UnaryExpression(self, operator, argument, **kwargs):
|
||||
if operator == 'typeof' and argument[
|
||||
'type'] == 'Identifier': # todo fix typeof
|
||||
self.emit('TYPEOF', argument['name'])
|
||||
elif operator == 'delete':
|
||||
if argument['type'] == 'MemberExpression':
|
||||
self.emit(argument['object'])
|
||||
if argument['property']['type'] == 'Identifier':
|
||||
self.emit('LOAD_STRING',
|
||||
unicode(argument['property']['name']))
|
||||
else:
|
||||
self.emit(argument['property'])
|
||||
self.emit('DELETE_MEMBER')
|
||||
elif argument['type'] == 'Identifier':
|
||||
self.emit('DELETE', argument['name'])
|
||||
else:
|
||||
self.emit('LOAD_BOOLEAN', 1)
|
||||
elif operator in UNARY_OPERATIONS:
|
||||
self.emit(argument)
|
||||
self.emit('UNARY_OP', operator)
|
||||
else:
|
||||
raise MakeError('SyntaxError',
|
||||
'Unknown unary operator %s' % operator)
|
||||
|
||||
def UpdateExpression(self, operator, argument, prefix, **kwargs):
|
||||
incr = int(operator == "++")
|
||||
post = int(not prefix)
|
||||
if argument['type'] == 'MemberExpression':
|
||||
if argument['computed']:
|
||||
self.emit(argument['object'])
|
||||
self.emit(argument['property'])
|
||||
self.emit('POSTFIX_MEMBER', post, incr)
|
||||
else:
|
||||
self.emit(argument['object'])
|
||||
name = to_key(argument['property'])
|
||||
self.emit('POSTFIX_MEMBER_DOT', post, incr, name)
|
||||
elif argument['type'] == 'Identifier':
|
||||
name = to_key(argument)
|
||||
self.emit('POSTFIX', post, incr, name)
|
||||
else:
|
||||
raise MakeError('SyntaxError',
|
||||
'Invalid left-hand side in assignment')
|
||||
|
||||
def VariableDeclaration(self, declarations, kind, **kwargs):
|
||||
if kind != 'var':
|
||||
raise NotImplementedError(
|
||||
'Only var variable declaration is supported by ECMA 5.1')
|
||||
for d in declarations:
|
||||
self.emit(d)
|
||||
|
||||
def LexicalDeclaration(self, declarations, kind, **kwargs):
|
||||
raise NotImplementedError('Not supported by ECMA 5.1')
|
||||
|
||||
def VariableDeclarator(self, id, init, **kwargs):
|
||||
name = id['name']
|
||||
if name in SPECIAL_IDENTIFIERS:
|
||||
raise MakeError('Invalid left-hand side in assignment')
|
||||
self.declared_vars.append(name)
|
||||
if init is not None:
|
||||
self.emit(init)
|
||||
self.emit('STORE', name)
|
||||
self.emit('POP')
|
||||
|
||||
def WhileStatement(self, test, body, **kwargs):
|
||||
continue_label = self.exe.get_new_label()
|
||||
break_label = self.exe.get_new_label()
|
||||
|
||||
self.emit('LABEL', continue_label)
|
||||
self.emit(test)
|
||||
self.emit('JUMP_IF_FALSE', break_label)
|
||||
|
||||
# translate the body, remember to add and afterwards remove implicit break/continue labels
|
||||
|
||||
self.implicit_continues.append(continue_label)
|
||||
self.implicit_breaks.append(break_label)
|
||||
self.emit(body)
|
||||
self.implicit_continues.pop()
|
||||
self.implicit_breaks.pop()
|
||||
|
||||
self.emit('JUMP', continue_label) # loop back
|
||||
self.emit('LABEL', break_label)
|
||||
|
||||
def WithStatement(self, object, body, **kwargs):
|
||||
beg_label = self.exe.get_new_label()
|
||||
end_label = self.exe.get_new_label()
|
||||
# scope
|
||||
self.emit(object)
|
||||
|
||||
# now the body
|
||||
self.emit('JUMP', end_label)
|
||||
self.emit('LABEL', beg_label)
|
||||
self.emit('LOAD_UNDEFINED')
|
||||
self.emit(body)
|
||||
self.emit('NOP')
|
||||
self.emit('LABEL', end_label)
|
||||
|
||||
# with statement implementation
|
||||
self.emit('WITH', beg_label, end_label)
|
||||
|
||||
def _emit_statement_list(self, statements):
|
||||
for statement in statements:
|
||||
self.emit(statement)
|
||||
|
||||
def emit(self, what, *args):
|
||||
''' what can be either name of the op, or node, or a list of statements.'''
|
||||
if isinstance(what, basestring):
|
||||
return self.exe.emit(what, *args)
|
||||
elif isinstance(what, list):
|
||||
self._emit_statement_list(what)
|
||||
else:
|
||||
return getattr(self, what['type'])(**what)
|
||||
|
||||
|
||||
import os, codecs
|
||||
|
||||
|
||||
def path_as_local(path):
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
# relative to cwd
|
||||
return os.path.join(os.getcwd(), path)
|
||||
|
||||
|
||||
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 main():
|
||||
from space import Space
|
||||
import fill_space
|
||||
|
||||
from pyjsparser import parse
|
||||
import json
|
||||
a = ByteCodeGenerator(Code())
|
||||
|
||||
s = Space()
|
||||
fill_space.fill_space(s, a)
|
||||
|
||||
a.exe.space = s
|
||||
s.exe = a.exe
|
||||
con = get_file_contents('internals/esprima.js')
|
||||
d = parse(con + (
|
||||
''';JSON.stringify(exports.parse(%s), 4, 4)''' % json.dumps(con)))
|
||||
# d = parse('''
|
||||
# function x(n) {
|
||||
# log(n)
|
||||
# return x(n+1)
|
||||
# }
|
||||
# x(0)
|
||||
# ''')
|
||||
|
||||
# var v = 333333;
|
||||
# while (v) {
|
||||
# v--
|
||||
#
|
||||
# }
|
||||
a.emit(d)
|
||||
print(a.declared_vars)
|
||||
print(a.exe.tape)
|
||||
print(len(a.exe.tape))
|
||||
|
||||
a.exe.compile()
|
||||
|
||||
def log(this, args):
|
||||
print(args[0])
|
||||
return 999
|
||||
|
||||
print(a.exe.run(a.exe.space.GlobalObj))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,227 @@
|
||||
from .opcodes import *
|
||||
from .space import *
|
||||
from .base import *
|
||||
|
||||
|
||||
class Code:
|
||||
'''Can generate, store and run sequence of ops representing js code'''
|
||||
|
||||
def __init__(self, is_strict=False, debug_mode=False):
|
||||
self.tape = []
|
||||
self.compiled = False
|
||||
self.label_locs = None
|
||||
self.is_strict = is_strict
|
||||
self.debug_mode = debug_mode
|
||||
|
||||
self.contexts = []
|
||||
self.current_ctx = None
|
||||
self.return_locs = []
|
||||
self._label_count = 0
|
||||
self.label_locs = None
|
||||
|
||||
# useful references
|
||||
self.GLOBAL_THIS = None
|
||||
self.space = None
|
||||
|
||||
# dbg
|
||||
self.ctx_depth = 0
|
||||
|
||||
|
||||
def get_new_label(self):
|
||||
self._label_count += 1
|
||||
return self._label_count
|
||||
|
||||
def emit(self, op_code, *args):
|
||||
''' Adds op_code with specified args to tape '''
|
||||
self.tape.append(OP_CODES[op_code](*args))
|
||||
|
||||
def compile(self, start_loc=0):
|
||||
''' Records locations of labels and compiles the code '''
|
||||
self.label_locs = {} if self.label_locs is None else self.label_locs
|
||||
loc = start_loc
|
||||
while loc < len(self.tape):
|
||||
if type(self.tape[loc]) == LABEL:
|
||||
self.label_locs[self.tape[loc].num] = loc
|
||||
del self.tape[loc]
|
||||
continue
|
||||
loc += 1
|
||||
self.compiled = True
|
||||
|
||||
def _call(self, func, this, args):
|
||||
''' Calls a bytecode function func
|
||||
NOTE: use !ONLY! when calling functions from native methods! '''
|
||||
assert not func.is_native
|
||||
# fake call - the the runner to return to the end of the file
|
||||
old_contexts = self.contexts
|
||||
old_return_locs = self.return_locs
|
||||
old_curr_ctx = self.current_ctx
|
||||
|
||||
self.contexts = [FakeCtx()]
|
||||
self.return_locs = [len(self.tape)] # target line after return
|
||||
|
||||
# prepare my ctx
|
||||
my_ctx = func._generate_my_context(this, args)
|
||||
self.current_ctx = my_ctx
|
||||
|
||||
# execute dunction
|
||||
ret = self.run(my_ctx, starting_loc=self.label_locs[func.code])
|
||||
|
||||
# bring back old execution
|
||||
self.current_ctx = old_curr_ctx
|
||||
self.contexts = old_contexts
|
||||
self.return_locs = old_return_locs
|
||||
|
||||
return ret
|
||||
|
||||
def execute_fragment_under_context(self, ctx, start_label, end_label):
|
||||
''' just like run but returns if moved outside of the specified fragment
|
||||
# 4 different exectution results
|
||||
# 0=normal, 1=return, 2=jump_outside, 3=errors
|
||||
# execute_fragment_under_context returns:
|
||||
# (return_value, typ, return_value/jump_loc/py_error)
|
||||
# IMPARTANT: It is guaranteed that the length of the ctx.stack is unchanged.
|
||||
'''
|
||||
old_curr_ctx = self.current_ctx
|
||||
self.ctx_depth += 1
|
||||
old_stack_len = len(ctx.stack)
|
||||
old_ret_len = len(self.return_locs)
|
||||
old_ctx_len = len(self.contexts)
|
||||
try:
|
||||
self.current_ctx = ctx
|
||||
return self._execute_fragment_under_context(
|
||||
ctx, start_label, end_label)
|
||||
except JsException as err:
|
||||
if self.debug_mode:
|
||||
self._on_fragment_exit("js errors")
|
||||
# undo the things that were put on the stack (if any) to ensure a proper error recovery
|
||||
del ctx.stack[old_stack_len:]
|
||||
del self.return_locs[old_ret_len:]
|
||||
del self.contexts[old_ctx_len :]
|
||||
return undefined, 3, err
|
||||
finally:
|
||||
self.ctx_depth -= 1
|
||||
self.current_ctx = old_curr_ctx
|
||||
assert old_stack_len == len(ctx.stack)
|
||||
|
||||
def _get_dbg_indent(self):
|
||||
return self.ctx_depth * ' '
|
||||
|
||||
def _on_fragment_exit(self, mode):
|
||||
print(self._get_dbg_indent() + 'ctx exit (%s)' % mode)
|
||||
|
||||
def _execute_fragment_under_context(self, ctx, start_label, end_label):
|
||||
start, end = self.label_locs[start_label], self.label_locs[end_label]
|
||||
initial_len = len(ctx.stack)
|
||||
loc = start
|
||||
entry_level = len(self.contexts)
|
||||
# for e in self.tape[start:end]:
|
||||
# print e
|
||||
if self.debug_mode:
|
||||
print(self._get_dbg_indent() + 'ctx entry (from:%d, to:%d)' % (start, end))
|
||||
while loc < len(self.tape):
|
||||
if len(self.contexts) == entry_level and loc >= end:
|
||||
if self.debug_mode:
|
||||
self._on_fragment_exit('normal')
|
||||
assert loc == end
|
||||
delta_stack = len(ctx.stack) - initial_len
|
||||
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
|
||||
return ctx.stack.pop(), 0, None # means normal return
|
||||
|
||||
# execute instruction
|
||||
if self.debug_mode:
|
||||
print(self._get_dbg_indent() + str(loc), self.tape[loc])
|
||||
status = self.tape[loc].eval(ctx)
|
||||
|
||||
# check status for special actions
|
||||
if status is not None:
|
||||
if type(status) == int: # jump to label
|
||||
loc = self.label_locs[status]
|
||||
if len(self.contexts) == entry_level:
|
||||
# check if jumped outside of the fragment and break if so
|
||||
if not start <= loc < end:
|
||||
if self.debug_mode:
|
||||
self._on_fragment_exit('jump outside loc:%d label:%d' % (loc, status))
|
||||
delta_stack = len(ctx.stack) - initial_len
|
||||
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
|
||||
return ctx.stack.pop(), 2, status # jump outside
|
||||
continue
|
||||
|
||||
elif len(status) == 2: # a call or a return!
|
||||
# call: (new_ctx, func_loc_label_num)
|
||||
if status[0] is not None:
|
||||
# append old state to the stack
|
||||
self.contexts.append(ctx)
|
||||
self.return_locs.append(loc + 1)
|
||||
# set new state
|
||||
loc = self.label_locs[status[1]]
|
||||
ctx = status[0]
|
||||
self.current_ctx = ctx
|
||||
continue
|
||||
|
||||
# return: (None, None)
|
||||
else:
|
||||
if len(self.contexts) == entry_level:
|
||||
if self.debug_mode:
|
||||
self._on_fragment_exit('return')
|
||||
delta_stack = len(ctx.stack) - initial_len
|
||||
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
|
||||
return undefined, 1, ctx.stack.pop(
|
||||
) # return signal
|
||||
return_value = ctx.stack.pop()
|
||||
ctx = self.contexts.pop()
|
||||
self.current_ctx = ctx
|
||||
ctx.stack.append(return_value)
|
||||
|
||||
loc = self.return_locs.pop()
|
||||
continue
|
||||
# next instruction
|
||||
loc += 1
|
||||
if self.debug_mode:
|
||||
self._on_fragment_exit('internal error - unexpected end of tape, will crash')
|
||||
assert False, 'Remember to add NOP at the end!'
|
||||
|
||||
def run(self, ctx, starting_loc=0):
|
||||
loc = starting_loc
|
||||
self.current_ctx = ctx
|
||||
while loc < len(self.tape):
|
||||
# execute instruction
|
||||
if self.debug_mode:
|
||||
print(loc, self.tape[loc])
|
||||
status = self.tape[loc].eval(ctx)
|
||||
|
||||
# check status for special actions
|
||||
if status is not None:
|
||||
if type(status) == int: # jump to label
|
||||
loc = self.label_locs[status]
|
||||
continue
|
||||
|
||||
elif len(status) == 2: # a call or a return!
|
||||
# call: (new_ctx, func_loc_label_num)
|
||||
if status[0] is not None:
|
||||
# append old state to the stack
|
||||
self.contexts.append(ctx)
|
||||
self.return_locs.append(loc + 1)
|
||||
# set new state
|
||||
loc = self.label_locs[status[1]]
|
||||
ctx = status[0]
|
||||
self.current_ctx = ctx
|
||||
continue
|
||||
|
||||
# return: (None, None)
|
||||
else:
|
||||
return_value = ctx.stack.pop()
|
||||
ctx = self.contexts.pop()
|
||||
self.current_ctx = ctx
|
||||
ctx.stack.append(return_value)
|
||||
|
||||
loc = self.return_locs.pop()
|
||||
continue
|
||||
# next instruction
|
||||
loc += 1
|
||||
assert len(ctx.stack) == 1, ctx.stack
|
||||
return ctx.stack.pop()
|
||||
|
||||
|
||||
class FakeCtx(object):
|
||||
def __init__(self):
|
||||
self.stack = []
|
||||
@@ -0,0 +1 @@
|
||||
__author__ = 'Piotr Dabkowski'
|
||||
@@ -0,0 +1,28 @@
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
|
||||
def Array(this, args):
|
||||
return ArrayConstructor(args, args.space)
|
||||
|
||||
|
||||
def ArrayConstructor(args, space):
|
||||
if len(args) == 1:
|
||||
l = get_arg(args, 0)
|
||||
if type(l) == float:
|
||||
if to_uint32(l) == l:
|
||||
return space.NewArray(l)
|
||||
else:
|
||||
raise MakeError(
|
||||
'RangeError',
|
||||
'Invalid length specified for Array constructor (must be uint32)'
|
||||
)
|
||||
else:
|
||||
return space.ConstructArray([l])
|
||||
else:
|
||||
return space.ConstructArray(list(args))
|
||||
|
||||
|
||||
def isArray(this, args):
|
||||
x = get_arg(args, 0)
|
||||
return is_object(x) and x.Class == u'Array'
|
||||
@@ -0,0 +1,14 @@
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
|
||||
def Boolean(this, args):
|
||||
return to_boolean(get_arg(args, 0))
|
||||
|
||||
|
||||
def BooleanConstructor(args, space):
|
||||
temp = space.NewObject()
|
||||
temp.prototype = space.BooleanPrototype
|
||||
temp.Class = 'Boolean'
|
||||
temp.value = to_boolean(get_arg(args, 0))
|
||||
return temp
|
||||
@@ -0,0 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from js2py.internals.conversions import *
|
||||
from js2py.internals.func_utils import *
|
||||
|
||||
|
||||
class ConsoleMethods:
|
||||
def log(this, args):
|
||||
x = ' '.join(to_string(e) for e in args)
|
||||
print(x)
|
||||
return undefined
|
||||
@@ -0,0 +1,405 @@
|
||||
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:
|
||||
try:
|
||||
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
except:
|
||||
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ")
|
||||
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
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
from ..base import *
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
from pyjsparser import parse
|
||||
from ..byte_trans import ByteCodeGenerator, Code
|
||||
|
||||
|
||||
def Function(this, args):
|
||||
# convert arguments to python list of strings
|
||||
a = map(to_string, tuple(args))
|
||||
_body = u';'
|
||||
_args = ()
|
||||
if len(a):
|
||||
_body = u'%s;' % a[-1]
|
||||
_args = a[:-1]
|
||||
return executable_function(_body, _args, args.space, global_context=True)
|
||||
|
||||
|
||||
def executable_function(_body, _args, space, global_context=True):
|
||||
func_str = u'(function (%s) { ; %s ; });' % (u', '.join(_args), _body)
|
||||
|
||||
co = executable_code(
|
||||
code_str=func_str, space=space, global_context=global_context)
|
||||
return co()
|
||||
|
||||
|
||||
# you can use this one lovely piece of function to compile and execute code on the fly! Watch out though as it may generate lots of code.
|
||||
# todo tape cleanup? we dont know which pieces are needed and which are not so rather impossible without smarter machinery something like GC,
|
||||
# a one solution would be to have a separate tape for functions
|
||||
def executable_code(code_str, space, global_context=True):
|
||||
# parse first to check if any SyntaxErrors
|
||||
parsed = parse(code_str)
|
||||
|
||||
old_tape_len = len(space.byte_generator.exe.tape)
|
||||
space.byte_generator.record_state()
|
||||
start = space.byte_generator.exe.get_new_label()
|
||||
skip = space.byte_generator.exe.get_new_label()
|
||||
space.byte_generator.emit('JUMP', skip)
|
||||
space.byte_generator.emit('LABEL', start)
|
||||
space.byte_generator.emit(parsed)
|
||||
space.byte_generator.emit('NOP')
|
||||
space.byte_generator.emit('LABEL', skip)
|
||||
space.byte_generator.emit('NOP')
|
||||
space.byte_generator.restore_state()
|
||||
|
||||
space.byte_generator.exe.compile(
|
||||
start_loc=old_tape_len
|
||||
) # dont read the code from the beginning, dont be stupid!
|
||||
|
||||
ctx = space.GlobalObj if global_context else space.exe.current_ctx
|
||||
|
||||
def ex_code():
|
||||
ret, status, token = space.byte_generator.exe.execute_fragment_under_context(
|
||||
ctx, start, skip)
|
||||
# todo Clean up the tape!
|
||||
# this is NOT a way to do that because the fragment may contain the executable code! We dont want to remove it
|
||||
#del space.byte_generator.exe.tape[old_tape_len:]
|
||||
if status == 0:
|
||||
return ret
|
||||
elif status == 3:
|
||||
raise token
|
||||
else:
|
||||
raise RuntimeError(
|
||||
'Unexpected return status during JIT execution: %d' % status)
|
||||
|
||||
return ex_code
|
||||
|
||||
|
||||
def _eval(this, args):
|
||||
code_str = to_string(get_arg(args, 0))
|
||||
return executable_code(code_str, args.space, global_context=True)()
|
||||
|
||||
|
||||
def log(this, args):
|
||||
print(' '.join(map(to_string, args)))
|
||||
return undefined
|
||||
@@ -0,0 +1,157 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
import math
|
||||
import random
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
class MathFunctions:
|
||||
def abs(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
return abs(a)
|
||||
|
||||
def acos(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return math.acos(a)
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def asin(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return math.asin(a)
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def atan(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
return math.atan(a)
|
||||
|
||||
def atan2(this, args):
|
||||
x = get_arg(args, 0)
|
||||
y = get_arg(args, 1)
|
||||
a = to_number(x)
|
||||
b = to_number(y)
|
||||
if a != a or b != b: # it must be a nan
|
||||
return NaN
|
||||
return math.atan2(a, b)
|
||||
|
||||
def ceil(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
return float(math.ceil(a))
|
||||
|
||||
def floor(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
return float(math.floor(a))
|
||||
|
||||
def round(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
return float(round(a))
|
||||
|
||||
def sin(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if not is_finite(a): # it must be a nan
|
||||
return NaN
|
||||
return math.sin(a)
|
||||
|
||||
def cos(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if not is_finite(a): # it must be a nan
|
||||
return NaN
|
||||
return math.cos(a)
|
||||
|
||||
def tan(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if not is_finite(a): # it must be a nan
|
||||
return NaN
|
||||
return math.tan(a)
|
||||
|
||||
def log(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return math.log(a)
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def exp(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
return math.exp(a)
|
||||
|
||||
def pow(this, args):
|
||||
x = get_arg(args, 0)
|
||||
y = get_arg(args, 1)
|
||||
a = to_number(x)
|
||||
b = to_number(y)
|
||||
if a != a or b != b: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return a**b
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def sqrt(this, args):
|
||||
x = get_arg(args, 0)
|
||||
a = to_number(x)
|
||||
if a != a: # it must be a nan
|
||||
return NaN
|
||||
try:
|
||||
return a**0.5
|
||||
except:
|
||||
return NaN
|
||||
|
||||
def min(this, args):
|
||||
if len(args) == 0:
|
||||
return Infinity
|
||||
return min(map(to_number, tuple(args)))
|
||||
|
||||
def max(this, args):
|
||||
if len(args) == 0:
|
||||
return -Infinity
|
||||
return max(map(to_number, tuple(args)))
|
||||
|
||||
def random(this, args):
|
||||
return random.random()
|
||||
@@ -0,0 +1,27 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
|
||||
def Number(this, args):
|
||||
if len(args) == 0:
|
||||
return 0.
|
||||
return to_number(args[0])
|
||||
|
||||
|
||||
def NumberConstructor(args, space):
|
||||
temp = space.NewObject()
|
||||
temp.prototype = space.NumberPrototype
|
||||
temp.Class = 'Number'
|
||||
temp.value = float(to_number(get_arg(args, 0)) if len(args) > 0 else 0.)
|
||||
return temp
|
||||
|
||||
|
||||
CONSTS = {
|
||||
'MAX_VALUE': 1.7976931348623157e308,
|
||||
'MIN_VALUE': 5.0e-324,
|
||||
'NaN': NaN,
|
||||
'NEGATIVE_INFINITY': Infinity,
|
||||
'POSITIVE_INFINITY': -Infinity
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
from __future__ import unicode_literals
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
from ..base import is_data_descriptor
|
||||
import six
|
||||
|
||||
|
||||
def Object(this, args):
|
||||
val = get_arg(args, 0)
|
||||
if is_null(val) or is_undefined(val):
|
||||
return args.space.NewObject()
|
||||
return to_object(val, args.space)
|
||||
|
||||
|
||||
def ObjectCreate(args, space):
|
||||
if len(args):
|
||||
val = get_arg(args, 0)
|
||||
if is_object(val):
|
||||
# Implementation dependent, but my will simply return :)
|
||||
return val
|
||||
elif type(val) in (NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE):
|
||||
return to_object(val, space)
|
||||
return space.NewObject()
|
||||
|
||||
|
||||
class ObjectMethods:
|
||||
def getPrototypeOf(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError',
|
||||
'Object.getPrototypeOf called on non-object')
|
||||
return null if obj.prototype is None else obj.prototype
|
||||
|
||||
def getOwnPropertyDescriptor(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
prop = get_arg(args, 1)
|
||||
if not is_object(obj):
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Object.getOwnPropertyDescriptor called on non-object')
|
||||
desc = obj.own.get(to_string(prop))
|
||||
return convert_to_js_type(desc, args.space)
|
||||
|
||||
def getOwnPropertyNames(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Object.getOwnPropertyDescriptor called on non-object')
|
||||
return args.space.ConstructArray(obj.own.keys())
|
||||
|
||||
def create(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not (is_object(obj) or is_null(obj)):
|
||||
raise MakeError('TypeError',
|
||||
'Object prototype may only be an Object or null')
|
||||
temp = args.space.NewObject()
|
||||
temp.prototype = None if is_null(obj) else obj
|
||||
if len(args) > 1 and not is_undefined(args[1]):
|
||||
if six.PY2:
|
||||
args.tup = (args[1], )
|
||||
ObjectMethods.defineProperties.__func__(temp, args)
|
||||
else:
|
||||
args.tup = (args[1], )
|
||||
ObjectMethods.defineProperties(temp, args)
|
||||
return temp
|
||||
|
||||
def defineProperty(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
prop = get_arg(args, 1)
|
||||
attrs = get_arg(args, 2)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError',
|
||||
'Object.defineProperty called on non-object')
|
||||
name = to_string(prop)
|
||||
if not obj.define_own_property(name, ToPropertyDescriptor(attrs),
|
||||
False):
|
||||
raise MakeError('TypeError', 'Cannot redefine property: %s' % name)
|
||||
return obj
|
||||
|
||||
def defineProperties(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
properties = get_arg(args, 1)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError',
|
||||
'Object.defineProperties called on non-object')
|
||||
props = to_object(properties, args.space)
|
||||
for k, v in props.own.items():
|
||||
if not v.get('enumerable'):
|
||||
continue
|
||||
desc = ToPropertyDescriptor(props.get(unicode(k)))
|
||||
if not obj.define_own_property(unicode(k), desc, False):
|
||||
raise MakeError('TypeError',
|
||||
'Failed to define own property: %s' % k)
|
||||
return obj
|
||||
|
||||
def seal(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
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(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
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(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError',
|
||||
'Object.preventExtensions on non-object')
|
||||
obj.extensible = False
|
||||
return obj
|
||||
|
||||
def isSealed(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError',
|
||||
'Object.isSealed called on non-object')
|
||||
if obj.extensible:
|
||||
return False
|
||||
for desc in obj.own.values():
|
||||
if desc.get('configurable'):
|
||||
return False
|
||||
return True
|
||||
|
||||
def isFrozen(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError',
|
||||
'Object.isFrozen called on non-object')
|
||||
if obj.extensible:
|
||||
return False
|
||||
for desc in obj.own.values():
|
||||
if desc.get('configurable'):
|
||||
return False
|
||||
if is_data_descriptor(desc) and desc.get('writable'):
|
||||
return False
|
||||
return True
|
||||
|
||||
def isExtensible(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError',
|
||||
'Object.isExtensible called on non-object')
|
||||
return obj.extensible
|
||||
|
||||
def keys(this, args):
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError', 'Object.keys called on non-object')
|
||||
return args.space.ConstructArray([
|
||||
unicode(e) for e, d in six.iteritems(obj.own)
|
||||
if d.get('enumerable')
|
||||
])
|
||||
|
||||
|
||||
# some utility functions:
|
||||
|
||||
|
||||
def ToPropertyDescriptor(obj): # page 38 (50 absolute)
|
||||
if not is_object(obj):
|
||||
raise MakeError('TypeError',
|
||||
'Can\'t convert non-object to property descriptor')
|
||||
desc = {}
|
||||
if obj.has_property('enumerable'):
|
||||
desc['enumerable'] = to_boolean(obj.get('enumerable'))
|
||||
if obj.has_property('configurable'):
|
||||
desc['configurable'] = to_boolean(obj.get('configurable'))
|
||||
if obj.has_property('value'):
|
||||
desc['value'] = obj.get('value')
|
||||
if obj.has_property('writable'):
|
||||
desc['writable'] = to_boolean(obj.get('writable'))
|
||||
if obj.has_property('get'):
|
||||
cand = obj.get('get')
|
||||
if not (is_undefined(cand) or is_callable(cand)):
|
||||
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 (is_undefined(cand) or is_callable(cand)):
|
||||
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
|
||||
@@ -0,0 +1,41 @@
|
||||
from __future__ import unicode_literals
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
from ..base import SpaceTuple
|
||||
|
||||
REG_EXP_FLAGS = ('g', 'i', 'm')
|
||||
|
||||
|
||||
def RegExp(this, args):
|
||||
pattern = get_arg(args, 0)
|
||||
flags = get_arg(args, 1)
|
||||
if GetClass(pattern) == 'RegExp':
|
||||
if not is_undefined(flags):
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Cannot supply flags when constructing one RegExp from another'
|
||||
)
|
||||
# return unchanged
|
||||
return pattern
|
||||
#pattern is not a regexp
|
||||
if is_undefined(pattern):
|
||||
pattern = u''
|
||||
else:
|
||||
pattern = to_string(pattern)
|
||||
flags = to_string(flags) if not is_undefined(flags) else u''
|
||||
for flag in flags:
|
||||
if flag not in REG_EXP_FLAGS:
|
||||
raise MakeError(
|
||||
'SyntaxError',
|
||||
'Invalid flags supplied to RegExp constructor "%s"' % flag)
|
||||
if len(set(flags)) != len(flags):
|
||||
raise MakeError(
|
||||
'SyntaxError',
|
||||
'Invalid flags supplied to RegExp constructor "%s"' % flags)
|
||||
return args.space.NewRegExp(pattern, flags)
|
||||
|
||||
|
||||
def RegExpCreate(args, space):
|
||||
_args = SpaceTuple(args)
|
||||
_args.space = space
|
||||
return RegExp(undefined, _args)
|
||||
@@ -0,0 +1,23 @@
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
|
||||
def fromCharCode(this, args):
|
||||
res = u''
|
||||
for e in args:
|
||||
res += unichr(to_uint16(e))
|
||||
return res
|
||||
|
||||
|
||||
def String(this, args):
|
||||
if len(args) == 0:
|
||||
return u''
|
||||
return to_string(args[0])
|
||||
|
||||
|
||||
def StringConstructor(args, space):
|
||||
temp = space.NewObject()
|
||||
temp.prototype = space.StringPrototype
|
||||
temp.Class = 'String'
|
||||
temp.value = to_string(get_arg(args, 0)) if len(args) > 0 else u''
|
||||
return temp
|
||||
@@ -0,0 +1,209 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# 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)
|
||||
@@ -0,0 +1,141 @@
|
||||
from __future__ import unicode_literals
|
||||
# Type Conversions. to_type. All must return PyJs subclass instance
|
||||
from .simplex import *
|
||||
|
||||
|
||||
def to_primitive(self, hint=None):
|
||||
if is_primitive(self):
|
||||
return self
|
||||
if hint is None and (self.Class == 'Number' or self.Class == 'Boolean'):
|
||||
# favour number for Class== Number or Boolean default = String
|
||||
hint = 'Number'
|
||||
return self.default_value(hint)
|
||||
|
||||
|
||||
def to_boolean(self):
|
||||
typ = Type(self)
|
||||
if typ == 'Boolean': # no need to convert
|
||||
return self
|
||||
elif typ == 'Null' or typ == 'Undefined': # they are both always false
|
||||
return False
|
||||
elif typ == 'Number': # false only for 0, and NaN
|
||||
return self and self == self # test for nan (nan -> flase)
|
||||
elif typ == 'String':
|
||||
return bool(self)
|
||||
else: # object - always True
|
||||
return True
|
||||
|
||||
|
||||
def to_number(self):
|
||||
typ = Type(self)
|
||||
if typ == 'Number': # or self.Class=='Number': # no need to convert
|
||||
return self
|
||||
elif typ == 'Null': # null is 0
|
||||
return 0.
|
||||
elif typ == 'Undefined': # undefined is NaN
|
||||
return NaN
|
||||
elif typ == 'Boolean': # 1 for True 0 for false
|
||||
return float(self)
|
||||
elif typ == 'String':
|
||||
s = self.strip() # Strip white space
|
||||
if not s: # '' is simply 0
|
||||
return 0.
|
||||
if 'x' in s or 'X' in s[:3]: # hex (positive only)
|
||||
try: # try to convert
|
||||
num = int(s, 16)
|
||||
except ValueError: # could not convert -> NaN
|
||||
return NaN
|
||||
return float(num)
|
||||
sign = 1 # get sign
|
||||
if s[0] in '+-':
|
||||
if s[0] == '-':
|
||||
sign = -1
|
||||
s = s[1:]
|
||||
if s == 'Infinity': # Check for infinity keyword. 'NaN' will be NaN anyway.
|
||||
return sign * Infinity
|
||||
try: # decimal try
|
||||
num = sign * float(s) # Converted
|
||||
except ValueError:
|
||||
return NaN # could not convert to decimal > return NaN
|
||||
return float(num)
|
||||
else: # object - most likely it will be NaN.
|
||||
return to_number(to_primitive(self, 'Number'))
|
||||
|
||||
|
||||
def to_string(self):
|
||||
typ = Type(self)
|
||||
if typ == 'String':
|
||||
return self
|
||||
elif typ == 'Null':
|
||||
return 'null'
|
||||
elif typ == 'Undefined':
|
||||
return 'undefined'
|
||||
elif typ == 'Boolean':
|
||||
return 'true' if self else 'false'
|
||||
elif typ == 'Number': # or self.Class=='Number':
|
||||
return js_dtoa(self)
|
||||
else: # object
|
||||
return to_string(to_primitive(self, 'String'))
|
||||
|
||||
|
||||
def to_object(self, space):
|
||||
typ = Type(self)
|
||||
if typ == 'Object':
|
||||
return self
|
||||
elif typ == 'Boolean': # Unsure ... todo check here
|
||||
return space.Boolean.create((self, ), space)
|
||||
elif typ == 'Number': # ?
|
||||
return space.Number.create((self, ), space)
|
||||
elif typ == 'String': # ?
|
||||
return space.String.create((self, ), space)
|
||||
elif typ == 'Null' or typ == 'Undefined':
|
||||
raise MakeError('TypeError',
|
||||
'undefined or null can\'t be converted to object')
|
||||
else:
|
||||
raise RuntimeError()
|
||||
|
||||
|
||||
def to_int32(self):
|
||||
num = to_number(self)
|
||||
if is_nan(num) or is_infinity(num):
|
||||
return 0
|
||||
int32 = int(num) % 2**32
|
||||
return int(int32 - 2**32 if int32 >= 2**31 else int32)
|
||||
|
||||
|
||||
def to_int(self):
|
||||
num = to_number(self)
|
||||
if is_nan(num):
|
||||
return 0
|
||||
elif is_infinity(num):
|
||||
return 10**20 if num > 0 else -10**20
|
||||
return int(num)
|
||||
|
||||
|
||||
def to_uint32(self):
|
||||
num = to_number(self)
|
||||
if is_nan(num) or is_infinity(num):
|
||||
return 0
|
||||
return int(num) % 2**32
|
||||
|
||||
|
||||
def to_uint16(self):
|
||||
num = to_number(self)
|
||||
if is_nan(num) or is_infinity(num):
|
||||
return 0
|
||||
return int(num) % 2**16
|
||||
|
||||
|
||||
def to_int16(self):
|
||||
num = to_number(self)
|
||||
if is_nan(num) or is_infinity(num):
|
||||
return 0
|
||||
int16 = int(num) % 2**16
|
||||
return int(int16 - 2**16 if int16 >= 2**15 else int16)
|
||||
|
||||
|
||||
def cok(self):
|
||||
"""Check object coercible"""
|
||||
if type(self) in (UNDEFINED_TYPE, NULL_TYPE):
|
||||
raise MakeError('TypeError',
|
||||
'undefined or null can\'t be converted to object')
|
||||
@@ -0,0 +1,90 @@
|
||||
# todo make sure what they mean by desc undefined? None or empty? Answer: None :) it can never be empty but None is sometimes returned.
|
||||
|
||||
# I am implementing everything as dicts to speed up property creation
|
||||
|
||||
# Warning: value, get, set props of dest are PyJs types. Rest is Py!
|
||||
|
||||
|
||||
def is_data_descriptor(desc):
|
||||
return desc and ('value' in desc or 'writable' in desc)
|
||||
|
||||
|
||||
def is_accessor_descriptor(desc):
|
||||
return desc and ('get' in desc or 'set' in desc)
|
||||
|
||||
|
||||
def is_generic_descriptor(
|
||||
desc
|
||||
): # generic means not the data and not the setter - therefore it must be one that changes only enum and conf
|
||||
return desc and not (is_data_descriptor(desc)
|
||||
or is_accessor_descriptor(desc))
|
||||
|
||||
|
||||
def from_property_descriptor(desc, space):
|
||||
if not desc:
|
||||
return {}
|
||||
obj = space.NewObject()
|
||||
if is_data_descriptor(desc):
|
||||
obj.define_own_property(
|
||||
'value', {
|
||||
'value': desc['value'],
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
obj.define_own_property(
|
||||
'writable', {
|
||||
'value': desc['writable'],
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
else:
|
||||
obj.define_own_property(
|
||||
'get', {
|
||||
'value': desc['get'],
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
obj.define_own_property(
|
||||
'set', {
|
||||
'value': desc['set'],
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
obj.define_own_property(
|
||||
'writable', {
|
||||
'value': desc['writable'],
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
obj.define_own_property(
|
||||
'enumerable', {
|
||||
'value': desc['enumerable'],
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
return obj
|
||||
|
||||
|
||||
def to_property_descriptor(obj):
|
||||
if obj._type() != 'Object':
|
||||
raise TypeError()
|
||||
desc = {}
|
||||
for e in ('enumerable', 'configurable', 'writable'):
|
||||
if obj.has_property(e):
|
||||
desc[e] = obj.get(e).to_boolean().value
|
||||
if obj.has_property('value'):
|
||||
desc['value'] = obj.get('value')
|
||||
for e in ('get', 'set'):
|
||||
if obj.has_property(e):
|
||||
cand = obj.get(e)
|
||||
if not (cand.is_callable() or cand.is_undefined()):
|
||||
raise TypeError()
|
||||
if ('get' in desc or 'set' in desc) and ('value' in desc
|
||||
or 'writable' in desc):
|
||||
raise TypeError()
|
||||
@@ -0,0 +1,281 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .base import Scope
|
||||
from .func_utils import *
|
||||
from .conversions import *
|
||||
import six
|
||||
from .prototypes.jsboolean import BooleanPrototype
|
||||
from .prototypes.jserror import ErrorPrototype
|
||||
from .prototypes.jsfunction import FunctionPrototype
|
||||
from .prototypes.jsnumber import NumberPrototype
|
||||
from .prototypes.jsobject import ObjectPrototype
|
||||
from .prototypes.jsregexp import RegExpPrototype
|
||||
from .prototypes.jsstring import StringPrototype
|
||||
from .prototypes.jsarray import ArrayPrototype
|
||||
from .prototypes import jsjson
|
||||
from .prototypes import jsutils
|
||||
|
||||
from .constructors import jsnumber, jsstring, jsarray, jsboolean, jsregexp, jsmath, jsobject, jsfunction, jsconsole
|
||||
|
||||
|
||||
|
||||
def fill_proto(proto, proto_class, space):
|
||||
for i in dir(proto_class):
|
||||
e = getattr(proto_class, i)
|
||||
if six.PY2:
|
||||
if hasattr(e, '__func__'):
|
||||
meth = e.__func__
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
if hasattr(e, '__call__') and not i.startswith('__'):
|
||||
meth = e
|
||||
else:
|
||||
continue
|
||||
meth_name = meth.__name__.strip('_') # RexExp._exec -> RegExp.exec
|
||||
js_meth = space.NewFunction(meth, space.ctx, (), meth_name, False, ())
|
||||
set_non_enumerable(proto, meth_name, js_meth)
|
||||
return proto
|
||||
|
||||
|
||||
def easy_func(f, space):
|
||||
return space.NewFunction(f, space.ctx, (), f.__name__, False, ())
|
||||
|
||||
|
||||
def Empty(this, args):
|
||||
return undefined
|
||||
|
||||
|
||||
def set_non_enumerable(obj, name, prop):
|
||||
obj.define_own_property(
|
||||
unicode(name), {
|
||||
'value': prop,
|
||||
'writable': True,
|
||||
'enumerable': False,
|
||||
'configurable': True
|
||||
}, True)
|
||||
|
||||
|
||||
def set_protected(obj, name, prop):
|
||||
obj.define_own_property(
|
||||
unicode(name), {
|
||||
'value': prop,
|
||||
'writable': False,
|
||||
'enumerable': False,
|
||||
'configurable': False
|
||||
}, True)
|
||||
|
||||
|
||||
def fill_space(space, byte_generator):
|
||||
# set global scope
|
||||
global_scope = Scope({}, space, parent=None)
|
||||
global_scope.THIS_BINDING = global_scope
|
||||
global_scope.registers(byte_generator.declared_vars)
|
||||
space.GlobalObj = global_scope
|
||||
|
||||
space.byte_generator = byte_generator
|
||||
|
||||
# first init all protos, later take care of constructors and details
|
||||
|
||||
# Function must be first obviously, we have to use a small trick to do that...
|
||||
function_proto = space.NewFunction(Empty, space.ctx, (), 'Empty', False,
|
||||
())
|
||||
space.FunctionPrototype = function_proto # this will fill the prototypes of the methods!
|
||||
fill_proto(function_proto, FunctionPrototype, space)
|
||||
|
||||
# Object next
|
||||
object_proto = space.NewObject() # no proto
|
||||
fill_proto(object_proto, ObjectPrototype, space)
|
||||
space.ObjectPrototype = object_proto
|
||||
function_proto.prototype = object_proto
|
||||
|
||||
# Number
|
||||
number_proto = space.NewObject()
|
||||
number_proto.prototype = object_proto
|
||||
fill_proto(number_proto, NumberPrototype, space)
|
||||
number_proto.value = 0.
|
||||
number_proto.Class = 'Number'
|
||||
space.NumberPrototype = number_proto
|
||||
|
||||
# String
|
||||
string_proto = space.NewObject()
|
||||
string_proto.prototype = object_proto
|
||||
fill_proto(string_proto, StringPrototype, space)
|
||||
string_proto.value = u''
|
||||
string_proto.Class = 'String'
|
||||
space.StringPrototype = string_proto
|
||||
|
||||
# Boolean
|
||||
boolean_proto = space.NewObject()
|
||||
boolean_proto.prototype = object_proto
|
||||
fill_proto(boolean_proto, BooleanPrototype, space)
|
||||
boolean_proto.value = False
|
||||
boolean_proto.Class = 'Boolean'
|
||||
space.BooleanPrototype = boolean_proto
|
||||
|
||||
# Array
|
||||
array_proto = space.NewArray(0)
|
||||
array_proto.prototype = object_proto
|
||||
fill_proto(array_proto, ArrayPrototype, space)
|
||||
space.ArrayPrototype = array_proto
|
||||
|
||||
# JSON
|
||||
json = space.NewObject()
|
||||
json.put(u'stringify', easy_func(jsjson.stringify, space))
|
||||
json.put(u'parse', easy_func(jsjson.parse, space))
|
||||
|
||||
# Utils
|
||||
parseFloat = easy_func(jsutils.parseFloat, space)
|
||||
parseInt = easy_func(jsutils.parseInt, space)
|
||||
isNaN = easy_func(jsutils.isNaN, space)
|
||||
isFinite = easy_func(jsutils.isFinite, space)
|
||||
|
||||
# Error
|
||||
error_proto = space.NewError(u'Error', u'')
|
||||
error_proto.prototype = object_proto
|
||||
error_proto.put(u'name', u'Error')
|
||||
fill_proto(error_proto, ErrorPrototype, space)
|
||||
space.ErrorPrototype = error_proto
|
||||
|
||||
def construct_constructor(typ):
|
||||
def creator(this, args):
|
||||
message = get_arg(args, 0)
|
||||
if not is_undefined(message):
|
||||
msg = to_string(message)
|
||||
else:
|
||||
msg = u''
|
||||
return space.NewError(typ, msg)
|
||||
|
||||
j = easy_func(creator, space)
|
||||
j.name = unicode(typ)
|
||||
|
||||
set_protected(j, 'prototype', space.ERROR_TYPES[typ])
|
||||
|
||||
set_non_enumerable(space.ERROR_TYPES[typ], 'constructor', j)
|
||||
|
||||
def new_create(args, space):
|
||||
message = get_arg(args, 0)
|
||||
if not is_undefined(message):
|
||||
msg = to_string(message)
|
||||
else:
|
||||
msg = u''
|
||||
return space.NewError(typ, msg)
|
||||
|
||||
j.create = new_create
|
||||
return j
|
||||
|
||||
# fill remaining error types
|
||||
error_constructors = {}
|
||||
for err_type_name in (u'Error', u'EvalError', u'RangeError',
|
||||
u'ReferenceError', u'SyntaxError', u'TypeError',
|
||||
u'URIError'):
|
||||
extra_err = space.NewError(u'Error', u'')
|
||||
extra_err.put(u'name', err_type_name)
|
||||
setattr(space, err_type_name + u'Prototype', extra_err)
|
||||
error_constructors[err_type_name] = construct_constructor(
|
||||
err_type_name)
|
||||
|
||||
assert space.TypeErrorPrototype is not None
|
||||
|
||||
# RegExp
|
||||
regexp_proto = space.NewRegExp(u'(?:)', u'')
|
||||
regexp_proto.prototype = object_proto
|
||||
fill_proto(regexp_proto, RegExpPrototype, space)
|
||||
space.RegExpPrototype = regexp_proto
|
||||
|
||||
# Json
|
||||
|
||||
# now all these boring constructors...
|
||||
|
||||
# Number
|
||||
number = easy_func(jsnumber.Number, space)
|
||||
space.Number = number
|
||||
number.create = jsnumber.NumberConstructor
|
||||
set_non_enumerable(number_proto, 'constructor', number)
|
||||
set_protected(number, 'prototype', number_proto)
|
||||
# number has some extra constants
|
||||
for k, v in jsnumber.CONSTS.items():
|
||||
set_protected(number, k, v)
|
||||
|
||||
# String
|
||||
string = easy_func(jsstring.String, space)
|
||||
space.String = string
|
||||
string.create = jsstring.StringConstructor
|
||||
set_non_enumerable(string_proto, 'constructor', string)
|
||||
set_protected(string, 'prototype', string_proto)
|
||||
# string has an extra function
|
||||
set_non_enumerable(string, 'fromCharCode',
|
||||
easy_func(jsstring.fromCharCode, space))
|
||||
|
||||
# Boolean
|
||||
boolean = easy_func(jsboolean.Boolean, space)
|
||||
space.Boolean = boolean
|
||||
boolean.create = jsboolean.BooleanConstructor
|
||||
set_non_enumerable(boolean_proto, 'constructor', boolean)
|
||||
set_protected(boolean, 'prototype', boolean_proto)
|
||||
|
||||
# Array
|
||||
array = easy_func(jsarray.Array, space)
|
||||
space.Array = array
|
||||
array.create = jsarray.ArrayConstructor
|
||||
set_non_enumerable(array_proto, 'constructor', array)
|
||||
set_protected(array, 'prototype', array_proto)
|
||||
array.put(u'isArray', easy_func(jsarray.isArray, space))
|
||||
|
||||
# RegExp
|
||||
regexp = easy_func(jsregexp.RegExp, space)
|
||||
space.RegExp = regexp
|
||||
regexp.create = jsregexp.RegExpCreate
|
||||
set_non_enumerable(regexp_proto, 'constructor', regexp)
|
||||
set_protected(regexp, 'prototype', regexp_proto)
|
||||
|
||||
# Object
|
||||
_object = easy_func(jsobject.Object, space)
|
||||
space.Object = _object
|
||||
_object.create = jsobject.ObjectCreate
|
||||
set_non_enumerable(object_proto, 'constructor', _object)
|
||||
set_protected(_object, 'prototype', object_proto)
|
||||
fill_proto(_object, jsobject.ObjectMethods, space)
|
||||
|
||||
# Function
|
||||
function = easy_func(jsfunction.Function, space)
|
||||
space.Function = function
|
||||
|
||||
# Math
|
||||
math = space.NewObject()
|
||||
math.Class = 'Math'
|
||||
fill_proto(math, jsmath.MathFunctions, space)
|
||||
for k, v in jsmath.CONSTANTS.items():
|
||||
set_protected(math, k, v)
|
||||
|
||||
console = space.NewObject()
|
||||
fill_proto(console, jsconsole.ConsoleMethods, space)
|
||||
|
||||
# set global object
|
||||
builtins = {
|
||||
'String': string,
|
||||
'Number': number,
|
||||
'Boolean': boolean,
|
||||
'RegExp': regexp,
|
||||
'exports': convert_to_js_type({}, space),
|
||||
'Math': math,
|
||||
#'Date',
|
||||
'Object': _object,
|
||||
'Function': function,
|
||||
'JSON': json,
|
||||
'Array': array,
|
||||
'parseFloat': parseFloat,
|
||||
'parseInt': parseInt,
|
||||
'isFinite': isFinite,
|
||||
'isNaN': isNaN,
|
||||
'eval': easy_func(jsfunction._eval, space),
|
||||
'console': console,
|
||||
'log': console.get(u'log'),
|
||||
}
|
||||
|
||||
builtins.update(error_constructors)
|
||||
|
||||
set_protected(global_scope, 'NaN', NaN)
|
||||
set_protected(global_scope, 'Infinity', Infinity)
|
||||
for k, v in builtins.items():
|
||||
set_non_enumerable(global_scope, k, v)
|
||||
@@ -0,0 +1,73 @@
|
||||
from .simplex import *
|
||||
from .conversions import *
|
||||
|
||||
import six
|
||||
if six.PY3:
|
||||
basestring = str
|
||||
long = int
|
||||
xrange = range
|
||||
unicode = str
|
||||
|
||||
|
||||
def get_arg(arguments, n):
|
||||
if len(arguments) <= n:
|
||||
return undefined
|
||||
return arguments[n]
|
||||
|
||||
|
||||
def ensure_js_types(args, space=None):
|
||||
return tuple(convert_to_js_type(e, space=space) for e in args)
|
||||
|
||||
|
||||
def convert_to_js_type(e, space=None):
|
||||
t = type(e)
|
||||
if is_js_type(e):
|
||||
return e
|
||||
if t in (int, long, float):
|
||||
return float(e)
|
||||
elif isinstance(t, basestring):
|
||||
return unicode(t)
|
||||
elif t in (list, tuple):
|
||||
if space is None:
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Actually an internal error, could not convert to js type because space not specified'
|
||||
)
|
||||
return space.ConstructArray(ensure_js_types(e, space=space))
|
||||
elif t == dict:
|
||||
if space is None:
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Actually an internal error, could not convert to js type because space not specified'
|
||||
)
|
||||
new = {}
|
||||
for k, v in e.items():
|
||||
new[to_string(convert_to_js_type(k, space))] = convert_to_js_type(
|
||||
v, space)
|
||||
return space.ConstructObject(new)
|
||||
else:
|
||||
raise MakeError('TypeError', 'Could not convert to js type!')
|
||||
|
||||
|
||||
def is_js_type(e):
|
||||
if type(e) in PRIMITIVES:
|
||||
return True
|
||||
elif hasattr(e, 'Class') and hasattr(e, 'value'): # not perfect but works
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# todo optimise these 2!
|
||||
def js_array_to_tuple(arr):
|
||||
length = to_uint32(arr.get(u'length'))
|
||||
return tuple(arr.get(unicode(e)) for e in xrange(length))
|
||||
|
||||
|
||||
def js_array_to_list(arr):
|
||||
length = to_uint32(arr.get(u'length'))
|
||||
return [arr.get(unicode(e)) for e in xrange(length)]
|
||||
|
||||
|
||||
def js_arr_length(arr):
|
||||
return to_uint32(arr.get(u'length'))
|
||||
@@ -0,0 +1,805 @@
|
||||
from .operations import *
|
||||
from .base import get_member, get_member_dot, PyJsFunction, Scope
|
||||
|
||||
|
||||
class OP_CODE(object):
|
||||
_params = []
|
||||
|
||||
# def eval(self, ctx):
|
||||
# raise
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + str(
|
||||
tuple([getattr(self, e) for e in self._params]))
|
||||
|
||||
|
||||
# --------------------- UNARY ----------------------
|
||||
|
||||
|
||||
class UNARY_OP(OP_CODE):
|
||||
_params = ['operator']
|
||||
|
||||
def __init__(self, operator):
|
||||
self.operator = operator
|
||||
|
||||
def eval(self, ctx):
|
||||
val = ctx.stack.pop()
|
||||
ctx.stack.append(UNARY_OPERATIONS[self.operator](val))
|
||||
|
||||
|
||||
# special unary operations
|
||||
|
||||
|
||||
class TYPEOF(OP_CODE):
|
||||
_params = ['identifier']
|
||||
|
||||
def __init__(self, identifier):
|
||||
self.identifier = identifier
|
||||
|
||||
def eval(self, ctx):
|
||||
# typeof something_undefined does not throw reference error
|
||||
val = ctx.get(self.identifier,
|
||||
False) # <= this makes it slightly different!
|
||||
ctx.stack.append(typeof_uop(val))
|
||||
|
||||
|
||||
class POSTFIX(OP_CODE):
|
||||
_params = ['cb', 'ca', 'identifier']
|
||||
|
||||
def __init__(self, post, incr, identifier):
|
||||
self.identifier = identifier
|
||||
self.cb = 1 if incr else -1
|
||||
self.ca = -self.cb if post else 0
|
||||
|
||||
def eval(self, ctx):
|
||||
target = to_number(ctx.get(self.identifier)) + self.cb
|
||||
ctx.put(self.identifier, target)
|
||||
ctx.stack.append(target + self.ca)
|
||||
|
||||
|
||||
class POSTFIX_MEMBER(OP_CODE):
|
||||
_params = ['cb', 'ca']
|
||||
|
||||
def __init__(self, post, incr):
|
||||
self.cb = 1 if incr else -1
|
||||
self.ca = -self.cb if post else 0
|
||||
|
||||
def eval(self, ctx):
|
||||
name = ctx.stack.pop()
|
||||
left = ctx.stack.pop()
|
||||
|
||||
target = to_number(get_member(left, name, ctx.space)) + self.cb
|
||||
if type(left) not in PRIMITIVES:
|
||||
left.put_member(name, target)
|
||||
|
||||
ctx.stack.append(target + self.ca)
|
||||
|
||||
|
||||
class POSTFIX_MEMBER_DOT(OP_CODE):
|
||||
_params = ['cb', 'ca', 'prop']
|
||||
|
||||
def __init__(self, post, incr, prop):
|
||||
self.cb = 1 if incr else -1
|
||||
self.ca = -self.cb if post else 0
|
||||
self.prop = prop
|
||||
|
||||
def eval(self, ctx):
|
||||
left = ctx.stack.pop()
|
||||
|
||||
target = to_number(get_member_dot(left, self.prop,
|
||||
ctx.space)) + self.cb
|
||||
if type(left) not in PRIMITIVES:
|
||||
left.put(self.prop, target)
|
||||
|
||||
ctx.stack.append(target + self.ca)
|
||||
|
||||
|
||||
class DELETE(OP_CODE):
|
||||
_params = ['name']
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(ctx.delete(self.name))
|
||||
|
||||
|
||||
class DELETE_MEMBER(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
prop = to_string(ctx.stack.pop())
|
||||
obj = to_object(ctx.stack.pop(), ctx)
|
||||
ctx.stack.append(obj.delete(prop, False))
|
||||
|
||||
|
||||
# --------------------- BITWISE ----------------------
|
||||
|
||||
|
||||
class BINARY_OP(OP_CODE):
|
||||
_params = ['operator']
|
||||
|
||||
def __init__(self, operator):
|
||||
self.operator = operator
|
||||
|
||||
def eval(self, ctx):
|
||||
right = ctx.stack.pop()
|
||||
left = ctx.stack.pop()
|
||||
ctx.stack.append(BINARY_OPERATIONS[self.operator](left, right))
|
||||
|
||||
|
||||
# &&, || and conditional are implemented in bytecode
|
||||
|
||||
# --------------------- JUMPS ----------------------
|
||||
|
||||
|
||||
# simple label that will be removed from code after compilation. labels ID will be translated
|
||||
# to source code position.
|
||||
class LABEL(OP_CODE):
|
||||
_params = ['num']
|
||||
|
||||
def __init__(self, num):
|
||||
self.num = num
|
||||
|
||||
|
||||
# I implemented interpreter in the way that when an integer is returned by eval operation the execution will jump
|
||||
# to the location of the label (it is loc = label_locations[label])
|
||||
|
||||
|
||||
class BASE_JUMP(OP_CODE):
|
||||
_params = ['label']
|
||||
|
||||
def __init__(self, label):
|
||||
self.label = label
|
||||
|
||||
|
||||
class JUMP(BASE_JUMP):
|
||||
def eval(self, ctx):
|
||||
return self.label
|
||||
|
||||
|
||||
class JUMP_IF_TRUE(BASE_JUMP):
|
||||
def eval(self, ctx):
|
||||
val = ctx.stack.pop()
|
||||
if to_boolean(val):
|
||||
return self.label
|
||||
|
||||
|
||||
class JUMP_IF_EQ(BASE_JUMP):
|
||||
# this one is used in switch statement - compares last 2 values using === operator and jumps popping both if true else pops last.
|
||||
def eval(self, ctx):
|
||||
cmp = ctx.stack.pop()
|
||||
if strict_equality_op(ctx.stack[-1], cmp):
|
||||
ctx.stack.pop()
|
||||
return self.label
|
||||
|
||||
|
||||
class JUMP_IF_TRUE_WITHOUT_POP(BASE_JUMP):
|
||||
def eval(self, ctx):
|
||||
val = ctx.stack[-1]
|
||||
if to_boolean(val):
|
||||
return self.label
|
||||
|
||||
|
||||
class JUMP_IF_FALSE(BASE_JUMP):
|
||||
def eval(self, ctx):
|
||||
val = ctx.stack.pop()
|
||||
if not to_boolean(val):
|
||||
return self.label
|
||||
|
||||
|
||||
class JUMP_IF_FALSE_WITHOUT_POP(BASE_JUMP):
|
||||
def eval(self, ctx):
|
||||
val = ctx.stack[-1]
|
||||
if not to_boolean(val):
|
||||
return self.label
|
||||
|
||||
|
||||
class POP(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
# todo remove this check later
|
||||
assert len(ctx.stack), 'Popped from empty stack!'
|
||||
del ctx.stack[-1]
|
||||
|
||||
|
||||
# class REDUCE(OP_CODE):
|
||||
# def eval(self, ctx):
|
||||
# assert len(ctx.stack)==2
|
||||
# ctx.stack[0] = ctx.stack[1]
|
||||
# del ctx.stack[1]
|
||||
|
||||
# --------------- LOADING --------------
|
||||
|
||||
|
||||
class LOAD_NONE(OP_CODE): # be careful with this :)
|
||||
_params = []
|
||||
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(None)
|
||||
|
||||
|
||||
class LOAD_N_TUPLE(
|
||||
OP_CODE
|
||||
): # loads the tuple composed of n last elements on stack. elements are popped.
|
||||
_params = ['n']
|
||||
|
||||
def __init__(self, n):
|
||||
self.n = n
|
||||
|
||||
def eval(self, ctx):
|
||||
tup = tuple(ctx.stack[-self.n:])
|
||||
del ctx.stack[-self.n:]
|
||||
ctx.stack.append(tup)
|
||||
|
||||
|
||||
class LOAD_UNDEFINED(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(undefined)
|
||||
|
||||
|
||||
class LOAD_NULL(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(null)
|
||||
|
||||
|
||||
class LOAD_BOOLEAN(OP_CODE):
|
||||
_params = ['val']
|
||||
|
||||
def __init__(self, val):
|
||||
assert val in (0, 1)
|
||||
self.val = bool(val)
|
||||
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(self.val)
|
||||
|
||||
|
||||
class LOAD_STRING(OP_CODE):
|
||||
_params = ['val']
|
||||
|
||||
def __init__(self, val):
|
||||
assert isinstance(val, basestring)
|
||||
self.val = unicode(val)
|
||||
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(self.val)
|
||||
|
||||
|
||||
class LOAD_NUMBER(OP_CODE):
|
||||
_params = ['val']
|
||||
|
||||
def __init__(self, val):
|
||||
assert isinstance(val, (float, int, long))
|
||||
self.val = float(val)
|
||||
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(self.val)
|
||||
|
||||
|
||||
class LOAD_REGEXP(OP_CODE):
|
||||
_params = ['body', 'flags']
|
||||
|
||||
def __init__(self, body, flags):
|
||||
self.body = body
|
||||
self.flags = flags
|
||||
|
||||
def eval(self, ctx):
|
||||
# we have to generate a new regexp - they are mutable
|
||||
ctx.stack.append(ctx.space.NewRegExp(self.body, self.flags))
|
||||
|
||||
|
||||
class LOAD_FUNCTION(OP_CODE):
|
||||
_params = ['start', 'params', 'name', 'is_declaration', 'definitions']
|
||||
|
||||
def __init__(self, start, params, name, is_declaration, definitions):
|
||||
assert type(start) == int
|
||||
self.start = start # its an ID of label pointing to the beginning of the function bytecode
|
||||
self.params = params
|
||||
self.name = name
|
||||
self.is_declaration = bool(is_declaration)
|
||||
self.definitions = tuple(set(definitions + params))
|
||||
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(
|
||||
ctx.space.NewFunction(self.start, ctx, self.params, self.name,
|
||||
self.is_declaration, self.definitions))
|
||||
|
||||
|
||||
class LOAD_OBJECT(OP_CODE):
|
||||
_params = [
|
||||
'props'
|
||||
] # props are py string pairs (prop_name, kind): kind can be either i, g or s. (init, get, set)
|
||||
|
||||
def __init__(self, props):
|
||||
self.num = len(props)
|
||||
self.props = props
|
||||
|
||||
def eval(self, ctx):
|
||||
obj = ctx.space.NewObject()
|
||||
if self.num:
|
||||
obj._init(self.props, ctx.stack[-self.num:])
|
||||
del ctx.stack[-self.num:]
|
||||
|
||||
ctx.stack.append(obj)
|
||||
|
||||
|
||||
class LOAD_ARRAY(OP_CODE):
|
||||
_params = ['num']
|
||||
|
||||
def __init__(self, num):
|
||||
self.num = num
|
||||
|
||||
def eval(self, ctx):
|
||||
arr = ctx.space.NewArray(self.num)
|
||||
if self.num:
|
||||
arr._init(ctx.stack[-self.num:])
|
||||
del ctx.stack[-self.num:]
|
||||
ctx.stack.append(arr)
|
||||
|
||||
|
||||
class LOAD_THIS(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(ctx.THIS_BINDING)
|
||||
|
||||
|
||||
class LOAD(OP_CODE): # todo check!
|
||||
_params = ['identifier']
|
||||
|
||||
def __init__(self, identifier):
|
||||
self.identifier = identifier
|
||||
|
||||
# 11.1.2
|
||||
def eval(self, ctx):
|
||||
ctx.stack.append(ctx.get(self.identifier, throw=True))
|
||||
|
||||
|
||||
class LOAD_MEMBER(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
prop = ctx.stack.pop()
|
||||
obj = ctx.stack.pop()
|
||||
ctx.stack.append(get_member(obj, prop, ctx.space))
|
||||
|
||||
|
||||
class LOAD_MEMBER_DOT(OP_CODE):
|
||||
_params = ['prop']
|
||||
|
||||
def __init__(self, prop):
|
||||
self.prop = prop
|
||||
|
||||
def eval(self, ctx):
|
||||
obj = ctx.stack.pop()
|
||||
ctx.stack.append(get_member_dot(obj, self.prop, ctx.space))
|
||||
|
||||
|
||||
# --------------- STORING --------------
|
||||
|
||||
|
||||
class STORE(OP_CODE):
|
||||
_params = ['identifier']
|
||||
|
||||
def __init__(self, identifier):
|
||||
self.identifier = identifier
|
||||
|
||||
def eval(self, ctx):
|
||||
value = ctx.stack[-1] # don't pop
|
||||
ctx.put(self.identifier, value)
|
||||
|
||||
|
||||
class STORE_MEMBER(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
value = ctx.stack.pop()
|
||||
name = ctx.stack.pop()
|
||||
left = ctx.stack.pop()
|
||||
|
||||
typ = type(left)
|
||||
if typ in PRIMITIVES:
|
||||
prop = to_string(name)
|
||||
if typ == NULL_TYPE:
|
||||
raise MakeError('TypeError',
|
||||
"Cannot set property '%s' of null" % prop)
|
||||
elif typ == UNDEFINED_TYPE:
|
||||
raise MakeError('TypeError',
|
||||
"Cannot set property '%s' of undefined" % prop)
|
||||
# just ignore...
|
||||
else:
|
||||
left.put_member(name, value)
|
||||
|
||||
ctx.stack.append(value)
|
||||
|
||||
|
||||
class STORE_MEMBER_DOT(OP_CODE):
|
||||
_params = ['prop']
|
||||
|
||||
def __init__(self, prop):
|
||||
self.prop = prop
|
||||
|
||||
def eval(self, ctx):
|
||||
value = ctx.stack.pop()
|
||||
left = ctx.stack.pop()
|
||||
|
||||
typ = type(left)
|
||||
if typ in PRIMITIVES:
|
||||
if typ == NULL_TYPE:
|
||||
raise MakeError('TypeError',
|
||||
"Cannot set property '%s' of null" % self.prop)
|
||||
elif typ == UNDEFINED_TYPE:
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
"Cannot set property '%s' of undefined" % self.prop)
|
||||
# just ignore...
|
||||
else:
|
||||
left.put(self.prop, value)
|
||||
ctx.stack.append(value)
|
||||
|
||||
|
||||
class STORE_OP(OP_CODE):
|
||||
_params = ['identifier', 'op']
|
||||
|
||||
def __init__(self, identifier, op):
|
||||
self.identifier = identifier
|
||||
self.op = op
|
||||
|
||||
def eval(self, ctx):
|
||||
value = ctx.stack.pop()
|
||||
new_value = BINARY_OPERATIONS[self.op](ctx.get(self.identifier), value)
|
||||
ctx.put(self.identifier, new_value)
|
||||
ctx.stack.append(new_value)
|
||||
|
||||
|
||||
class STORE_MEMBER_OP(OP_CODE):
|
||||
_params = ['op']
|
||||
|
||||
def __init__(self, op):
|
||||
self.op = op
|
||||
|
||||
def eval(self, ctx):
|
||||
value = ctx.stack.pop()
|
||||
name = ctx.stack.pop()
|
||||
left = ctx.stack.pop()
|
||||
|
||||
typ = type(left)
|
||||
if typ in PRIMITIVES:
|
||||
if typ is NULL_TYPE:
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
"Cannot set property '%s' of null" % to_string(name))
|
||||
elif typ is UNDEFINED_TYPE:
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
"Cannot set property '%s' of undefined" % to_string(name))
|
||||
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member(
|
||||
left, name, ctx.space), value))
|
||||
return
|
||||
else:
|
||||
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member(
|
||||
left, name, ctx.space), value))
|
||||
left.put_member(name, ctx.stack[-1])
|
||||
|
||||
|
||||
class STORE_MEMBER_DOT_OP(OP_CODE):
|
||||
_params = ['prop', 'op']
|
||||
|
||||
def __init__(self, prop, op):
|
||||
self.prop = prop
|
||||
self.op = op
|
||||
|
||||
def eval(self, ctx):
|
||||
value = ctx.stack.pop()
|
||||
left = ctx.stack.pop()
|
||||
|
||||
typ = type(left)
|
||||
if typ in PRIMITIVES:
|
||||
if typ == NULL_TYPE:
|
||||
raise MakeError('TypeError',
|
||||
"Cannot set property '%s' of null" % self.prop)
|
||||
elif typ == UNDEFINED_TYPE:
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
"Cannot set property '%s' of undefined" % self.prop)
|
||||
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot(
|
||||
left, self.prop, ctx.space), value))
|
||||
return
|
||||
else:
|
||||
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot(
|
||||
left, self.prop, ctx.space), value))
|
||||
left.put(self.prop, ctx.stack[-1])
|
||||
|
||||
|
||||
# --------------- CALLS --------------
|
||||
|
||||
|
||||
def bytecode_call(ctx, func, this, args):
|
||||
if type(func) is not PyJsFunction:
|
||||
raise MakeError('TypeError', "%s is not a function" % Type(func))
|
||||
if func.is_native: # call to built-in function or method
|
||||
ctx.stack.append(func.call(this, args))
|
||||
return None
|
||||
|
||||
# therefore not native. we have to return (new_context, function_label) to instruct interpreter to call
|
||||
return func._generate_my_context(this, args), func.code
|
||||
|
||||
|
||||
class CALL(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
args = ctx.stack.pop()
|
||||
func = ctx.stack.pop()
|
||||
|
||||
return bytecode_call(ctx, func, ctx.space.GlobalObj, args)
|
||||
|
||||
|
||||
class CALL_METHOD(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
args = ctx.stack.pop()
|
||||
prop = ctx.stack.pop()
|
||||
base = ctx.stack.pop()
|
||||
|
||||
func = get_member(base, prop, ctx.space)
|
||||
|
||||
return bytecode_call(ctx, func, base, args)
|
||||
|
||||
|
||||
class CALL_METHOD_DOT(OP_CODE):
|
||||
_params = ['prop']
|
||||
|
||||
def __init__(self, prop):
|
||||
self.prop = prop
|
||||
|
||||
def eval(self, ctx):
|
||||
args = ctx.stack.pop()
|
||||
base = ctx.stack.pop()
|
||||
|
||||
func = get_member_dot(base, self.prop, ctx.space)
|
||||
|
||||
return bytecode_call(ctx, func, base, args)
|
||||
|
||||
|
||||
class CALL_NO_ARGS(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
func = ctx.stack.pop()
|
||||
|
||||
return bytecode_call(ctx, func, ctx.space.GlobalObj, ())
|
||||
|
||||
|
||||
class CALL_METHOD_NO_ARGS(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
prop = ctx.stack.pop()
|
||||
base = ctx.stack.pop()
|
||||
|
||||
func = get_member(base, prop, ctx.space)
|
||||
|
||||
return bytecode_call(ctx, func, base, ())
|
||||
|
||||
|
||||
class CALL_METHOD_DOT_NO_ARGS(OP_CODE):
|
||||
_params = ['prop']
|
||||
|
||||
def __init__(self, prop):
|
||||
self.prop = prop
|
||||
|
||||
def eval(self, ctx):
|
||||
base = ctx.stack.pop()
|
||||
|
||||
func = get_member_dot(base, self.prop, ctx.space)
|
||||
|
||||
return bytecode_call(ctx, func, base, ())
|
||||
|
||||
|
||||
class NOP(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
pass
|
||||
|
||||
|
||||
class RETURN(OP_CODE):
|
||||
def eval(
|
||||
self, ctx
|
||||
): # remember to load the return value on stack before using RETURN op.
|
||||
return (None, None)
|
||||
|
||||
|
||||
class NEW(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
args = ctx.stack.pop()
|
||||
constructor = ctx.stack.pop()
|
||||
if type(constructor) in PRIMITIVES or not hasattr(
|
||||
constructor, 'create'):
|
||||
raise MakeError('TypeError',
|
||||
'%s is not a constructor' % Type(constructor))
|
||||
ctx.stack.append(constructor.create(args, space=ctx.space))
|
||||
|
||||
|
||||
class NEW_NO_ARGS(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
constructor = ctx.stack.pop()
|
||||
if type(constructor) in PRIMITIVES or not hasattr(
|
||||
constructor, 'create'):
|
||||
raise MakeError('TypeError',
|
||||
'%s is not a constructor' % Type(constructor))
|
||||
ctx.stack.append(constructor.create((), space=ctx.space))
|
||||
|
||||
|
||||
# --------------- EXCEPTIONS --------------
|
||||
|
||||
|
||||
class THROW(OP_CODE):
|
||||
def eval(self, ctx):
|
||||
raise MakeError(None, None, ctx.stack.pop())
|
||||
|
||||
|
||||
class TRY_CATCH_FINALLY(OP_CODE):
|
||||
_params = [
|
||||
'try_label', 'catch_label', 'catch_variable', 'finally_label',
|
||||
'finally_present', 'end_label'
|
||||
]
|
||||
|
||||
def __init__(self, try_label, catch_label, catch_variable, finally_label,
|
||||
finally_present, end_label):
|
||||
self.try_label = try_label
|
||||
self.catch_label = catch_label
|
||||
self.catch_variable = catch_variable
|
||||
self.finally_label = finally_label
|
||||
self.finally_present = finally_present
|
||||
self.end_label = end_label
|
||||
|
||||
def eval(self, ctx):
|
||||
# 4 different exectution results
|
||||
# 0=normal, 1=return, 2=jump_outside, 3=errors
|
||||
# execute_fragment_under_context returns:
|
||||
# (return_value, typ, jump_loc/error)
|
||||
|
||||
ctx.stack.pop()
|
||||
|
||||
# execute try statement
|
||||
try_status = ctx.space.exe.execute_fragment_under_context(
|
||||
ctx, self.try_label, self.catch_label)
|
||||
|
||||
errors = try_status[1] == 3
|
||||
|
||||
# catch
|
||||
if errors and self.catch_variable is not None:
|
||||
# generate catch block context...
|
||||
catch_context = Scope({
|
||||
self.catch_variable:
|
||||
try_status[2].get_thrown_value(ctx.space)
|
||||
}, ctx.space, ctx)
|
||||
catch_context.THIS_BINDING = ctx.THIS_BINDING
|
||||
catch_status = ctx.space.exe.execute_fragment_under_context(
|
||||
catch_context, self.catch_label, self.finally_label)
|
||||
else:
|
||||
catch_status = None
|
||||
|
||||
# finally
|
||||
if self.finally_present:
|
||||
finally_status = ctx.space.exe.execute_fragment_under_context(
|
||||
ctx, self.finally_label, self.end_label)
|
||||
else:
|
||||
finally_status = None
|
||||
|
||||
# now return controls
|
||||
other_status = catch_status or try_status
|
||||
if finally_status is None or (finally_status[1] == 0
|
||||
and other_status[1] != 0):
|
||||
winning_status = other_status
|
||||
else:
|
||||
winning_status = finally_status
|
||||
|
||||
val, typ, spec = winning_status
|
||||
if typ == 0: # normal
|
||||
ctx.stack.append(val)
|
||||
return
|
||||
elif typ == 1: # return
|
||||
ctx.stack.append(spec)
|
||||
return None, None # send return signal
|
||||
elif typ == 2: # jump outside
|
||||
ctx.stack.append(val)
|
||||
return spec
|
||||
elif typ == 3:
|
||||
# throw is made with empty stack as usual
|
||||
raise spec
|
||||
else:
|
||||
raise RuntimeError('Invalid return code')
|
||||
|
||||
|
||||
# ------------ WITH + ITERATORS ----------
|
||||
|
||||
|
||||
class WITH(OP_CODE):
|
||||
_params = ['beg_label', 'end_label']
|
||||
|
||||
def __init__(self, beg_label, end_label):
|
||||
self.beg_label = beg_label
|
||||
self.end_label = end_label
|
||||
|
||||
def eval(self, ctx):
|
||||
obj = to_object(ctx.stack.pop(), ctx.space)
|
||||
|
||||
with_context = Scope(
|
||||
obj, ctx.space, ctx) # todo actually use the obj to modify the ctx
|
||||
with_context.THIS_BINDING = ctx.THIS_BINDING
|
||||
status = ctx.space.exe.execute_fragment_under_context(
|
||||
with_context, self.beg_label, self.end_label)
|
||||
|
||||
val, typ, spec = status
|
||||
|
||||
if typ != 3: # exception
|
||||
ctx.stack.pop()
|
||||
|
||||
if typ == 0: # normal
|
||||
ctx.stack.append(val)
|
||||
return
|
||||
elif typ == 1: # return
|
||||
ctx.stack.append(spec)
|
||||
return None, None # send return signal
|
||||
elif typ == 2: # jump outside
|
||||
ctx.stack.append(val)
|
||||
return spec
|
||||
elif typ == 3: # exception
|
||||
# throw is made with empty stack as usual
|
||||
raise spec
|
||||
else:
|
||||
raise RuntimeError('Invalid return code')
|
||||
|
||||
|
||||
class FOR_IN(OP_CODE):
|
||||
_params = ['name', 'body_start_label', 'continue_label', 'break_label']
|
||||
|
||||
def __init__(self, name, body_start_label, continue_label, break_label):
|
||||
self.name = name
|
||||
self.body_start_label = body_start_label
|
||||
self.continue_label = continue_label
|
||||
self.break_label = break_label
|
||||
|
||||
def eval(self, ctx):
|
||||
iterable = ctx.stack.pop()
|
||||
if is_null(iterable) or is_undefined(iterable):
|
||||
ctx.stack.pop()
|
||||
ctx.stack.append(undefined)
|
||||
return self.break_label
|
||||
|
||||
obj = to_object(iterable, ctx.space)
|
||||
|
||||
for e in sorted(obj.own):
|
||||
if not obj.own[e]['enumerable']:
|
||||
continue
|
||||
|
||||
ctx.put(
|
||||
self.name, e
|
||||
) # JS would have been so much nicer if this was ctx.space.put(self.name, obj.get(e))
|
||||
|
||||
# evaluate the body
|
||||
status = ctx.space.exe.execute_fragment_under_context(
|
||||
ctx, self.body_start_label, self.break_label)
|
||||
|
||||
val, typ, spec = status
|
||||
|
||||
if typ != 3: # exception
|
||||
ctx.stack.pop()
|
||||
|
||||
if typ == 0: # normal
|
||||
ctx.stack.append(val)
|
||||
continue
|
||||
elif typ == 1: # return
|
||||
ctx.stack.append(spec)
|
||||
return None, None # send return signal
|
||||
elif typ == 2: # jump outside
|
||||
# now have to figure out whether this is a continue or something else...
|
||||
ctx.stack.append(val)
|
||||
if spec == self.continue_label:
|
||||
# just a continue, perform next iteration as normal
|
||||
continue
|
||||
return spec # break or smth, go there and finish the iteration
|
||||
elif typ == 3: # exception
|
||||
# throw is made with empty stack as usual
|
||||
raise spec
|
||||
else:
|
||||
raise RuntimeError('Invalid return code')
|
||||
|
||||
return self.break_label
|
||||
|
||||
|
||||
# all opcodes...
|
||||
OP_CODES = {}
|
||||
g = ''
|
||||
for g in globals():
|
||||
try:
|
||||
if not issubclass(globals()[g], OP_CODE) or g is 'OP_CODE':
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
OP_CODES[g] = globals()[g]
|
||||
@@ -0,0 +1,314 @@
|
||||
from __future__ import unicode_literals
|
||||
from .simplex import *
|
||||
from .conversions import *
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Unary operations
|
||||
|
||||
|
||||
# -x
|
||||
def minus_uop(self):
|
||||
return -to_number(self)
|
||||
|
||||
|
||||
# +x
|
||||
def plus_uop(self): # +u
|
||||
return to_number(self)
|
||||
|
||||
|
||||
# !x
|
||||
def logical_negation_uop(self): # !u cant do 'not u' :(
|
||||
return not to_boolean(self)
|
||||
|
||||
|
||||
# typeof x
|
||||
def typeof_uop(self):
|
||||
if is_callable(self):
|
||||
return u'function'
|
||||
typ = Type(self).lower()
|
||||
if typ == u'null':
|
||||
typ = u'object' # absolutely idiotic...
|
||||
return typ
|
||||
|
||||
|
||||
# ~u
|
||||
def bit_invert_uop(self):
|
||||
return float(to_int32(float(~to_int32(self))))
|
||||
|
||||
|
||||
# void
|
||||
def void_op(self):
|
||||
return undefined
|
||||
|
||||
|
||||
UNARY_OPERATIONS = {
|
||||
'+': plus_uop,
|
||||
'-': minus_uop,
|
||||
'!': logical_negation_uop,
|
||||
'~': bit_invert_uop,
|
||||
'void': void_op,
|
||||
'typeof':
|
||||
typeof_uop, # this one only for member expressions! for identifiers its slightly different...
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ----- binary ops -------
|
||||
|
||||
# Bitwise operators
|
||||
# <<, >>, &, ^, |, ~
|
||||
|
||||
|
||||
# <<
|
||||
def bit_lshift_op(self, other):
|
||||
lnum = to_int32(self)
|
||||
rnum = to_uint32(other)
|
||||
shiftCount = rnum & 0x1F
|
||||
return float(to_int32(float(lnum << shiftCount)))
|
||||
|
||||
|
||||
# >>
|
||||
def bit_rshift_op(self, other):
|
||||
lnum = to_int32(self)
|
||||
rnum = to_uint32(other)
|
||||
shiftCount = rnum & 0x1F
|
||||
return float(to_int32(float(lnum >> shiftCount)))
|
||||
|
||||
|
||||
# >>>
|
||||
def bit_bshift_op(self, other):
|
||||
lnum = to_uint32(self)
|
||||
rnum = to_uint32(other)
|
||||
shiftCount = rnum & 0x1F
|
||||
return float(to_uint32(float(lnum >> shiftCount)))
|
||||
|
||||
|
||||
# &
|
||||
def bit_and_op(self, other):
|
||||
lnum = to_int32(self)
|
||||
rnum = to_int32(other)
|
||||
return float(to_int32(float(lnum & rnum)))
|
||||
|
||||
|
||||
# ^
|
||||
def bit_xor_op(self, other):
|
||||
lnum = to_int32(self)
|
||||
rnum = to_int32(other)
|
||||
return float(to_int32(float(lnum ^ rnum)))
|
||||
|
||||
|
||||
# |
|
||||
def bit_or_op(self, other):
|
||||
lnum = to_int32(self)
|
||||
rnum = to_int32(other)
|
||||
return float(to_int32(float(lnum | rnum)))
|
||||
|
||||
|
||||
# Additive operators
|
||||
# + and - are implemented here
|
||||
|
||||
|
||||
# +
|
||||
def add_op(self, other):
|
||||
if type(self) is float and type(other) is float:
|
||||
return self + other
|
||||
if type(self) is unicode and type(other) is unicode:
|
||||
return self + other
|
||||
# standard way...
|
||||
a = to_primitive(self)
|
||||
b = to_primitive(other)
|
||||
if type(a) is unicode or type(b) is unicode: # string wins hehe
|
||||
return to_string(a) + to_string(b)
|
||||
return to_number(a) + to_number(b)
|
||||
|
||||
|
||||
# -
|
||||
def sub_op(self, other):
|
||||
return to_number(self) - to_number(other)
|
||||
|
||||
|
||||
# Multiplicative operators
|
||||
# *, / and % are implemented here
|
||||
|
||||
|
||||
# *
|
||||
def mul_op(self, other):
|
||||
return to_number(self) * to_number(other)
|
||||
|
||||
|
||||
# /
|
||||
def div_op(self, other):
|
||||
a = to_number(self)
|
||||
b = to_number(other)
|
||||
if b:
|
||||
return a / float(b) # ensure at least one is a float.
|
||||
if not a or a != a:
|
||||
return NaN
|
||||
return Infinity if a > 0 else -Infinity
|
||||
|
||||
|
||||
# %
|
||||
def mod_op(self, other):
|
||||
a = to_number(self)
|
||||
b = to_number(other)
|
||||
if abs(a) == Infinity or not b:
|
||||
return NaN
|
||||
if abs(b) == Infinity:
|
||||
return a
|
||||
pyres = a % b # different signs in python and javascript
|
||||
# python has the same sign as b and js has the same
|
||||
# sign as a.
|
||||
if a < 0 and pyres > 0:
|
||||
pyres -= abs(b)
|
||||
elif a > 0 and pyres < 0:
|
||||
pyres += abs(b)
|
||||
return float(pyres)
|
||||
|
||||
|
||||
# Comparisons
|
||||
# <, <=, !=, ==, >=, > are implemented here.
|
||||
def abstract_relational_comparison(self, other,
|
||||
self_first=True): # todo speed up!
|
||||
''' self<other if self_first else other<self.
|
||||
Returns the result of the question: is self smaller than other?
|
||||
in case self_first is false it returns the answer of:
|
||||
is other smaller than self.
|
||||
result is PyJs type: bool or undefined'''
|
||||
|
||||
px = to_primitive(self, 'Number')
|
||||
py = to_primitive(other, 'Number')
|
||||
if not self_first: # reverse order
|
||||
px, py = py, px
|
||||
if not (Type(px) == 'String' and Type(py) == 'String'):
|
||||
px, py = to_number(px), to_number(py)
|
||||
if is_nan(px) or is_nan(py):
|
||||
return None # watch out here!
|
||||
return px < py # same cmp algorithm
|
||||
else:
|
||||
# I am pretty sure that python has the same
|
||||
# string cmp algorithm but I have to confirm it
|
||||
return px < py
|
||||
|
||||
|
||||
# <
|
||||
def less_op(self, other):
|
||||
res = abstract_relational_comparison(self, other, True)
|
||||
if res is None:
|
||||
return False
|
||||
return res
|
||||
|
||||
|
||||
# <=
|
||||
def less_eq_op(self, other):
|
||||
res = abstract_relational_comparison(self, other, False)
|
||||
if res is None:
|
||||
return False
|
||||
return not res
|
||||
|
||||
|
||||
# >=
|
||||
def greater_eq_op(self, other):
|
||||
res = abstract_relational_comparison(self, other, True)
|
||||
if res is None:
|
||||
return False
|
||||
return not res
|
||||
|
||||
|
||||
# >
|
||||
def greater_op(self, other):
|
||||
res = abstract_relational_comparison(self, other, False)
|
||||
if res is None:
|
||||
return False
|
||||
return res
|
||||
|
||||
|
||||
# equality
|
||||
|
||||
|
||||
def abstract_equality_op(self, other):
|
||||
''' returns the result of JS == compare.
|
||||
result is PyJs type: bool'''
|
||||
tx, ty = Type(self), Type(other)
|
||||
if tx == ty:
|
||||
if tx == 'Undefined' or tx == 'Null':
|
||||
return True
|
||||
if tx == 'Number' or tx == 'String' or tx == 'Boolean':
|
||||
return self == other
|
||||
return self is other # Object
|
||||
elif (tx == 'Undefined' and ty == 'Null') or (ty == 'Undefined'
|
||||
and tx == 'Null'):
|
||||
return True
|
||||
elif tx == 'Number' and ty == 'String':
|
||||
return abstract_equality_op(self, to_number(other))
|
||||
elif tx == 'String' and ty == 'Number':
|
||||
return abstract_equality_op(to_number(self), other)
|
||||
elif tx == 'Boolean':
|
||||
return abstract_equality_op(to_number(self), other)
|
||||
elif ty == 'Boolean':
|
||||
return abstract_equality_op(self, to_number(other))
|
||||
elif (tx == 'String' or tx == 'Number') and is_object(other):
|
||||
return abstract_equality_op(self, to_primitive(other))
|
||||
elif (ty == 'String' or ty == 'Number') and is_object(self):
|
||||
return abstract_equality_op(to_primitive(self), other)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def abstract_inequality_op(self, other):
|
||||
return not abstract_equality_op(self, other)
|
||||
|
||||
|
||||
def strict_equality_op(self, other):
|
||||
typ = Type(self)
|
||||
if typ != Type(other):
|
||||
return False
|
||||
if typ == 'Undefined' or typ == 'Null':
|
||||
return True
|
||||
if typ == 'Boolean' or typ == 'String' or typ == 'Number':
|
||||
return self == other
|
||||
else: # object
|
||||
return self is other # Id compare.
|
||||
|
||||
|
||||
def strict_inequality_op(self, other):
|
||||
return not strict_equality_op(self, other)
|
||||
|
||||
|
||||
def instanceof_op(self, other):
|
||||
'''checks if self is instance of other'''
|
||||
if not hasattr(other, 'has_instance'):
|
||||
return False
|
||||
return other.has_instance(self)
|
||||
|
||||
|
||||
def in_op(self, other):
|
||||
'''checks if self is in other'''
|
||||
if not is_object(other):
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
"You can\'t use 'in' operator to search in non-objects")
|
||||
return other.has_property(to_string(self))
|
||||
|
||||
|
||||
BINARY_OPERATIONS = {
|
||||
'+': add_op,
|
||||
'-': sub_op,
|
||||
'*': mul_op,
|
||||
'/': div_op,
|
||||
'%': mod_op,
|
||||
'<<': bit_lshift_op,
|
||||
'>>': bit_rshift_op,
|
||||
'>>>': bit_bshift_op,
|
||||
'|': bit_or_op,
|
||||
'&': bit_and_op,
|
||||
'^': bit_xor_op,
|
||||
'==': abstract_equality_op,
|
||||
'!=': abstract_inequality_op,
|
||||
'===': strict_equality_op,
|
||||
'!==': strict_inequality_op,
|
||||
'<': less_op,
|
||||
'<=': less_eq_op,
|
||||
'>': greater_op,
|
||||
'>=': greater_eq_op,
|
||||
'in': in_op,
|
||||
'instanceof': instanceof_op,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
__author__ = 'Piotr Dabkowski'
|
||||
@@ -0,0 +1,489 @@
|
||||
from __future__ import unicode_literals
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
from ..operations import strict_equality_op
|
||||
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
xrange = range
|
||||
import functools
|
||||
|
||||
ARR_STACK = set({})
|
||||
|
||||
|
||||
class ArrayPrototype:
|
||||
def toString(this, args):
|
||||
arr = to_object(this, args.space)
|
||||
func = arr.get('join')
|
||||
if not is_callable(func):
|
||||
return u'[object %s]' % GetClass(arr)
|
||||
return func.call(this, ())
|
||||
|
||||
def toLocaleString(this, args):
|
||||
array = to_object(this, args.space)
|
||||
arr_len = js_arr_length(array)
|
||||
# separator is simply a comma ','
|
||||
if not arr_len:
|
||||
return ''
|
||||
res = []
|
||||
for i in xrange(arr_len):
|
||||
element = array.get(unicode(i))
|
||||
if is_undefined(element) or is_null(element):
|
||||
res.append('')
|
||||
else:
|
||||
cand = to_object(element, args.space)
|
||||
str_func = cand.get('toLocaleString')
|
||||
if not is_callable(str_func):
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'toLocaleString method of item at index %d is not callable'
|
||||
% i)
|
||||
res.append(to_string(str_func.call(cand, ())))
|
||||
return ','.join(res)
|
||||
|
||||
def concat(this, args):
|
||||
array = to_object(this, args.space)
|
||||
items = [array]
|
||||
items.extend(tuple(args))
|
||||
A = []
|
||||
for E in items:
|
||||
if GetClass(E) == 'Array':
|
||||
k = 0
|
||||
e_len = js_arr_length(E)
|
||||
while k < e_len:
|
||||
if E.has_property(unicode(k)):
|
||||
A.append(E.get(unicode(k)))
|
||||
k += 1
|
||||
else:
|
||||
A.append(E)
|
||||
return args.space.ConstructArray(A)
|
||||
|
||||
def join(this, args):
|
||||
ARR_STACK.add(this)
|
||||
array = to_object(this, args.space)
|
||||
separator = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
separator = ',' if is_undefined(separator) else to_string(separator)
|
||||
elems = []
|
||||
for e in xrange(arr_len):
|
||||
elem = array.get(unicode(e))
|
||||
if elem in ARR_STACK:
|
||||
s = ''
|
||||
else:
|
||||
s = to_string(elem)
|
||||
elems.append(
|
||||
s if not (is_undefined(elem) or is_null(elem)) else '')
|
||||
res = separator.join(elems)
|
||||
ARR_STACK.remove(this)
|
||||
return res
|
||||
|
||||
def pop(this, args): #todo check
|
||||
array = to_object(this, args.space)
|
||||
arr_len = js_arr_length(array)
|
||||
if not arr_len:
|
||||
array.put('length', float(arr_len))
|
||||
return undefined
|
||||
ind = unicode(arr_len - 1)
|
||||
element = array.get(ind)
|
||||
array.delete(ind)
|
||||
array.put('length', float(arr_len - 1))
|
||||
return element
|
||||
|
||||
def push(this, args):
|
||||
array = to_object(this, args.space)
|
||||
arr_len = js_arr_length(array)
|
||||
to_put = tuple(args)
|
||||
i = arr_len
|
||||
for i, e in enumerate(to_put, arr_len):
|
||||
array.put(unicode(i), e, True)
|
||||
array.put('length', float(arr_len + len(to_put)), True)
|
||||
return float(i)
|
||||
|
||||
def reverse(this, args):
|
||||
array = to_object(this, args.space)
|
||||
vals = js_array_to_list(array)
|
||||
has_props = [
|
||||
array.has_property(unicode(e))
|
||||
for e in xrange(js_arr_length(array))
|
||||
]
|
||||
vals.reverse()
|
||||
has_props.reverse()
|
||||
for i, val in enumerate(vals):
|
||||
if has_props[i]:
|
||||
array.put(unicode(i), val)
|
||||
else:
|
||||
array.delete(unicode(i))
|
||||
return array
|
||||
|
||||
def shift(this, args):
|
||||
array = to_object(this, args.space)
|
||||
arr_len = js_arr_length(array)
|
||||
if not arr_len:
|
||||
array.put('length', 0.)
|
||||
return undefined
|
||||
first = array.get('0')
|
||||
for k in xrange(1, arr_len):
|
||||
from_s, to_s = unicode(k), unicode(k - 1)
|
||||
if array.has_property(from_s):
|
||||
array.put(to_s, array.get(from_s))
|
||||
else:
|
||||
array.delete(to_s)
|
||||
array.delete(unicode(arr_len - 1))
|
||||
array.put('length', float(arr_len - 1))
|
||||
return first
|
||||
|
||||
def slice(this, args): # todo check
|
||||
array = to_object(this, args.space)
|
||||
start = get_arg(args, 0)
|
||||
end = get_arg(args, 1)
|
||||
arr_len = js_arr_length(array)
|
||||
relative_start = to_int(start)
|
||||
k = max((arr_len + relative_start), 0) if relative_start < 0 else min(
|
||||
relative_start, arr_len)
|
||||
relative_end = arr_len if is_undefined(end) else to_int(end)
|
||||
final = max((arr_len + relative_end), 0) if relative_end < 0 else min(
|
||||
relative_end, arr_len)
|
||||
res = []
|
||||
n = 0
|
||||
while k < final:
|
||||
pk = unicode(k)
|
||||
if array.has_property(pk):
|
||||
res.append(array.get(pk))
|
||||
k += 1
|
||||
n += 1
|
||||
return args.space.ConstructArray(res)
|
||||
|
||||
def sort(
|
||||
this, args
|
||||
): # todo: this assumes array continous (not sparse) - fix for sparse arrays
|
||||
cmpfn = get_arg(args, 0)
|
||||
if not GetClass(this) in ('Array', 'Arguments'):
|
||||
return to_object(this, args.space) # do nothing
|
||||
arr_len = js_arr_length(this)
|
||||
if not arr_len:
|
||||
return this
|
||||
arr = [
|
||||
(this.get(unicode(e)) if this.has_property(unicode(e)) else None)
|
||||
for e in xrange(arr_len)
|
||||
]
|
||||
if not is_callable(cmpfn):
|
||||
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(arr_len):
|
||||
if arr[i] is None:
|
||||
this.delete(unicode(i))
|
||||
else:
|
||||
this.put(unicode(i), arr[i])
|
||||
return this
|
||||
|
||||
def splice(this, args):
|
||||
# 1-8
|
||||
array = to_object(this, args.space)
|
||||
start = get_arg(args, 0)
|
||||
deleteCount = get_arg(args, 1)
|
||||
arr_len = js_arr_length(this)
|
||||
relative_start = to_int(start)
|
||||
actual_start = max(
|
||||
(arr_len + relative_start), 0) if relative_start < 0 else min(
|
||||
relative_start, arr_len)
|
||||
actual_delete_count = min(
|
||||
max(to_int(deleteCount), 0), arr_len - actual_start)
|
||||
k = 0
|
||||
A = args.space.NewArray(0)
|
||||
# 9
|
||||
while k < actual_delete_count:
|
||||
if array.has_property(unicode(actual_start + k)):
|
||||
A.put(unicode(k), array.get(unicode(actual_start + k)))
|
||||
k += 1
|
||||
# 10-11
|
||||
items = list(args)[2:]
|
||||
items_len = len(items)
|
||||
# 12
|
||||
if items_len < actual_delete_count:
|
||||
k = actual_start
|
||||
while k < (arr_len - actual_delete_count):
|
||||
fr = unicode(k + actual_delete_count)
|
||||
to = unicode(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(unicode(k - 1))
|
||||
k -= 1
|
||||
# 13
|
||||
elif items_len > actual_delete_count:
|
||||
k = arr_len - actual_delete_count
|
||||
while k > actual_start:
|
||||
fr = unicode(k + actual_delete_count - 1)
|
||||
to = unicode(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(unicode(k), E)
|
||||
k += 1
|
||||
array.put('length', float(arr_len - actual_delete_count + items_len))
|
||||
return A
|
||||
|
||||
def unshift(this, args):
|
||||
array = to_object(this, args.space)
|
||||
arr_len = js_arr_length(array)
|
||||
argCount = len(args)
|
||||
k = arr_len
|
||||
while k > 0:
|
||||
fr = unicode(k - 1)
|
||||
to = unicode(k + argCount - 1)
|
||||
if array.has_property(fr):
|
||||
array.put(to, array.get(fr))
|
||||
else:
|
||||
array.delete(to)
|
||||
k -= 1
|
||||
items = tuple(args)
|
||||
for j, e in enumerate(items):
|
||||
array.put(unicode(j), e)
|
||||
array.put('length', float(arr_len + argCount))
|
||||
return float(arr_len + argCount)
|
||||
|
||||
def indexOf(this, args):
|
||||
array = to_object(this, args.space)
|
||||
searchElement = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if arr_len == 0:
|
||||
return -1.
|
||||
if len(args) > 1:
|
||||
n = to_int(args[1])
|
||||
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(unicode(k)):
|
||||
elementK = array.get(unicode(k))
|
||||
if strict_equality_op(searchElement, elementK):
|
||||
return float(k)
|
||||
k += 1
|
||||
return -1.
|
||||
|
||||
def lastIndexOf(this, args):
|
||||
array = to_object(this, args.space)
|
||||
searchElement = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if arr_len == 0:
|
||||
return -1.
|
||||
if len(args) > 1:
|
||||
n = to_int(args[1])
|
||||
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(unicode(k)):
|
||||
elementK = array.get(unicode(k))
|
||||
if strict_equality_op(searchElement, elementK):
|
||||
return float(k)
|
||||
k -= 1
|
||||
return -1.
|
||||
|
||||
def every(this, args):
|
||||
array = to_object(this, args.space)
|
||||
callbackfn = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if not is_callable(callbackfn):
|
||||
raise MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = get_arg(args, 1)
|
||||
k = 0
|
||||
while k < arr_len:
|
||||
if array.has_property(unicode(k)):
|
||||
kValue = array.get(unicode(k))
|
||||
if not to_boolean(
|
||||
callbackfn.call(T, (kValue, float(k), array))):
|
||||
return False
|
||||
k += 1
|
||||
return True
|
||||
|
||||
def some(this, args):
|
||||
array = to_object(this, args.space)
|
||||
callbackfn = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if not is_callable(callbackfn):
|
||||
raise MakeError('TypeError', 'callbackfn must be a function')
|
||||
T = get_arg(args, 1)
|
||||
k = 0
|
||||
while k < arr_len:
|
||||
if array.has_property(unicode(k)):
|
||||
kValue = array.get(unicode(k))
|
||||
if to_boolean(callbackfn.call(T, (kValue, float(k), array))):
|
||||
return True
|
||||
k += 1
|
||||
return False
|
||||
|
||||
def forEach(this, args):
|
||||
array = to_object(this, args.space)
|
||||
callbackfn = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if not is_callable(callbackfn):
|
||||
raise MakeError('TypeError', 'callbackfn must be a function')
|
||||
_this = get_arg(args, 1)
|
||||
k = 0
|
||||
while k < arr_len:
|
||||
sk = unicode(k)
|
||||
if array.has_property(sk):
|
||||
kValue = array.get(sk)
|
||||
callbackfn.call(_this, (kValue, float(k), array))
|
||||
k += 1
|
||||
return undefined
|
||||
|
||||
def map(this, args):
|
||||
array = to_object(this, args.space)
|
||||
callbackfn = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if not is_callable(callbackfn):
|
||||
raise MakeError('TypeError', 'callbackfn must be a function')
|
||||
_this = get_arg(args, 1)
|
||||
k = 0
|
||||
A = args.space.NewArray(0)
|
||||
while k < arr_len:
|
||||
Pk = unicode(k)
|
||||
if array.has_property(Pk):
|
||||
kValue = array.get(Pk)
|
||||
mappedValue = callbackfn.call(_this, (kValue, float(k), array))
|
||||
A.define_own_property(
|
||||
Pk, {
|
||||
'value': mappedValue,
|
||||
'writable': True,
|
||||
'enumerable': True,
|
||||
'configurable': True
|
||||
}, False)
|
||||
k += 1
|
||||
return A
|
||||
|
||||
def filter(this, args):
|
||||
array = to_object(this, args.space)
|
||||
callbackfn = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if not is_callable(callbackfn):
|
||||
raise MakeError('TypeError', 'callbackfn must be a function')
|
||||
_this = get_arg(args, 1)
|
||||
k = 0
|
||||
res = []
|
||||
while k < arr_len:
|
||||
if array.has_property(unicode(k)):
|
||||
kValue = array.get(unicode(k))
|
||||
if to_boolean(
|
||||
callbackfn.call(_this, (kValue, float(k), array))):
|
||||
res.append(kValue)
|
||||
k += 1
|
||||
return args.space.ConstructArray(res)
|
||||
|
||||
def reduce(this, args):
|
||||
array = to_object(this, args.space)
|
||||
callbackfn = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if not is_callable(callbackfn):
|
||||
raise MakeError('TypeError', 'callbackfn must be a function')
|
||||
if not arr_len and len(args) < 2:
|
||||
raise MakeError('TypeError',
|
||||
'Reduce of empty array with no initial value')
|
||||
k = 0
|
||||
accumulator = undefined
|
||||
if len(args) > 1: # initial value present
|
||||
accumulator = args[1]
|
||||
else:
|
||||
kPresent = False
|
||||
while not kPresent and k < arr_len:
|
||||
kPresent = array.has_property(unicode(k))
|
||||
if kPresent:
|
||||
accumulator = array.get(unicode(k))
|
||||
k += 1
|
||||
if not kPresent:
|
||||
raise MakeError('TypeError',
|
||||
'Reduce of empty array with no initial value')
|
||||
while k < arr_len:
|
||||
if array.has_property(unicode(k)):
|
||||
kValue = array.get(unicode(k))
|
||||
accumulator = callbackfn.call(
|
||||
undefined, (accumulator, kValue, float(k), array))
|
||||
k += 1
|
||||
return accumulator
|
||||
|
||||
def reduceRight(this, args):
|
||||
array = to_object(this, args.space)
|
||||
callbackfn = get_arg(args, 0)
|
||||
arr_len = js_arr_length(array)
|
||||
if not is_callable(callbackfn):
|
||||
raise MakeError('TypeError', 'callbackfn must be a function')
|
||||
if not arr_len and len(args) < 2:
|
||||
raise MakeError('TypeError',
|
||||
'Reduce of empty array with no initial value')
|
||||
k = arr_len - 1
|
||||
accumulator = undefined
|
||||
|
||||
if len(args) > 1: # initial value present
|
||||
accumulator = args[1]
|
||||
else:
|
||||
kPresent = False
|
||||
while not kPresent and k >= 0:
|
||||
kPresent = array.has_property(unicode(k))
|
||||
if kPresent:
|
||||
accumulator = array.get(unicode(k))
|
||||
k -= 1
|
||||
if not kPresent:
|
||||
raise MakeError('TypeError',
|
||||
'Reduce of empty array with no initial value')
|
||||
while k >= 0:
|
||||
if array.has_property(unicode(k)):
|
||||
kValue = array.get(unicode(k))
|
||||
accumulator = callbackfn.call(
|
||||
undefined, (accumulator, kValue, float(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 is_undefined(a):
|
||||
if is_undefined(b):
|
||||
return 0
|
||||
return 1
|
||||
if is_undefined(b):
|
||||
if is_undefined(a):
|
||||
return 0
|
||||
return -1
|
||||
if comp is not None:
|
||||
res = comp.call(undefined, (a, b))
|
||||
return to_int(res)
|
||||
x, y = to_string(a), to_string(b)
|
||||
if x < y:
|
||||
return -1
|
||||
elif x > y:
|
||||
return 1
|
||||
return 0
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
|
||||
class BooleanPrototype:
|
||||
def toString(this, args):
|
||||
if GetClass(this) != 'Boolean':
|
||||
raise MakeError('TypeError',
|
||||
'Boolean.prototype.toString is not generic')
|
||||
if is_object(this):
|
||||
this = this.value
|
||||
return u'true' if this else u'false'
|
||||
|
||||
def valueOf(this, args):
|
||||
if GetClass(this) != 'Boolean':
|
||||
raise MakeError('TypeError',
|
||||
'Boolean.prototype.valueOf is not generic')
|
||||
if is_object(this):
|
||||
this = this.value
|
||||
return this
|
||||
@@ -0,0 +1,15 @@
|
||||
from __future__ import unicode_literals
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
|
||||
class ErrorPrototype:
|
||||
def toString(this, args):
|
||||
if Type(this) != 'Object':
|
||||
raise MakeError('TypeError',
|
||||
'Error.prototype.toString called on non-object')
|
||||
name = this.get('name')
|
||||
name = u'Error' if is_undefined(name) else to_string(name)
|
||||
msg = this.get('message')
|
||||
msg = '' if is_undefined(msg) else to_string(msg)
|
||||
return name + (name and msg and ': ') + msg
|
||||
@@ -0,0 +1,61 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
# 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(this, args):
|
||||
if not is_callable(this):
|
||||
raise MakeError('TypeError',
|
||||
'Function.prototype.toString is not generic')
|
||||
|
||||
args = u', '.join(map(unicode, this.params))
|
||||
return u'function %s(%s) { [native code] }' % (this.name if this.name
|
||||
else u'', args)
|
||||
|
||||
def call(this, args):
|
||||
if not is_callable(this):
|
||||
raise MakeError('TypeError',
|
||||
'Function.prototype.call is not generic')
|
||||
_this = get_arg(args, 0)
|
||||
_args = tuple(args)[1:]
|
||||
return this.call(_this, _args)
|
||||
|
||||
def apply(this, args):
|
||||
if not is_callable(this):
|
||||
raise MakeError('TypeError',
|
||||
'Function.prototype.apply is not generic')
|
||||
_args = get_arg(args, 1)
|
||||
if not is_object(_args):
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'argList argument to Function.prototype.apply must an Object')
|
||||
_this = get_arg(args, 0)
|
||||
return this.call(_this, js_array_to_tuple(_args))
|
||||
|
||||
def bind(this, args):
|
||||
if not is_callable(this):
|
||||
raise MakeError('TypeError',
|
||||
'Function.prototype.bind is not generic')
|
||||
bound_this = get_arg(args, 0)
|
||||
bound_args = tuple(args)[1:]
|
||||
|
||||
def bound(dummy_this, extra_args):
|
||||
return this.call(bound_this, bound_args + tuple(extra_args))
|
||||
|
||||
js_bound = args.space.NewFunction(bound, this.ctx, (), u'', False, ())
|
||||
js_bound.put(u'length',
|
||||
float(max(len(this.params) - len(bound_args), 0.)))
|
||||
js_bound.put(u'name', u'boundFunc')
|
||||
return js_bound
|
||||
@@ -0,0 +1,205 @@
|
||||
from __future__ import unicode_literals
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
from ..operations import strict_equality_op
|
||||
import json
|
||||
|
||||
indent = ''
|
||||
# python 3 support
|
||||
import six
|
||||
if six.PY3:
|
||||
basestring = str
|
||||
long = int
|
||||
xrange = range
|
||||
unicode = str
|
||||
|
||||
|
||||
def parse(this, args):
|
||||
text, reviver = get_arg(args, 0), get_arg(args, 1)
|
||||
s = to_string(text)
|
||||
try:
|
||||
unfiltered = json.loads(s)
|
||||
except:
|
||||
raise MakeError(
|
||||
'SyntaxError',
|
||||
'JSON.parse could not parse JSON string - Invalid syntax')
|
||||
unfiltered = to_js(unfiltered, args.space)
|
||||
if is_callable(reviver):
|
||||
root = args.space.ConstructObject({'': unfiltered})
|
||||
return walk(root, '', reviver)
|
||||
else:
|
||||
return unfiltered
|
||||
|
||||
|
||||
def stringify(this, args):
|
||||
global indent
|
||||
value, replacer, space = get_arg(args, 0), get_arg(args, 1), get_arg(
|
||||
args, 2)
|
||||
stack = set([])
|
||||
indent = ''
|
||||
property_list = replacer_function = undefined
|
||||
if is_object(replacer):
|
||||
if is_callable(replacer):
|
||||
replacer_function = replacer
|
||||
elif replacer.Class == 'Array':
|
||||
property_list = []
|
||||
for e in replacer:
|
||||
v = replacer[e]
|
||||
item = undefined
|
||||
typ = Type(v)
|
||||
if typ == 'Number':
|
||||
item = to_string(v)
|
||||
elif typ == 'String':
|
||||
item = v
|
||||
elif typ == 'Object':
|
||||
if GetClass(v) in ('String', 'Number'):
|
||||
item = to_string(v)
|
||||
if not is_undefined(item) and item not in property_list:
|
||||
property_list.append(item)
|
||||
if is_object(space):
|
||||
if GetClass(space) == 'Number':
|
||||
space = to_number(space)
|
||||
elif GetClass(space) == 'String':
|
||||
space = to_string(space)
|
||||
if Type(space) == 'Number':
|
||||
space = min(10, to_int(space))
|
||||
gap = max(int(space), 0) * ' '
|
||||
elif Type(space) == 'String':
|
||||
gap = space[:10]
|
||||
else:
|
||||
gap = ''
|
||||
return Str('', args.space.ConstructObject({
|
||||
'': value
|
||||
}), replacer_function, property_list, gap, stack, space)
|
||||
|
||||
|
||||
def Str(key, holder, replacer_function, property_list, gap, stack, space):
|
||||
value = holder.get(key)
|
||||
if is_object(value):
|
||||
to_json = value.get('toJSON')
|
||||
if is_callable(to_json):
|
||||
value = to_json.call(value, (key, ))
|
||||
if not is_undefined(replacer_function):
|
||||
value = replacer_function.call(holder, (key, value))
|
||||
|
||||
if is_object(value):
|
||||
if value.Class == 'String':
|
||||
value = to_string(value)
|
||||
elif value.Class == 'Number':
|
||||
value = to_number(value)
|
||||
elif value.Class == 'Boolean':
|
||||
value = to_boolean(value)
|
||||
typ = Type(value)
|
||||
if is_null(value):
|
||||
return 'null'
|
||||
elif typ == 'Boolean':
|
||||
return 'true' if value else 'false'
|
||||
elif typ == 'String':
|
||||
return Quote(value)
|
||||
elif typ == 'Number':
|
||||
if not is_infinity(value):
|
||||
return to_string(value)
|
||||
return 'null'
|
||||
if is_object(value) and not is_callable(value):
|
||||
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 undefined
|
||||
|
||||
|
||||
def jo(value, stack, gap, property_list, replacer_function, space):
|
||||
global indent
|
||||
if value in stack:
|
||||
raise MakeError('TypeError', 'Converting circular structure to JSON')
|
||||
stack.add(value)
|
||||
stepback = indent
|
||||
indent += gap
|
||||
if not is_undefined(property_list):
|
||||
k = property_list
|
||||
else:
|
||||
k = [unicode(e) for e, d in value.own.items() if d.get('enumerable')]
|
||||
partial = []
|
||||
for p in k:
|
||||
str_p = Str(p, value, replacer_function, property_list, gap, stack,
|
||||
space)
|
||||
if not is_undefined(str_p):
|
||||
member = json.dumps(p) + ':' + (
|
||||
' ' if gap else
|
||||
'') + str_p # 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 MakeError('TypeError', 'Converting circular structure to JSON')
|
||||
stack.add(value)
|
||||
stepback = indent
|
||||
indent += gap
|
||||
partial = []
|
||||
length = js_arr_length(value)
|
||||
for index in xrange(length):
|
||||
index = unicode(index)
|
||||
str_index = Str(index, value, replacer_function, property_list, gap,
|
||||
stack, space)
|
||||
if is_undefined(str_index):
|
||||
partial.append('null')
|
||||
else:
|
||||
partial.append(str_index)
|
||||
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 json.dumps(string)
|
||||
|
||||
|
||||
def to_js(d, _args_space):
|
||||
return convert_to_js_type(d, _args_space)
|
||||
|
||||
|
||||
def walk(holder, name, reviver):
|
||||
val = holder.get(name)
|
||||
if GetClass(val) == 'Array':
|
||||
for i in xrange(js_arr_length(val)):
|
||||
i = unicode(i)
|
||||
new_element = walk(val, i, reviver)
|
||||
if is_undefined(new_element):
|
||||
val.delete(i)
|
||||
else:
|
||||
new_element.put(i, new_element)
|
||||
elif is_object(val):
|
||||
for key in [
|
||||
unicode(e) for e, d in val.own.items() if d.get('enumerable')
|
||||
]:
|
||||
new_element = walk(val, key, reviver)
|
||||
if is_undefined(new_element):
|
||||
val.delete(key)
|
||||
else:
|
||||
val.put(key, new_element)
|
||||
return reviver.call(holder, (name, val))
|
||||
@@ -0,0 +1,163 @@
|
||||
from __future__ import unicode_literals
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
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 is_nan(num):
|
||||
return 'NaN'
|
||||
elif is_infinity(num):
|
||||
sign = '-' if num < 0 else ''
|
||||
return sign + 'Infinity'
|
||||
elif int(num) == num: # dont print .0
|
||||
return unicode(int(num))
|
||||
return unicode(num) # todo: Make it 100% consistent with Node
|
||||
|
||||
|
||||
class NumberPrototype:
|
||||
def toString(this, args):
|
||||
if GetClass(this) != 'Number':
|
||||
raise MakeError('TypeError',
|
||||
'Number.prototype.valueOf is not generic')
|
||||
if type(this) != float:
|
||||
this = this.value
|
||||
radix = get_arg(args, 0)
|
||||
if is_undefined(radix):
|
||||
return to_str_rep(this)
|
||||
r = to_int(radix)
|
||||
if r == 10:
|
||||
return to_str_rep(this)
|
||||
if r not in xrange(2, 37) or radix != r:
|
||||
raise MakeError(
|
||||
'RangeError',
|
||||
'Number.prototype.toString() radix argument must be an integer between 2 and 36'
|
||||
)
|
||||
num = to_int(this)
|
||||
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(this, args):
|
||||
if GetClass(this) != 'Number':
|
||||
raise MakeError('TypeError',
|
||||
'Number.prototype.valueOf is not generic')
|
||||
if type(this) != float:
|
||||
this = this.value
|
||||
return this
|
||||
|
||||
def toFixed(this, args):
|
||||
if GetClass(this) != 'Number':
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Number.prototype.toFixed called on incompatible receiver')
|
||||
if type(this) != float:
|
||||
this = this.value
|
||||
fractionDigits = get_arg(args, 0)
|
||||
digs = to_int(fractionDigits)
|
||||
if digs < 0 or digs > 20:
|
||||
raise MakeError(
|
||||
'RangeError',
|
||||
'toFixed() digits argument must be between 0 and 20')
|
||||
elif is_infinity(this):
|
||||
return 'Infinity' if this > 0 else '-Infinity'
|
||||
elif is_nan(this):
|
||||
return 'NaN'
|
||||
return format(this, '-.%df' % digs)
|
||||
|
||||
def toExponential(this, args):
|
||||
if GetClass(this) != 'Number':
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Number.prototype.toExponential called on incompatible receiver'
|
||||
)
|
||||
if type(this) != float:
|
||||
this = this.value
|
||||
fractionDigits = get_arg(args, 0)
|
||||
digs = to_int(fractionDigits)
|
||||
if digs < 0 or digs > 20:
|
||||
raise MakeError(
|
||||
'RangeError',
|
||||
'toFixed() digits argument must be between 0 and 20')
|
||||
elif is_infinity(this):
|
||||
return 'Infinity' if this > 0 else '-Infinity'
|
||||
elif is_nan(this):
|
||||
return 'NaN'
|
||||
return format(this, '-.%de' % digs)
|
||||
|
||||
def toPrecision(this, args):
|
||||
if GetClass(this) != 'Number':
|
||||
raise MakeError(
|
||||
'TypeError',
|
||||
'Number.prototype.toPrecision called on incompatible receiver')
|
||||
if type(this) != float:
|
||||
this = this.value
|
||||
precision = get_arg(args, 0)
|
||||
if is_undefined(precision):
|
||||
return to_string(this)
|
||||
prec = to_int(precision)
|
||||
if is_nan(this):
|
||||
return 'NaN'
|
||||
elif is_infinity(this):
|
||||
return 'Infinity' if this > 0 else '-Infinity'
|
||||
digs = prec - len(str(int(this)))
|
||||
if digs >= 0:
|
||||
return format(this, '-.%df' % digs)
|
||||
else:
|
||||
return format(this, '-.%df' % (prec - 1))
|
||||
|
||||
|
||||
NumberPrototype.toLocaleString = NumberPrototype.toString
|
||||
@@ -0,0 +1,48 @@
|
||||
from __future__ import unicode_literals
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
|
||||
class ObjectPrototype:
|
||||
def toString(this, args):
|
||||
if type(this) == UNDEFINED_TYPE:
|
||||
return u'[object Undefined]'
|
||||
elif type(this) == NULL_TYPE:
|
||||
return u'[object Null]'
|
||||
return u'[object %s]' % GetClass(to_object(this, args.space))
|
||||
|
||||
def valueOf(this, args):
|
||||
return to_object(this, args.space)
|
||||
|
||||
def toLocaleString(this, args):
|
||||
o = to_object(this, args.space)
|
||||
toString = o.get(u'toString')
|
||||
if not is_callable(toString):
|
||||
raise MakeError('TypeError', 'toString of this is not callcable')
|
||||
else:
|
||||
return toString.call(this, args)
|
||||
|
||||
def hasOwnProperty(this, args):
|
||||
prop = get_arg(args, 0)
|
||||
o = to_object(this, args.space)
|
||||
return o.get_own_property(to_string(prop)) is not None
|
||||
|
||||
def isPrototypeOf(this, args):
|
||||
# a bit stupid specification because of object conversion that will cause invalid values for primitives
|
||||
# for example Object.prototype.isPrototypeOf.call((5).__proto__, 5) gives false
|
||||
obj = get_arg(args, 0)
|
||||
if not is_object(obj):
|
||||
return False
|
||||
o = to_object(this, args.space)
|
||||
while 1:
|
||||
obj = obj.prototype
|
||||
if obj is None or is_null(obj):
|
||||
return False
|
||||
if obj is o:
|
||||
return True
|
||||
|
||||
def propertyIsEnumerable(this, args):
|
||||
prop = get_arg(args, 0)
|
||||
o = to_object(this, args.space)
|
||||
cand = o.own.get(to_string(prop))
|
||||
return cand is not None and cand.get('enumerable')
|
||||
@@ -0,0 +1,56 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
|
||||
|
||||
class RegExpPrototype:
|
||||
def toString(this, args):
|
||||
flags = u''
|
||||
try:
|
||||
if this.glob:
|
||||
flags += u'g'
|
||||
if this.ignore_case:
|
||||
flags += u'i'
|
||||
if this.multiline:
|
||||
flags += u'm'
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
v = this.value if this.value else u'(?:)'
|
||||
except:
|
||||
v = u'(?:)'
|
||||
return u'/%s/' % v + flags
|
||||
|
||||
def test(this, args):
|
||||
string = get_arg(args, 0)
|
||||
return RegExpExec(this, string, args.space) is not null
|
||||
|
||||
def _exec(
|
||||
this, args
|
||||
): # will be changed to exec in base.py. cant name it exec here...
|
||||
string = get_arg(args, 0)
|
||||
return RegExpExec(this, string, args.space)
|
||||
|
||||
|
||||
def RegExpExec(this, string, space):
|
||||
if GetClass(this) != 'RegExp':
|
||||
raise MakeError('TypeError', 'RegExp.prototype.exec is not generic!')
|
||||
string = to_string(string)
|
||||
length = len(string)
|
||||
i = to_int(this.get('lastIndex')) if this.glob else 0
|
||||
matched = False
|
||||
while not matched:
|
||||
if i < 0 or i > length:
|
||||
this.put('lastIndex', 0.)
|
||||
return null
|
||||
matched = this.match(string, i)
|
||||
i += 1
|
||||
start, end = matched.span() #[0]+i-1, matched.span()[1]+i-1
|
||||
if this.glob:
|
||||
this.put('lastIndex', float(end))
|
||||
arr = convert_to_js_type(
|
||||
[matched.group()] + list(matched.groups()), space=space)
|
||||
arr.put('index', float(start))
|
||||
arr.put('input', unicode(string))
|
||||
return arr
|
||||
@@ -0,0 +1,323 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from ..conversions import *
|
||||
from ..func_utils import *
|
||||
from .jsregexp import RegExpExec
|
||||
|
||||
DIGS = set(u'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(this, args):
|
||||
if GetClass(this) != 'String':
|
||||
raise MakeError('TypeError',
|
||||
'String.prototype.toString is not generic')
|
||||
if type(this) == unicode:
|
||||
return this
|
||||
assert type(this.value) == unicode
|
||||
return this.value
|
||||
|
||||
def valueOf(this, args):
|
||||
if GetClass(this) != 'String':
|
||||
raise MakeError('TypeError',
|
||||
'String.prototype.valueOf is not generic')
|
||||
if type(this) == unicode:
|
||||
return this
|
||||
assert type(this.value) == unicode
|
||||
return this.value
|
||||
|
||||
def charAt(this, args):
|
||||
cok(this)
|
||||
pos = to_int(get_arg(args, 0))
|
||||
s = to_string(this)
|
||||
if 0 <= pos < len(s):
|
||||
return s[pos]
|
||||
return u''
|
||||
|
||||
def charCodeAt(this, args):
|
||||
cok(this)
|
||||
pos = to_int(get_arg(args, 0))
|
||||
s = to_string(this)
|
||||
if 0 <= pos < len(s):
|
||||
return float(ord(s[pos]))
|
||||
return NaN
|
||||
|
||||
def concat(this, args):
|
||||
cok(this)
|
||||
return to_string(this) + u''.join(map(to_string, args))
|
||||
|
||||
def indexOf(this, args):
|
||||
cok(this)
|
||||
search = to_string(get_arg(args, 0))
|
||||
pos = to_int(get_arg(args, 1))
|
||||
s = to_string(this)
|
||||
return float(s.find(search, min(max(pos, 0), len(s))))
|
||||
|
||||
def lastIndexOf(this, args):
|
||||
cok(this)
|
||||
search = to_string(get_arg(args, 0))
|
||||
pos = get_arg(args, 1)
|
||||
s = to_string(this)
|
||||
pos = 10**12 if is_nan(pos) else to_int(pos)
|
||||
return float(s.rfind(search, 0, min(max(pos, 0) + 1, len(s))))
|
||||
|
||||
def localeCompare(this, args):
|
||||
cok(this)
|
||||
s = to_string(this)
|
||||
that = to_string(get_arg(args, 0))
|
||||
if s < that:
|
||||
return -1.
|
||||
elif s > that:
|
||||
return 1.
|
||||
return 0.
|
||||
|
||||
def match(this, args):
|
||||
cok(this)
|
||||
s = to_string(this)
|
||||
regexp = get_arg(args, 0)
|
||||
r = args.space.NewRegExp(
|
||||
regexp, '') if GetClass(regexp) != 'RegExp' else regexp
|
||||
if not r.glob:
|
||||
return RegExpExec(r, s, space=args.space)
|
||||
r.put('lastIndex', float(0))
|
||||
found = []
|
||||
previous_last_index = 0
|
||||
last_match = True
|
||||
while last_match:
|
||||
result = RegExpExec(r, s, space=args.space)
|
||||
if is_null(result):
|
||||
last_match = False
|
||||
else:
|
||||
this_index = r.get('lastIndex')
|
||||
if this_index == previous_last_index:
|
||||
r.put('lastIndex', float(this_index + 1))
|
||||
previous_last_index += 1
|
||||
else:
|
||||
previous_last_index = this_index
|
||||
matchStr = result.get('0')
|
||||
found.append(matchStr)
|
||||
if not found:
|
||||
return null
|
||||
return args.space.ConstructArray(found)
|
||||
|
||||
def replace(this, args):
|
||||
# VERY COMPLICATED. to check again.
|
||||
cok(this)
|
||||
s = to_string(this)
|
||||
searchValue = get_arg(args, 0)
|
||||
replaceValue = get_arg(args, 1)
|
||||
res = ''
|
||||
if not is_callable(replaceValue):
|
||||
replaceValue = to_string(replaceValue)
|
||||
func = False
|
||||
else:
|
||||
func = True
|
||||
# Replace all ( global )
|
||||
if GetClass(searchValue) == '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)
|
||||
call_args = (e.group(), ) + e.groups() + (e.span()[1], s)
|
||||
# convert all types to JS before Js bytecode call...
|
||||
res += to_string(
|
||||
replaceValue.call(
|
||||
this, ensure_js_types(call_args,
|
||||
space=args.space)))
|
||||
else:
|
||||
res += replacement_template(replaceValue, s, e.span(),
|
||||
e.groups())
|
||||
last = e.span()[1]
|
||||
res += s[last:]
|
||||
return res
|
||||
elif GetClass(searchValue) == 'RegExp':
|
||||
e = re.search(searchValue.pat, s)
|
||||
if e is None:
|
||||
return s
|
||||
span = e.span()
|
||||
pars = e.groups()
|
||||
match = e.group()
|
||||
else:
|
||||
match = to_string(searchValue)
|
||||
ind = s.find(match)
|
||||
if ind == -1:
|
||||
return s
|
||||
span = ind, ind + len(match)
|
||||
pars = ()
|
||||
res = s[:span[0]]
|
||||
if func:
|
||||
call_args = (match, ) + pars + (span[1], s)
|
||||
# convert all types to JS before Js bytecode call...
|
||||
res += to_string(
|
||||
replaceValue.call(this,
|
||||
ensure_js_types(call_args,
|
||||
space=args.space)))
|
||||
else:
|
||||
res += replacement_template(replaceValue, s, span, pars)
|
||||
res += s[span[1]:]
|
||||
return res
|
||||
|
||||
def search(this, args):
|
||||
cok(this)
|
||||
string = to_string(this)
|
||||
regexp = get_arg(args, 0)
|
||||
if GetClass(regexp) == 'RegExp':
|
||||
rx = regexp
|
||||
else:
|
||||
rx = args.space.NewRegExp(regexp, '')
|
||||
res = re.search(rx.pat, string)
|
||||
if res is not None:
|
||||
return float(res.span()[0])
|
||||
return -1.
|
||||
|
||||
def slice(this, args):
|
||||
cok(this)
|
||||
s = to_string(this)
|
||||
start = to_int(get_arg(args, 0))
|
||||
length = len(s)
|
||||
end = get_arg(args, 1)
|
||||
end = length if is_undefined(end) else to_int(end)
|
||||
#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[start:end]
|
||||
|
||||
def split(this, args):
|
||||
# its a bit different from re.split!
|
||||
cok(this)
|
||||
s = to_string(this)
|
||||
separator = get_arg(args, 0)
|
||||
limit = get_arg(args, 1)
|
||||
lim = 2**32 - 1 if is_undefined(limit) else to_uint32(limit)
|
||||
if not lim:
|
||||
return args.space.ConstructArray([])
|
||||
if is_undefined(separator):
|
||||
return args.space.ConstructArray([s])
|
||||
len_s = len(s)
|
||||
res = []
|
||||
R = separator if GetClass(separator) == 'RegExp' else to_string(
|
||||
separator)
|
||||
if not len_s:
|
||||
if SplitMatch(s, 0, R) is None:
|
||||
return args.space.ConstructArray([s])
|
||||
return args.space.ConstructArray([])
|
||||
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 args.space.ConstructArray(res)
|
||||
for element in cap:
|
||||
res.append(element)
|
||||
if len(res) == lim:
|
||||
return args.space.ConstructArray(res)
|
||||
res.append(s[p:])
|
||||
return args.space.ConstructArray(res)
|
||||
|
||||
def substring(this, args):
|
||||
cok(this)
|
||||
s = to_string(this)
|
||||
start = to_int(get_arg(args, 0))
|
||||
length = len(s)
|
||||
end = get_arg(args, 1)
|
||||
end = length if is_undefined(end) else to_int(end)
|
||||
fstart = min(max(start, 0), length)
|
||||
fend = min(max(end, 0), length)
|
||||
return s[min(fstart, fend):max(fstart, fend)]
|
||||
|
||||
def substr(this, args):
|
||||
cok(this)
|
||||
#I hate this function and its description in specification
|
||||
r1 = to_string(this)
|
||||
r2 = to_int(get_arg(args, 0))
|
||||
length = get_arg(args, 1)
|
||||
r3 = 10**20 if is_undefined(length) else to_int(length)
|
||||
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, args):
|
||||
cok(this)
|
||||
return to_string(this).lower()
|
||||
|
||||
def toLocaleLowerCase(this, args):
|
||||
cok(this)
|
||||
return to_string(this).lower()
|
||||
|
||||
def toUpperCase(this, args):
|
||||
cok(this)
|
||||
return to_string(this).upper()
|
||||
|
||||
def toLocaleUpperCase(this, args):
|
||||
cok(this)
|
||||
return to_string(this).upper()
|
||||
|
||||
def trim(this, args):
|
||||
cok(this)
|
||||
return to_string(this).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 GetClass(R) == '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):
|
||||
return q + len(R), ()
|
||||
return None, ()
|
||||
@@ -0,0 +1,149 @@
|
||||
from __future__ import unicode_literals
|
||||
from ..conversions import *
|
||||
from ..func_utils 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
|
||||
}
|
||||
|
||||
# parseFloat
|
||||
# parseInt
|
||||
# isFinite
|
||||
# isNaN
|
||||
|
||||
|
||||
def parseInt(this, args):
|
||||
string, radix = get_arg(args, 0), get_arg(args, 1)
|
||||
string = to_string(string).lstrip()
|
||||
sign = 1
|
||||
if string and string[0] in ('+', '-'):
|
||||
if string[0] == '-':
|
||||
sign = -1
|
||||
string = string[1:]
|
||||
r = to_int32(radix)
|
||||
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 float(sign * num)
|
||||
|
||||
|
||||
def parseFloat(this, args):
|
||||
string = get_arg(args, 0)
|
||||
string = to_string(string).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])
|
||||
|
||||
|
||||
def isNaN(this, args):
|
||||
number = get_arg(args, 0)
|
||||
if is_nan(to_number(number)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def isFinite(this, args):
|
||||
number = get_arg(args, 0)
|
||||
num = to_number(number)
|
||||
if is_nan(num) or is_infinity(num):
|
||||
return False
|
||||
return True
|
||||
@@ -0,0 +1,33 @@
|
||||
import pyjsparser
|
||||
from .space import Space
|
||||
from . import fill_space
|
||||
from .byte_trans import ByteCodeGenerator
|
||||
from .code import Code
|
||||
from .simplex import *
|
||||
|
||||
|
||||
pyjsparser.parser.ENABLE_JS2PY_ERRORS = lambda msg: MakeError(u'SyntaxError', unicode(msg))
|
||||
|
||||
def get_js_bytecode(js):
|
||||
a = ByteCodeGenerator(Code())
|
||||
d = pyjsparser.parse(js)
|
||||
a.emit(d)
|
||||
return a.exe.tape
|
||||
|
||||
def eval_js_vm(js, debug=False):
|
||||
a = ByteCodeGenerator(Code(debug_mode=debug))
|
||||
s = Space()
|
||||
a.exe.space = s
|
||||
s.exe = a.exe
|
||||
|
||||
d = pyjsparser.parse(js)
|
||||
|
||||
a.emit(d)
|
||||
fill_space.fill_space(s, a)
|
||||
if debug:
|
||||
from pprint import pprint
|
||||
pprint(a.exe.tape)
|
||||
print()
|
||||
a.exe.compile()
|
||||
|
||||
return a.exe.run(a.exe.space.GlobalObj)
|
||||
@@ -0,0 +1,160 @@
|
||||
from __future__ import unicode_literals
|
||||
import six
|
||||
if six.PY3:
|
||||
basestring = str
|
||||
long = int
|
||||
xrange = range
|
||||
unicode = str
|
||||
|
||||
#Undefined
|
||||
class PyJsUndefined(object):
|
||||
TYPE = 'Undefined'
|
||||
Class = 'Undefined'
|
||||
|
||||
|
||||
undefined = PyJsUndefined()
|
||||
|
||||
|
||||
#Null
|
||||
class PyJsNull(object):
|
||||
TYPE = 'Null'
|
||||
Class = 'Null'
|
||||
|
||||
|
||||
null = PyJsNull()
|
||||
|
||||
Infinity = float('inf')
|
||||
NaN = float('nan')
|
||||
|
||||
UNDEFINED_TYPE = PyJsUndefined
|
||||
NULL_TYPE = PyJsNull
|
||||
STRING_TYPE = unicode if six.PY2 else str
|
||||
NUMBER_TYPE = float
|
||||
BOOLEAN_TYPE = bool
|
||||
|
||||
# exactly 5 simplexes!
|
||||
PRIMITIVES = frozenset(
|
||||
[UNDEFINED_TYPE, NULL_TYPE, STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE])
|
||||
|
||||
TYPE_NAMES = {
|
||||
UNDEFINED_TYPE: 'Undefined',
|
||||
NULL_TYPE: 'Null',
|
||||
STRING_TYPE: 'String',
|
||||
NUMBER_TYPE: 'Number',
|
||||
BOOLEAN_TYPE: 'Boolean',
|
||||
}
|
||||
|
||||
|
||||
def Type(x):
|
||||
# Any -> Str
|
||||
return TYPE_NAMES.get(type(x), 'Object')
|
||||
|
||||
|
||||
def GetClass(x):
|
||||
# Any -> Str
|
||||
cand = TYPE_NAMES.get(type(x))
|
||||
if cand is None:
|
||||
return x.Class
|
||||
return cand
|
||||
|
||||
|
||||
def is_undefined(self):
|
||||
return self is undefined
|
||||
|
||||
|
||||
def is_null(self):
|
||||
return self is null
|
||||
|
||||
|
||||
def is_primitive(self):
|
||||
return type(self) in PRIMITIVES
|
||||
|
||||
|
||||
def is_object(self):
|
||||
return not is_primitive(self)
|
||||
|
||||
|
||||
def is_callable(self):
|
||||
return hasattr(self, 'call')
|
||||
|
||||
|
||||
def is_infinity(self):
|
||||
return self == Infinity or self == -Infinity
|
||||
|
||||
|
||||
def is_nan(self):
|
||||
return self != self # nan!=nan evaluates to True
|
||||
|
||||
|
||||
def is_finite(self):
|
||||
return not (is_nan(self) or is_infinity(self))
|
||||
|
||||
|
||||
class JsException(Exception):
|
||||
def __init__(self, typ=None, message=None, throw=None):
|
||||
if typ is None and message is None and throw is None:
|
||||
# it means its the trasnlator based error (old format), do nothing
|
||||
self._translator_based = True
|
||||
else:
|
||||
assert throw is None or (typ is None
|
||||
and message is None), (throw, typ,
|
||||
message)
|
||||
self._translator_based = False
|
||||
self.typ = typ
|
||||
self.message = message
|
||||
self.throw = throw
|
||||
|
||||
def get_thrown_value(self, space):
|
||||
if self.throw is not None:
|
||||
return self.throw
|
||||
else:
|
||||
return space.NewError(self.typ, self.message)
|
||||
|
||||
def __str__(self):
|
||||
if self._translator_based:
|
||||
if self.mes.Class == 'Error':
|
||||
return self.mes.callprop('toString').value
|
||||
else:
|
||||
return self.mes.to_string().value
|
||||
else:
|
||||
if self.throw is not None:
|
||||
from .conversions import to_string
|
||||
return to_string(self.throw)
|
||||
else:
|
||||
return self.typ + ': ' + self.message
|
||||
|
||||
|
||||
def MakeError(typ, message=u'no info', throw=None):
|
||||
return JsException(typ,
|
||||
unicode(message) if message is not None else message,
|
||||
throw)
|
||||
|
||||
|
||||
def value_from_js_exception(js_exception, space):
|
||||
if js_exception.throw is not None:
|
||||
return js_exception.throw
|
||||
else:
|
||||
return space.NewError(js_exception.typ, js_exception.message)
|
||||
|
||||
|
||||
def js_dtoa(number):
|
||||
if is_nan(number):
|
||||
return u'NaN'
|
||||
elif is_infinity(number):
|
||||
if number > 0:
|
||||
return u'Infinity'
|
||||
return u'-Infinity'
|
||||
elif number == 0.:
|
||||
return u'0'
|
||||
elif abs(number) < 1e-6 or abs(number) >= 1e21:
|
||||
frac, exponent = unicode(repr(float(number))).split('e')
|
||||
# Remove leading zeros from the exponent.
|
||||
exponent = int(exponent)
|
||||
return frac + ('e' if exponent < 0 else 'e+') + unicode(exponent)
|
||||
elif abs(number) < 1e-4: # python starts to return exp notation while we still want the prec
|
||||
frac, exponent = unicode(repr(float(number))).split('e-')
|
||||
base = u'0.' + u'0' * (int(exponent) - 1) + frac.lstrip('-').replace('.', '')
|
||||
return base if number > 0. else u'-' + base
|
||||
elif isinstance(number, long) or number.is_integer(): # dont print .0
|
||||
return unicode(int(number))
|
||||
return unicode(repr(number)) # python representation should be equivalent.
|
||||
@@ -0,0 +1,92 @@
|
||||
from .base import *
|
||||
from .simplex import *
|
||||
|
||||
|
||||
class Space(object):
|
||||
def __init__(self):
|
||||
self.GlobalObj = None
|
||||
self.ctx = None
|
||||
self.byte_generator = None
|
||||
|
||||
self.Number = None
|
||||
self.String = None
|
||||
self.Boolean = None
|
||||
self.RegExp = None
|
||||
self.Object = None
|
||||
self.Array = None
|
||||
self.Function = None
|
||||
|
||||
self.BooleanPrototype = None
|
||||
self.NumberPrototype = None
|
||||
self.StringPrototype = None
|
||||
|
||||
self.FunctionPrototype = None
|
||||
self.ArrayPrototype = None
|
||||
self.RegExpPrototype = None
|
||||
self.DatePrototype = None
|
||||
self.ObjectPrototype = None
|
||||
|
||||
self.ErrorPrototype = None
|
||||
self.EvalErrorPrototype = None
|
||||
self.RangeErrorPrototype = None
|
||||
self.ReferenceErrorPrototype = None
|
||||
self.SyntaxErrorPrototype = None
|
||||
self.TypeErrorPrototype = None
|
||||
self.URIErrorPrototype = None
|
||||
|
||||
self.interpreter = None
|
||||
|
||||
@property
|
||||
def ERROR_TYPES(self):
|
||||
return {
|
||||
'Error': self.ErrorPrototype,
|
||||
'EvalError': self.EvalErrorPrototype,
|
||||
'RangeError': self.RangeErrorPrototype,
|
||||
'ReferenceError': self.ReferenceErrorPrototype,
|
||||
'SyntaxError': self.SyntaxErrorPrototype,
|
||||
'TypeError': self.TypeErrorPrototype,
|
||||
'URIError': self.URIErrorPrototype,
|
||||
}
|
||||
|
||||
def get_global_environment(self):
|
||||
return self.GlobalCtx.variable_environment()
|
||||
|
||||
def NewObject(self):
|
||||
return PyJsObject(self.ObjectPrototype)
|
||||
|
||||
def NewFunction(self, code, ctx, params, name, is_declaration,
|
||||
definitions):
|
||||
return PyJsFunction(
|
||||
code,
|
||||
ctx,
|
||||
params,
|
||||
name,
|
||||
self,
|
||||
is_declaration,
|
||||
definitions,
|
||||
prototype=self.FunctionPrototype)
|
||||
|
||||
def NewDate(self, value):
|
||||
return PyJsDate(value, self.DatePrototype)
|
||||
|
||||
def NewArray(self, length=0):
|
||||
return PyJsArray(length, self.ArrayPrototype)
|
||||
|
||||
def NewError(self, typ, message):
|
||||
return PyJsError(message, self.ERROR_TYPES[typ])
|
||||
|
||||
def NewRegExp(self, body, flags):
|
||||
return PyJsRegExp(body, flags, self.RegExpPrototype)
|
||||
|
||||
def ConstructArray(self, py_arr):
|
||||
''' note py_arr elems are NOT converted to PyJs types!'''
|
||||
arr = self.NewArray(len(py_arr))
|
||||
arr._init(py_arr)
|
||||
return arr
|
||||
|
||||
def ConstructObject(self, py_obj):
|
||||
''' note py_obj items are NOT converted to PyJs types! '''
|
||||
obj = self.NewObject()
|
||||
for k, v in py_obj.items():
|
||||
obj.put(unicode(k), v)
|
||||
return obj
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user