diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..0194daf --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,39 @@ +name: Python package + +on: + push: + branches: [ '*' ] + pull_request: + branches: [ '*' ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y python3-dev g++ + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install cython pytest setuptools + - name: Cythonize and build extension + run: | + python setup.py cythonize + - name: Install package + run: | + pip install -e . + - name: Test with pytest + run: | + pytest diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..92fac6b --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,36 @@ +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y python3-dev g++ + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install cython build + - name: Cythonize + run: | + python setup.py cythonize + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore index 1290439..9dd61a1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ luastyle/tests/__pycache__/ MANIFEST dist/ luastyle/__init__.pyc +indenter.html diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 87890ac..0000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: python - -env: - matrix: - - LUA=lua5.3 LUAC=luac5.3 - -python: - - "3.6" -# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true - -# command to install dependencies -install: - - pip install -r requirements.txt - - python setup.py cythonize - - pip install . - -# command to run tests -script: - - pytest diff --git a/luastyle/indenter.pxd b/luastyle/indenter.pxd index af5493c..3511dc0 100644 --- a/luastyle/indenter.pxd +++ b/luastyle/indenter.pxd @@ -1,6 +1,5 @@ import logging from luaparser import ast, astnodes -from luaparser.builder import Tokens from libcpp cimport bool from libcpp.vector cimport vector from libcpp.unordered_set cimport unordered_set @@ -62,70 +61,80 @@ cdef enum Expr: cdef enum CTokens: - AND = 1 - BREAK = 2 - DO = 3 - ELSETOK = 4 - ELSEIF = 5 - END = 6 - FALSE = 7 - FOR = 8 - FUNCTION = 9 - GOTO = 10 - IFTOK = 11 - IN = 12 - LOCAL = 13 - NIL = 14 - NOT = 15 - OR = 16 - REPEAT = 17 - RETURN = 18 - THEN = 19 - TRUE = 20 - UNTIL = 21 - WHILE = 22 - ADD = 23 - MINUS = 24 - MULT = 25 - DIV = 26 - FLOOR = 27 - MOD = 28 - POW = 29 - LENGTH = 30 - EQ = 31 - NEQ = 32 - LTEQ = 33 - GTEQ = 34 - LT = 35 - GT = 36 - ASSIGN = 37 - BITAND = 38 - BITOR = 39 - BITNOT = 40 - BITRSHIFT = 41 - BITRLEFT = 42 - OPAR = 43 - CPAR = 44 - OBRACE = 45 - CBRACE = 46 - OBRACK = 47 - CBRACK = 48 - COLCOL = 49 - COL = 50 - COMMA = 51 - VARARGS = 52 - CONCAT = 53 - DOT = 54 - SEMCOL = 55 - NAME = 56 - NUMBER = 57 - STRING = 58 - COMMENT = 59 - LINE_COMMENT = 60 - SPACE = 61 - NEWLINE = 62 - SHEBANG = 63 - LongBracket = 64 + # Updated for luaparser >=4.0 / antlr4 LuaLexer token types + # See: luaparser.parser.LuaLexer + # Updated for luaparser >=4.0 — actual antlr4 token type values + # (NOT the LuaLexer constant names, which differ from runtime types) + SEMCOL = 1 # was 55; actual: SEMI + ASSIGN = 2 # was 37; actual: = (EQ constant) + BREAK = 3 # was 2 + GOTO = 4 # was 10 + DO = 5 # was 3 + END = 6 # was 6 + WHILE = 7 # was 22 + REPEAT = 8 # was 17 + UNTIL = 9 # was 21 + IFTOK = 10 # was 11; actual: IF + THEN = 11 # was 19 + ELSEIF = 12 # was 5 + ELSETOK = 13 # was 4; actual: ELSE + FOR = 14 # was 8 + COMMA = 15 # was 51 + IN = 16 # was 12 + FUNCTION = 17 # was 9 + LOCAL = 18 # was 13 + LT = 19 # was 35 + GT = 20 # was 36 + RETURN = 21 # was 18 + COLCOL = 22 # was 49; actual: :: (CC constant) + NIL = 23 # was 14 + FALSE = 24 # was 7 + TRUE = 25 # was 20 + DOT = 26 # was 54 + BITNOT = 27 # was 40; actual: ~ (SQUIG constant) + MINUS = 28 # was 24 + LENGTH = 29 # was 30; actual: # (POUND constant) + OPAR = 30 # was 43; actual: ( (OP constant) + CPAR = 31 # was 44; actual: ) (CP constant) + NOT = 32 # was 15 + BITRLEFT = 33 # was 42; actual: << (LL constant) + BITRSHIFT = 34 # was 41; actual: >> (GG constant) + BITAND = 35 # was 38; actual: & (AMP constant) + FLOOR = 36 # was 27; actual: // (SS constant) + MOD = 37 # was 28; actual: % (PER constant) + COL = 38 # was 50 + LTEQ = 39 # was 33; actual: <= (LE constant) + GTEQ = 40 # was 34; actual: >= (GE constant) + AND = 41 # was 1 + OR = 42 # was 16 + ADD = 43 # was 23; actual: + (PLUS constant) + MULT = 44 # was 25; actual: * (STAR constant) + OBRACE = 45 # was 45; actual: { (runtime type, NOT OB constant=47) + CBRACE = 46 # was 46; actual: } (runtime type, NOT CB constant=48) + OBRACK = 47 # was 47; actual: [ (runtime type, NOT OCU constant=45) + CBRACK = 48 # was 48; actual: ] (runtime type, NOT CCU constant=46) + EQ = 49 # was 31; actual: == (EE constant, single token in antlr4) + CONCAT = 50 # was 53; actual: .. (DD constant) + BITOR = 51 # was 39; actual: | (PIPE constant) + POW = 52 # was 29; actual: ^ (CARET constant) + DIV = 53 # was 26; actual: / (SLASH constant) + VARARGS = 54 # was 52; actual: ... (DDD constant) + NEQ = 55 # was 32; actual: ~= (SQEQ constant, runtime type) + NAME = 56 # was 56 + STRING = 57 # was 58; actual: NORMALSTRING + NORMALSTRING = 57 + CHARSTRING = 58 + LONGSTRING = 59 + NUMBER = 60 # was 57; actual: INT + INT = 60 + HEX = 61 + FLOAT = 62 + HEX_FLOAT = 63 + COMMENT = 64 # was 59 + LINE_COMMENT = 65 # was 60 + SPACE = 66 # was 61; actual: WS + NEWLINE = 67 # was 62; actual: NL + SHEBANG = 68 # was 63 cdef struct ParseFieldResult: @@ -175,6 +184,7 @@ cdef class IndentProcessor: cdef unordered_set[int] MULT_OP cdef unordered_set[int] BITWISE_OP cdef unordered_set[int] ATOM_OP + cdef unordered_set[int] STRING_TYPES cdef unordered_set[int] COMMA_SEMCOL cdef inline void inc_level(self, int n=1) diff --git a/luastyle/indenter.pyx b/luastyle/indenter.pyx index 8c05027..4debb32 100644 --- a/luastyle/indenter.pyx +++ b/luastyle/indenter.pyx @@ -152,7 +152,14 @@ cdef class IndentProcessor: self.ATOM_OP.insert(CTokens.VARARGS) self.ATOM_OP.insert(CTokens.NUMBER) + self.ATOM_OP.insert(CTokens.INT) + self.ATOM_OP.insert(CTokens.HEX) + self.ATOM_OP.insert(CTokens.FLOAT) + self.ATOM_OP.insert(CTokens.HEX_FLOAT) self.ATOM_OP.insert(CTokens.STRING) + self.ATOM_OP.insert(CTokens.NORMALSTRING) + self.ATOM_OP.insert(CTokens.CHARSTRING) + self.ATOM_OP.insert(CTokens.LONGSTRING) self.ATOM_OP.insert(CTokens.NIL) self.ATOM_OP.insert(CTokens.TRUE) self.ATOM_OP.insert(CTokens.FALSE) @@ -160,6 +167,10 @@ cdef class IndentProcessor: self.COMMA_SEMCOL.insert(CTokens.COMMA) self.COMMA_SEMCOL.insert(CTokens.SEMCOL) + self.STRING_TYPES.insert(CTokens.NORMALSTRING) + self.STRING_TYPES.insert(CTokens.CHARSTRING) + self.STRING_TYPES.insert(CTokens.LONGSTRING) + # init indentation token self._indentation_token.type = -2 # indentation token @@ -528,6 +539,16 @@ cdef class IndentProcessor: token.type = t.type token.text = t.text.encode('UTF-8') self.render(token) + # render() pushes indentation after each newline. + # For consecutive newlines, remove the intermediate indent + # so empty lines don't carry over indentation. + if self._src.size() >= 3: + if (self._src.back().type == CTokens.NEWLINE and + self._src[self._src.size() - 2].type == -2 and + self._src[self._src.size() - 3].type == CTokens.NEWLINE): + self._src.pop_back() # remove NL + self._src.pop_back() # remove INDENT + self._src.push_back(token) # re-push NL is_newline = True elif t.type == CTokens.SPACE: if not is_newline: @@ -782,7 +803,7 @@ cdef class IndentProcessor: self.failure_save() if self.next_is_rc(CTokens.COL) and self.next_is_rc(CTokens.NAME): result.last_line = self._line_count - if self.next_is_rc(CTokens.STRING, False): + if self.next_is_rc(CTokens.STRING, False) or self.next_is_rc(CTokens.CHARSTRING, False) or self.next_is_rc(CTokens.LONGSTRING, False): self.success() return result @@ -808,7 +829,7 @@ cdef class IndentProcessor: return result self.failure_save() - if self.next_is_rc(CTokens.STRING, False): + if self.next_is_rc(CTokens.STRING, False) or self.next_is_rc(CTokens.CHARSTRING, False) or self.next_is_rc(CTokens.LONGSTRING, False): result.is_chainable = False self.success() return result diff --git a/setup.py b/setup.py index f7ddc25..be4fee8 100644 --- a/setup.py +++ b/setup.py @@ -65,9 +65,12 @@ def __init__(self, *args, **kwargs): packages=['luastyle', 'luastyle.tests'], zip_safe=False, classifiers=[ - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7' + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12' ], + python_requires='>=3.9', install_requires=['luaparser>=2.0'], entry_points={ 'console_scripts': [