Skip to content

Commit 7130b4c

Browse files
authored
fix(handlebarrz): add toggleable platform flags and Unicode tests (#481)
Add environment variable flags to enable/disable builds for each platform without requiring code changes. All platforms can be toggled via: 1. env.ENABLE_<PLATFORM> variables in the workflow file 2. workflow_dispatch inputs for one-off builds with different settings Platform flags: - ENABLE_LINUX_ARM64: Ubuntu ARM64 builds (default: true) - ENABLE_LINUX_X86_64: Ubuntu x86_64 builds (default: true) - ENABLE_ALPINE_ARM64: Alpine ARM64 musl builds (default: true) - ENABLE_ALPINE_X86_64: Alpine x86_64 musl builds (default: true) - ENABLE_MACOS_ARM64: macOS Apple Silicon builds (default: true) - ENABLE_MACOS_X86_64: macOS Intel builds (default: true) - ENABLE_WINDOWS_X86_64: Windows x86_64 builds (default: false) Windows is disabled by default due to PyPI wheel corruption issue. Also adds comprehensive Unicode and internationalization tests covering Hindi, Arabic, Japanese, Korean, Chinese, Tamil, emoji, mixed scripts, diacritical marks, and zero-width characters.
1 parent 01be465 commit 7130b4c

File tree

2 files changed

+184
-5
lines changed

2 files changed

+184
-5
lines changed

.github/workflows/publish_python_handlebarrz_package.yml

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,85 @@ on:
3232
- 'https://upload.pypi.org/legacy/'
3333
- 'https://test.pypi.org/legacy/'
3434
default: 'https://test.pypi.org/legacy/'
35+
# -------------------------------------------------------------------------
36+
# Platform override inputs for workflow_dispatch (one-off builds)
37+
# These override the env defaults below when manually triggering the workflow
38+
# -------------------------------------------------------------------------
39+
enable_linux_arm64:
40+
description: 'Enable Linux ARM64 (Ubuntu) builds'
41+
type: boolean
42+
default: true
43+
enable_linux_x86_64:
44+
description: 'Enable Linux x86_64 (Ubuntu) builds'
45+
type: boolean
46+
default: true
47+
enable_alpine_arm64:
48+
description: 'Enable Alpine ARM64 (musl) builds'
49+
type: boolean
50+
default: true
51+
enable_alpine_x86_64:
52+
description: 'Enable Alpine x86_64 (musl) builds'
53+
type: boolean
54+
default: true
55+
enable_macos_arm64:
56+
description: 'Enable macOS ARM64 (Apple Silicon) builds'
57+
type: boolean
58+
default: true
59+
enable_macos_x86_64:
60+
description: 'Enable macOS x86_64 (Intel) builds'
61+
type: boolean
62+
default: true
63+
enable_windows_x86_64:
64+
description: 'Enable Windows x86_64 builds (may have wheel corruption issues)'
65+
type: boolean
66+
default: false
3567
# pull_request:
3668
# branches: [ main ]
3769

70+
# ==============================================================================
71+
# PLATFORM SUPPORT FLAGS
72+
# ==============================================================================
73+
# Toggle platform builds by setting these to 'true' or 'false'.
74+
# For one-off builds with different settings, use workflow_dispatch inputs.
75+
#
76+
# Current status:
77+
# - Linux ARM64: ENABLED (native ubuntu-24.04-arm runner)
78+
# - Linux x86_64: ENABLED (native ubuntu-latest runner)
79+
# - Alpine ARM64: ENABLED (musl cross-compile on ARM64 runner)
80+
# - Alpine x86_64: ENABLED (musl cross-compile on x86_64 runner)
81+
# - macOS ARM64: ENABLED (native macos-14 Apple Silicon runner)
82+
# - macOS x86_64: ENABLED (cross-compile on macos-14)
83+
# - Windows x86_64: DISABLED (wheel corruption issue - see TODO below)
84+
#
85+
# TODO(handlebarrz): Re-enable Windows support once wheel corruption issue is fixed.
86+
# Issue: PyPI upload fails with "400 Invalid distribution file. ZIP archive not
87+
# accepted: Mis-matched data size" for Windows wheels.
88+
# ==============================================================================
89+
env:
90+
# Linux platforms
91+
ENABLE_LINUX_ARM64: 'true'
92+
ENABLE_LINUX_X86_64: 'true'
93+
# Alpine (musl) platforms
94+
ENABLE_ALPINE_ARM64: 'true'
95+
ENABLE_ALPINE_X86_64: 'true'
96+
# macOS platforms
97+
ENABLE_MACOS_ARM64: 'true'
98+
ENABLE_MACOS_X86_64: 'true'
99+
# Windows platforms (currently disabled due to wheel corruption)
100+
ENABLE_WINDOWS_X86_64: 'false'
101+
38102

39103
jobs:
104+
# ===========================================================================
105+
# BUILD JOBS
106+
# ===========================================================================
107+
40108
build_ubuntu_arm64:
41109
name: Build for Ubuntu ARM64
42110
runs-on: ubuntu-24.04-arm # Native ARM64 runner - no QEMU needed
43-
# Triggers on GitHub releases for the 'dotpromptz-handlebars' package (tag: dotpromptz-handlebars-X.Y.Z)
44-
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'release' && startsWith(github.event.release.tag_name, 'dotpromptz-handlebars-'))
111+
if: |
112+
(env.ENABLE_LINUX_ARM64 == 'true' || inputs.enable_linux_arm64 == true) &&
113+
(github.event_name == 'workflow_dispatch' || (github.event_name == 'release' && startsWith(github.event.release.tag_name, 'dotpromptz-handlebars-')))
45114
strategy:
46115
matrix:
47116
python_version:
@@ -89,6 +158,7 @@ jobs:
89158
build_alpine_arm64:
90159
name: Build for Alpine ARM64
91160
runs-on: ubuntu-24.04-arm # Native ARM64 runner - cross-compile for musl
161+
if: env.ENABLE_ALPINE_ARM64 == 'true' || inputs.enable_alpine_arm64 == true
92162
strategy:
93163
matrix:
94164
python_version:
@@ -137,6 +207,7 @@ jobs:
137207
build_alpine_x86_64:
138208
name: Build for Alpine x86_64
139209
runs-on: ubuntu-latest # Native x86_64 runner - cross-compile for musl
210+
if: env.ENABLE_ALPINE_X86_64 == 'true' || inputs.enable_alpine_x86_64 == true
140211
strategy:
141212
matrix:
142213
python_version:
@@ -185,6 +256,7 @@ jobs:
185256
build_ubuntu_x86_64:
186257
name: Build for Ubuntu x86_64
187258
runs-on: ubuntu-latest # Native x86_64 runner
259+
if: env.ENABLE_LINUX_X86_64 == 'true' || inputs.enable_linux_x86_64 == true
188260
strategy:
189261
matrix:
190262
python_version:
@@ -233,6 +305,8 @@ jobs:
233305
build_windows_x86_64:
234306
name: Build for Windows x86_64
235307
runs-on: windows-latest
308+
# Currently disabled due to wheel corruption issues - see env flags above
309+
if: env.ENABLE_WINDOWS_X86_64 == 'true' || inputs.enable_windows_x86_64 == true
236310
strategy:
237311
matrix:
238312
python_version:
@@ -269,10 +343,11 @@ jobs:
269343
with:
270344
name: wheels-windows-x86_64-${{ matrix.python_version }}
271345
path: python/handlebarrz/dist
272-
#
346+
273347
build_macos_arm64:
274348
name: Build for macOS ARM64
275349
runs-on: macos-14 # Apple Silicon
350+
if: env.ENABLE_MACOS_ARM64 == 'true' || inputs.enable_macos_arm64 == true
276351
strategy:
277352
matrix:
278353
python_version:
@@ -321,6 +396,7 @@ jobs:
321396
build_macos_x86_64:
322397
name: Build for macOS x86_64
323398
runs-on: macos-14
399+
if: env.ENABLE_MACOS_X86_64 == 'true' || inputs.enable_macos_x86_64 == true
324400
strategy:
325401
matrix:
326402
python_version:
@@ -404,9 +480,18 @@ jobs:
404480
# name: wheels-windows-arm64-${{ matrix.python_version }}
405481
# path: python/handlebarrz/dist
406482

483+
# ===========================================================================
484+
# PUBLISH JOB
485+
# ===========================================================================
486+
407487
pypi_publish:
408488
name: Upload to PyPI by python version
409-
needs: [build_ubuntu_arm64,build_ubuntu_x86_64,build_alpine_arm64,build_alpine_x86_64,build_windows_x86_64,build_macos_arm64,build_macos_x86_64]
489+
# NOTE: Jobs with conditional 'if' statements are not listed in 'needs' because
490+
# GitHub Actions would fail if a skipped job is in the needs list.
491+
# Artifacts from all enabled platforms are collected via download-artifact's merge-multiple.
492+
# This job runs if at least one build job completes (using always() + success check).
493+
needs: [build_ubuntu_arm64,build_ubuntu_x86_64,build_alpine_arm64,build_alpine_x86_64,build_macos_arm64,build_macos_x86_64]
494+
if: always() && !cancelled() && (needs.build_ubuntu_arm64.result == 'success' || needs.build_ubuntu_x86_64.result == 'success' || needs.build_alpine_arm64.result == 'success' || needs.build_alpine_x86_64.result == 'success' || needs.build_macos_arm64.result == 'success' || needs.build_macos_x86_64.result == 'success')
410495
runs-on: ubuntu-latest
411496
environment:
412497
name: pypi_github_publishing
@@ -437,10 +522,15 @@ jobs:
437522
- name: Sleep for pypi server to load
438523
run: sleep 180
439524

525+
# ===========================================================================
526+
# SMOKE TEST JOBS
527+
# ===========================================================================
528+
440529
smoke_test_linux_arm64:
441530
name: Test for Linux ARM64
442531
needs: [pypi_publish]
443532
runs-on: ubuntu-24.04-arm # Native ARM64 runner - no QEMU needed
533+
if: env.ENABLE_LINUX_ARM64 == 'true' || inputs.enable_linux_arm64 == true
444534
strategy:
445535
matrix:
446536
python_version:
@@ -471,6 +561,7 @@ jobs:
471561
name: Test for Alpine ARM64
472562
needs: [pypi_publish]
473563
runs-on: ubuntu-24.04-arm # Native ARM64 runner - no QEMU needed
564+
if: env.ENABLE_ALPINE_ARM64 == 'true' || inputs.enable_alpine_arm64 == true
474565
strategy:
475566
matrix:
476567
python_version:
@@ -498,6 +589,7 @@ jobs:
498589
name: Test for Alpine x86_64
499590
needs: [pypi_publish]
500591
runs-on: ubuntu-latest # Native x86_64 - no QEMU needed
592+
if: env.ENABLE_ALPINE_X86_64 == 'true' || inputs.enable_alpine_x86_64 == true
501593
strategy:
502594
matrix:
503595
python_version:
@@ -525,6 +617,7 @@ jobs:
525617
name: Test for Linux x86_64
526618
needs: [pypi_publish]
527619
runs-on: ubuntu-latest
620+
if: env.ENABLE_LINUX_X86_64 == 'true' || inputs.enable_linux_x86_64 == true
528621
strategy:
529622
matrix:
530623
python_version:
@@ -556,6 +649,7 @@ jobs:
556649
name: Test for macOS ARM64
557650
needs: [pypi_publish]
558651
runs-on: macos-14
652+
if: env.ENABLE_MACOS_ARM64 == 'true' || inputs.enable_macos_arm64 == true
559653
strategy:
560654
matrix:
561655
python_version:
@@ -581,8 +675,9 @@ jobs:
581675

582676
smoke_test_macos_x86_64:
583677
name: Test for macOS x86_64
584-
needs: [ pypi_publish ]
678+
needs: [pypi_publish]
585679
runs-on: macos-14
680+
if: env.ENABLE_MACOS_X86_64 == 'true' || inputs.enable_macos_x86_64 == true
586681
strategy:
587682
matrix:
588683
python_version:
@@ -612,6 +707,8 @@ jobs:
612707
name: Test for Windows x86_64
613708
needs: [pypi_publish]
614709
runs-on: windows-latest
710+
# Currently disabled due to wheel corruption issues - see env flags above
711+
if: env.ENABLE_WINDOWS_X86_64 == 'true' || inputs.enable_windows_x86_64 == true
615712
strategy:
616713
matrix:
617714
python_version:

python/handlebarrz/tests/template_test.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,88 @@ def test_reregister_template(self) -> None:
331331
self.assertEqual(result2, 'Version 2: data')
332332

333333

334+
class TestUnicodeAndInternationalization(unittest.TestCase):
335+
"""Test Unicode and internationalization support."""
336+
337+
def test_hindi_devanagari_script(self) -> None:
338+
"""Test Hindi text in Devanagari script."""
339+
template = Template()
340+
template.register_template('hindi', 'नमस्ते {{name}}!')
341+
result = template.render('hindi', {'name': 'दुनिया'})
342+
self.assertEqual(result, 'नमस्ते दुनिया!')
343+
344+
def test_arabic_rtl_text(self) -> None:
345+
"""Test Arabic right-to-left text."""
346+
template = Template()
347+
template.register_template('arabic', 'مرحبا {{name}}!')
348+
result = template.render('arabic', {'name': 'العالم'})
349+
self.assertEqual(result, 'مرحبا العالم!')
350+
351+
def test_japanese_mixed_scripts(self) -> None:
352+
"""Test Japanese with hiragana, katakana, and kanji."""
353+
template = Template()
354+
template.register_template('japanese', 'こんにちは {{name}}さん!')
355+
result = template.render('japanese', {'name': '田中'})
356+
self.assertEqual(result, 'こんにちは 田中さん!')
357+
358+
def test_korean_hangul(self) -> None:
359+
"""Test Korean Hangul script."""
360+
template = Template()
361+
template.register_template('korean', '안녕하세요 {{name}}님!')
362+
result = template.render('korean', {'name': '세계'})
363+
self.assertEqual(result, '안녕하세요 세계님!')
364+
365+
def test_chinese_simplified(self) -> None:
366+
"""Test Simplified Chinese characters."""
367+
template = Template()
368+
template.register_template('chinese', '你好 {{name}}!')
369+
result = template.render('chinese', {'name': '世界'})
370+
self.assertEqual(result, '你好 世界!')
371+
372+
def test_tamil_script(self) -> None:
373+
"""Test Tamil script."""
374+
template = Template()
375+
template.register_template('tamil', 'வணக்கம் {{name}}!')
376+
result = template.render('tamil', {'name': 'உலகம்'})
377+
self.assertEqual(result, 'வணக்கம் உலகம்!')
378+
379+
def test_emoji_in_template(self) -> None:
380+
"""Test emoji characters in templates."""
381+
template = Template()
382+
template.register_template('emoji', '{{greeting}} 🎉🎊 {{name}} 🌍🌎🌏')
383+
result = template.render('emoji', {'greeting': 'Hello', 'name': 'World'})
384+
self.assertEqual(result, 'Hello 🎉🎊 World 🌍🌎🌏')
385+
386+
def test_mixed_scripts_in_single_template(self) -> None:
387+
"""Test multiple scripts in a single template."""
388+
template = Template()
389+
template.register_template(
390+
'mixed',
391+
'English: {{en}}, 中文: {{zh}}, हिंदी: {{hi}}, العربية: {{ar}}',
392+
)
393+
result = template.render(
394+
'mixed',
395+
{'en': 'Hello', 'zh': '你好', 'hi': 'नमस्ते', 'ar': 'مرحبا'},
396+
)
397+
self.assertEqual(result, 'English: Hello, 中文: 你好, हिंदी: नमस्ते, العربية: مرحبا')
398+
399+
def test_combining_diacritical_marks(self) -> None:
400+
"""Test characters with combining diacritical marks."""
401+
template = Template()
402+
# é can be represented as e + combining acute accent
403+
template.register_template('diacritics', 'Café: {{name}}')
404+
result = template.render('diacritics', {'name': 'résumé'})
405+
self.assertEqual(result, 'Café: résumé')
406+
407+
def test_zero_width_characters(self) -> None:
408+
"""Test handling of zero-width characters."""
409+
template = Template()
410+
# Zero-width joiner (U+200D) is used in some scripts
411+
template.register_template('zwj', 'Family: {{emoji}}')
412+
result = template.render('zwj', {'emoji': '👨‍👩‍👧‍👦'}) # Family emoji with ZWJ
413+
self.assertEqual(result, 'Family: 👨‍👩‍👧‍👦')
414+
415+
334416
class TestHandlebarsAlias(unittest.TestCase):
335417
"""Test that the Handlebars alias works like Template."""
336418

0 commit comments

Comments
 (0)