[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\r\nupdates:\r\n  - package-ecosystem: \"github-actions\"\r\n    directory: \"/\"\r\n    schedule:\r\n      interval: \"daily\"\r\n\r\n"
  },
  {
    "path": ".github/workflows/comment.yml",
    "content": "on:\n  workflow_dispatch:\n    inputs:\n      issue_number:\n        required: true\n      owner:\n        required: true\n      repo:\n        required: true\n      body:\n        required: true\n\njobs:\n  comment:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/github-script@v9\n        with:\n          github-token: ${{ secrets.WINGET_TOKEN }}\n          script: |\n            github.rest.issues.createComment({\n              issue_number: ${{ github.event.inputs.issue_number }},\n              owner: '${{ github.event.inputs.owner }}',\n              repo: '${{ github.event.inputs.repo }}',\n              body: '${{ github.event.inputs.body }}'\n            })\n"
  },
  {
    "path": ".github/workflows/nightly.yml",
    "content": "name: nightly\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n  schedule:\n    - cron: '0 0 * * *'\n  workflow_dispatch:\n\njobs:\n  checkver:\n    runs-on: ubuntu-latest\n\n    outputs:\n      new_version: ${{ steps.check_new_version.outputs.new_version }}\n\n    steps:\n    - uses: actions/checkout@v6\n    - uses: actions/setup-python@v6\n    - name: Compare versions\n      run: python version_compare.py\n    - name: Check if new version exists\n      id: check_new_version\n      run: |\n        # If new.txt exists and is not empty\n        if [ -s \"new.txt\"  ]; then\n          NEW_VERSION=$(cat new.txt)\n          echo \"New version is $NEW_VERSION\"\n          echo \"new_version=$NEW_VERSION\" >> $GITHUB_OUTPUT\n        fi\n\n  build:\n    runs-on: windows-${{ matrix.arch == 'arm' && '2022' || 'latest' }}\n    needs: checkver\n    if: ${{ needs.checkver.outputs.new_version }}\n    strategy:\n      matrix:\n        arch: [x64, x86, arm64, arm]\n\n    steps:\n    - uses: actions/checkout@v6\n    - uses: actions/setup-python@v6\n    - uses: ilammy/msvc-dev-cmd@v1\n      with:\n        arch: ${{ matrix.arch != 'x64' && 'amd64_' || '' }}${{ matrix.arch }}\n        sdk: ${{ matrix.arch == 'arm' && '10.0.22621.0' || '' }}\n\n    - name: Build\n      run: python .\\build.py\n\n    - name: Upload less to artifact\n      uses: actions/upload-artifact@v7\n      with:\n        name: less-${{ matrix.arch }}\n        path: |\n          less.exe\n          lesskey.exe\n\n  release:\n    needs: [checkver, build]\n    if: ${{ github.event_name != 'pull_request' }}\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      discussions: write\n\n    steps:\n    - name: Get all artifacts\n      uses: actions/download-artifact@v8\n    - name: Zip each artifact\n      run: find . -type d ! -path . -execdir zip -9 -rj \"{}.zip\" \"{}\" \\;\n\n    - uses: octokit/request-action@v2.x\n      id: get_workflow_runtime\n      with:\n        route: GET /repos/{owner}/{repo}/actions/runs/{run_id}\n        owner: ${{ github.repository_owner }}\n        repo: less-Windows\n        run_id: ${{ github.run_id }}\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    - id: extract_major_version\n      run: |\n        MAJOR_VERSION=$(echo ${{ needs.checkver.outputs.new_version }} | cut -d. -f1)\n        echo \"major_version=$MAJOR_VERSION\" >> $GITHUB_OUTPUT\n\n    - uses: softprops/action-gh-release@v3\n      with:\n        files: '*.zip'\n        body: |\n          Built with GitHub Actions at ${{ fromJson(steps.get_workflow_runtime.outputs.data).updated_at }}\n\n          Release notes can be found [here](http://greenwoodsoftware.com/less/news.${{ steps.extract_major_version.outputs.major_version }}.html).\n        tag_name: less-v${{ needs.checkver.outputs.new_version }}\n        discussion_category_name: Announcements\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  winget:\n    name: Publish to WinGet\n    needs: [checkver, release]\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: vedantmgoyal9/winget-releaser@main\n      with:\n        identifier: jftuga.less\n        version: ${{ needs.checkver.outputs.new_version }}\n        release-tag: less-v${{ needs.checkver.outputs.new_version }}\n        installers-regex: '\\.zip$'\n        token: ${{ secrets.WINGET_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "less-*/\nless*.zip\ncompile.bat\ndownload.html\n__pycache__/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 J Taylor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# less-Windows [![nightly](https://github.com/jftuga/less-Windows/actions/workflows/nightly.yml/badge.svg)](https://github.com/jftuga/less-Windows/actions/workflows/nightly.yml)\n\nGNU [less](https://en.wikipedia.org/wiki/Less_\\(Unix\\)) compiled for Windows from the [less source](http://greenwoodsoftware.com/less/) via GitHub Actions. New versions are being checked daily, and builds are compiled with the latest version of Visual Studio.\n\n## Installation\n\nBinaries for `less.exe` (and `lesskey.exe`) are provided on the [Releases Page](https://github.com/jftuga/less-Windows/releases). Download the appropriate one for your system. If you prefer to install less via a package manager, you can choose one of the following options:\n\n### Winget\n\nA new version is pushed to the upstream [winget-pkgs](https://github.com/microsoft/winget-pkgs) for every release:\n\n```powershell\nwinget install jftuga.less\n```\n\n### Chocolatey\n\n[less](https://community.chocolatey.org/packages/less) is available in the Community Repository:\n```powershell\nchoco install less\n```\n\n### Scoop\n\n[less](https://scoop.sh/#/apps?q=main%2Fless&s=0&d=1&o=true) is available in the Main bucket:\n```powershell\nscoop install less\n```\n"
  },
  {
    "path": "build.py",
    "content": "#!/usr/bin/env python3\n\nr\"\"\"\nbuilder.py\n-John Taylor\nMay-13-2020\n\nDownload and compile GNU less with Visual Studio\nless.exe and lesskey.exe are created\n\"\"\"\n\nimport os\nimport os.path\nimport shutil\nimport subprocess\nimport sys\nimport time\nimport urllib.request\nimport zipfile\nfrom shared import download_less_web_page, get_latest_version_url, LESSURL\n\n\ndef download_and_save(url: str) -> bool:\n    \"\"\"Download the less .zip file and save it to the current directory\n    \"\"\"\n\n    # something like less-561.zip\n    archive = url.split(\"/\")[-1]\n    if os.path.exists(archive):\n        sz = os.stat(archive).st_size\n        print(\"File already exists: %s with size: %d\" % (archive, sz))\n        return archive\n\n    try:\n        urllib.request.urlretrieve(url, archive)\n    except:\n        return False\n\n    return archive\n\n\ndef extract_archive(archive: str) -> str:\n    \"\"\"Unzip the archive file, remove preexisting directory\n    \"\"\"\n\n    # given \"less-561.zip\", return \"less-561\n    zip_dest = os.path.splitext(archive)[0]\n\n    if os.path.exists(zip_dest):\n        print(\"Removing preexisting directory: %s\" % (zip_dest))\n        try:\n            shutil.rmtree(zip_dest)\n            time.sleep(1)\n        except:\n            return False\n    try:\n        with zipfile.ZipFile(archive, \"r\") as z:\n            z.extractall(\".\")\n    except:\n        return False\n\n    return zip_dest\n\n\ndef create_compile_batchfile(archive_dest: str):\n    \"\"\"Create a .bat file containing environment setup\n    and nmake compile commands\n    \"\"\"\n\n    bat = \"compile.bat\"\n    try:\n        with open(bat, \"w\") as fp:\n            fp.write(\"@echo off\\n\")\n            fp.write(\"cd %s\\n\" % (archive_dest))\n            fp.write(\"nmake /f Makefile.wnm\\n\")\n            fp.write(\"copy /y less.exe ..\\n\")\n            fp.write(\"copy /y lesskey.exe ..\\n\")\n    except:\n        return False\n\n    return bat\n\n\ndef main():\n    if not (page := download_less_web_page()):\n        print(\"Unable to download URL: %s\" % (LESSURL))\n        sys.exit(10)\n        return\n\n    version, url = get_latest_version_url(page)\n    if version is None:\n        print(\"Unable to extract version from: %s\" % (LESSURL), file=sys.stderr)\n        sys.exit(20)\n\n    if not (archive := download_and_save(url)):\n        print(\"Unable to download file: %s\" % (url), file=sys.stderr)\n        sys.exit(30)\n\n    if not (archive_dest := extract_archive(archive)):\n        print(\"Unable to unzip archive: %s\" % (archive), file=sys.stderr)\n        sys.exit(40)\n\n    if not (cmd := create_compile_batchfile(archive_dest)):\n        print(\"Unable to create batch file\", file=sys.stderr)\n        sys.exit(50)\n\n    result = subprocess.run((cmd,), shell=True, capture_output=True)\n    if result.returncode > 0:\n        err = result.stderr.decode(\"utf-8\")\n        out = result.stdout.decode(\"utf-8\")\n        print(\"Compile failed:\\n%s\\n\\n%s\\n\" % (out, err))\n        sys.exit(60)\n\n\nif \"__main__\" == __name__:\n    main()\n\n# end of script\n"
  },
  {
    "path": "shared.py",
    "content": "#!/usr/bin/env python3\n\nr\"\"\"\nshared.py\n-John Taylor\nMay-14-2020\n\nFunctions shared between build.py and version_compare.py\n\"\"\"\n\nimport re\nimport time\nimport urllib.request\n\nLESSURL = \"http://greenwoodsoftware.com/less/download.html\"\nversion_url_re = re.compile(r\"\"\"Download <strong>RECOMMENDED</strong> version (.*?) \"\"\", re.M | re.S | re.I)\nNEWFILE = \"new.txt\"\n\n\ndef download_less_web_page() -> str:\n    \"\"\"Download LESSURL and save the contents to fname\n\n    Returns:\n        An in-memory version of the downloaded web page\n    \"\"\"\n\n    fname = \"download.html\"\n    try:\n        urllib.request.urlretrieve(LESSURL, fname)\n        time.sleep(1)\n    except:\n        return False\n\n    try:\n        with open(fname) as fp:\n            page = fp.read()\n    except:\n        return False\n\n    return page\n\n\ndef get_latest_version_url(page: str) -> tuple:\n    \"\"\"Return the URL for the \"RECOMMENDED version\"\n\n    Args:\n        page: an HTML web page, provided in LESSURL\n\n    Returns:\n        A tuple containing: (version number, zip archive URL)\n        Ex: 551, http://greenwoodsoftware.com/less/less-551.zip\n    \"\"\"\n\n    match = version_url_re.findall(page)\n    if not len(match):\n        return (None, None)\n\n    version = match[0]\n    archive = \"less-%s.zip\" % version\n    url = LESSURL.replace(\"download.html\", archive)\n    return version, url\n"
  },
  {
    "path": "version_compare.py",
    "content": "#!/usr/bin/env python3\n\nr\"\"\"\nversion_compare.py\n-John Taylor\nMay-14-2020\n\nCompare local github version with less web site\n\"\"\"\n\nimport json\nimport urllib.request\nimport re\nimport sys\nfrom shared import download_less_web_page, get_latest_version_url, LESSURL, NEWFILE\n\nLOCALURL = \"https://api.github.com/repos/jftuga/less-Windows/releases\"\n\n\ndef download_local_web_page() -> str:\n    \"\"\"Download and return JSON from LOCALURL\n    \"\"\"\n    try:\n        with urllib.request.urlopen(LOCALURL) as f:\n            page = f.read()\n    except:\n        return False\n\n    page = page.decode(\"utf-8\")\n    return page\n\n\ndef get_latest_local_version(page: str) -> str:\n    \"\"\"Extract and return the lastest release version from a JSON page\n       Ex: 561\n    \"\"\"\n    try:\n        j = json.loads(page)\n    except:\n        return False\n\n    if not len(j):\n        return \"500\"\n\n    newest = j[0]\n    print(f'{newest[\"tag_name\"]=}')\n    # The initial version is different than future versions\n    if \"v560\" == newest[\"tag_name\"]:\n        return \"560\"\n\n    # given less-v561.17, return 561\n    release_version = newest[\"tag_name\"][6:11]\n    if release_version.endswith(\".0\"):\n        release_version = re.sub(r\"\\.0$\", \"\", release_version)\n    print(f'{release_version=}')\n    return release_version\n\n\ndef main():\n    \"\"\"Write to new.txt when a new version needs to be downloaded\n    \"\"\"\n    if not (page := download_local_web_page()):\n        print(\"Unable to download URL: %s\" % (LOCALURL))\n        sys.exit(10)\n\n    if not (local_version := get_latest_local_version(page)):\n        print(\"Unable to extract version from URL: %s\" % (LOCALURL))\n        sys.exit(20)\n\n    if not (page := download_less_web_page()):\n        print(\"Unable to download URL: %s\" % (LESSURL))\n        sys.exit(30)\n        return\n\n    remote_version, _ = get_latest_version_url(page)\n    if remote_version is None:\n        print(\"Unable to extract version from: %s\" % (LESSURL), file=sys.stderr)\n        sys.exit(40)\n\n    if remote_version == local_version:\n        print(f\"Versions are the same: remote_version: {remote_version}   local_version: {local_version}\")\n        return\n\n    if float(local_version) >= float(remote_version):\n        print(f\"Local version is newer: local_version: {local_version}   remote_version: {remote_version}\")\n        sys.exit(120)\n\n    print(f\"Remote version is newer: remote_version: {remote_version}   local_version: {local_version}\")\n    print(f\"Saving new version to file: {NEWFILE}\")\n    try:\n        with open(NEWFILE, mode=\"w\") as fp:\n            fp.write(\"%s\\n\" % remote_version)\n    except:\n        print(f\"Unable able to open file for writing: {NEWFILE}\")\n        sys.exit(50)\n\n\nif \"__main__\" == __name__:\n    main()\n"
  }
]