Skip to content

Commit 404c63b

Browse files
committed
Test on windows
1 parent b5e9446 commit 404c63b

File tree

8 files changed

+522
-11
lines changed

8 files changed

+522
-11
lines changed

.github/workflows/test.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,40 @@ jobs:
3030
image: danielflook/python-minifier-build:${{ matrix.python }}-2024-09-15
3131
run: |
3232
tox -r -e $(echo "${{ matrix.python }}" | tr -d .)
33+
34+
test-windows:
35+
name: Test Windows
36+
runs-on: windows-latest
37+
strategy:
38+
fail-fast: false
39+
matrix:
40+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
41+
steps:
42+
- name: Checkout
43+
uses: actions/checkout@v4.2.2
44+
with:
45+
fetch-depth: 1
46+
show-progress: false
47+
persist-credentials: false
48+
49+
- name: Set up Python
50+
uses: actions/setup-python@v5
51+
with:
52+
python-version: ${{ matrix.python-version }}
53+
54+
- name: Set version statically
55+
shell: powershell
56+
run: |
57+
$content = Get-Content setup.py
58+
$content = $content -replace "setup_requires=.*", "version='0.0.0',"
59+
$content = $content -replace "use_scm_version=.*", ""
60+
Set-Content setup.py $content
61+
62+
- name: Install tox
63+
run: |
64+
python -m pip install --upgrade pip
65+
pip install tox
66+
67+
- name: Run tests
68+
run: |
69+
tox -c tox-windows.ini -r -e ${{ matrix.python-version }}

src/python_minifier/__init__.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ast
22

3-
from typing import Any, AnyStr, List, Optional, Text, Union
3+
from typing import Any, List, Optional, Text, Union
44

55
from .transforms.remove_annotations_options import RemoveAnnotationsOptions as RemoveAnnotationsOptions
66

@@ -10,7 +10,7 @@ class UnstableMinification(RuntimeError):
1010

1111

1212
def minify(
13-
source: AnyStr,
13+
source: Union[str, bytes],
1414
filename: Optional[str] = ...,
1515
remove_annotations: Union[bool, RemoveAnnotationsOptions] = ...,
1616
remove_pass: bool = ...,
@@ -36,7 +36,7 @@ def unparse(module: ast.Module) -> Text: ...
3636

3737

3838
def awslambda(
39-
source: AnyStr,
39+
source: Union[str, bytes],
4040
filename: Optional[Text] = ...,
4141
entrypoint: Optional[Text] = ...
4242
) -> Text: ...

src/python_minifier/__main__.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@
77
from python_minifier import minify
88
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
99

10+
# Python 2.7 compatibility for UTF-8 file writing
11+
if sys.version_info[0] == 2:
12+
import codecs
13+
def open_utf8(filename, mode):
14+
return codecs.open(filename, mode, encoding='utf-8')
15+
else:
16+
def open_utf8(filename, mode):
17+
return open(filename, mode, encoding='utf-8')
18+
19+
def safe_stdout_write(text):
20+
"""Write text to stdout with proper encoding handling."""
21+
try:
22+
sys.stdout.write(text)
23+
except UnicodeEncodeError:
24+
# Fallback: encode to UTF-8 and write to stdout.buffer (Python 3) or sys.stdout (Python 2)
25+
if sys.version_info[0] >= 3 and hasattr(sys.stdout, 'buffer'):
26+
sys.stdout.buffer.write(text.encode('utf-8'))
27+
else:
28+
# Python 2.7 or no buffer attribute - write UTF-8 encoded bytes
29+
sys.stdout.write(text.encode('utf-8'))
30+
1031

1132
if sys.version_info >= (3, 8):
1233
from importlib import metadata
@@ -53,10 +74,10 @@ def main():
5374
source = sys.stdin.buffer.read() if sys.version_info >= (3, 0) else sys.stdin.read()
5475
minified = do_minify(source, 'stdin', args)
5576
if args.output:
56-
with open(args.output, 'w') as f:
77+
with open_utf8(args.output, 'w') as f:
5778
f.write(minified)
5879
else:
59-
sys.stdout.write(minified)
80+
safe_stdout_write(minified)
6081

6182
else:
6283
# minify source paths
@@ -70,13 +91,13 @@ def main():
7091
minified = do_minify(source, path, args)
7192

7293
if args.in_place:
73-
with open(path, 'w') as f:
94+
with open_utf8(path, 'w') as f:
7495
f.write(minified)
7596
elif args.output:
76-
with open(args.output, 'w') as f:
97+
with open_utf8(args.output, 'w') as f:
7798
f.write(minified)
7899
else:
79-
sys.stdout.write(minified)
100+
safe_stdout_write(minified)
80101

81102

82103
def parse_args():

src/python_minifier/module_printer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ def __call__(self, module):
2828
assert isinstance(module, ast.Module)
2929

3030
self.visit_Module(module)
31-
return str(self.printer).rstrip('\n' + self.indent_char + ';')
31+
# On Python 2.7, preserve unicode strings to avoid encoding issues
32+
code = unicode(self.printer) if sys.version_info[0] < 3 else str(self.printer)
33+
return code.rstrip('\n' + self.indent_char + ';')
3234

3335
@property
3436
def code(self):
35-
return str(self.printer).rstrip('\n' + self.indent_char + ';')
37+
# On Python 2.7, preserve unicode strings to avoid encoding issues
38+
code = unicode(self.printer) if sys.version_info[0] < 3 else str(self.printer)
39+
return code.rstrip('\n' + self.indent_char + ';')
3640

3741
# region Simple Statements
3842

src/python_minifier/token_printer.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,22 @@ def __init__(self, prefer_single_line=False, allow_invalid_num_warnings=False):
9191
self._prefer_single_line = prefer_single_line
9292
self._allow_invalid_num_warnings = allow_invalid_num_warnings
9393

94-
self._code = ''
94+
# Initialize as unicode string on Python 2.7 to handle Unicode content
95+
if sys.version_info[0] < 3:
96+
self._code = u''
97+
else:
98+
self._code = ''
9599
self.indent = 0
96100
self.unicode_literals = False
97101
self.previous_token = TokenTypes.NoToken
98102

99103
def __str__(self):
100104
"""Return the output code."""
101105
return self._code
106+
107+
def __unicode__(self):
108+
"""Return the output code as unicode (for Python 2.7 compatibility)."""
109+
return self._code
102110

103111
def identifier(self, name):
104112
"""Add an identifier to the output code."""

test/test_utf8_encoding.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# -*- coding: utf-8 -*-
2+
import pytest
3+
import python_minifier
4+
import tempfile
5+
import os
6+
import codecs
7+
8+
9+
def test_minify_utf8_file():
10+
"""Test minifying a Python file with UTF-8 characters not in Windows default encoding."""
11+
12+
# Create Python source with UTF-8 characters that are not in Windows-1252
13+
# Using Greek letters, Cyrillic, and mathematical symbols
14+
source_code = u'''"""
15+
This module contains UTF-8 characters that are not in Windows-1252 encoding:
16+
- Greek: α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω
17+
- Cyrillic: а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я
18+
- Mathematical: ∀ ∃ ∈ ∉ ∅ ∞ ∑ ∏ √ ∫ ∇ ∂ ≠ ≤ ≥ ≈ ≡ ⊂ ⊃ ⊆ ⊇
19+
- Arrows: ← → ↑ ↓ ↔ ↕ ↖ ↗ ↘ ↙
20+
"""
21+
22+
def greet_in_greek():
23+
return u"Γεια σας κόσμος" # "Hello world" in Greek
24+
25+
def mathematical_formula():
26+
# Using mathematical symbols in comments
27+
# ∀x ∈ ℝ: x² ≥ 0
28+
return u"∑ from i=1 to ∞ of 1/i² = π²/6"
29+
30+
def arrow_symbols():
31+
directions = {
32+
u"left": u"←",
33+
u"right": u"→",
34+
u"up": u"↑",
35+
u"down": u"↓"
36+
}
37+
return directions
38+
39+
if __name__ == "__main__":
40+
print(greet_in_greek())
41+
print(greet_in_russian())
42+
print(mathematical_formula())
43+
print(arrow_symbols())
44+
'''
45+
46+
# Write to temporary file with UTF-8 encoding
47+
# Python 2.7 doesn't support encoding parameter, so use binary mode
48+
with tempfile.NamedTemporaryFile(mode='wb', suffix='.py', delete=False) as f:
49+
f.write(source_code.encode('utf-8'))
50+
temp_file = f.name
51+
52+
try:
53+
# Read the file and minify it
54+
# Python 2.7 doesn't support encoding parameter in open()
55+
with codecs.open(temp_file, 'r', encoding='utf-8') as f:
56+
original_content = f.read()
57+
58+
# This should work - minify the UTF-8 content
59+
minified = python_minifier.minify(original_content)
60+
61+
# Verify the minified code still contains the UTF-8 characters
62+
# On Python 2.7, Unicode characters in string literals are escaped but preserved
63+
# Test by executing the minified code and checking the actual values
64+
minified_globals = {}
65+
exec(minified, minified_globals)
66+
67+
# The minified code should contain the same functions that return Unicode
68+
assert 'greet_in_greek' in minified_globals
69+
assert u"Γεια σας κόσμος" == minified_globals['greet_in_greek']()
70+
71+
# Test that mathematical symbols are also preserved
72+
assert 'mathematical_formula' in minified_globals
73+
assert u"∑ from i=1 to ∞" in minified_globals['mathematical_formula']()
74+
75+
finally:
76+
# Clean up
77+
os.unlink(temp_file)
78+
79+
80+
def test_minify_utf8_file_direct():
81+
"""Test minifying a file directly with UTF-8 characters."""
82+
83+
# Create Python source with UTF-8 characters
84+
source_code = u'''# UTF-8 test file
85+
def emoji_function():
86+
"""Function with emoji and special characters: 🐍 ∆ ∑ ∫ ∞"""
87+
return u"Python is 🐍 awesome! Math symbols: ∆x ≈ 0, ∑∞ = ∞"
88+
89+
class UnicodeClass:
90+
"""Class with unicode: ñ ü ö ä ë ï ÿ"""
91+
def __init__(self):
92+
self.message = u"Héllö Wörld with àccénts!"
93+
94+
def get_symbols(self):
95+
return u"Symbols: ™ © ® ° ± × ÷ ≠ ≤ ≥"
96+
'''
97+
98+
# Test direct minification
99+
minified = python_minifier.minify(source_code)
100+
101+
# Verify UTF-8 characters are preserved by executing the minified code
102+
minified_globals = {}
103+
exec(minified, minified_globals)
104+
105+
# Test that the functions return the correct Unicode strings
106+
assert u"🐍" in minified_globals['emoji_function']()
107+
assert u"∆" in minified_globals['emoji_function']()
108+
109+
# Test the class
110+
unicode_obj = minified_globals['UnicodeClass']()
111+
assert u"Héllö" in unicode_obj.message
112+
assert u"àccénts" in unicode_obj.message
113+
assert u"™" in unicode_obj.get_symbols()
114+
assert u"©" in unicode_obj.get_symbols()

0 commit comments

Comments
 (0)