import re import os import base64 import subprocess ROOT = os.path.dirname(os.path.abspath(__file__)) SRC = ROOT + "/src" OUT = ROOT + "/jet" ASSETS = ROOT + "/assets" IMG_CACHE = ROOT + "/assets/images" os.makedirs(OUT, exist_ok=True) os.makedirs(IMG_CACHE, exist_ok=True) IMG_MIME = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".svg": "image/svg+xml", ".webp": "image/webp", ".ico": "image/x-icon", } def resolve_images(html): """Find any remaining /images/ reference (src="..." or url('...')) and inline it as a data: URI, fetching+caching from barant.com on first use - APIServer has no static-file serving, same reason the chrome logo and page CSS/fonts needed inlining earlier this session.""" def repl(m): path = m.group(1) # e.g. images/grid.png fname = path.split("/")[-1] ext = os.path.splitext(fname)[1].lower() mime = IMG_MIME.get(ext, "application/octet-stream") local_path = IMG_CACHE + "/" + fname if not os.path.exists(local_path): subprocess.run(["curl", "-s", "-o", local_path, "https://barant.com/" + path], check=True) return data_uri(local_path, mime) html = re.sub(r'/images/([A-Za-z0-9_.-]+)(?=["\'])', repl, html) return html def read(path): with open(path, "r", encoding="utf-8", errors="replace") as f: return f.read() def data_uri(path, mime): with open(path, "rb") as f: b64 = base64.b64encode(f.read()).decode("ascii") return "data:" + mime + ";base64," + b64 # --------------------------------------------------------------------------- # linkbutton -> plain . The old SESSIONCTL PAGE/PROCESS form-postback model # doesn't exist in this static conversion (no shared
state machine, no # session), so a JS document.forms[0]... postback would just be dead code - # every page is now its own real #api GET/POST route, so a real href is both # simpler and more correct for this architecture. # --------------------------------------------------------------------------- LINKBUTTON_RE = re.compile(r']*)>(.*?)', re.IGNORECASE | re.DOTALL) def linkbutton_attr(attrs, name): m = re.search(name + r'="([^"]*)"', attrs, re.IGNORECASE) return m.group(1) if m else "" def flatten_linkbuttons(html): def repl(m): attrs, content = m.group(1), m.group(2) page = linkbutton_attr(attrs, "page") cls = linkbutton_attr(attrs, "class") if not page: # process-only actions (logout, addtocart, etc.) have no static # target in this conversion - render as inert text, not a link. return content cls_attr = (' class="%s"' % cls) if cls else "" return '%s' % (page, cls_attr, content) return LINKBUTTON_RE.sub(repl, html) # --------------------------------------------------------------------------- # sectionframe -> its barant.tag expansion. # --------------------------------------------------------------------------- SECTIONFRAME_RE = re.compile(r'(.*?)', re.IGNORECASE | re.DOTALL) def flatten_sectionframes(html): def repl(m): title, content = m.group(1), m.group(2) heading = ('
%s
' % title) if title else "" return '
%s
%s
' % (heading, content) # Run twice to catch one level of nesting (not expected, but cheap insurance). html = SECTIONFRAME_RE.sub(repl, html) html = SECTIONFRAME_RE.sub(repl, html) return html def flatten_tabs(menu_bar_html): def repl(m): content = m.group(1) return '%s ' % content return re.sub(r'(.*?)', repl, menu_bar_html, flags=re.IGNORECASE | re.DOTALL) def drop_tag_block(html, tag): """Remove a ... definition block entirely.""" pattern = re.compile(r'' % tag, re.IGNORECASE | re.DOTALL) return pattern.sub('', html) def strip_setenv(html): return re.sub(r'.*?', '', html, flags=re.IGNORECASE | re.DOTALL) def strip_retain(html): """ is a SESSIONCTL.tag helper that echoes a posted form field back as a hidden input - meaningless with no /session in this static conversion, and (since it isn't a tag jetc's fallback interpreter knows about) would otherwise render as literal untouched text rather than erroring, which is worse than just dropping it.""" return re.sub(r'', '', html, flags=re.IGNORECASE) # Pages that need a live product-row lookup - (SESSIONCTL-only, # not implemented by the new JetCore port) is replaced with the real # tag it should have used all along. host="localhost" matches # barant.com's own config.cfg verbatim: MySQL is only reachable from the # server itself, so a locally-run .jtc genuinely can't connect here - by # design, per instruction, this fails at runtime exactly as if the # database were down, and APIServer's existing catch(coreutils::Exception) # path (ApiSession.cpp) already turns that into a clean 500, not a crash. # # The real credential is deliberately NOT a literal here - it's supplied # via the BARANT_DB_PASSWORD env var at conversion time, since the .jet # files this produces for these pages get gitignored rather than committed # (see .gitignore) precisely so the password never lands in git history. MYSQL_PAGES = {"vst", "modular1", "modular1_patches"} DB_PASSWORD = os.environ.get("BARANT_DB_PASSWORD", "") MYSQL_OPEN = '' % DB_PASSWORD MYSQL_CLOSE = '' def selfclose_set(html): """Old JET shorthand allows with no closing tag and no container - jetc's own real codegen requires this to be self-closed () or it reads as having an (empty) container, which conflicts with value= at runtime ("set tag cannot have both value and a container").""" return re.sub(r']*[^/])>', r'', html, flags=re.IGNORECASE) # --------------------------------------------------------------------------- # Build the shared CSS (same assets already used for barant.jet). # --------------------------------------------------------------------------- main_css = read(ASSETS + "/main.css") control_css = read(ASSETS + "/control.css") for old, new_mime, path in [ ("url('https://barant.com/fonts/AvenirLTStd-Light.ttf')", "font/ttf", ASSETS + "/AvenirLTStd-Light.ttf"), ("url('https://barant.com/fonts/jackinput.TTF')", "font/ttf", ASSETS + "/jackinput.TTF"), ("url('https://barant.com/fonts/Exo2-RegularCondensed.otf')", "font/otf", ASSETS + "/Exo2-RegularCondensed.otf"), ]: main_css = main_css.replace(old, "url('" + data_uri(path, new_mime) + "')") STYLE_BLOCK = "" # --------------------------------------------------------------------------- # Build the nav bar (menu.bar), flattened. # --------------------------------------------------------------------------- menu_bar = read(SRC + "/menu.bar") menu_bar = drop_tag_block(menu_bar, "tab") menu_bar = menu_bar.replace('width: 100%">', 'width="100%">') # same stray-quote bug as barant.jet's source menu_bar = flatten_tabs(menu_bar) menu_bar = flatten_linkbuttons(menu_bar) # Session/login-gated nav items: CART only shows with an active cart (never, # here); ACCOUNT/ADMIN only show logged in (never, here) - only the LOGIN # fallback applies for an anonymous visitor, which is this conversion's only # supported state (no session backend exists in this static conversion). menu_bar = re.sub(r'', '', menu_bar, flags=re.DOTALL) menu_bar = re.sub( r'\s*(.*?LOGIN.*?.*?)', r'\1', menu_bar, flags=re.DOTALL) NAV_HTML = menu_bar # --------------------------------------------------------------------------- # Build the default sidebar (opensource.menu; cart.menu/login.menu are both # session-gated and always empty for an anonymous visitor, so they're # omitted rather than included-and-always-false). # --------------------------------------------------------------------------- opensource_menu = read(SRC + "/opensource.menu") opensource_menu = flatten_sectionframes(opensource_menu) opensource_menu = flatten_linkbuttons(opensource_menu) SIDEBAR_HTML = opensource_menu # --------------------------------------------------------------------------- # Build the page chrome from mainpagetemplate.temp: the part of