Last active
April 27, 2023 16:27
-
-
Save worldoptimizer/68dcb3a3d39f46a7e3eabd59e7afddff to your computer and use it in GitHub Desktop.
Hype Template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# HypeTemplate.hype-export.py | |
# Export Script for Tumult Hype to produce offer a generic template system | |
# | |
# MIT License | |
# Copyright (c) 2022 Max Ziebell | |
# | |
import argparse | |
import json | |
import sys | |
import distutils.util | |
import os | |
# update info | |
# current_script_version = 1 | |
# version_info_url = ".../latest_script_version.txt" # only returns a version number | |
# download_url = "..." # gives a user info to download and install | |
# minimum_update_check_duration_in_seconds = 60 * 60 * 24 # once a day | |
# defaults_bundle_identifier = "com.tumult.Hype2.hype-export.HypeTemplate" | |
import sys | |
from os import path | |
class HypeURLType: | |
Unknown = 0 | |
HypeJS = 1 | |
Resource = 2 | |
Link = 3 | |
ResourcesFolder = 4 | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument('--hype_version') | |
parser.add_argument('--hype_build') | |
parser.add_argument('--export_uid') | |
parser.add_argument('--get_options', action='store_true') | |
parser.add_argument('--replace_url') | |
parser.add_argument('--url_type') | |
parser.add_argument('--is_reference', default="False") | |
parser.add_argument('--should_preload') | |
parser.add_argument('--modify_staging_path') | |
parser.add_argument('--destination_path') | |
parser.add_argument('--export_info_json_path') | |
parser.add_argument('--is_preview', default="False") | |
parser.add_argument('--check_for_updates', action='store_true') | |
args, unknown = parser.parse_known_args() | |
## --get_options | |
## return arguments to be presented in the Hype UI as a dictionary: | |
## 'export_options' is a dictionary of key/value pairs that make modifications to Hype's export/preview system. Some useful ones: | |
## 'exportShouldInlineHypeJS' : boolean | |
## 'exportShouldInlineDocumentLoader' : boolean | |
## 'exportShouldUseExternalRuntime' : boolean | |
## 'exportExternalRuntimeURL' : string | |
## 'exportShouldSaveHTMLFile' : boolean | |
## 'indexTitle' : string | |
## 'exportShouldBustBrowserCaching' : boolean | |
## 'exportShouldIncludeTextContents' : boolean | |
## 'exportShouldIncludePIE' : boolean | |
## 'exportSupportInternetExplorer6789' : boolean | |
## 'initialSceneIndex' : integer | |
## 'save_options' is a dictionary of key/value pairs that for determining when/how to export. valid keys: | |
## 'file_extension' : the final extension when exported (ex. "zip") | |
## 'allows_export' : should show up in the File > Export as HTML5 menu and Advanced Export | |
## 'allows_preview' : should show up in the Preview menu, if so --is_preview True is passed into the --modify_staging_path call | |
## 'document_arguments' should be an array of keys, these will be passed to subsequent calls via --key value | |
## 'extra_actions' should be an array of dictionaries | |
## 'label': string that is the user presented name | |
## 'function': javascript function to call if this action is triggered, just the name of it | |
## 'arguments': array of dictionaries that represent arguments passed into the function | |
## 'label': string that is presented to Hype UI | |
## 'type': string that is either "String" (will be quoted and escaped) or "Expression" (passed directly to function argument as-is) | |
if args.get_options: | |
def export_options(): | |
return { | |
"exportShouldInlineHypeJS" : False, | |
"exportShouldInlineDocumentLoader" : False, | |
#"exportShouldUseExternalRuntime" : False, | |
#"exportExternalRuntimeURL" : "", | |
"exportShouldSaveHTMLFile" : True, | |
"exportShouldNameAsIndexDotHTML" : True, | |
#"indexTitle" : "", | |
"exportShouldBustBrowserCaching" : False, | |
"exportShouldIncludeTextContents" : False, | |
"exportShouldIncludePIE" : False, | |
"exportSupportInternetExplorer6789" : False, | |
"exportShouldSaveRestorableDocument" : False, | |
} | |
def document_arguments(): | |
return [ | |
"Template name", | |
] | |
def save_options(): | |
return { | |
"file_extension" : "zip", | |
"allows_export" : True, | |
"allows_preview" : True, | |
} | |
options = { | |
"export_options" : export_options(), | |
"document_arguments" : document_arguments(), | |
"save_options" : save_options(), | |
"min_hype_build_version" : "574", # build number (ex "574") and *not* marketing version (ex "3.6.0") | |
#"max_hype_build_version" : "10000", # build number (ex "574") and *not* marketing version (ex "3.6.0") | |
} | |
exit_with_result(options) | |
## --replace_url [url] --url_type [HypeURLType] --is_reference [True|False] --should_preload [None|True|False] --is_preview [True|False] --export_uid [identifier] | |
## return a dictionary with "url", "is_reference", and optional "should_preload" keys | |
## if HypeURLType.ResourcesFolder, you can set the url to "." so there is no .hyperesources folder and everything | |
## is placed next to the .html file | |
## should_preload may be None type in cases where it won't be used | |
elif args.replace_url != None: | |
url_info = {} | |
url_info['is_reference'] = bool(distutils.util.strtobool(args.is_reference)) | |
if args.should_preload != None: | |
url_info['should_preload'] = bool(distutils.util.strtobool(args.should_preload)) | |
if int(args.url_type) == HypeURLType.ResourcesFolder: | |
url_info['url'] = "." | |
else: | |
url_info['url'] = args.replace_url | |
exit_with_result(url_info) | |
## --modify_staging_path [filepath] --destination_path [filepath] --export_info_json_path [filepath] --is_preview [True|False] --export_uid [identifier] | |
## return True if you moved successfully to the destination_path, otherwise don't return anything and Hype will make the move | |
## make any changes you'd like before the save is complete | |
## for example, if you are a zip, you need to zip and write to the destination_path | |
## or you may want to inject items into the HTML file | |
## if it is a preview, you shouldn't do things like zip it up, as Hype needs to know where the index.html file is | |
## export_info_json_path is a json object holding keys: | |
## html_filename: string that is the filename for the html file which you may want to inject changes into | |
## main_container_width: number representing the width of the document in pixels | |
## main_container_height: number representing the height of the document in pixels | |
## document_arguments: dictionary of key/value pairs based on what was passed in from the earlier --get_options call | |
## extra_actions: array of dictionaries for all usages of the extra actions. There is no guarantee these all originated from this script or version. | |
## function: string of function name (as passed in from --get_options) | |
## arguments: array of strings | |
elif args.modify_staging_path != None: | |
import os | |
import string | |
import re | |
import random | |
is_preview = bool(distutils.util.strtobool(args.is_preview)) | |
# read export_info.json file | |
export_info_file = open(args.export_info_json_path) | |
export_info = json.loads(export_info_file.read()) | |
export_info_file.close() | |
# ---------------------------------- | |
# TEMPLATE FEATURE | |
# ---------------------------------- | |
# make sure we have the key | |
if "Template name" in export_info["document_arguments"]: | |
template_name = export_info["document_arguments"]["Template name"] | |
#set default if not provided | |
if template_name == "": | |
template_name = "HypeTemplate" | |
# start with no template content | |
template_content = None | |
# look for template content in project | |
for root, dirs, files in os.walk(args.modify_staging_path): | |
for file in files: | |
if file.endswith(template_name+'.html'): | |
template_content = open(root + '/' + file).read() | |
os.remove(root + '/' + file) | |
break | |
# look for template content in export script folder | |
if template_content == None: | |
extension_folder = path.dirname(path.abspath(str(sys.modules['__main__'].__file__))) | |
for root, dirs, files in os.walk(extension_folder): | |
for file in files: | |
if file.endswith(template_name+'.html'): | |
template_content = open(root + '/' + file).read() | |
break | |
# proceed if we have template content | |
if template_content != None: | |
# load existing html | |
index_path = os.path.join(args.modify_staging_path, export_info['html_filename'].encode("utf-8")) | |
index_contents = None | |
with open(index_path, 'r') as target_file: | |
index_contents = target_file.read() | |
if index_contents == None: | |
return | |
# Extract infos from html | |
html_title = re.search(r'<title>(.*)</title>', index_contents).group(1) | |
hype_div = re.search(r'<div id="(.*)" class="HYPE_document" style="(.*)">', index_contents) | |
hype_div_id= hype_div.group(1) | |
hype_div_styles= hype_div.group(2) | |
custom_head_html = re.search(r'<!-- copy these lines to your document head: -->(.*?)<!-- end copy -->', index_contents, re.DOTALL).group(1) | |
viewport_meta_regex = re.compile(r'<meta name="viewport" content="(.*?)" />') | |
viewport_meta_tag = viewport_meta_regex.search(custom_head_html).group(0) | |
custom_head_html = custom_head_html.replace(viewport_meta_tag, '') | |
document_loader_html = re.search(r'<!-- copy these lines to your document: -->(.*?)<!-- end copy -->', index_contents, re.DOTALL).group(1) | |
document_loader_script_src = re.search(r'<script type="text/javascript" charset="utf-8" src="(.*)"></script>', document_loader_html).group(1) | |
document_loader_filename = re.search(r'/(.*_hype_generated_script\.js)', document_loader_script_src).group(1) | |
document_loader_path = re.search(r'(.*/)', document_loader_script_src).group(1) | |
hype_document_style_width_value = re.search(r'width:(.*?)(px|%)', hype_div_styles).group(1) | |
hype_document_style_width_unit = re.search(r'width:(.*?)(px|%)', hype_div_styles).group(2) | |
hype_document_style_height_value = re.search(r'height:(.*?)(px|%)', hype_div_styles).group(1) | |
hype_document_style_height_unit = re.search(r'height:(.*?)(px|%)', hype_div_styles).group(2) | |
hype_document_style_width = hype_document_style_width_value + hype_document_style_width_unit | |
hype_document_style_height = hype_document_style_height_value + hype_document_style_height_unit | |
# remove any reference to bundle in head html | |
custom_head_html = re.sub(r'<script type="text/javascript" src=".*HypeTemplate.js"></script>', '', custom_head_html) | |
# substitute in template | |
template_engine = string.Template(template_content) | |
template_content = template_engine.safe_substitute({ | |
'containerWidth': export_info['main_container_width'], | |
'containerHeight': export_info['main_container_height'], | |
'htmlFilename': export_info['html_filename'], | |
'customHeadHTML': custom_head_html, | |
'documentLoaderHTML': document_loader_html, | |
'title': html_title, | |
'hypeDocumentStyles': hype_div_styles, | |
'hypeDocumentId': hype_div_id, | |
'documentLoaderScriptSource': document_loader_script_src, | |
'documentLoaderScriptPath': document_loader_path, | |
'documentLoaderFilename': document_loader_filename, | |
'hypeDocumentStyleWidth': hype_document_style_width, | |
'hypeDocumentStyleHeight': hype_document_style_height, | |
'hypeDocumentStyleWidthValue': hype_document_style_width_value, | |
'hypeDocumentStyleHeightValue': hype_document_style_height_value, | |
'hypeDocumentStyleWidthUnit': hype_document_style_width_unit, | |
'hypeDocumentStyleHeightUnit': hype_document_style_height_unit, | |
'cacheBuster': str(random.randint(10000,99999)), | |
'viewportMetaTag': viewport_meta_tag, | |
# can be extended ... | |
}) | |
# write substituted template back to index | |
with open(index_path, 'w') as target_file: | |
target_file.write(template_content) | |
# ---------------------------------- | |
# BUNDLED JAVASCRIPT FEATURE | |
# ---------------------------------- | |
# load bundled javascript content and inject it in document loader | |
hype_template_bundled_script_content = None | |
for root, dirs, files in os.walk(args.modify_staging_path): | |
for file in files: | |
if file == "HypeTemplate.js": | |
hype_template_bundled_script = os.path.join(root, file) | |
hype_template_bundled_script_content = open(hype_template_bundled_script).read() | |
os.remove(hype_template_bundled_script) | |
break | |
if hype_template_bundled_script_content is not None: | |
for root, dirs, files in os.walk(args.modify_staging_path): | |
for file in files: | |
if file.endswith("_hype_generated_script.js"): | |
hype_generated_script = os.path.join(root, file) | |
with open(hype_generated_script, "r") as f: | |
hype_generated_script_content = f.read() | |
with open(hype_generated_script, "w") as f: | |
f.write(hype_template_bundled_script_content + hype_generated_script_content) | |
break | |
import shutil | |
shutil.rmtree(args.destination_path, ignore_errors=True) | |
if is_preview == True: | |
shutil.move(args.modify_staging_path, args.destination_path) | |
exit_with_result(True) | |
else: | |
zip(args.modify_staging_path, args.destination_path) | |
exit_with_result(True) | |
## --check_for_updates | |
## return a dictionary with "url", "from_version", and "to_version" keys if there is an update, otherwise don't return anything and exit | |
## it is your responsibility to decide how often to check | |
elif args.check_for_updates: | |
import subprocess | |
import urllib2 | |
last_check_timestamp = None | |
try: | |
last_check_timestamp = subprocess.check_output(["defaults", "read", defaults_bundle_identifier, "last_check_timestamp"]).strip() | |
except: | |
pass | |
try: | |
timestamp_now = subprocess.check_output(["date", "+%s"]).strip() | |
if (last_check_timestamp == None) or ((int(timestamp_now) - int(last_check_timestamp)) > minimum_update_check_duration_in_seconds): | |
subprocess.check_output(["defaults", "write", defaults_bundle_identifier, "last_check_timestamp", timestamp_now]) | |
request = urllib2.Request(version_info_url, headers={'User-Agent' : "Magic Browser"}) | |
latest_script_version = int(urllib2.urlopen(request).read().strip()) | |
if latest_script_version > current_script_version: | |
exit_with_result({"url" : download_url, "from_version" : str(current_script_version), "to_version" : str(latest_script_version)}) | |
except: | |
pass | |
# UTILITIES | |
# communicate info back to Hype | |
# uses delimiter (20 equal signs) so any above printing doesn't interfere with json data | |
def exit_with_result(result): | |
import sys | |
print "====================" | |
print json.dumps({"result" : result}) | |
sys.exit(0) | |
# from http://stackoverflow.com/questions/14568647/create-zip-in-python | |
def zip(src, dst): | |
import os | |
import zipfile | |
zf = zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) | |
abs_src = os.path.abspath(src) | |
for dirname, subdirs, files in os.walk(src): | |
for filename in files: | |
absname = os.path.abspath(os.path.join(dirname, filename)) | |
arcname = absname[len(abs_src) + 1:] | |
zf.write(absname, arcname) | |
zf.close() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Just a quick suggestion to change html_titel to html_title