diff --git a/.flake8 b/.flake8 index ecea7b82a0b..0a48712c808 100644 --- a/.flake8 +++ b/.flake8 @@ -24,31 +24,10 @@ per-file-ignores = imagery/i.atcorr/create_iwave.py: F632, F821, W293 doc/python/m.distance.py: E501 doc/gui/wxpython/example/dialogs.py: F401 - locale/grass_po_stats.py: E122, E128, E231, E401, E722 gui/scripts/d.wms.py: E501 - gui/wxpython/core/gconsole.py: E722 - gui/wxpython/core/render.py: E722 - gui/wxpython/core/settings.py: E722 - gui/wxpython/core/toolboxes.py: E722 - gui/wxpython/core/utils.py: E722 - gui/wxpython/core/workspace.py: E722 - gui/wxpython/datacatalog/tree.py: E731, E402 - gui/wxpython/dbmgr/base.py: E722 - gui/wxpython/dbmgr/dialogs.py: E722 - gui/wxpython/dbmgr/sqlbuilder.py: E722 - gui/wxpython/dbmgr/manager.py: E722 - gui/wxpython/docs/wxgui_sphinx/conf.py: E402, W291 - gui/wxpython/gcp/manager.py: E722 - gui/wxpython/gui_core/*: E266, E722 - gui/wxpython/gui_core/dialogs.py: E722 - gui/wxpython/gui_core/forms.py: E722 - gui/wxpython/gui_core/ghelp.py: E722 - gui/wxpython/gui_core/gselect.py: E266, E722 - gui/wxpython/gui_core/preferences.py: E266 - gui/wxpython/gui_core/widgets.py: E722, E266 - gui/wxpython/image2target/*: F841, E722, E265 - gui/wxpython/image2target/g.gui.image2target.py: E501, E265, F841 - gui/wxpython/iscatt/*: F841, E722, F405, F403 + gui/wxpython/image2target/*: F841, E722 + gui/wxpython/image2target/g.gui.image2target.py: E501, F841 + gui/wxpython/iscatt/*: F841 gui/wxpython/lmgr/frame.py: F841, E722 # layertree still includes some formatting issues (it is ignored by Black) gui/wxpython/lmgr/layertree.py: E722, E266, W504, E225 diff --git a/.github/workflows/additional_checks.yml b/.github/workflows/additional_checks.yml index d25cbb49194..1f8e355b487 100644 --- a/.github/workflows/additional_checks.yml +++ b/.github/workflows/additional_checks.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout repository contents - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: fetch-depth: 31 diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index 12b0b070ec9..f68483dea20 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -16,7 +16,7 @@ jobs: name: Formatting Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: persist-credentials: false - uses: DoozyX/clang-format-lint-action@c71d0bf4e21876ebec3e5647491186f8797fde31 # v0.18.2 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b4fb254d11d..acafe77daea 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: @@ -52,11 +52,11 @@ jobs: sudo apt-get install -y wget git gawk findutils xargs -a <(awk '! /^ *(#|$)/' ".github/workflows/apt.txt") -r -- \ sudo apt-get install -y --no-install-recommends --no-install-suggests - - uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1 + - uses: rui314/setup-mold@c49f92ee787f4e2bba3330d01755ef8c7221699a # v1 if: ${{ matrix.language == 'c-cpp' }} - name: Initialize CodeQL - uses: github/codeql-action/init@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 + uses: github/codeql-action/init@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml @@ -81,6 +81,6 @@ jobs: run: .github/workflows/build_ubuntu-22.04.sh "${HOME}/install" - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 + uses: github/codeql-action/analyze@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 9f1fc5d843c..24c98c96601 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-22.04 if: github.repository == 'OSGeo/grass' steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Get dependencies run: | diff --git a/.github/workflows/create_release_draft.yml b/.github/workflows/create_release_draft.yml index 09da8ad1317..4751c3c43a0 100644 --- a/.github/workflows/create_release_draft.yml +++ b/.github/workflows/create_release_draft.yml @@ -30,7 +30,7 @@ jobs: contents: write steps: - name: Checks-out repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: ref: ${{ github.ref }} fetch-depth: 0 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1f60ed4c6d7..4056ed63ea4 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -49,7 +49,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: fetch-depth: 0 - name: Docker meta @@ -68,7 +68,7 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 + uses: docker/setup-buildx-action@8026d2bc3645ea78b0d2544766a1225eb5691f89 # v3.7.0 - name: Login to DockerHub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: @@ -76,7 +76,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push id: docker_build - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 with: push: true pull: true diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml index e3e23f43c04..24b9dcb4043 100644 --- a/.github/workflows/gcc.yml +++ b/.github/workflows/gcc.yml @@ -26,7 +26,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Get dependencies run: | sudo apt-get update -y diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 4fad9d315a7..e1166cdacc2 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -44,13 +44,13 @@ jobs: -mindepth 1 -maxdepth 1 -type f -print -delete # Rehash to forget about the deleted files hash -r - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Get current date cache key segment id: date # Year and week of year so cache key changes weekly run: echo "date=$(date +%Y-%U)" >> "${GITHUB_OUTPUT}" - name: Setup Mamba - uses: mamba-org/setup-micromamba@f8b8a1e23a26f60a44c853292711bacfd3eac822 # v1.9.0 + uses: mamba-org/setup-micromamba@59b11321ffd9186cd5165633a02c5bba47de6d13 # v1.10.0 with: init-shell: bash environment-file: .github/workflows/macos_dependencies.txt diff --git a/.github/workflows/osgeo4w.yml b/.github/workflows/osgeo4w.yml index 79a44937755..d9c67cc37c9 100644 --- a/.github/workflows/osgeo4w.yml +++ b/.github/workflows/osgeo4w.yml @@ -31,7 +31,7 @@ jobs: run: | git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - uses: msys2/setup-msys2@ddf331adaebd714795f1042345e6ca57bd66cea8 # v2.24.1 with: path-type: inherit @@ -66,9 +66,13 @@ jobs: pdal-devel pdcurses proj-devel + python3-core + python3-jupyter python3-matplotlib python3-numpy + python3-pip python3-ply + python3-pytest python3-pywin32 python3-wxpython regex-devel @@ -98,6 +102,19 @@ jobs: shell: msys2 {0} run: .github/workflows/test_simple.sh + - name: Install pytest plugins + run: python -m pip install pytest-timeout + shell: cmd /D /E:ON /V:OFF /S /C "CALL C:/OSGeo4W/OSGeo4W.bat "{0}"" + - name: Run pytest with a single worker + run: | + call %OSGEO4W_ROOT%\opt\grass\etc\env.bat + set PYTHONPATH=%GISBASE%\etc\python;%PYTHONPATH% + path %GISBASE%\lib;%GISBASE%\bin;%PATH% + pytest --verbose --color=yes ^ + --durations=0 --durations-min=0.5 ^ + -ra . + shell: cmd /D /E:ON /V:OFF /S /C "CALL C:/OSGeo4W/OSGeo4W.bat "{0}"" + - name: Run tests run: .github/workflows/test_thorough.bat 'C:\OSGeo4W\opt\grass\grass85.bat' 'C:\OSGeo4W\bin\python3' diff --git a/.github/workflows/osgeo4w_gunittest.cfg b/.github/workflows/osgeo4w_gunittest.cfg new file mode 100644 index 00000000000..b9252cf49ef --- /dev/null +++ b/.github/workflows/osgeo4w_gunittest.cfg @@ -0,0 +1,32 @@ +[gunittest] + +# Files (or wildcard patterns) to exclude from testing separated by newline or +# space. This would be ideally empty or it would include just special cases, +# but it includes mainly tests which can (and should) be fixed. +exclude = + gui/wxpython/core/testsuite/toolboxes.sh + lib/init/testsuite/test_grass_tmp_mapset.py + python/grass/gunittest/testsuite/test_gunitest_doctests.py + python/grass/pygrass/raster/testsuite/test_pygrass_raster_doctests.py + python/grass/pygrass/rpc/testsuite/test_pygrass_rpc_doctests.py + python/grass/script/testsuite/test_script_doctests.py + python/grass/temporal/testsuite/unittests_temporal_raster_conditionals_complement_else.py + temporal/t.info/testsuite/test.t.info.sh + temporal/t.rast.accdetect/testsuite/test.t.rast.accdetect.reverse.sh + temporal/t.rast.accdetect/testsuite/test.t.rast.accdetect.sh + temporal/t.rast.aggregate/testsuite/test_aggregation_relative.py + vector/v.edit/testsuite/select_all_flag.sh + vector/v.in.lidar/testsuite/decimation_test.py + vector/v.in.lidar/testsuite/mask_test.py + vector/v.in.lidar/testsuite/test_v_in_lidar_basic.py + vector/v.in.lidar/testsuite/test_v_in_lidar_filter.py + vector/v.what.rast3/testsuite/test.v.what.rast3.sh + vector/v.what/testsuite/test_vwhat_layers.py + vector/v.what/testsuite/test_vwhat_ncspm.py + +# Maximum time for execution of one test file (not a test function) +# after which test is terminated (which may not terminate child processes +# from that test). +# Using 5 minutes as maximum (average test time should be much shorter, +# couple seconds per file, median should be around one second). +timeout = 300 diff --git a/.github/workflows/periodic_update.yml b/.github/workflows/periodic_update.yml index bea3b53af4a..f5441297250 100644 --- a/.github/workflows/periodic_update.yml +++ b/.github/workflows/periodic_update.yml @@ -21,7 +21,7 @@ jobs: - name: Create URL to the run output id: vars run: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: "Check that autoconf scripts are up-to-date:" run: | rm -f config.guess config.sub diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 8e3b2286b1c..cb724086ea0 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -32,7 +32,7 @@ jobs: PYTHONWARNINGS: always steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 @@ -48,7 +48,7 @@ jobs: xargs -a <(awk '! /^ *(#|$)/' ".github/workflows/apt.txt") -r -- \ sudo apt-get install -y --no-install-recommends --no-install-suggests - - uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1 + - uses: rui314/setup-mold@c49f92ee787f4e2bba3330d01755ef8c7221699a # v1 - name: Install Python dependencies run: | @@ -108,7 +108,7 @@ jobs: coverage html - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: verbose: true flags: pytest-python-${{ matrix.python-version }} diff --git a/.github/workflows/python-code-quality.yml b/.github/workflows/python-code-quality.yml index 92ef7d788b2..92c647875b0 100644 --- a/.github/workflows/python-code-quality.yml +++ b/.github/workflows/python-code-quality.yml @@ -34,9 +34,9 @@ jobs: # renovate: datasource=pypi depName=pylint PYLINT_VERSION: "2.12.2" # renovate: datasource=pypi depName=bandit - BANDIT_VERSION: "1.7.9" + BANDIT_VERSION: "1.7.10" # renovate: datasource=pypi depName=ruff - RUFF_VERSION: "0.6.7" + RUFF_VERSION: "0.6.8" runs-on: ${{ matrix.os }} permissions: @@ -54,7 +54,7 @@ jobs: echo Bandit: ${{ env.BANDIT_VERSION }} echo Ruff: ${{ env.RUFF_VERSION }} - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 @@ -135,7 +135,7 @@ jobs: path: bandit.sarif - name: Upload SARIF File into Security Tab - uses: github/codeql-action/upload-sarif@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 + uses: github/codeql-action/upload-sarif@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 with: sarif_file: bandit.sarif @@ -147,7 +147,7 @@ jobs: run: | echo "MAKEFLAGS=-j$(nproc)" >> $GITHUB_ENV - - uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1 + - uses: rui314/setup-mold@c49f92ee787f4e2bba3330d01755ef8c7221699a # v1 - name: Build run: .github/workflows/build_${{ matrix.os }}.sh $HOME/install diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index 44da8d9acb7..117f5cd43fb 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -25,7 +25,7 @@ jobs: statuses: write steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: # super-linter needs the full git history to get the # list of files that changed across commits diff --git a/.github/workflows/test-nix.yml b/.github/workflows/test-nix.yml index 22fa4f0061f..03f767730a9 100644 --- a/.github/workflows/test-nix.yml +++ b/.github/workflows/test-nix.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install nix uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14 diff --git a/.github/workflows/test_thorough.bat b/.github/workflows/test_thorough.bat index 40df9534a20..843adb4c4f0 100644 --- a/.github/workflows/test_thorough.bat +++ b/.github/workflows/test_thorough.bat @@ -2,4 +2,4 @@ set grass=%1 set python=%2 call %grass% --tmp-project XY --exec g.download.project url=https://grass.osgeo.org/sampledata/north_carolina/nc_spm_full_v2alpha2.tar.gz path=%USERPROFILE% -call %grass% --tmp-project XY --exec %python% -m grass.gunittest.main --grassdata %USERPROFILE% --location nc_spm_full_v2alpha2 --location-type nc --min-success 86 +call %grass% --tmp-project XY --exec %python% -m grass.gunittest.main --grassdata %USERPROFILE% --location nc_spm_full_v2alpha2 --location-type nc --min-success 96 --config .github\workflows\osgeo4w_gunittest.cfg diff --git a/.github/workflows/titles.yml b/.github/workflows/titles.yml index b8e33c2fb37..f90e7275a2e 100644 --- a/.github/workflows/titles.yml +++ b/.github/workflows/titles.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout base repository (doesn't include the PR changes) - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Call PR title validation function run: python utils/generate_release_notes.py check "${PR_TITLE}" "" "" env: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 57b0821eec4..25ce2afdd67 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -59,7 +59,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Invert inclusion list to an exclusion list id: get-exclude diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e1f98c7a2a5..b7a517fba37 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,13 +37,13 @@ repos: ) - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.6.7 + rev: v0.6.8 hooks: # Run the linter. - id: ruff args: [--fix, --preview] - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.41.0 + rev: v0.42.0 hooks: - id: markdownlint-fix # Using this mirror lets us use mypyc-compiled black, which is about 2x faster diff --git a/Dockerfile b/Dockerfile index c6366eeb873..41fb3264c07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,8 +19,9 @@ WORKDIR /tmp ARG GUI # Todo: re-consider required dev packages for addons (~400MB in dev packages) -ARG GRASS_RUN_PACKAGES="build-essential \ +ARG GRASS_RUN_PACKAGES="\ bison \ + build-essential \ bzip2 \ curl \ flex \ @@ -28,8 +29,6 @@ ARG GRASS_RUN_PACKAGES="build-essential \ gcc \ gdal-bin \ geos-bin \ - proj-bin \ - netcdf-bin \ git \ language-pack-en-base \ libcairo2 \ @@ -38,28 +37,29 @@ ARG GRASS_RUN_PACKAGES="build-essential \ libfftw3-dev \ libfreetype6 \ libgdal-dev \ + libgeos-dev \ + libgsl-dev \ libgsl27 \ libjpeg-turbo8 \ libjsoncpp-dev \ - libmagic1 \ + liblapacke-dev \ libmagic-mgc \ + libmagic1 \ libncurses5 \ - libopenblas-dev \ + libomp-dev \ + libomp5 \ libopenblas-base \ + libopenblas-dev \ libopenjp2-7 \ - libomp5 \ - libomp-dev \ - libgeos-dev \ - libpdal-dev \ - libproj-dev \ - libpq-dev \ - libgsl-dev \ libpdal-base13 \ + libpdal-dev \ libpdal-plugin-hdf \ libpdal-plugins \ libpdal-util13 \ libpnglite0 \ + libpq-dev \ libpq5 \ + libproj-dev \ libpython3-all-dev \ libreadline8 \ libsqlite3-0 \ @@ -70,7 +70,9 @@ ARG GRASS_RUN_PACKAGES="build-essential \ mesa-utils \ moreutils \ ncurses-bin \ + netcdf-bin \ pdal \ + proj-bin \ proj-data \ python-is-python3 \ python3 \ @@ -86,56 +88,58 @@ ARG GRASS_RUN_PACKAGES="build-essential \ ENV GRASS_RUN_PACKAGES=${GRASS_RUN_PACKAGES} # Define build packages -ARG GRASS_BUILD_PACKAGES="cmake \ +ARG GRASS_BUILD_PACKAGES="\ + cmake \ libbz2-dev \ libcairo2-dev \ libfreetype6-dev \ - zlib1g-dev \ + libjpeg-dev \ + libncurses5-dev \ libnetcdf-dev \ libopenjp2-7-dev \ - libreadline-dev \ - libjpeg-dev \ libpnglite-dev \ + libreadline-dev \ libsqlite3-dev \ libtiff-dev \ libzstd-dev \ - libncurses5-dev \ mesa-common-dev \ zlib1g-dev \ " ENV GRASS_BUILD_PACKAGES=${GRASS_BUILD_PACKAGES} -ARG GRASS_CONFIG="--with-cxx \ +ARG GRASS_CONFIG="\ --enable-largefile \ - --with-proj-share=/usr/share/proj \ - --with-gdal=/usr/bin/gdal-config \ - --with-geos \ - --with-sqlite \ + --with-blas \ + --with-bzlib \ --with-cairo --with-cairo-ldflags=-lfontconfig \ - --with-freetype --with-freetype-includes=/usr/include/freetype2/ \ + --with-cxx \ --with-fftw \ - --with-postgres --with-postgres-includes=/usr/include/postgresql \ - --with-netcdf \ - --with-zstd \ - --with-bzlib \ - --with-pdal \ - --without-mysql \ - --with-blas \ + --with-freetype --with-freetype-includes=/usr/include/freetype2/ \ + --with-gdal=/usr/bin/gdal-config \ + --with-geos \ --with-lapack \ - --with-readline \ + --with-netcdf \ --with-odbc \ --with-openmp \ + --with-pdal \ + --with-postgres --with-postgres-includes=/usr/include/postgresql \ + --with-proj-share=/usr/share/proj \ + --with-readline \ + --with-sqlite \ + --with-zstd \ + --without-mysql \ " -ARG GRASS_PYTHON_PACKAGES="pip \ - setuptools \ - python-dateutil \ - python-magic \ - numpy \ +ARG GRASS_PYTHON_PACKAGES="\ Pillow \ - ply \ matplotlib \ + numpy \ + pip \ + ply \ psycopg2 \ + python-dateutil \ + python-magic \ + setuptools \ " ENV GRASS_PYTHON_PACKAGES=${GRASS_PYTHON_PACKAGES} @@ -147,31 +151,33 @@ ENV GRASS_CONFIG=${GRASS_CONFIG} FROM common_start as grass_with_gui -ARG GRASS_RUN_PACKAGES="${GRASS_RUN_PACKAGES} adwaita-icon-theme-full \ - libglu1-mesa \ - libgtk-3-0 \ - libnotify4 \ - libsdl2-2.0-0 \ - libxtst6 \ - librsvg2-common \ - gettext \ +ARG GRASS_RUN_PACKAGES="${GRASS_RUN_PACKAGES} \ + adwaita-icon-theme-full \ freeglut3 \ + gettext \ + libglu1-mesa \ libgstreamer-plugins-base1.0 \ + libgtk-3-0 \ libjpeg8 \ + libnotify4 \ libpng16-16 \ + librsvg2-common \ + libsdl2-2.0-0 \ libsm6 \ libtiff5 \ libwebkit2gtk-4.0 \ + libxtst6 \ " # librsvg2-common \ # (fix error (wxgui.py:7782): Gtk-WARNING **: 19:53:09.774: # Could not load a pixbuf from /org/gtk/libgtk/theme/Adwaita/assets/check-symbolic.svg. # This may indicate that pixbuf loaders or the mime database could not be found.) -ARG GRASS_BUILD_PACKAGES="${GRASS_BUILD_PACKAGES} adwaita-icon-theme-full \ +ARG GRASS_BUILD_PACKAGES="${GRASS_BUILD_PACKAGES} \ + adwaita-icon-theme-full \ + freeglut3-dev \ libgl1-mesa-dev \ libglu1-mesa-dev \ - freeglut3-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ libjpeg-dev \ @@ -184,10 +190,11 @@ ARG GRASS_BUILD_PACKAGES="${GRASS_BUILD_PACKAGES} adwaita-icon-theme-full \ libxtst-dev \ " -ARG GRASS_CONFIG="${GRASS_CONFIG} --with-opengl \ - --with-x \ +ARG GRASS_CONFIG="${GRASS_CONFIG} \ --with-nls \ + --with-opengl \ --with-readline \ + --with-x \ " ARG GRASS_PYTHON_PACKAGES="${GRASS_PYTHON_PACKAGES} wxPython" # If you do not use any Gnome Accessibility features, to suppress warning diff --git a/display/d.mon/render_cmd.py b/display/d.mon/render_cmd.py index 7678a1442e4..cf153c0b017 100644 --- a/display/d.mon/render_cmd.py +++ b/display/d.mon/render_cmd.py @@ -39,7 +39,7 @@ def remove_mapfile(mapfile): # read environment variables from file def read_env_file(env_file): width = height = legfile = None - fd = open(env_file, "r") + fd = open(env_file) if fd is None: grass.fatal("Unable to open file '{0}'".format(env_file)) lines = fd.readlines() diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile index a099617759c..b6dbebf6666 100644 --- a/docker/alpine/Dockerfile +++ b/docker/alpine/Dockerfile @@ -11,9 +11,9 @@ ARG PYTHON_VERSION=3 # List of packages to be installed (proj-data omitted: 570.04 MB) ENV GRASS_RUN_PACKAGES="\ attr \ - build-base \ bash \ bison \ + build-base \ bzip2 \ cairo \ curl \ @@ -29,15 +29,15 @@ ENV GRASS_RUN_PACKAGES="\ gdal-driver-JP2OpenJPEG \ gdal-driver-LIBKML \ gdal-driver-MSSQLSpatial \ - gdal-driver-netCDF \ gdal-driver-ODBC \ gdal-driver-PG \ gdal-driver-PNG \ gdal-driver-WMS \ + gdal-driver-netCDF \ gdal-tools \ - gettext \ geos \ geos-dev \ + gettext \ git \ gnutls \ jsoncpp \ @@ -52,15 +52,15 @@ ENV GRASS_RUN_PACKAGES="\ musl \ musl-utils \ ncurses \ - openjpeg \ openblas \ - py3-numpy \ - py3-pillow \ - python3 \ + openjpeg \ pdal \ pdal-dev \ postgresql15-client \ proj-util \ + py3-numpy \ + py3-pillow \ + python3 \ sqlite \ sqlite-libs \ subversion \ @@ -89,23 +89,23 @@ FROM common as build # set configuration options, without wxGUI ENV GRASS_CONFIG="\ --enable-largefile \ + --with-bzlib \ + --with-cairo --with-cairo-ldflags=-lfontconfig \ --with-cxx \ - --with-proj-share=/usr/share/proj \ + --with-fftw \ --with-gdal \ - --with-pdal \ --with-geos \ + --with-openmp \ + --with-pdal \ + --with-postgres --with-postgres-includes=/usr/include/postgresql \ + --with-proj-share=/usr/share/proj \ --with-sqlite \ - --with-bzlib \ --with-zstd \ - --with-cairo --with-cairo-ldflags=-lfontconfig \ - --with-fftw \ - --with-postgres --with-postgres-includes=/usr/include/postgresql \ - --with-openmp \ --without-freetype \ - --without-opengl \ - --without-nls \ --without-mysql \ + --without-nls \ --without-odbc \ + --without-opengl \ " # Set environmental variables for GRASS GIS compilation, without debug symbols @@ -132,13 +132,13 @@ ENV GRASS_BUILD_PACKAGES="\ libjpeg-turbo-dev \ libpng-dev \ libpq-dev \ - openjpeg-dev \ openblas-dev \ + openjpeg-dev \ pdal \ pdal-dev \ proj-dev \ - python3-dev \ py3-numpy-dev \ + python3-dev \ sqlite-dev \ tar \ tiff-dev \ diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile index 2ed59e1482e..8021ccf409e 100644 --- a/docker/debian/Dockerfile +++ b/docker/debian/Dockerfile @@ -18,8 +18,8 @@ WORKDIR /tmp RUN apt-get update && apt-get upgrade -y && \ apt-get install -y --no-install-recommends --no-install-suggests \ - build-essential \ bison \ + build-essential \ bzip2 \ cmake \ curl \ @@ -40,8 +40,8 @@ RUN apt-get update && apt-get upgrade -y && \ libgsl0-dev \ libjpeg-dev \ libjsoncpp-dev \ - libnetcdf-dev \ libncurses-dev \ + libnetcdf-dev \ libopenblas-dev \ libopenjp2-7 \ libopenjp2-7-dev \ @@ -153,24 +153,24 @@ ENV CXXFLAGS "$MYCXXFLAGS" ENV NUMTHREADS=4 RUN make distclean || echo "nothing to clean" RUN /src/grass_build/configure \ - --with-cxx \ --enable-largefile \ - --with-proj-share=/usr/share/proj \ - --with-gdal=/usr/bin/gdal-config \ - --with-geos \ - --with-sqlite \ + --with-bzlib \ --with-cairo --with-cairo-ldflags=-lfontconfig \ - --with-freetype --with-freetype-includes="/usr/include/freetype2/" \ + --with-cxx \ --with-fftw \ - --with-postgres --with-postgres-includes="/usr/include/postgresql" \ + --with-freetype --with-freetype-includes="/usr/include/freetype2/" \ + --with-gdal=/usr/bin/gdal-config \ + --with-geos \ --with-netcdf \ - --with-zstd \ - --with-bzlib \ --with-pdal \ + --with-postgres --with-postgres-includes="/usr/include/postgresql" \ + --with-proj-share=/usr/share/proj \ + --with-sqlite \ + --with-zstd \ --without-mysql \ --without-odbc \ - --without-openmp \ --without-opengl \ + --without-openmp \ && make -j $NUMTHREADS \ && make install && ldconfig diff --git a/docker/ubuntu/Dockerfile b/docker/ubuntu/Dockerfile index c6366eeb873..41fb3264c07 100644 --- a/docker/ubuntu/Dockerfile +++ b/docker/ubuntu/Dockerfile @@ -19,8 +19,9 @@ WORKDIR /tmp ARG GUI # Todo: re-consider required dev packages for addons (~400MB in dev packages) -ARG GRASS_RUN_PACKAGES="build-essential \ +ARG GRASS_RUN_PACKAGES="\ bison \ + build-essential \ bzip2 \ curl \ flex \ @@ -28,8 +29,6 @@ ARG GRASS_RUN_PACKAGES="build-essential \ gcc \ gdal-bin \ geos-bin \ - proj-bin \ - netcdf-bin \ git \ language-pack-en-base \ libcairo2 \ @@ -38,28 +37,29 @@ ARG GRASS_RUN_PACKAGES="build-essential \ libfftw3-dev \ libfreetype6 \ libgdal-dev \ + libgeos-dev \ + libgsl-dev \ libgsl27 \ libjpeg-turbo8 \ libjsoncpp-dev \ - libmagic1 \ + liblapacke-dev \ libmagic-mgc \ + libmagic1 \ libncurses5 \ - libopenblas-dev \ + libomp-dev \ + libomp5 \ libopenblas-base \ + libopenblas-dev \ libopenjp2-7 \ - libomp5 \ - libomp-dev \ - libgeos-dev \ - libpdal-dev \ - libproj-dev \ - libpq-dev \ - libgsl-dev \ libpdal-base13 \ + libpdal-dev \ libpdal-plugin-hdf \ libpdal-plugins \ libpdal-util13 \ libpnglite0 \ + libpq-dev \ libpq5 \ + libproj-dev \ libpython3-all-dev \ libreadline8 \ libsqlite3-0 \ @@ -70,7 +70,9 @@ ARG GRASS_RUN_PACKAGES="build-essential \ mesa-utils \ moreutils \ ncurses-bin \ + netcdf-bin \ pdal \ + proj-bin \ proj-data \ python-is-python3 \ python3 \ @@ -86,56 +88,58 @@ ARG GRASS_RUN_PACKAGES="build-essential \ ENV GRASS_RUN_PACKAGES=${GRASS_RUN_PACKAGES} # Define build packages -ARG GRASS_BUILD_PACKAGES="cmake \ +ARG GRASS_BUILD_PACKAGES="\ + cmake \ libbz2-dev \ libcairo2-dev \ libfreetype6-dev \ - zlib1g-dev \ + libjpeg-dev \ + libncurses5-dev \ libnetcdf-dev \ libopenjp2-7-dev \ - libreadline-dev \ - libjpeg-dev \ libpnglite-dev \ + libreadline-dev \ libsqlite3-dev \ libtiff-dev \ libzstd-dev \ - libncurses5-dev \ mesa-common-dev \ zlib1g-dev \ " ENV GRASS_BUILD_PACKAGES=${GRASS_BUILD_PACKAGES} -ARG GRASS_CONFIG="--with-cxx \ +ARG GRASS_CONFIG="\ --enable-largefile \ - --with-proj-share=/usr/share/proj \ - --with-gdal=/usr/bin/gdal-config \ - --with-geos \ - --with-sqlite \ + --with-blas \ + --with-bzlib \ --with-cairo --with-cairo-ldflags=-lfontconfig \ - --with-freetype --with-freetype-includes=/usr/include/freetype2/ \ + --with-cxx \ --with-fftw \ - --with-postgres --with-postgres-includes=/usr/include/postgresql \ - --with-netcdf \ - --with-zstd \ - --with-bzlib \ - --with-pdal \ - --without-mysql \ - --with-blas \ + --with-freetype --with-freetype-includes=/usr/include/freetype2/ \ + --with-gdal=/usr/bin/gdal-config \ + --with-geos \ --with-lapack \ - --with-readline \ + --with-netcdf \ --with-odbc \ --with-openmp \ + --with-pdal \ + --with-postgres --with-postgres-includes=/usr/include/postgresql \ + --with-proj-share=/usr/share/proj \ + --with-readline \ + --with-sqlite \ + --with-zstd \ + --without-mysql \ " -ARG GRASS_PYTHON_PACKAGES="pip \ - setuptools \ - python-dateutil \ - python-magic \ - numpy \ +ARG GRASS_PYTHON_PACKAGES="\ Pillow \ - ply \ matplotlib \ + numpy \ + pip \ + ply \ psycopg2 \ + python-dateutil \ + python-magic \ + setuptools \ " ENV GRASS_PYTHON_PACKAGES=${GRASS_PYTHON_PACKAGES} @@ -147,31 +151,33 @@ ENV GRASS_CONFIG=${GRASS_CONFIG} FROM common_start as grass_with_gui -ARG GRASS_RUN_PACKAGES="${GRASS_RUN_PACKAGES} adwaita-icon-theme-full \ - libglu1-mesa \ - libgtk-3-0 \ - libnotify4 \ - libsdl2-2.0-0 \ - libxtst6 \ - librsvg2-common \ - gettext \ +ARG GRASS_RUN_PACKAGES="${GRASS_RUN_PACKAGES} \ + adwaita-icon-theme-full \ freeglut3 \ + gettext \ + libglu1-mesa \ libgstreamer-plugins-base1.0 \ + libgtk-3-0 \ libjpeg8 \ + libnotify4 \ libpng16-16 \ + librsvg2-common \ + libsdl2-2.0-0 \ libsm6 \ libtiff5 \ libwebkit2gtk-4.0 \ + libxtst6 \ " # librsvg2-common \ # (fix error (wxgui.py:7782): Gtk-WARNING **: 19:53:09.774: # Could not load a pixbuf from /org/gtk/libgtk/theme/Adwaita/assets/check-symbolic.svg. # This may indicate that pixbuf loaders or the mime database could not be found.) -ARG GRASS_BUILD_PACKAGES="${GRASS_BUILD_PACKAGES} adwaita-icon-theme-full \ +ARG GRASS_BUILD_PACKAGES="${GRASS_BUILD_PACKAGES} \ + adwaita-icon-theme-full \ + freeglut3-dev \ libgl1-mesa-dev \ libglu1-mesa-dev \ - freeglut3-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ libjpeg-dev \ @@ -184,10 +190,11 @@ ARG GRASS_BUILD_PACKAGES="${GRASS_BUILD_PACKAGES} adwaita-icon-theme-full \ libxtst-dev \ " -ARG GRASS_CONFIG="${GRASS_CONFIG} --with-opengl \ - --with-x \ +ARG GRASS_CONFIG="${GRASS_CONFIG} \ --with-nls \ + --with-opengl \ --with-readline \ + --with-x \ " ARG GRASS_PYTHON_PACKAGES="${GRASS_PYTHON_PACKAGES} wxPython" # If you do not use any Gnome Accessibility features, to suppress warning diff --git a/docker/ubuntu_wxgui/Dockerfile b/docker/ubuntu_wxgui/Dockerfile index 36d33ffeb20..e15d5dcf478 100644 --- a/docker/ubuntu_wxgui/Dockerfile +++ b/docker/ubuntu_wxgui/Dockerfile @@ -29,8 +29,8 @@ WORKDIR /tmp RUN apt-get update && apt-get upgrade -y && \ apt-get install -y --no-install-recommends --no-install-suggests \ adwaita-icon-theme-full \ - build-essential \ bison \ + build-essential \ bzip2 \ cmake \ curl \ @@ -48,17 +48,17 @@ RUN apt-get update && apt-get upgrade -y && \ libfftw3-bin \ libfftw3-dev \ libfreetype6-dev \ - libgl1-mesa-dev \ libgdal-dev \ libgeos-dev \ + libgl1-mesa-dev \ libglu1-mesa-dev \ libgsl0-dev \ libgtk-3-0 \ libgtk-3-dev \ libjpeg-dev \ libjsoncpp-dev \ - libnetcdf-dev \ libncurses5-dev \ + libnetcdf-dev \ libnotify4 \ libopenblas-base \ libopenblas-dev \ @@ -68,8 +68,8 @@ RUN apt-get update && apt-get upgrade -y && \ libpq-dev \ libproj-dev \ libpython3-all-dev \ - librsvg2-common \ libreadline-dev \ + librsvg2-common \ libsdl2-2.0-0 \ libsqlite3-dev \ libtiff-dev \ @@ -191,26 +191,26 @@ ENV CXXFLAGS "$MYCXXFLAGS" ENV NUMTHREADS=4 RUN make distclean || echo "nothing to clean" RUN /src/grass_build/configure \ - --with-cxx \ --enable-largefile \ - --with-proj-share=/usr/share/proj \ - --with-gdal=/usr/bin/gdal-config \ - --with-geos \ - --with-sqlite \ + --with-bzlib \ --with-cairo --with-cairo-ldflags=-lfontconfig \ - --with-freetype --with-freetype-includes="/usr/include/freetype2/" \ + --with-cxx \ --with-fftw \ - --with-postgres --with-postgres-includes="/usr/include/postgresql" \ + --with-freetype --with-freetype-includes="/usr/include/freetype2/" \ + --with-gdal=/usr/bin/gdal-config \ + --with-geos \ --with-netcdf \ - --with-zstd \ - --with-bzlib \ + --with-nls \ --with-pdal \ + --with-postgres --with-postgres-includes="/usr/include/postgresql" \ + --with-proj-share=/usr/share/proj \ + --with-readline \ + --with-sqlite \ + --with-x \ + --with-zstd \ --without-mysql \ --without-odbc \ --without-openmp \ - --with-x \ - --with-nls \ - --with-readline \ && make -j $NUMTHREADS \ && make install && ldconfig diff --git a/flake.lock b/flake.lock index 257ec2317b7..51f61fb86f9 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1725234343, - "narHash": "sha256-+ebgonl3NbiKD2UD0x4BszCZQ6sTfL4xioaM49o5B3Y=", + "lastModified": 1726153070, + "narHash": "sha256-HO4zgY0ekfwO5bX0QH/3kJ/h4KvUDFZg8YpkNwIbg1U=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "567b938d64d4b4112ee253b9274472dc3a346eb6", + "rev": "bcef6817a8b2aa20a5a6dbb19b43e63c5bf8619a", "type": "github" }, "original": { @@ -19,11 +19,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1725194671, - "narHash": "sha256-tLGCFEFTB5TaOKkpfw3iYT9dnk4awTP/q4w+ROpMfuw=", + "lastModified": 1727648392, + "narHash": "sha256-VTlVv1nSxImFxY6RPQpNZxvEOQ0u5s1wBFDgixySNDo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b833ff01a0d694b910daca6e2ff4a3f26dee478c", + "rev": "4e0c36e4dd53f35d5a6385bdae88895ec5832f70", "type": "github" }, "original": { diff --git a/general/g.mapsets/tests/g_mapsets_list_format_test.py b/general/g.mapsets/tests/g_mapsets_list_format_test.py index 79555085bb0..05fe946a6ae 100644 --- a/general/g.mapsets/tests/g_mapsets_list_format_test.py +++ b/general/g.mapsets/tests/g_mapsets_list_format_test.py @@ -14,6 +14,7 @@ """Test parsing and structure of CSV and JSON outputs from g.mapsets""" import json +import sys import pytest import grass.script as gs from grass.script import utils as gutils @@ -30,6 +31,10 @@ def _check_parsed_list(mapsets, text, sep="|"): assert text == sep.join(mapsets) + "\n" +@pytest.mark.xfail( + sys.platform == "win32", + reason="universal_newlines or text subprocess option not used", +) @pytest.mark.parametrize("separator", SEPARATORS) def test_plain_list_output(simple_dataset, separator): """Test that the separators are properly applied with list flag""" @@ -38,6 +43,10 @@ def test_plain_list_output(simple_dataset, separator): _check_parsed_list(mapsets, text, gutils.separator(separator)) +@pytest.mark.xfail( + sys.platform == "win32", + reason="universal_newlines or text subprocess option not used", +) @pytest.mark.parametrize("separator", SEPARATORS) def test_plain_print_output(simple_dataset, separator): """Test that the separators are properly applied with print flag""" diff --git a/general/g.version/tests/conftest.py b/general/g.version/tests/conftest.py new file mode 100644 index 00000000000..e4357b2f672 --- /dev/null +++ b/general/g.version/tests/conftest.py @@ -0,0 +1,17 @@ +import os +import pytest +import grass.script as gs + + +@pytest.fixture(scope="module") +def session(tmp_path_factory): + """Set up a GRASS session for the tests.""" + tmp_path = tmp_path_factory.mktemp("grass_session") + project = "test_project" + + # Create a test location + gs.create_project(tmp_path, project) + + # Initialize the GRASS session + with gs.setup.init(tmp_path / project, env=os.environ.copy()) as session: + yield session diff --git a/general/g.version/tests/g_version_test.py b/general/g.version/tests/g_version_test.py new file mode 100644 index 00000000000..805518382a2 --- /dev/null +++ b/general/g.version/tests/g_version_test.py @@ -0,0 +1,83 @@ +import grass.script as gs + + +def test_g_version_no_flag(session): + """Test that g.version output contains the word 'GRASS'.""" + output = gs.read_command("g.version", env=session.env).strip() + assert ( + "GRASS" in output + ), "Expected 'GRASS' in g.version output, but it was not found." + + +def test_c_flag(session): + """Test the output of g.version -c for Copyright and License Statement.""" + expected_text = "Copyright and License Statement" + output = gs.read_command("g.version", flags="c", env=session.env).strip() + assert ( + expected_text in output + ), f"Expected '{expected_text}' in g.version -c output, but got: '{output}'" + + +def test_e_flag(session): + """Test that g.version -e contains the expected keys.""" + expected_keys = ["PROJ:", "GDAL/OGR:", "SQLite:"] + output = gs.read_command("g.version", flags="e", env=session.env).strip() + for key in expected_keys: + assert ( + key in output + ), f"Expected key '{key}' in g.version -e output, but it was not found." + + +def test_b_flag(session): + """Test that g.version -b output contains the word 'GRASS'.""" + output = gs.read_command("g.version", flags="b", env=session.env).strip() + assert ( + "GRASS" in output + ), "Expected 'GRASS' in g.version -b output, but it was not found." + + +def test_g_flag(session): + """Test that g.version -g contains the expected keys.""" + expected_keys = [ + "version", + "date", + "revision", + "build_date", + "build_platform", + "build_off_t_size", + ] + output = gs.parse_command("g.version", flags="g", env=session.env) + for key in expected_keys: + assert ( + key in output + ), f"Expected key '{key}' in g.version -g output, but it was not found." + + +def test_r_flag(session): + """Test that g.version -r contains the expected keys.""" + expected_texts = ["libgis revision:", "libgis date:"] + output = gs.read_command("g.version", flags="r", env=session.env).strip() + for text in expected_texts: + assert ( + text in output + ), f"Expected key '{text}' in g.version -r output, but it was not found." + + +def test_x_flag(session): + """Test that g.version -x output has paired curly brackets.""" + output = gs.read_command("g.version", flags="x", env=session.env).strip() + + def curly_brackets_paired(text): + counter = 0 + for character in text: + if character == "{": + counter += 1 + elif character == "}": + counter -= 1 + if counter < 0: + return False + return counter == 0 + + assert curly_brackets_paired( + output + ), "Curly brackets are not properly paired in the g.version -x output." diff --git a/gui/wxpython/animation/controller.py b/gui/wxpython/animation/controller.py index 1a920f77de7..1a8cb1fe9cc 100644 --- a/gui/wxpython/animation/controller.py +++ b/gui/wxpython/animation/controller.py @@ -465,9 +465,7 @@ def EvaluateInput(self, animationData): mapCount.add(len(layer.maps)) windowIndex.append(anim.windowIndex) - if maps and stds: - temporalMode = TemporalMode.NONTEMPORAL - elif maps: + if (maps and stds) or maps: temporalMode = TemporalMode.NONTEMPORAL elif stds: temporalMode = TemporalMode.TEMPORAL diff --git a/gui/wxpython/animation/dialogs.py b/gui/wxpython/animation/dialogs.py index 3d70dbd2639..f6e4db341c5 100644 --- a/gui/wxpython/animation/dialogs.py +++ b/gui/wxpython/animation/dialogs.py @@ -24,6 +24,9 @@ import wx import copy import datetime + +from pathlib import Path + import wx.lib.filebrowsebutton as filebrowse import wx.lib.scrolledpanel as SP import wx.lib.colourselect as csel @@ -488,7 +491,7 @@ def _create3DPanel(self, parent): labelText=_("Workspace file:"), dialogTitle=_("Choose workspace file to import 3D view parameters"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=0, fileMask="GRASS Workspace File (*.gxw)|*.gxw", ) @@ -1089,7 +1092,7 @@ def _createDecorationsProperties(self, panel): labelText=_("Image file:"), dialogTitle=_("Choose image file"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=wx.FD_OPEN, changeCallback=self.OnSetImage, ) @@ -1191,7 +1194,7 @@ def _createExportFormatPanel(self, notebook): labelText=_("Directory:"), dialogTitle=_("Choose directory for export"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), ) dirGridSizer = wx.GridBagSizer(hgap=5, vgap=5) @@ -1219,7 +1222,7 @@ def _createExportFormatPanel(self, notebook): labelText=_("GIF file:"), dialogTitle=_("Choose file to save animation"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=wx.FD_SAVE, ) gifGridSizer = wx.GridBagSizer(hgap=5, vgap=5) @@ -1242,7 +1245,7 @@ def _createExportFormatPanel(self, notebook): labelText=_("SWF file:"), dialogTitle=_("Choose file to save animation"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=wx.FD_SAVE, ) swfGridSizer = wx.GridBagSizer(hgap=5, vgap=5) @@ -1273,7 +1276,7 @@ def _createExportFormatPanel(self, notebook): labelText=_("AVI file:"), dialogTitle=_("Choose file to save animation"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=wx.FD_SAVE, ) encodingLabel = StaticText( @@ -1572,7 +1575,7 @@ def _export_file_validation(self, filebrowsebtn, file_path, file_postfix): caption=_("Overwrite?"), style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION, ) - if not overwrite_dlg.ShowModal() == wx.ID_YES: + if overwrite_dlg.ShowModal() != wx.ID_YES: overwrite_dlg.Destroy() return False overwrite_dlg.Destroy() diff --git a/gui/wxpython/animation/temporal_manager.py b/gui/wxpython/animation/temporal_manager.py index 150eeab510f..ef782543089 100644 --- a/gui/wxpython/animation/temporal_manager.py +++ b/gui/wxpython/animation/temporal_manager.py @@ -18,6 +18,7 @@ """ import datetime +from operator import itemgetter import grass.script as gs import grass.temporal as tgis @@ -195,9 +196,9 @@ def GetLabelsAndMaps(self): # by a temporary dataset, I don't know how it would work with point # data if self.temporalType == TemporalType.ABSOLUTE: - timestamps = sorted(list(labelListSet), key=lambda x: x[0]) + timestamps = sorted(list(labelListSet), key=itemgetter(0)) else: - timestamps = sorted(list(labelListSet), key=lambda x: x[0]) + timestamps = sorted(list(labelListSet), key=itemgetter(0)) newMapLists = [] for mapList, labelList in zip(mapLists, labelLists): diff --git a/gui/wxpython/core/gcmd.py b/gui/wxpython/core/gcmd.py index b93789f22f7..eb40ec57103 100644 --- a/gui/wxpython/core/gcmd.py +++ b/gui/wxpython/core/gcmd.py @@ -645,11 +645,11 @@ def _formatMsg(text): for line in text.splitlines(): if len(line) == 0: continue - elif "GRASS_INFO_MESSAGE" in line: - message += line.split(":", 1)[1].strip() + "\n" - elif "GRASS_INFO_WARNING" in line: - message += line.split(":", 1)[1].strip() + "\n" - elif "GRASS_INFO_ERROR" in line: + elif ( + "GRASS_INFO_MESSAGE" in line + or "GRASS_INFO_WARNING" in line + or "GRASS_INFO_ERROR" in line + ): message += line.split(":", 1)[1].strip() + "\n" elif "GRASS_INFO_END" in line: return message diff --git a/gui/wxpython/core/gconsole.py b/gui/wxpython/core/gconsole.py index bad4aac29cd..092f9bc704a 100644 --- a/gui/wxpython/core/gconsole.py +++ b/gui/wxpython/core/gconsole.py @@ -678,7 +678,7 @@ def load_source(modname, filename): skipInterface = True if os.path.splitext(command[0])[1] in {".py", ".sh"}: try: - with open(command[0], "r") as sfile: + with open(command[0]) as sfile: for line in sfile: if len(line) < 3: continue @@ -691,7 +691,7 @@ def load_source(modname, filename): if len(command) == 1 and not skipInterface: try: task = gtask.parse_interface(command[0]) - except: + except Exception: task = None else: task = None diff --git a/gui/wxpython/core/render.py b/gui/wxpython/core/render.py index b98e4323578..1d4ed6f5386 100644 --- a/gui/wxpython/core/render.py +++ b/gui/wxpython/core/render.py @@ -896,7 +896,7 @@ def GetWindow(self): env["GISDBASE"], env["LOCATION_NAME"], env["MAPSET"], "WIND" ) try: - windfile = open(filename, "r") + windfile = open(filename) except OSError as e: sys.exit( _("Error: Unable to open '%(file)s'. Reason: %(ret)s. wxGUI exited.\n") @@ -1237,7 +1237,7 @@ def SetRegion(self, windres=False, windres3=False): return grass_region - except: + except Exception: return None def GetListOfLayers( diff --git a/gui/wxpython/core/settings.py b/gui/wxpython/core/settings.py index 45620220aed..ef3a3031cee 100644 --- a/gui/wxpython/core/settings.py +++ b/gui/wxpython/core/settings.py @@ -110,7 +110,7 @@ def _generateLocale(self): self.locs.sort() # Add a default choice to not override system locale self.locs.insert(0, "system") - except: + except Exception: # No NLS self.locs = ["system"] @@ -922,7 +922,7 @@ def update_nested_dict_by_dict(dictionary, update): return dictionary try: - with open(self.filePath, "r") as f: + with open(self.filePath) as f: update = json.load(f, object_hook=settings_JSON_decode_hook) update_nested_dict_by_dict(settings, update) except json.JSONDecodeError as e: @@ -942,7 +942,7 @@ def _readLegacyFile(self, settings=None): settings = self.userSettings try: - fd = open(self.legacyFilePath, "r") + fd = open(self.legacyFilePath) except OSError: sys.stderr.write( _("Unable to read settings file <%s>\n") % self.legacyFilePath @@ -992,7 +992,7 @@ def SaveToFile(self, settings=None): if not os.path.exists(dirPath): try: os.mkdir(dirPath) - except: + except OSError: GError(_("Unable to create settings directory")) return try: diff --git a/gui/wxpython/core/testsuite/test_gcmd.py b/gui/wxpython/core/testsuite/test_gcmd.py index 121ede99b77..75d3bf5932b 100644 --- a/gui/wxpython/core/testsuite/test_gcmd.py +++ b/gui/wxpython/core/testsuite/test_gcmd.py @@ -1,5 +1,6 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows class Rcv: @@ -16,6 +17,8 @@ def recv(self): class Recv_SomeTest(TestCase): + + @xfail_windows def test_decode(self): """ Multibyte chars should not be split diff --git a/gui/wxpython/core/toolboxes.py b/gui/wxpython/core/toolboxes.py index ae6b1b6a0a6..e77010ff213 100644 --- a/gui/wxpython/core/toolboxes.py +++ b/gui/wxpython/core/toolboxes.py @@ -209,7 +209,7 @@ def getMenudataFile(userRootFile, newFile, fallback): fh.write(xml) fh.close() return menudataFile - except: + except Exception: _debug( 2, ( diff --git a/gui/wxpython/core/utils.py b/gui/wxpython/core/utils.py index 10d6dca2759..4be6c18e2a2 100644 --- a/gui/wxpython/core/utils.py +++ b/gui/wxpython/core/utils.py @@ -77,7 +77,7 @@ def GetTempfile(pref=None): return os.path.join(pref, file) else: return tempfile - except: + except Exception: return None @@ -255,7 +255,7 @@ def ListOfCatsToRange(cats): try: cats = list(map(int, cats)) - except: + except ValueError: return catstr i = 0 @@ -579,7 +579,7 @@ def GetListOfLocations(dbase): os.path.join(location, "*") ): listOfLocations.append(os.path.basename(location)) - except: + except OSError: pass ListSortLower(listOfLocations) @@ -632,7 +632,7 @@ def _getGDALFormats(): """Get dictionary of available GDAL drivers""" try: ret = grass.read_command("r.in.gdal", quiet=True, flags="f") - except: + except grass.CalledModuleError: ret = None return _parseFormats(ret), _parseFormats(ret, writableOnly=True) @@ -642,7 +642,7 @@ def _getOGRFormats(): """Get dictionary of available OGR drivers""" try: ret = grass.read_command("v.in.ogr", quiet=True, flags="f") - except: + except grass.CalledModuleError: ret = None return _parseFormats(ret), _parseFormats(ret, writableOnly=True) diff --git a/gui/wxpython/core/watchdog.py b/gui/wxpython/core/watchdog.py index 652bc1d755d..76302aeea0a 100644 --- a/gui/wxpython/core/watchdog.py +++ b/gui/wxpython/core/watchdog.py @@ -19,6 +19,9 @@ import os import time + +from pathlib import Path + import wx from wx.lib.newevent import NewEvent @@ -59,13 +62,13 @@ def on_modified(self, event): not event.is_directory and os.path.basename(event.src_path) == self.rcfile_name ): - timestamp = os.stat(event.src_path).st_mtime + timestamp = Path(event.src_path).stat().st_mtime if timestamp - self.modified_time < 0.5: return self.modified_time = timestamp # wait to make sure file writing is done time.sleep(0.1) - with open(event.src_path, "r") as f: + with open(event.src_path) as f: gisrc = {} for line in f: key, val = line.split(":") diff --git a/gui/wxpython/core/workspace.py b/gui/wxpython/core/workspace.py index 63f8b91cb89..be4eb1fda37 100644 --- a/gui/wxpython/core/workspace.py +++ b/gui/wxpython/core/workspace.py @@ -123,7 +123,7 @@ def __processFile(self): try: self.layerManager["pos"] = (posVal[0], posVal[1]) self.layerManager["size"] = (posVal[2], posVal[3]) - except: + except IndexError: pass # current working directory cwdPath = self.__getNodeText(node_lm, "cwd") @@ -155,7 +155,7 @@ def __processFile(self): try: pos = (posVal[0], posVal[1]) size = (posVal[2], posVal[3]) - except: + except IndexError: pos = None size = None # this happens on Windows when mapwindow is minimized when @@ -1742,7 +1742,7 @@ def read(self, parent): :return: list of map layers """ try: - file = open(self.filename, "r") + file = open(self.filename) except OSError: wx.MessageBox( parent=parent, @@ -2019,7 +2019,7 @@ def _get_value(self, line): """Get value of element""" try: return line.strip(" ").split(" ")[1].strip(" ") - except: + except IndexError: return "" def _get_element(self, line): diff --git a/gui/wxpython/datacatalog/catalog.py b/gui/wxpython/datacatalog/catalog.py index 880190b0943..4835061d968 100644 --- a/gui/wxpython/datacatalog/catalog.py +++ b/gui/wxpython/datacatalog/catalog.py @@ -19,6 +19,8 @@ import wx import os +from pathlib import Path + from core.debug import Debug from datacatalog.tree import DataCatalogTree from datacatalog.toolbars import DataCatalogToolbar, DataCatalogSearch @@ -200,7 +202,10 @@ def OnReloadCurrentMapset(self, event): def OnAddGrassDB(self, event): """Add grass database""" dlg = wx.DirDialog( - self, _("Choose GRASS data directory:"), os.getcwd(), wx.DD_DEFAULT_STYLE + self, + _("Choose GRASS data directory:"), + str(Path.cwd()), + wx.DD_DEFAULT_STYLE, ) if dlg.ShowModal() == wx.ID_OK: grassdatabase = dlg.GetPath() diff --git a/gui/wxpython/datacatalog/tree.py b/gui/wxpython/datacatalog/tree.py index faa0a6aac73..9a460d05f53 100644 --- a/gui/wxpython/datacatalog/tree.py +++ b/gui/wxpython/datacatalog/tree.py @@ -1378,15 +1378,20 @@ def OnPasteMap(self, event): ) if not new_name: continue - callback = lambda gisrc2=gisrc2, gisrc=gisrc, cLayer=self.copy_layer[ - i - ], cMapset=self.copy_mapset[ - i - ], cMode=self.copy_mode, sMapset=self.selected_mapset[ - 0 - ], name=new_name: self._onDoneReprojection( - env2, gisrc2, gisrc, cLayer, cMapset, cMode, sMapset, name - ) + + def callback( + gisrc2=gisrc2, + gisrc=gisrc, + cLayer=self.copy_layer[i], + cMapset=self.copy_mapset[i], + cMode=self.copy_mode, + sMapset=self.selected_mapset[0], + name=new_name, + ): + self._onDoneReprojection( + env2, gisrc2, gisrc, cLayer, cMapset, cMode, sMapset, name + ) + dlg = CatalogReprojectionDialog( self, self._giface, diff --git a/gui/wxpython/dbmgr/base.py b/gui/wxpython/dbmgr/base.py index 66321fcafda..d7ea818da4c 100644 --- a/gui/wxpython/dbmgr/base.py +++ b/gui/wxpython/dbmgr/base.py @@ -208,7 +208,7 @@ def LoadData(self, layer, columns=None, where=None, sql=None): try: # for maps connected via v.external keyId = columns.index(keyColumn) - except: + except ValueError: keyId = -1 # read data @@ -961,7 +961,7 @@ def OnLayerPageChanged(self, event): self.layerPage[self.selLayer]["data"] ).GetItemCount() ) - except: + except Exception: pass if idCol: @@ -1640,7 +1640,7 @@ def OnDataItemAdd(self, event): if dlg.ShowModal() == wx.ID_OK: try: # get category number cat = int(dlg.GetValues(columns=[keyColumn])[0]) - except: + except ValueError: cat = -1 try: @@ -1672,7 +1672,7 @@ def OnDataItemAdd(self, event): values[i] = int(float(values[i])) elif tlist.columns[columnName[i]]["ctype"] == float: values[i] = float(values[i]) - except: + except ValueError: raise ValueError( _("Value '%(value)s' needs to be entered as %(type)s.") % { @@ -1680,6 +1680,13 @@ def OnDataItemAdd(self, event): "type": tlist.columns[columnName[i]]["type"], } ) + except KeyError: + raise KeyError( + _("Column '%(column)s' does not exist.") + % { + "column": columnName[i], + } + ) columnsString += "%s," % columnName[i] if tlist.columns[columnName[i]]["ctype"] == str: @@ -3809,7 +3816,7 @@ def OnDeleteLayer(self, event): """Delete layer""" try: layer = int(self.deleteLayer.GetValue()) - except: + except ValueError: return RunCommand( @@ -3856,10 +3863,10 @@ def OnChangeLayer(self, event): """Layer number of layer to be deleted is changed""" try: layer = int(event.GetString()) - except: + except ValueError: try: - layer = self.mapDBInfo.layers.keys()[0] - except: + layer = list(self.mapDBInfo.layers.keys())[0] + except IndexError: return if self.GetCurrentPage() == self.modifyPanel: diff --git a/gui/wxpython/dbmgr/dialogs.py b/gui/wxpython/dbmgr/dialogs.py index 26e6d876371..0d7c6e9e523 100644 --- a/gui/wxpython/dbmgr/dialogs.py +++ b/gui/wxpython/dbmgr/dialogs.py @@ -222,10 +222,11 @@ def GetSQLString(self, updateValues=False): ctype = columns[name]["ctype"] value = columns[name]["values"][idx] id = columns[name]["ids"][idx] + widget = self.FindWindowById(id) try: - newvalue = self.FindWindowById(id).GetValue() - except: - newvalue = self.FindWindowById(id).GetLabel() + newvalue = widget.GetValue() + except AttributeError: + newvalue = widget.GetLabel() if newvalue: try: diff --git a/gui/wxpython/dbmgr/manager.py b/gui/wxpython/dbmgr/manager.py index c866bd55650..b1cbd93ab71 100644 --- a/gui/wxpython/dbmgr/manager.py +++ b/gui/wxpython/dbmgr/manager.py @@ -70,7 +70,7 @@ def __init__( self.parent = parent try: mapdisplay = self.parent.GetMapDisplay() - except: + except AttributeError: mapdisplay = None DbMgrBase.__init__( diff --git a/gui/wxpython/dbmgr/sqlbuilder.py b/gui/wxpython/dbmgr/sqlbuilder.py index 36827698efc..5e6fe0e7706 100644 --- a/gui/wxpython/dbmgr/sqlbuilder.py +++ b/gui/wxpython/dbmgr/sqlbuilder.py @@ -359,7 +359,7 @@ def OnUniqueValues(self, event, justsample=False): try: idx = self.list_columns.GetSelections()[0] column = self.list_columns.GetString(idx) - except: + except IndexError: self.list_values.Clear() return diff --git a/gui/wxpython/docs/wxgui_sphinx/conf.py b/gui/wxpython/docs/wxgui_sphinx/conf.py index 3641c2aab48..f0b61a31996 100644 --- a/gui/wxpython/docs/wxgui_sphinx/conf.py +++ b/gui/wxpython/docs/wxgui_sphinx/conf.py @@ -10,21 +10,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys -import os from datetime import date import string from shutil import copy - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -if not os.getenv("GISBASE"): - sys.exit("GISBASE not defined") -sys.path.insert( - 0, os.path.abspath(os.path.join(os.environ["GISBASE"], "etc", "python", "grass")) -) - from grass.script import core footer_tmpl = string.Template( diff --git a/gui/wxpython/gcp/manager.py b/gui/wxpython/gcp/manager.py index e5e0dd37471..3b6dd398b51 100644 --- a/gui/wxpython/gcp/manager.py +++ b/gui/wxpython/gcp/manager.py @@ -119,7 +119,7 @@ def __init__(self, parent, giface): self.target_gisrc = os.environ["GISRC"] self.gisrc_dict = {} try: - f = open(self.target_gisrc, "r") + f = open(self.target_gisrc) for line in f: line = line.replace("\n", "").strip() if len(line) < 1: @@ -880,20 +880,6 @@ def OnSrcSelection(self, event): else: wx.FindWindowById(wx.ID_FORWARD).Enable(True) - try: - # set computational region to match selected map and zoom display - # to region - if maptype == "raster": - p = RunCommand("g.region", "raster=src_map") - elif maptype == "vector": - p = RunCommand("g.region", "vector=src_map") - - if p.returncode == 0: - print("returncode = ", str(p.returncode)) - self.parent.Map.region = self.parent.Map.GetRegion() - except: - pass - def OnTgtRastSelection(self, event): """Source map to display selected""" global tgt_map @@ -1603,7 +1589,7 @@ def ReadGCPs(self): GError(parent=self, message=_("target mapwin not defined")) try: - f = open(self.file["points"], "r") + f = open(self.file["points"]) GCPcnt = 0 for line in f: @@ -1810,7 +1796,7 @@ def OnGeorect(self, event): overwrite=self.overwrite, ) if overwrite_dlg: - if not overwrite_dlg.ShowModal() == wx.ID_YES: + if overwrite_dlg.ShowModal() != wx.ID_YES: overwrite_dlg.Destroy() return overwrite_dlg.Destroy() @@ -1870,7 +1856,7 @@ def OnGeorect(self, event): overwrite=self.overwrite, ) if overwrite_dlg: - if not overwrite_dlg.ShowModal() == wx.ID_YES: + if overwrite_dlg.ShowModal() != wx.ID_YES: overwrite_dlg.Destroy() return overwrite_dlg.Destroy() @@ -2300,7 +2286,7 @@ def AdjustMap(self, newreg): def OnZoomToSource(self, event): """Set target map window to match extents of source map window""" - if not self.MapWindow == self.TgtMapWindow: + if self.MapWindow != self.TgtMapWindow: self.MapWindow = self.TgtMapWindow self.Map = self.TgtMap self.UpdateActive(self.TgtMapWindow) @@ -2313,7 +2299,7 @@ def OnZoomToSource(self, event): def OnZoomToTarget(self, event): """Set source map window to match extents of target map window""" - if not self.MapWindow == self.SrcMapWindow: + if self.MapWindow != self.SrcMapWindow: self.MapWindow = self.SrcMapWindow self.Map = self.SrcMap self.UpdateActive(self.SrcMapWindow) @@ -3323,7 +3309,7 @@ def OnSrcSelection(self, event): tmp_map = self.srcselection.GetValue() - if not tmp_map == "" and not tmp_map == src_map: + if tmp_map not in ("", src_map): self.new_src_map = tmp_map def OnTgtRastSelection(self, event): diff --git a/gui/wxpython/gcp/mapdisplay.py b/gui/wxpython/gcp/mapdisplay.py index 66daa70d2ca..0681b03e4e8 100644 --- a/gui/wxpython/gcp/mapdisplay.py +++ b/gui/wxpython/gcp/mapdisplay.py @@ -575,7 +575,7 @@ def GetMapToolbar(self): return self.toolbars["gcpdisp"] def _setActiveMapWindow(self, mapWindow): - if not self.MapWindow == mapWindow: + if self.MapWindow != mapWindow: self.MapWindow = mapWindow self.Map = mapWindow.Map self.UpdateActive(mapWindow) diff --git a/gui/wxpython/gmodeler/canvas.py b/gui/wxpython/gmodeler/canvas.py index 7c604ec6624..bfe5f7a90af 100644 --- a/gui/wxpython/gmodeler/canvas.py +++ b/gui/wxpython/gmodeler/canvas.py @@ -320,19 +320,19 @@ def OnRightClick(self, x, y, keys=0, attachment=0): popupMenu = Menu() popupMenu.Append(self.popupID["remove"], _("Remove")) self.frame.Bind(wx.EVT_MENU, self.OnRemove, id=self.popupID["remove"]) - if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop): + if isinstance(shape, (ModelAction, ModelLoop)): if shape.IsEnabled(): popupMenu.Append(self.popupID["enable"], _("Disable")) self.frame.Bind(wx.EVT_MENU, self.OnDisable, id=self.popupID["enable"]) else: popupMenu.Append(self.popupID["enable"], _("Enable")) self.frame.Bind(wx.EVT_MENU, self.OnEnable, id=self.popupID["enable"]) - if isinstance(shape, ModelAction) or isinstance(shape, ModelComment): + if isinstance(shape, (ModelAction, ModelComment)): popupMenu.AppendSeparator() if isinstance(shape, ModelAction): popupMenu.Append(self.popupID["label"], _("Set label")) self.frame.Bind(wx.EVT_MENU, self.OnSetLabel, id=self.popupID["label"]) - if isinstance(shape, ModelAction) or isinstance(shape, ModelComment): + if isinstance(shape, (ModelAction, ModelComment)): popupMenu.Append(self.popupID["comment"], _("Set comment")) self.frame.Bind(wx.EVT_MENU, self.OnSetComment, id=self.popupID["comment"]) @@ -377,11 +377,7 @@ def OnRightClick(self, x, y, keys=0, attachment=0): if self.GetShape().IsIntermediate(): popupMenu.Enable(self.popupID["display"], False) - if ( - isinstance(shape, ModelData) - or isinstance(shape, ModelAction) - or isinstance(shape, ModelLoop) - ): + if isinstance(shape, (ModelData, ModelAction, ModelLoop)): popupMenu.AppendSeparator() popupMenu.Append(self.popupID["props"], _("Properties")) self.frame.Bind(wx.EVT_MENU, self.OnProperties, id=self.popupID["props"]) diff --git a/gui/wxpython/gmodeler/model.py b/gui/wxpython/gmodeler/model.py index a609a1c8bd8..98a7204abbe 100644 --- a/gui/wxpython/gmodeler/model.py +++ b/gui/wxpython/gmodeler/model.py @@ -546,7 +546,7 @@ def _substituteFile(self, item, params=None, checkOnly=False): for finput in self.fileInput: # read lines - fd = open(finput, "r") + fd = open(finput) try: data = self.fileInput[finput] = fd.read() finally: @@ -2641,7 +2641,7 @@ def _writeItem(self, item, ignoreBlock=True, variables={}): self._writePythonAction( item, variables, self.model.GetIntermediateData()[:3] ) - elif isinstance(item, ModelLoop) or isinstance(item, ModelCondition): + elif isinstance(item, (ModelLoop, ModelCondition)): # substitute condition cond = item.GetLabel() for variable in self.model.GetVariables(): diff --git a/gui/wxpython/gmodeler/panels.py b/gui/wxpython/gmodeler/panels.py index 93ef770efe1..416a9b85592 100644 --- a/gui/wxpython/gmodeler/panels.py +++ b/gui/wxpython/gmodeler/panels.py @@ -25,6 +25,8 @@ import random import math +from pathlib import Path + import wx from wx.lib import ogl @@ -834,7 +836,7 @@ def OnModelOpen(self, event): dlg = wx.FileDialog( parent=self, message=_("Choose model file"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("GRASS Model File (*.gxm)|*.gxm"), ) if dlg.ShowModal() == wx.ID_OK: @@ -892,7 +894,7 @@ def OnModelSaveAs(self, event=None): dlg = wx.FileDialog( parent=self, message=_("Choose file to save current model"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("GRASS Model File (*.gxm)|*.gxm"), style=wx.FD_SAVE, ) @@ -1740,7 +1742,7 @@ def SaveAs(self, force=False): parent=self, message=_("Choose file to save"), defaultFile=os.path.basename(self.parent.GetModelFile(ext=False)), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=fn_wildcard, style=wx.FD_SAVE, ) diff --git a/gui/wxpython/gui_core/dialogs.py b/gui/wxpython/gui_core/dialogs.py index f7c7711cadf..cd2a8dcc24d 100644 --- a/gui/wxpython/gui_core/dialogs.py +++ b/gui/wxpython/gui_core/dialogs.py @@ -1213,7 +1213,7 @@ def _filter(self, data): try: if re.compile(self.flt_pattern).search(dt): flt_data.append(dt) - except: + except re.error: pass return flt_data @@ -1656,7 +1656,7 @@ def OnFilter(self, event): try: if re.compile(event.GetString()).search(layer): list.append(layer) - except: + except re.error: pass list = naturally_sorted(list) diff --git a/gui/wxpython/gui_core/forms.py b/gui/wxpython/gui_core/forms.py index 84fd849908c..c1123497abf 100644 --- a/gui/wxpython/gui_core/forms.py +++ b/gui/wxpython/gui_core/forms.py @@ -58,6 +58,7 @@ import codecs from threading import Thread +from pathlib import Path import wx @@ -2015,7 +2016,7 @@ def __init__(self, parent, giface, task, id=wx.ID_ANY, frame=None, *args, **kwar # check wildcard try: fExt = os.path.splitext(p.get("key_desc", ["*.*"])[0])[1] - except: + except IndexError: fExt = None if not fExt: fMask = "*" @@ -2034,7 +2035,7 @@ def __init__(self, parent, giface, task, id=wx.ID_ANY, frame=None, *args, **kwar dialogTitle=_("Choose %s") % p.get("description", _("file")).lower(), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=fmode, changeCallback=self.OnSetValue, ) @@ -2145,7 +2146,7 @@ def __init__(self, parent, giface, task, id=wx.ID_ANY, frame=None, *args, **kwar dialogTitle=_("Choose %s") % p.get("description", _("Directory")), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), newDirectory=True, changeCallback=self.OnSetValue, ) @@ -2587,7 +2588,7 @@ def OnFileLoad(self, event): data = "" try: - f = open(path, "r") + f = open(path) except OSError as e: gcmd.GError( parent=self, @@ -2624,7 +2625,7 @@ def OnFileSave(self, event): dlg = wx.FileDialog( parent=self, message=_("Save input as..."), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) diff --git a/gui/wxpython/gui_core/ghelp.py b/gui/wxpython/gui_core/ghelp.py index 93879998133..0c4f6cdadfb 100644 --- a/gui/wxpython/gui_core/ghelp.py +++ b/gui/wxpython/gui_core/ghelp.py @@ -25,6 +25,7 @@ import sys import wx from wx.html import HtmlWindow +from operator import itemgetter try: from wx.lib.agw.hyperlink import HyperLinkCtrl @@ -273,7 +274,7 @@ def _pageCopyright(self): """Copyright information""" copyfile = os.path.join(os.getenv("GISBASE"), "COPYING") if os.path.exists(copyfile): - copyrightFile = open(copyfile, "r") + copyrightFile = open(copyfile) copytext = copyrightFile.read() copyrightFile.close() else: @@ -302,7 +303,7 @@ def _pageLicense(self): """Licence about""" licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT") if os.path.exists(licfile): - licenceFile = open(licfile, "r") + licenceFile = open(licfile) license = "".join(licenceFile.readlines()) licenceFile.close() else: @@ -450,7 +451,7 @@ def _pageContributors(self, extra=False): text = StaticText(parent=contribwin, id=wx.ID_ANY, label=item) text.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, "")) contribBox.Add(text) - for vals in sorted(contribs, key=lambda x: x[0]): + for vals in sorted(contribs, key=itemgetter(0)): for item in vals: contribBox.Add( StaticText(parent=contribwin, id=wx.ID_ANY, label=item) @@ -565,15 +566,15 @@ def _langString(self, k, v): allStr = "%s :" % k.upper() try: allStr += _(" %d translated") % v["good"] - except: + except KeyError: pass try: allStr += _(" %d fuzzy") % v["fuzzy"] - except: + except KeyError: pass try: allStr += _(" %d untranslated") % v["bad"] - except: + except KeyError: pass return allStr @@ -588,7 +589,7 @@ def _langBox(self, par, k, v): ) tgood.SetForegroundColour(wx.Colour(35, 142, 35)) langBox.Add(tgood) - except: + except KeyError: tgood = StaticText(parent=par, id=wx.ID_ANY, label="") langBox.Add(tgood) try: @@ -597,7 +598,7 @@ def _langBox(self, par, k, v): ) tfuzzy.SetForegroundColour(wx.Colour(255, 142, 0)) langBox.Add(tfuzzy) - except: + except KeyError: tfuzzy = StaticText(parent=par, id=wx.ID_ANY, label="") langBox.Add(tfuzzy) try: @@ -606,7 +607,7 @@ def _langBox(self, par, k, v): ) tbad.SetForegroundColour(wx.Colour(255, 0, 0)) langBox.Add(tbad) - except: + except KeyError: tbad = StaticText(parent=par, id=wx.ID_ANY, label="") langBox.Add(tbad) return langBox @@ -843,7 +844,7 @@ def fillContentsFromFile(self, htmlFile, skipDescription=True): contents.append(line) self.SetPage("".join(contents)) self.loaded = True - except: # The Manual file was not found + except Exception: # The Manual file was not found self.loaded = False diff --git a/gui/wxpython/gui_core/gselect.py b/gui/wxpython/gui_core/gselect.py index cec6963a527..ec873f60eb4 100644 --- a/gui/wxpython/gui_core/gselect.py +++ b/gui/wxpython/gui_core/gselect.py @@ -47,6 +47,8 @@ import glob import ctypes +from pathlib import Path + import wx from core import globalvar @@ -414,7 +416,7 @@ def SetData(self, **kargs): self.multiple = kargs["multiple"] if "onPopup" in kargs: self.onPopup = kargs["onPopup"] - if kargs.get("layerTree", None): + if kargs.get("layerTree"): self.filterItems = [] # reset ltype = kargs["type"] for layer in kargs["layerTree"].GetVisibleLayers(skipDigitized=True): @@ -482,7 +484,7 @@ def GetElementList(self, elements=None, exclude=False): try: self.seltree.EnsureVisible(item) self.seltree.SelectItem(item) - except: + except Exception: pass def _getElementList(self, element, mapsets=None, elements=None, exclude=False): @@ -1289,7 +1291,7 @@ def __init__( style = 0 # disabled, read-only widget has no TextCtrl children (TODO: rewrite) # if not new and not multiple: - ### style = wx.CB_READONLY + # style = wx.CB_READONLY wx.ComboBox.__init__(self, parent, id, size=size, style=style, **kwargs) self.searchPath = searchPath @@ -1558,7 +1560,7 @@ def __init__( labelText=_("File:"), dialogTitle=_("Choose file to import"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), changeCallback=self.OnUpdate, fileMask=fileMask, ) @@ -1575,7 +1577,7 @@ def __init__( labelText=_("Directory:"), dialogTitle=_("Choose input directory"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), changeCallback=self.OnUpdate, ) browse.GetChildren()[1].SetName("GdalSelectDataSource") @@ -1628,7 +1630,7 @@ def __init__( labelText=_("Name:"), dialogTitle=_("Choose file"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), changeCallback=self.OnUpdate, ) browse.GetChildren()[1].SetName("GdalSelectDataSource") @@ -1663,7 +1665,7 @@ def __init__( labelText=_("Directory:"), dialogTitle=_("Choose input directory"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), changeCallback=self.OnUpdate, ) self.dbWidgets["dirbrowse"] = browse diff --git a/gui/wxpython/gui_core/preferences.py b/gui/wxpython/gui_core/preferences.py index c2020b2ffba..724e4a5aec0 100644 --- a/gui/wxpython/gui_core/preferences.py +++ b/gui/wxpython/gui_core/preferences.py @@ -28,6 +28,8 @@ import os import sys +from pathlib import Path + try: import pwd @@ -2419,28 +2421,27 @@ def LoadData(self): """Load data into list""" self.InsertColumn(0, _("Mapset")) self.InsertColumn(1, _("Owner")) - ### self.InsertColumn(2, _('Group')) + # self.InsertColumn(2, _('Group')) gisenv = gs.gisenv() locationPath = os.path.join(gisenv["GISDBASE"], gisenv["LOCATION_NAME"]) for mapset in self.parent.all_mapsets_ordered: index = self.InsertItem(self.GetItemCount(), mapset) - mapsetPath = os.path.join(locationPath, mapset) - stat_info = os.stat(mapsetPath) + stat_info = Path(locationPath, mapset).stat() if havePwd: try: self.SetItem(index, 1, "%s" % pwd.getpwuid(stat_info.st_uid)[0]) except KeyError: self.SetItem(index, 1, "nobody") # FIXME: get group name - ### self.SetStringItem(index, 2, "%-8s" % stat_info.st_gid) + # self.SetStringItem(index, 2, "%-8s" % stat_info.st_gid) else: # FIXME: no pwd under MS Windows (owner: 0, group: 0) self.SetItem(index, 1, "%-8s" % stat_info.st_uid) - ### self.SetStringItem(index, 2, "%-8s" % stat_info.st_gid) + # self.SetStringItem(index, 2, "%-8s" % stat_info.st_gid) self.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE) - ### self.SetColumnWidth(col = 1, width = wx.LIST_AUTOSIZE) + # self.SetColumnWidth(col = 1, width = wx.LIST_AUTOSIZE) def OnCheckItem(self, index, flag): """Mapset checked/unchecked""" diff --git a/gui/wxpython/gui_core/pyedit.py b/gui/wxpython/gui_core/pyedit.py index 2de2d45b066..5958ffb6d68 100644 --- a/gui/wxpython/gui_core/pyedit.py +++ b/gui/wxpython/gui_core/pyedit.py @@ -395,7 +395,7 @@ def SaveAs(self): dlg = wx.FileDialog( parent=self.guiparent, message=_("Choose file to save"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("Python script (*.py)|*.py"), style=wx.FD_SAVE, ) @@ -466,7 +466,7 @@ def Open(self): dlg = wx.FileDialog( parent=self.guiparent, message=_("Open file"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("Python script (*.py)|*.py"), style=wx.FD_OPEN, ) diff --git a/gui/wxpython/gui_core/vselect.py b/gui/wxpython/gui_core/vselect.py index 0ff30afaafd..16bb64b7a57 100644 --- a/gui/wxpython/gui_core/vselect.py +++ b/gui/wxpython/gui_core/vselect.py @@ -232,7 +232,7 @@ def AddVecInfo(self, vInfoDictTMP) -> bool: if self._dialog: self.slist.AddItem(vInfoDictTMP) - return not len(self.selectedFeatures) == 0 + return len(self.selectedFeatures) != 0 def _draw(self): """Call class 'VectorSelectHighlighter' to draw selected features""" diff --git a/gui/wxpython/gui_core/widgets.py b/gui/wxpython/gui_core/widgets.py index 0ec50553114..dc9041e3578 100644 --- a/gui/wxpython/gui_core/widgets.py +++ b/gui/wxpython/gui_core/widgets.py @@ -435,7 +435,6 @@ class NumTextCtrl(TextCtrl): """Class derived from wx.TextCtrl for numerical values only""" def __init__(self, parent, **kwargs): - ## self.precision = kwargs.pop('prec') TextCtrl.__init__( self, parent=parent, validator=NTCValidator(flag="DIGIT_ONLY"), **kwargs ) @@ -753,7 +752,7 @@ def _validate(self, win): if text: try: datetime.strptime(text, "%Y-%m-%d") - except: + except ValueError: self._notvalid() return False @@ -1567,7 +1566,7 @@ def _loadSettings(self): return data try: - fd = open(self.settingsFile, "r") + fd = open(self.settingsFile) except OSError: return data diff --git a/gui/wxpython/iclass/frame.py b/gui/wxpython/iclass/frame.py index 69c18780249..606c364c76f 100644 --- a/gui/wxpython/iclass/frame.py +++ b/gui/wxpython/iclass/frame.py @@ -570,7 +570,7 @@ def OnZoomMenu(self, event): def OnZoomToTraining(self, event): """Set preview display to match extents of training display""" - if not self.MapWindow == self.GetSecondWindow(): + if self.MapWindow != self.GetSecondWindow(): self.MapWindow = self.GetSecondWindow() self.Map = self.GetSecondMap() self.UpdateActive(self.GetSecondWindow()) @@ -583,7 +583,7 @@ def OnZoomToTraining(self, event): def OnZoomToPreview(self, event): """Set preview display to match extents of training display""" - if not self.MapWindow == self.GetFirstWindow(): + if self.MapWindow != self.GetFirstWindow(): self.MapWindow = self.GetFirstWindow() self.Map = self.GetFirstMap() self.UpdateActive(self.GetFirstWindow()) diff --git a/gui/wxpython/iclass/g.gui.iclass.html b/gui/wxpython/iclass/g.gui.iclass.html index f6169b1d8f6..dd789cd75b5 100644 --- a/gui/wxpython/iclass/g.gui.iclass.html +++ b/gui/wxpython/iclass/g.gui.iclass.html @@ -35,7 +35,6 @@

DESCRIPTION

  • write signature file
  • import vector map
  • export vector map with attribute table
  • -
    @@ -62,6 +61,13 @@

    DESCRIPTION

    By doing this, the user can see how much of the image is likely to be put into the class associated with the signature. +

    wxIClass can also import training areas defined in a vector layer. In that case the program expects the vector layer to have the following columns defined: +

    +

    The spectral signatures are composed of region means and covariance matrices. These region means and covariance matrices are used in diff --git a/gui/wxpython/image2target/g.gui.image2target.py b/gui/wxpython/image2target/g.gui.image2target.py index 97c67b20354..e2704dc371b 100755 --- a/gui/wxpython/image2target/g.gui.image2target.py +++ b/gui/wxpython/image2target/g.gui.image2target.py @@ -30,76 +30,9 @@ # % keyword: GCP # %end -##%option G_OPT_M_LOCATION -##% key: source_project -##% label: The name of the source project (has no projection) -##% description: The source project (location) has no CRS -###% section: source -##% required: yes -##%end - -##%option G_OPT_M_MAPSET -##% key: source_mapset -##% label: The name of the source mapset (has no projection) -##% description: The name of the source mapset (has no projection) -###% section: source -##% required: yes -##%end - -##%option G_OPT_I_GROUP -##% key: source_group -##% required: yes -##% section: source -##%end - -##%option G_OPT_R_INPUT -##% key: source_image -##% required: yes -###% section: source -##%end - -##%option G_OPT_R_INPUT -##% key: target_image -##% label: The name of the image that is already georeferenced used to find location of GCPs -##% description: The name of the image that is already georeferenced used to find the location of GCPs -###% section: target -##% required: no -##%end - -##%option -##% key: camera -##% type: string -##% label: The name of the camera (generated in i.ortho.camera) -##% description: The name of the camera (generated in i.ortho.camera) -##% required: yes -###% section: parameters -##%end - -##%option -##% key: order -##% type: string -##% label: The rectification order -##% description: The rectification order -##% required: yes -##% answer: 1 -###% section: parameters -##%end - -##%option -##% key: extension -##% type: string -##% label: The name of the output files extension -##% description: The name of the output files extension -##% required: yes -##% answer: _ii2t_out -##% section: target -##%end - """ Module to run GCP management tool as stadalone application. - -@author Vaclav Petras (standalone module) """ import os @@ -129,55 +62,10 @@ def main(): else: os.environ["GRASS_RENDER_IMMEDIATE"] = "cairo" - # if options["source_location"]: - # src_loc = options["source_location"] - # else: - # gscript.fatal(_("No georeferenced source location provided")) - - # if options["source_mapset"]: - # src_mpt = options["source_mapset"] - # else: - # gscript.fatal(_("No georeferenced source mapset provided")) - - # if options["source_group"]: - # src_grp = options["source_group"] - # else: - # gscript.fatal(_("Please provide a source group name to process")) - - # if options['source_image']: - # src_ras = options["source_image"] - # else: - # gscript.fatal(_("Please provide a source image map name to process")) - - # if options["target_image"]: - # tgt_ras = options["target_image"] - # else: - # gscript.fatal(_("No georeferenced target map provided")) - - # if options["camera"]: - # camera = options["camera"] - # else: - # gscript.fatal(_( - # "Please provide a camera name (generated by i.ortho.camera)" - # )) - - # if options["order"]: - # order = options["order"] - # else: - # gscript.fatal(_("Please provive an order value")) - - # if options["extension"]: - # extension = options["extension"] - # else: - # gscript.fatal(_("Please provide an output file extension")) - app = wx.App() - # wizard = GCPWizard(parent=None, giface=StandaloneGrassInterface(), - # srcloc=src_loc,srcmpt=src_mpt,srcgrp=src_grp,srcras=src_ras, - # tgtras=tgt_ras,camera=camera, order=order, extension=extension) + GCPWizard(parent=None, giface=StandaloneGrassInterface()) - wizard = GCPWizard(parent=None, giface=StandaloneGrassInterface()) app.MainLoop() diff --git a/gui/wxpython/image2target/ii2t_gis_set.py b/gui/wxpython/image2target/ii2t_gis_set.py index 0f51d969e93..4e5a5aee200 100644 --- a/gui/wxpython/image2target/ii2t_gis_set.py +++ b/gui/wxpython/image2target/ii2t_gis_set.py @@ -27,6 +27,8 @@ import platform import getpass +from pathlib import Path + from core import globalvar import wx import wx.lib.mixins.listctrl as listmix @@ -94,7 +96,7 @@ def __init__(self, parent=None, id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE): self.hbitmap = wx.StaticBitmap( self.panel, wx.ID_ANY, wx.Bitmap(name=name, type=wx.BITMAP_TYPE_PNG) ) - except: + except Exception: self.hbitmap = wx.StaticBitmap( self.panel, wx.ID_ANY, BitmapFromImage(wx.EmptyImage(530, 150)) ) @@ -315,7 +317,7 @@ def _set_properties(self, version, revision): if os.path.isdir(os.getenv("HOME")): self.gisdbase = os.getenv("HOME") else: - self.gisdbase = os.getcwd() + self.gisdbase = str(Path.cwd()) try: self.tgisdbase.SetValue(self.gisdbase) except UnicodeDecodeError: @@ -541,7 +543,7 @@ def _readGisRC(self): if gisrc and os.path.isfile(gisrc): try: - rc = open(gisrc, "r") + rc = open(gisrc) for line in rc: try: key, val = line.split(":", 1) @@ -822,8 +824,8 @@ def DeleteMapset(self, event): shutil.rmtree(os.path.join(self.gisdbase, location, mapset)) self.OnSelectLocation(None) self.lbmapsets.SetSelection(0) - except: - wx.MessageBox(message=_("Unable to delete mapset")) + except OSError as e: + wx.MessageBox(message=_("Unable to delete mapset: %s") % str(e)) dlg.Destroy() @@ -854,8 +856,8 @@ def DeleteLocation(self, event): self.lblocations.SetSelection(0) self.OnSelectLocation(None) self.lbmapsets.SetSelection(0) - except: - wx.MessageBox(message=_("Unable to delete location")) + except OSError as e: + wx.MessageBox(message=_("Unable to delete location: %s") % str(e)) dlg.Destroy() @@ -1161,7 +1163,7 @@ def _getDefaultMapsetName(self): defaultName = getpass.getuser() # raise error if not ascii (not valid mapset name) defaultName.encode("ascii") - except: # whatever might go wrong + except Exception: # whatever might go wrong defaultName = "user" return defaultName diff --git a/gui/wxpython/image2target/ii2t_manager.py b/gui/wxpython/image2target/ii2t_manager.py index 17811ceceb1..3adcf5ef9bd 100644 --- a/gui/wxpython/image2target/ii2t_manager.py +++ b/gui/wxpython/image2target/ii2t_manager.py @@ -159,7 +159,7 @@ def __init__(self, parent, giface): self.target_gisrc = os.environ["GISRC"] self.gisrc_dict = {} try: - f = open(self.target_gisrc, "r") + f = open(self.target_gisrc) for line in f: line = line.replace("\n", "").strip() if len(line) < 1: @@ -884,20 +884,6 @@ def OnSrcSelection(self, event): else: wx.FindWindowById(wx.ID_FORWARD).Enable(True) - try: - # set computational region to match selected map and zoom display - # to region - if maptype == "raster": - p = RunCommand("g.region", "raster=src_map") - elif maptype == "vector": - p = RunCommand("g.region", "vector=src_map") - - if p.returncode == 0: - print("returncode = ", str(p.returncode)) - self.parent.Map.region = self.parent.Map.GetRegion() - except: - pass - def OnTgtRastSelection(self, event): """Source map to display selected""" global tgt_map @@ -1627,7 +1613,7 @@ def ReadGCPs(self): GError(parent=self, message=_("target mapwin not defined")) try: - f = open(self.file["control_points"], "r") + f = open(self.file["control_points"]) GCPcnt = 0 for line in f: @@ -1781,22 +1767,20 @@ def OnGeorect(self, event): else: flags = "a" - busy = wx.BusyInfo(_("Rectifying images, please wait..."), parent=self) - wx.GetApp().Yield() - - ret, msg = RunCommand( - "i.ortho.rectify", - parent=self, - getErrorMsg=True, - quiet=True, - group=self.xygroup, - extension=self.extension, - method=self.gr_method, - angle=self.grwiz.cam_angle, - flags=flags, - ) + with wx.BusyInfo(_("Rectifying images, please wait..."), parent=self): + wx.GetApp().Yield() - del busy + ret, msg = RunCommand( + "i.ortho.rectify", + parent=self, + getErrorMsg=True, + quiet=True, + group=self.xygroup, + extension=self.extension, + method=self.gr_method, + angle=self.grwiz.cam_angle, + flags=flags, + ) # provide feedback on failure if ret != 0: @@ -1828,23 +1812,21 @@ def OnGeorect(self, event): ) ret = msg = "" - busy = wx.BusyInfo( + with wx.BusyInfo( _("Rectifying vector map <%s>, please wait...") % vect, parent=self - ) - wx.GetApp().Yield() - - ret, msg = RunCommand( - "v.rectify", - parent=self, - getErrorMsg=True, - quiet=True, - input=vect, - output=self.outname, - group=self.xygroup, - order=self.gr_order, - ) - - del busy + ): + wx.GetApp().Yield() + + ret, msg = RunCommand( + "v.rectify", + parent=self, + getErrorMsg=True, + quiet=True, + input=vect, + output=self.outname, + group=self.xygroup, + order=self.gr_order, + ) # provide feedback on failure if ret != 0: @@ -1971,7 +1953,6 @@ def OnGROrder(self, event): elif self.gr_order == 2: minNumOfItems = 6 - diff = 6 - numOfItems # self.SetStatusText(_( # "Insufficient points, 6+ points needed for 2nd order")) @@ -2245,7 +2226,7 @@ def AdjustMap(self, newreg): def OnZoomToSource(self, event): """Set target map window to match extents of source map window""" - if not self.MapWindow == self.TgtMapWindow: + if self.MapWindow != self.TgtMapWindow: self.MapWindow = self.TgtMapWindow self.Map = self.TgtMap self.UpdateActive(self.TgtMapWindow) @@ -2258,7 +2239,7 @@ def OnZoomToSource(self, event): def OnZoomToTarget(self, event): """Set source map window to match extents of target map window""" - if not self.MapWindow == self.SrcMapWindow: + if self.MapWindow != self.SrcMapWindow: self.MapWindow = self.SrcMapWindow self.Map = self.SrcMap self.UpdateActive(self.SrcMapWindow) @@ -2270,7 +2251,6 @@ def OnZoomToTarget(self, event): def OnZoomMenuGCP(self, event): """Popup Zoom menu""" - point = wx.GetMousePosition() zoommenu = Menu() # Add items to the menu @@ -3282,7 +3262,7 @@ def OnSrcSelection(self, event): tmp_map = self.srcselection.GetValue() - if not tmp_map == "" and not tmp_map == src_map: + if tmp_map not in ("", src_map): self.new_src_map = tmp_map def OnTgtRastSelection(self, event): @@ -3376,7 +3356,6 @@ def UpdateSettings(self): srcrenderVector = False tgtrender = False tgtrenderVector = False - reload_target = False if self.new_src_map != src_map: # remove old layer layers = self.parent.grwiz.SrcMap.GetListOfLayers() @@ -3414,7 +3393,6 @@ def UpdateSettings(self): del layers[0] layers = self.parent.grwiz.TgtMap.GetListOfLayers() # self.parent.grwiz.TgtMap.DeleteAllLayers() - reload_target = True tgt_map["raster"] = self.new_tgt_map["raster"] tgt_map["vector"] = self.new_tgt_map["vector"] diff --git a/gui/wxpython/image2target/ii2t_mapdisplay.py b/gui/wxpython/image2target/ii2t_mapdisplay.py index b85a70a152f..9ee3b77d118 100644 --- a/gui/wxpython/image2target/ii2t_mapdisplay.py +++ b/gui/wxpython/image2target/ii2t_mapdisplay.py @@ -474,7 +474,6 @@ def PrintMenu(self, event): """ Print options and output menu for map display """ - point = wx.GetMousePosition() printmenu = Menu() # Add items to the menu setup = wx.MenuItem(printmenu, wx.ID_ANY, _("Page setup")) @@ -518,7 +517,6 @@ def SaveDisplayRegion(self, event): def OnZoomMenu(self, event): """Popup Zoom menu""" - point = wx.GetMousePosition() zoommenu = Menu() # Add items to the menu @@ -567,7 +565,7 @@ def GetMapToolbar(self): return self.toolbars["gcpdisp"] def _setActiveMapWindow(self, mapWindow): - if not self.MapWindow == mapWindow: + if self.MapWindow != mapWindow: self.MapWindow = mapWindow self.Map = mapWindow.Map self.UpdateActive(mapWindow) diff --git a/gui/wxpython/image2target/ii2t_statusbar.py b/gui/wxpython/image2target/ii2t_statusbar.py index 3c5ee378a1e..7ff80e621d3 100644 --- a/gui/wxpython/image2target/ii2t_statusbar.py +++ b/gui/wxpython/image2target/ii2t_statusbar.py @@ -11,9 +11,6 @@ This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. - -@author Vaclav Petras (statusbar refactoring) -@author Anna Kratochvilova (statusbar refactoring) """ import wx diff --git a/gui/wxpython/iscatt/controllers.py b/gui/wxpython/iscatt/controllers.py index e8884f4f78b..aed917aacad 100644 --- a/gui/wxpython/iscatt/controllers.py +++ b/gui/wxpython/iscatt/controllers.py @@ -597,7 +597,7 @@ def _renderscattplts(self, scatt_ids, cats, cats_attrs): try: self.cat_ids.remove(c) scatt_dt[c]["render"] = True - except: + except ValueError: scatt_dt[c]["render"] = False if self.scatt_mgr.pol_sel_mode[0]: @@ -674,7 +674,7 @@ def ChangePosition(self, cat_id, new_pos): try: pos = self.cats_ids.index(cat_id) - except: + except ValueError: return False if pos > new_pos: diff --git a/gui/wxpython/iscatt/core_c.py b/gui/wxpython/iscatt/core_c.py index a2a3d5da9da..8be04964b90 100644 --- a/gui/wxpython/iscatt/core_c.py +++ b/gui/wxpython/iscatt/core_c.py @@ -11,17 +11,36 @@ @author Stepan Turek (mentor: Martin Landa) """ +import ctypes import sys -import numpy as np +from ctypes import POINTER, c_char_p, c_double, c_int, c_uint8, pointer from multiprocessing import Process, Queue -from ctypes import * +import numpy as np try: - from grass.lib.imagery import * from grass.lib.gis import G_get_window + from grass.lib.imagery import ( + SC_SCATT_CONDITIONS, + SC_SCATT_DATA, + I_apply_colormap, + I_compute_scatts, + I_create_cat_rast, + I_insert_patch_to_cat_rast, + I_merge_arrays, + I_rasterize, + I_sc_add_cat, + I_sc_free_cats, + I_sc_init_cats, + I_sc_insert_scatt_data, + I_scd_init_scatt_data, + scdScattData, + struct_Cell_head, + struct_Range, + struct_scCats, + ) except ImportError as e: - sys.stderr.write(_("Loading ctypes libs failed")) + sys.stderr.write(_("Loading ctypes libs failed: %s") % e) from core.gcmd import GException from grass.script import encode @@ -238,7 +257,7 @@ def _getComputationStruct(cats, cats_rasts, cats_type, n_bands): scatt_vals = scdScattData() - c_void_p = ctypes.POINTER(ctypes.c_void_p) + c_void_p = POINTER(ctypes.c_void_p) if cats_type == SC_SCATT_DATA: vals[:] = 0 @@ -269,7 +288,6 @@ def _updateCatRastProcess(patch_rast, region, cat_rast, output_queue): def _rasterize(polygon, rast, region, value, output_queue): - pol_size = len(polygon) * 2 pol = np.array(polygon, dtype=float) c_uint8_p = POINTER(c_uint8) diff --git a/gui/wxpython/iscatt/dialogs.py b/gui/wxpython/iscatt/dialogs.py index 3e2d7fa7a59..a2d054b64a4 100644 --- a/gui/wxpython/iscatt/dialogs.py +++ b/gui/wxpython/iscatt/dialogs.py @@ -90,8 +90,6 @@ def _layout(self): border = wx.BoxSizer(wx.VERTICAL) dialogSizer = wx.BoxSizer(wx.VERTICAL) - regionSizer = wx.BoxSizer(wx.HORIZONTAL) - dialogSizer.Add( self._addSelectSizer(title=self.band_1_label, sel=self.band_1_ch) ) @@ -356,7 +354,6 @@ def __init__( self.scatt_mgr = scatt_mgr - maxValue = 1e8 self.parent = parent self.settings = {} diff --git a/gui/wxpython/iscatt/frame.py b/gui/wxpython/iscatt/frame.py index 45f50622d2f..122aa053b9b 100644 --- a/gui/wxpython/iscatt/frame.py +++ b/gui/wxpython/iscatt/frame.py @@ -221,9 +221,6 @@ def __init__(self, parent, scatt_mgr, id=wx.ID_ANY): self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPlotPaneClosed) - dlgSize = (-1, 400) - # self.SetBestSize(dlgSize) - # self.SetInitialSize(dlgSize) self.SetAutoLayout(1) # fix goutput's pane size (required for Mac OSX) # if self.gwindow: @@ -249,7 +246,7 @@ def CursorPlotMove(self, x, y, scatt_id): x = int(round(x)) y = int(round(y)) coords = True - except: + except TypeError: coords = False pane = self._getPane(scatt_id) diff --git a/gui/wxpython/iscatt/plots.py b/gui/wxpython/iscatt/plots.py index 08afe5906ed..a29098e8bcd 100644 --- a/gui/wxpython/iscatt/plots.py +++ b/gui/wxpython/iscatt/plots.py @@ -168,7 +168,7 @@ def SetEmpty(self): return self.polygon_drawer.SetEmpty() def OnRelease(self, event): - if not self.mode == "zoom": + if self.mode != "zoom": return self.zoom_rect.set_visible(False) self.ZoomRectangle(event) @@ -348,7 +348,7 @@ def ZoomWheel(self, event): def ZoomRectangle(self, event): # get the current x and y limits - if not self.mode == "zoom": + if self.mode != "zoom": return if event.inaxes is None: return @@ -394,7 +394,7 @@ def OnCanvasLeave(self, event): def PanMotion(self, event): "on mouse movement" - if not self.mode == "pan": + if self.mode != "pan": return if event.inaxes is None: return @@ -426,7 +426,7 @@ def PanMotion(self, event): self.canvas.draw() def ZoomRectMotion(self, event): - if not self.mode == "zoom": + if self.mode != "zoom": return if event.inaxes is None: return @@ -811,11 +811,11 @@ def _deleteVertex(self, event): coords = [] for i, tup in enumerate(self.pol.xy): - if i == ind: - continue - elif i == 0 and ind == len(self.pol.xy) - 1: - continue - elif i == len(self.pol.xy) - 1 and ind == 0: + if ( + i == ind + or (i == 0 and ind == len(self.pol.xy) - 1) + or (i == len(self.pol.xy) - 1 and ind == 0) + ): continue coords.append(tup) @@ -866,7 +866,7 @@ def _addVertex(self, event): def motion_notify_callback(self, event): "on mouse movement" - if not self.mode == "move_vertex": + if self.mode != "move_vertex": return if not self.showverts: return diff --git a/gui/wxpython/lmgr/frame.py b/gui/wxpython/lmgr/frame.py index a17b168f747..4eae371f68b 100644 --- a/gui/wxpython/lmgr/frame.py +++ b/gui/wxpython/lmgr/frame.py @@ -24,6 +24,8 @@ import platform import re +from pathlib import Path + from core import globalvar import wx import wx.aui @@ -848,7 +850,7 @@ def OnRunModel(self, event): dlg = wx.FileDialog( parent=self, message=_("Choose model to run"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("GRASS Model File (*.gxm)|*.gxm"), ) if dlg.ShowModal() == wx.ID_OK: @@ -1223,7 +1225,7 @@ def OnRunScript(self, event): dlg = wx.FileDialog( parent=self, message=_("Choose script file to run"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("Python script (*.py)|*.py|Bash script (*.sh)|*.sh"), ) @@ -1377,9 +1379,7 @@ def write_beginning(parameter=None, command=None): self._giface.WriteCmdLog(" ".join(command)) def write_changed(): - self._giface.WriteLog( - _('Working directory changed to:\n"%s"') % os.getcwd() - ) + self._giface.WriteLog(_('Working directory changed to:\n"%s"') % Path.cwd()) def write_end(): self._giface.WriteCmdLog(" ") @@ -1433,7 +1433,7 @@ def write_help(): dlg = wx.DirDialog( parent=self, message=_("Choose a working directory"), - defaultPath=os.getcwd(), + defaultPath=str(Path.cwd()), ) if dlg.ShowModal() == wx.ID_OK: diff --git a/gui/wxpython/lmgr/workspace.py b/gui/wxpython/lmgr/workspace.py index bb40469fa56..a593c19b19c 100644 --- a/gui/wxpython/lmgr/workspace.py +++ b/gui/wxpython/lmgr/workspace.py @@ -18,6 +18,8 @@ import xml.etree.ElementTree as ET +from pathlib import Path + import wx import wx.aui @@ -103,7 +105,7 @@ def Open(self): dlg = wx.FileDialog( parent=self.lmgr, message=_("Choose workspace file"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("GRASS Workspace File (*.gxw)|*.gxw"), ) @@ -362,7 +364,7 @@ def SaveAs(self): dlg = wx.FileDialog( parent=self.lmgr, message=_("Choose file to save current workspace"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("GRASS Workspace File (*.gxw)|*.gxw"), style=wx.FD_SAVE, ) diff --git a/gui/wxpython/location_wizard/wizard.py b/gui/wxpython/location_wizard/wizard.py index 7ba03ebee04..28ce94fbc48 100644 --- a/gui/wxpython/location_wizard/wizard.py +++ b/gui/wxpython/location_wizard/wizard.py @@ -38,6 +38,8 @@ import locale import functools +from pathlib import Path + import wx import wx.lib.mixins.listctrl as listmix from core import globalvar @@ -318,7 +320,10 @@ def OnChangeName(self, event): def OnBrowse(self, event): """Choose GRASS data directory""" dlg = wx.DirDialog( - self, _("Choose GRASS data directory:"), os.getcwd(), wx.DD_DEFAULT_STYLE + self, + _("Choose GRASS data directory:"), + str(Path.cwd()), + wx.DD_DEFAULT_STYLE, ) if dlg.ShowModal() == wx.ID_OK: self.grassdatabase = dlg.GetPath() @@ -1493,7 +1498,7 @@ def OnText(self, event): def OnBrowse(self, event): """Choose file""" dlg = wx.FileDialog( - self, _("Select georeferenced file"), os.getcwd(), "", "*.*", wx.FD_OPEN + self, _("Select georeferenced file"), str(Path.cwd()), "", "*.*", wx.FD_OPEN ) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() @@ -1977,7 +1982,7 @@ def OnBrowse(self, event): """Define path for IAU code file""" path = os.path.dirname(self.tfile.GetValue()) if not path: - path = os.getcwd() + path = str(Path.cwd()) dlg = wx.FileDialog( parent=self, @@ -2538,7 +2543,7 @@ def __readData(self): """Get georeferencing information from tables in $GISBASE/etc/proj""" # read projection and parameters - f = open(os.path.join(globalvar.ETCDIR, "proj", "parms.table"), "r") + f = open(os.path.join(globalvar.ETCDIR, "proj", "parms.table")) self.projections = {} self.projdesc = {} for line in f: @@ -2561,7 +2566,7 @@ def __readData(self): f.close() # read datum definitions - f = open(os.path.join(globalvar.ETCDIR, "proj", "datum.table"), "r") + f = open(os.path.join(globalvar.ETCDIR, "proj", "datum.table")) self.datums = {} paramslist = [] for line in f: @@ -2579,7 +2584,7 @@ def __readData(self): f.close() # read Earth-based ellipsiod definitions - f = open(os.path.join(globalvar.ETCDIR, "proj", "ellipse.table"), "r") + f = open(os.path.join(globalvar.ETCDIR, "proj", "ellipse.table")) self.ellipsoids = {} for line in f: line = line.expandtabs(1) @@ -2595,9 +2600,7 @@ def __readData(self): f.close() # read Planetary ellipsiod definitions - f = open( - os.path.join(globalvar.ETCDIR, "proj", "ellipse.table.solar.system"), "r" - ) + f = open(os.path.join(globalvar.ETCDIR, "proj", "ellipse.table.solar.system")) self.planetary_ellipsoids = {} for line in f: line = line.expandtabs(1) @@ -2613,7 +2616,7 @@ def __readData(self): f.close() # read projection parameter description and parsing table - f = open(os.path.join(globalvar.ETCDIR, "proj", "desc.table"), "r") + f = open(os.path.join(globalvar.ETCDIR, "proj", "desc.table")) self.paramdesc = {} for line in f: line = line.strip() diff --git a/gui/wxpython/main_window/frame.py b/gui/wxpython/main_window/frame.py index 3b9b7f50261..8fc8dc8dde7 100644 --- a/gui/wxpython/main_window/frame.py +++ b/gui/wxpython/main_window/frame.py @@ -25,6 +25,8 @@ import platform import re +from pathlib import Path + from core import globalvar try: @@ -971,7 +973,7 @@ def OnRunModel(self, event): dlg = wx.FileDialog( parent=self, message=_("Choose model to run"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("GRASS Model File (*.gxm)|*.gxm"), ) if dlg.ShowModal() == wx.ID_OK: @@ -1374,7 +1376,7 @@ def OnRunScript(self, event): dlg = wx.FileDialog( parent=self, message=_("Choose script file to run"), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("Python script (*.py)|*.py|Bash script (*.sh)|*.sh"), ) @@ -1528,9 +1530,7 @@ def write_beginning(parameter=None, command=None): self._giface.WriteCmdLog(" ".join(command)) def write_changed(): - self._giface.WriteLog( - _('Working directory changed to:\n"%s"') % os.getcwd() - ) + self._giface.WriteLog(_('Working directory changed to:\n"%s"') % Path.cwd()) def write_end(): self._giface.WriteCmdLog(" ") @@ -1584,7 +1584,7 @@ def write_help(): dlg = wx.DirDialog( parent=self, message=_("Choose a working directory"), - defaultPath=os.getcwd(), + defaultPath=str(Path.cwd()), ) if dlg.ShowModal() == wx.ID_OK: diff --git a/gui/wxpython/mapdisp/frame.py b/gui/wxpython/mapdisp/frame.py index 0b948f63794..f13ffbe0a81 100644 --- a/gui/wxpython/mapdisp/frame.py +++ b/gui/wxpython/mapdisp/frame.py @@ -831,7 +831,7 @@ def _DToRastDone(): overwrite=overwrite, getErrorMsg=True, ) - if not returncode == 0: + if returncode != 0: self._giface.WriteError(_("Failed to run d.to.rast:\n") + messages) return # set region for composite @@ -839,7 +839,7 @@ def _DToRastDone(): returncode, messages = RunCommand( "g.region", raster=tmpName + ".red", quiet=True, getErrorMsg=True ) - if not returncode == 0: + if returncode != 0: gs.del_temp_region() self._giface.WriteError(_("Failed to run d.to.rast:\n") + messages) return @@ -862,7 +862,7 @@ def _DToRastDone(): quiet=True, name=[tmpName + ".red", tmpName + ".green", tmpName + ".blue"], ) - if not returncode == 0: + if returncode != 0: self._giface.WriteError(_("Failed to run d.to.rast:\n") + messages) gs.try_remove(pngFile) return diff --git a/gui/wxpython/mapdisp/main.py b/gui/wxpython/mapdisp/main.py index 1e94d3d8ba2..016261d248d 100644 --- a/gui/wxpython/mapdisp/main.py +++ b/gui/wxpython/mapdisp/main.py @@ -108,7 +108,7 @@ def __init__(self, giface, cmdfile=None, mapfile=None): self.renderMgr = RenderMapMgr(self) # update legend file variable with the one d.mon uses - with open(monFile["env"], "r") as f: + with open(monFile["env"]) as f: lines = f.readlines() for line in lines: if "GRASS_LEGEND_FILE" in line: @@ -123,7 +123,7 @@ def GetLayersFromCmdFile(self): nlayers = 0 try: - fd = open(self.cmdfile, "r") + fd = open(self.cmdfile) lines = fd.readlines() fd.close() # detect d.out.file, delete the line from the cmd file and export diff --git a/gui/wxpython/mapwin/buffered.py b/gui/wxpython/mapwin/buffered.py index e0442ed94ae..d8c6413017b 100644 --- a/gui/wxpython/mapwin/buffered.py +++ b/gui/wxpython/mapwin/buffered.py @@ -28,6 +28,8 @@ import wx +from operator import itemgetter + from grass.pydispatch.signal import Signal from core.globalvar import wxPythonPhoenix @@ -462,10 +464,10 @@ def Draw( brush = wx.TRANSPARENT_BRUSH pdc.SetBrush(brush) pdc.DrawPolygon(points=coords) - x = min(coords, key=lambda x: x[0])[0] - y = min(coords, key=lambda x: x[1])[1] - w = max(coords, key=lambda x: x[0])[0] - x - h = max(coords, key=lambda x: x[1])[1] - y + x = min(coords, key=itemgetter(0))[0] + y = min(coords, key=itemgetter(1))[1] + w = max(coords, key=itemgetter(0))[0] - x + h = max(coords, key=itemgetter(1))[1] - y pdc.SetIdBounds(drawid, Rect(x, y, w, h)) elif pdctype == "circle": # draw circle diff --git a/gui/wxpython/mapwin/decorations.py b/gui/wxpython/mapwin/decorations.py index 7ab010a1d3f..24db63d8492 100644 --- a/gui/wxpython/mapwin/decorations.py +++ b/gui/wxpython/mapwin/decorations.py @@ -209,9 +209,7 @@ def CmdIsValid(self) -> bool: inputs = 0 for param in self._cmd[1:]: param = param.split("=") - if len(param) == 1: - inputs += 1 - elif param[0] == "text" and len(param) == 2: + if len(param) == 1 or (param[0] == "text" and len(param) == 2): inputs += 1 return inputs >= 1 @@ -313,11 +311,11 @@ def CmdIsValid(self) -> bool: inputs = 0 for param in self._cmd[1:]: param = param.split("=") - if len(param) == 1: - inputs += 1 - elif param[0] == "raster" and len(param) == 2: - inputs += 1 - elif param[0] == "raster_3d" and len(param) == 2: + if ( + len(param) == 1 + or (param[0] == "raster" and len(param) == 2) + or (param[0] == "raster_3d" and len(param) == 2) + ): inputs += 1 return inputs == 1 diff --git a/gui/wxpython/modules/colorrules.py b/gui/wxpython/modules/colorrules.py index b5b1047382e..cad74f38c4b 100644 --- a/gui/wxpython/modules/colorrules.py +++ b/gui/wxpython/modules/colorrules.py @@ -27,6 +27,8 @@ import copy import tempfile +from pathlib import Path + import wx import wx.lib.colourselect as csel import wx.lib.scrolledpanel as scrolled @@ -448,7 +450,7 @@ def _createFileSelection(self, parent): dialogTitle=_("Choose file to load color table"), buttonText=_("Load"), toolTip=_("Type filename or click to choose file and load color table"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=wx.FD_OPEN, changeCallback=self.OnLoadRulesFile, ) @@ -460,7 +462,7 @@ def _createFileSelection(self, parent): dialogTitle=_("Choose file to save color table"), toolTip=_("Type filename or click to choose file and save color table"), buttonText=_("Save"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=wx.FD_SAVE, changeCallback=self.OnSaveRulesFile, ) @@ -698,7 +700,7 @@ def OnLoadRulesFile(self, event): self.rulesPanel.Clear() - fd = open(path, "r") + fd = open(path) self.ReadColorTable(ctable=fd.read()) fd.close() diff --git a/gui/wxpython/modules/import_export.py b/gui/wxpython/modules/import_export.py index 15d4f76b586..03d179ffc2f 100644 --- a/gui/wxpython/modules/import_export.py +++ b/gui/wxpython/modules/import_export.py @@ -22,6 +22,8 @@ import os +from pathlib import Path + import wx from core import globalvar import wx.lib.filebrowsebutton as filebrowse @@ -836,7 +838,7 @@ def __init__(self, parent, giface): labelText="", dialogTitle=_("Choose DXF file to import"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=0, changeCallback=self.OnSetDsn, fileMask="DXF File (*.dxf)|*.dxf", diff --git a/gui/wxpython/modules/mcalc_builder.py b/gui/wxpython/modules/mcalc_builder.py index e157375b4a4..b0f2f280409 100644 --- a/gui/wxpython/modules/mcalc_builder.py +++ b/gui/wxpython/modules/mcalc_builder.py @@ -759,7 +759,7 @@ def OnLoadExpression(self, event): return try: - fobj = open(path, "r") + fobj = open(path) mctxt = fobj.read() finally: fobj.close() diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index eaf0d266d4e..550939da7ce 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -534,7 +534,7 @@ def OnKeyDown(self, event): Used for fly-through mode. """ - if not self.mouse["use"] == "fly": + if self.mouse["use"] != "fly": return key = event.GetKeyCode() @@ -596,7 +596,7 @@ def OnKeyUp(self, event): Used for fly-through mode. """ - if not self.mouse["use"] == "fly": + if self.mouse["use"] != "fly": return key = event.GetKeyCode() diff --git a/gui/wxpython/nviz/tools.py b/gui/wxpython/nviz/tools.py index a1b0a9fcb1b..0659c5cc2ba 100644 --- a/gui/wxpython/nviz/tools.py +++ b/gui/wxpython/nviz/tools.py @@ -23,6 +23,8 @@ import sys import copy +from pathlib import Path + import wx import wx.lib.colourselect as csel import wx.lib.scrolledpanel as SP @@ -668,7 +670,7 @@ def _createAnimationPage(self): vSizer = wx.BoxSizer(wx.VERTICAL) gridSizer = wx.GridBagSizer(vgap=5, hgap=10) - pwd = os.getcwd() + pwd = str(Path.cwd()) dir = filebrowse.DirBrowseButton( parent=panel, id=wx.ID_ANY, diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 8002d6f32d2..e80a9fc9382 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -83,7 +83,7 @@ def print_progress(value): """Redirect progress info""" global progress if progress: - if not progress.GetRange() == 100: + if progress.GetRange() != 100: progress.SetRange(100) progress.SetValue(value) else: diff --git a/gui/wxpython/photo2image/g.gui.photo2image.py b/gui/wxpython/photo2image/g.gui.photo2image.py index 21635175374..65b4141873e 100755 --- a/gui/wxpython/photo2image/g.gui.photo2image.py +++ b/gui/wxpython/photo2image/g.gui.photo2image.py @@ -68,7 +68,6 @@ """ Module to run GCP management tool as stadalone application. -@author Vaclav Petras (standalone module) """ import os import grass.script as gs diff --git a/gui/wxpython/photo2image/ip2i_manager.py b/gui/wxpython/photo2image/ip2i_manager.py index f251a1fa36e..5a2a1f14f21 100644 --- a/gui/wxpython/photo2image/ip2i_manager.py +++ b/gui/wxpython/photo2image/ip2i_manager.py @@ -118,7 +118,7 @@ def __init__( self.source_gisrc = os.environ["GISRC"] self.gisrc_dict = {} try: - f = open(self.target_gisrc, "r") + f = open(self.target_gisrc) for line in f: line = line.replace("\n", "").strip() if len(line) < 1: @@ -429,7 +429,7 @@ def __init__( import re try: - fc = open(self.file["camera"], mode="r") + fc = open(self.file["camera"]) fc_count = 0 for line in fc: fc_count += 1 @@ -438,7 +438,7 @@ def __init__( numberOfFiducial = int(line.split()[-1]) dataFiducialX = [] dataFiducialY = [] - fc = open(self.file["camera"], mode="r") + fc = open(self.file["camera"]) fc_count = 0 for line in fc: fc_count += 1 @@ -955,7 +955,7 @@ def ReadGCPs(self): GError(parent=self, message=_("target mapwin not defined")) try: - f = open(self.file["points"], "r") + f = open(self.file["points"]) GCPcnt = 0 for line in f: @@ -1531,7 +1531,7 @@ def AdjustMap(self, newreg): def OnZoomToSource(self, event): """Set target map window to match extents of source map window""" - if not self.MapWindow == self.TgtMapWindow: + if self.MapWindow != self.TgtMapWindow: self.MapWindow = self.TgtMapWindow self.Map = self.TgtMap self.UpdateActive(self.TgtMapWindow) @@ -1544,7 +1544,7 @@ def OnZoomToSource(self, event): def OnZoomToTarget(self, event): """Set source map window to match extents of target map window""" - if not self.MapWindow == self.SrcMapWindow: + if self.MapWindow != self.SrcMapWindow: self.MapWindow = self.SrcMapWindow self.Map = self.SrcMap self.UpdateActive(self.SrcMapWindow) @@ -2381,7 +2381,7 @@ def OnSrcSelection(self, event): tmp_map = self.srcselection.GetValue() - if not tmp_map == "" and not tmp_map == src_map: + if tmp_map not in ("", src_map): self.new_src_map = tmp_map def OnTgtRastSelection(self, event): diff --git a/gui/wxpython/photo2image/ip2i_mapdisplay.py b/gui/wxpython/photo2image/ip2i_mapdisplay.py index f85be35ec4a..aa085f2b43a 100644 --- a/gui/wxpython/photo2image/ip2i_mapdisplay.py +++ b/gui/wxpython/photo2image/ip2i_mapdisplay.py @@ -559,7 +559,7 @@ def GetMapToolbar(self): return self.toolbars["gcpdisp"] def _setActiveMapWindow(self, mapWindow): - if not self.MapWindow == mapWindow: + if self.MapWindow != mapWindow: self.MapWindow = mapWindow self.Map = mapWindow.Map self.UpdateActive(mapWindow) diff --git a/gui/wxpython/photo2image/ip2i_statusbar.py b/gui/wxpython/photo2image/ip2i_statusbar.py index 336dcd6903f..75a39b8141e 100644 --- a/gui/wxpython/photo2image/ip2i_statusbar.py +++ b/gui/wxpython/photo2image/ip2i_statusbar.py @@ -11,9 +11,6 @@ This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. - -@author Vaclav Petras (statusbar refactoring) -@author Anna Kratochvilova (statusbar refactoring) """ import wx diff --git a/gui/wxpython/psmap/dialogs.py b/gui/wxpython/psmap/dialogs.py index 216782037be..41493795893 100644 --- a/gui/wxpython/psmap/dialogs.py +++ b/gui/wxpython/psmap/dialogs.py @@ -37,6 +37,8 @@ import os import string from copy import deepcopy +from operator import itemgetter +from pathlib import Path import wx import wx.lib.agw.floatspin as fs @@ -3622,9 +3624,7 @@ def _vectorLegend(self, notebook): self.vectorListCtrl.InsertColumn(0, _("Vector map")) self.vectorListCtrl.InsertColumn(1, _("Label")) if self.vectorId: - vectors = sorted( - self.instruction[self.vectorId]["list"], key=lambda x: x[3] - ) + vectors = sorted(self.instruction[self.vectorId]["list"], key=itemgetter(3)) for vector in vectors: index = self.vectorListCtrl.InsertItem( @@ -4455,7 +4455,7 @@ def updateDialog(self): if self.instruction.FindInstructionByType("vector"): vectors = sorted( self.instruction.FindInstructionByType("vector")["list"], - key=lambda x: x[3], + key=itemgetter(3), ) self.vectorListCtrl.DeleteAllItems() for vector in vectors: @@ -5965,7 +5965,7 @@ def OnPositionType(self, event): def _getImageDirectory(self): """Default image directory""" - return os.getcwd() + return str(Path.cwd()) def _addConvergence(self, panel, gridBagSizer): pass diff --git a/gui/wxpython/psmap/frame.py b/gui/wxpython/psmap/frame.py index 3effc33cd4d..fa4a150aa3b 100644 --- a/gui/wxpython/psmap/frame.py +++ b/gui/wxpython/psmap/frame.py @@ -308,7 +308,7 @@ def OnPsMapDialog(self, event): def OnPDFFile(self, event): """Generate PDF from PS with ps2pdf if available""" - if not sys.platform == "win32": + if sys.platform != "win32": try: p = gs.Popen(["ps2pdf"], stderr=gs.PIPE) p.stderr.close() diff --git a/gui/wxpython/psmap/instructions.py b/gui/wxpython/psmap/instructions.py index 8a535b96704..3c1b2e6d7bb 100644 --- a/gui/wxpython/psmap/instructions.py +++ b/gui/wxpython/psmap/instructions.py @@ -86,10 +86,7 @@ def __getitem__(self, id): def __contains__(self, id): """Test if instruction is included""" - for each in self.instruction: - if each.id == id: - return True - return False + return any(each.id == id for each in self.instruction) def __delitem__(self, id): """Delete instruction""" @@ -1211,7 +1208,7 @@ def GetImageOrigSize(self, imagePath): # if eps, read info from header if os.path.splitext(fileName)[1].lower() == ".eps": bbInfo = "%%BoundingBox" - file = open(imagePath, "r") + file = open(imagePath) w = h = 0 while file: line = file.readline() diff --git a/gui/wxpython/rdigit/dialogs.py b/gui/wxpython/rdigit/dialogs.py index 650b75feb63..c632a84c0fe 100644 --- a/gui/wxpython/rdigit/dialogs.py +++ b/gui/wxpython/rdigit/dialogs.py @@ -114,7 +114,7 @@ def OnOK(self, event): caption=_("Overwrite?"), style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION, ) - if not dlgOverwrite.ShowModal() == wx.ID_YES: + if dlgOverwrite.ShowModal() != wx.ID_YES: dlgOverwrite.Destroy() return else: diff --git a/gui/wxpython/timeline/frame.py b/gui/wxpython/timeline/frame.py index 3982034619c..9a783f4f5d5 100644 --- a/gui/wxpython/timeline/frame.py +++ b/gui/wxpython/timeline/frame.py @@ -22,6 +22,7 @@ import wx from functools import reduce +from operator import add try: import matplotlib as mpl @@ -495,9 +496,7 @@ def _checkDatasets(self, datasets): ] # flatten this list if allDatasets: - allDatasets = reduce( - lambda x, y: x + y, reduce(lambda x, y: x + y, allDatasets) - ) + allDatasets = reduce(add, reduce(add, allDatasets)) mapsets = tgis.get_tgis_c_library_interface().available_mapsets() allDatasets = [ i diff --git a/gui/wxpython/tplot/frame.py b/gui/wxpython/tplot/frame.py index db21da4cd14..8994485dacc 100755 --- a/gui/wxpython/tplot/frame.py +++ b/gui/wxpython/tplot/frame.py @@ -20,6 +20,7 @@ """ import os from itertools import cycle +from pathlib import Path import numpy as np import wx @@ -68,6 +69,7 @@ from gui_core.widgets import GNotebook from gui_core.wrap import CheckBox, TextCtrl, Button, StaticText +from operator import add ALPHA = 0.5 COLORS = ["b", "g", "r", "c", "m", "y", "k"] @@ -371,7 +373,7 @@ def _layout(self): labelText="", dialogTitle=_("CVS path"), buttonText=_("Browse"), - startDirectory=os.getcwd(), + startDirectory=str(Path.cwd()), fileMode=wx.FD_SAVE, ) self.headerLabel = StaticText( @@ -1152,9 +1154,7 @@ def _checkDatasets(self, datasets, typ): ] # flatten this list if allDatasets: - allDatasets = reduce( - lambda x, y: x + y, reduce(lambda x, y: x + y, allDatasets) - ) + allDatasets = reduce(add, reduce(add, allDatasets)) mapsets = tgis.get_tgis_c_library_interface().available_mapsets() allDatasets = [ i diff --git a/gui/wxpython/vdigit/wxdigit.py b/gui/wxpython/vdigit/wxdigit.py index ddcdc5a2755..154d5ab7c81 100644 --- a/gui/wxpython/vdigit/wxdigit.py +++ b/gui/wxpython/vdigit/wxdigit.py @@ -1084,7 +1084,7 @@ def EditLine(self, line, coords): # apply snapping (node or vertex) snap = self._getSnapMode() if snap != NO_SNAP: - modeSnap = not (snap == SNAP) + modeSnap = snap != SNAP Vedit_snap_line( self.poMapInfo, self.popoBgMapInfo, @@ -1889,7 +1889,7 @@ def _addFeature(self, ftype, coords, layer, cat, snap, threshold): if snap != NO_SNAP: # apply snapping (node or vertex) - modeSnap = not (snap == SNAP) + modeSnap = snap != SNAP Vedit_snap_line( self.poMapInfo, self.popoBgMapInfo, diff --git a/gui/wxpython/vdigit/wxdisplay.py b/gui/wxpython/vdigit/wxdisplay.py index b747f3fa44f..9085aae8caf 100644 --- a/gui/wxpython/vdigit/wxdisplay.py +++ b/gui/wxpython/vdigit/wxdisplay.py @@ -868,10 +868,7 @@ def GetSelectedVertex(self, pos): pos[0], pos[1], 0.0, points.x[idx], points.y[idx], points.z[idx], 0 ) - if idx == 0: - minDist = dist - Gid = idx - elif minDist > dist: + if idx == 0 or minDist > dist: minDist = dist Gid = idx diff --git a/gui/wxpython/vnet/dialogs.py b/gui/wxpython/vnet/dialogs.py index 3d829ffd7be..a445b01e8eb 100644 --- a/gui/wxpython/vnet/dialogs.py +++ b/gui/wxpython/vnet/dialogs.py @@ -1952,9 +1952,7 @@ def SetVirtualData(self, row, column, text): text = DegreesToRadians(text) # Tested allowed range of values - if text > math.pi: - text = 0.0 - elif text < -math.pi: + if text > math.pi or text < -math.pi: text = 0.0 self.data.SetValue(text, row, column) diff --git a/gui/wxpython/vnet/vnet_core.py b/gui/wxpython/vnet/vnet_core.py index 17d85ef6ca4..ac7625a4c22 100644 --- a/gui/wxpython/vnet/vnet_core.py +++ b/gui/wxpython/vnet/vnet_core.py @@ -828,9 +828,7 @@ def _prepareCmd(self, cmd): if c.find("=") == -1: continue v = c.split("=") - if len(v) != 2: - cmd.remove(c) - elif not v[1].strip(): + if len(v) != 2 or not v[1].strip(): cmd.remove(c) def _setCmdForSpecificAn(self, cmdParams): diff --git a/gui/wxpython/vnet/vnet_data.py b/gui/wxpython/vnet/vnet_data.py index 6404fcac5c0..9c5b592e043 100644 --- a/gui/wxpython/vnet/vnet_data.py +++ b/gui/wxpython/vnet/vnet_data.py @@ -1070,7 +1070,7 @@ def GetLastModified(self): "head", ) try: - head = open(headPath, "r") + head = open(headPath) for line in head: i = line.find( "MAP DATE:", diff --git a/gui/wxpython/vnet/widgets.py b/gui/wxpython/vnet/widgets.py index 12477cc93ea..ed1f2aa514c 100644 --- a/gui/wxpython/vnet/widgets.py +++ b/gui/wxpython/vnet/widgets.py @@ -542,7 +542,7 @@ def IsShown(self, colName) -> bool: :return: False - if is not shown """ - return not self._getColumnNum(colName) == -1 + return self._getColumnNum(colName) != -1 class EditItem(wx.Dialog): diff --git a/gui/wxpython/wxplot/profile.py b/gui/wxpython/wxplot/profile.py index 1d3de6681af..61efdcd67d0 100644 --- a/gui/wxpython/wxplot/profile.py +++ b/gui/wxpython/wxplot/profile.py @@ -20,6 +20,8 @@ import math import numpy as np +from pathlib import Path + import wx from wx.lib import plot @@ -410,7 +412,7 @@ def SaveProfileToFile(self, event): dlg = wx.FileDialog( parent=self, message=_("Choose prefix for file(s) where to save profile values..."), - defaultDir=os.getcwd(), + defaultDir=str(Path.cwd()), wildcard=_("Comma separated value (*.csv)|*.csv"), style=wx.FD_SAVE, ) diff --git a/imagery/i.atcorr/create_iwave.py b/imagery/i.atcorr/create_iwave.py index 8afc6bebd36..a575d8089cd 100644 --- a/imagery/i.atcorr/create_iwave.py +++ b/imagery/i.atcorr/create_iwave.py @@ -59,7 +59,7 @@ def read_input(csvfile): first column is wavelength values are those of the discrete band filter functions """ - infile = open(csvfile, "r") + infile = open(csvfile) # get number of bands and band names bands = infile.readline().split(",") diff --git a/imagery/i.gensig/testsuite/test_i_gensig.py b/imagery/i.gensig/testsuite/test_i_gensig.py index 0498cabaf79..d75e3da2af4 100644 --- a/imagery/i.gensig/testsuite/test_i_gensig.py +++ b/imagery/i.gensig/testsuite/test_i_gensig.py @@ -13,6 +13,7 @@ import stat import ctypes import shutil +from pathlib import Path from grass.pygrass import utils from grass.pygrass.gis import Mapset @@ -107,7 +108,7 @@ def test_creation(self): ) # File must be present - sig_stat = os.stat(f"{self.sig_dir1}/sig") + sig_stat = Path(self.sig_dir1, "sig").stat() self.assertTrue(stat.S_ISREG(sig_stat.st_mode)) # Compare values within sig file diff --git a/imagery/i.maxlik/testsuite/test_i_maxlik.py b/imagery/i.maxlik/testsuite/test_i_maxlik.py index 1f6829e92b4..e7fbf6c1a79 100644 --- a/imagery/i.maxlik/testsuite/test_i_maxlik.py +++ b/imagery/i.maxlik/testsuite/test_i_maxlik.py @@ -19,6 +19,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows from grass.lib.gis import G_mapset_path from grass.lib.raster import Rast_write_semantic_label @@ -172,6 +173,7 @@ def tearDownClass(cls): ) cls.runModule("g.remove", flags="f", type="group", name=cls.group, quiet=True) + @xfail_windows def test_v1(self): """Test v1 signature""" self.assertModule( @@ -193,6 +195,7 @@ def test_v1(self): self.assertEqual(res.get_cat(0)[1], 1) res.close() + @xfail_windows def test_v2(self): """Test v2 signature""" self.assertModule( diff --git a/include/grass/defs/raster.h b/include/grass/defs/raster.h index 7244da2a354..c2d26ccdccc 100644 --- a/include/grass/defs/raster.h +++ b/include/grass/defs/raster.h @@ -393,6 +393,7 @@ int Rast_option_to_interp_type(const struct Option *); /* mask_info.c */ char *Rast_mask_info(void); int Rast__mask_info(char *, char *); +bool Rast_mask_is_present(void); /* maskfd.c */ int Rast_maskfd(void); diff --git a/lib/external/shapelib/README.md b/lib/external/shapelib/README.md index e004d2d0419..d446eccd014 100644 --- a/lib/external/shapelib/README.md +++ b/lib/external/shapelib/README.md @@ -1,12 +1,13 @@ # Update history of SHAPELIB copy -* files `shpopen.c`, `shapefil.h`, `dbfopen.c` +* files `shpopen.c`, `shapefil.h`, `dbfopen.c`, `shapefil_private.h` from GDAL [ogr/ogrsf_frmts/shape/](https://github.com/OSGeo/gdal/tree/master/ogr/ogrsf_frmts/shape) * file `safileio.c` from [SHAPELIB](http://download.osgeo.org/shapelib/) ## Last update +* taken from GDAL 3.9.2 and SHAPELIB 1.6.0 (Sep 2024) * taken from GDAL 3.5.3 and SHAPELIB 1.5.0 (Dec 2022) * taken from GDAL 2.1.2 and SHAPELIB 1.3.0 (Thu Nov 24 10:45:41 CET 2016) * taken from GDAL 1.5.1-SVN (Sun Mar 30 11:20:43 CEST 2008) @@ -15,44 +16,5 @@ ## Summary of fixes -* dbfopen.c - around line 1229: GDAL bug [ticket-#809](http://trac.osgeo.org/gdal/ticket/809) - * safileio.c - SHP_CVSID: ISO C does not allow extra ‘;’ outside of a function - -## Full fix - -```diff -diff --git a/lib/external/shapelib/dbfopen.c b/lib/external/shapelib/dbfopen.c -index 5380e3e20b..5151148d33 100644 ---- a/lib/external/shapelib/dbfopen.c -+++ b/lib/external/shapelib/dbfopen.c -@@ -1226,9 +1226,10 @@ DBFGetFieldInfo( DBFHandle psDBF, int iField, char * pszFieldName, - else if( psDBF->pachFieldType[iField] == 'N' - || psDBF->pachFieldType[iField] == 'F' ) - { -- if( psDBF->panFieldDecimals[iField] > 0 -- || psDBF->panFieldSize[iField] >= 10 ) -+ if( psDBF->panFieldDecimals[iField] > 0 ) { -+ /* || psDBF->panFieldSize[iField] >= 10 ) */ /* GDAL bug #809 */ - return( FTDouble ); -+ } - else - return( FTInteger ); - } -diff --git a/lib/external/shapelib/safileio.c b/lib/external/shapelib/safileio.c -index 289d347eaf..7a614a5806 100644 ---- a/lib/external/shapelib/safileio.c -+++ b/lib/external/shapelib/safileio.c -@@ -74,7 +74,7 @@ - #include - #include - --SHP_CVSID("$Id: safileio.c,v 1.6 2018-06-15 19:56:32 erouault Exp $"); -+SHP_CVSID("$Id: safileio.c,v 1.6 2018-06-15 19:56:32 erouault Exp $") - - #ifdef SHPAPI_UTF8_HOOKS - # ifdef SHPAPI_WINDOWS - -``` + [shapelib commit 316ff87](https://github.com/OSGeo/shapelib/commit/316ff872566ea0d91d6b62fe01bfe39931db39aa#diff-f068bc465ca1a32e1b9c214d4eb9504ef9e0f3c4cabc1aa4bab8aa41e2248cc6R153) diff --git a/lib/external/shapelib/dbfopen.c b/lib/external/shapelib/dbfopen.c index 2bbba339e21..9078a29ba95 100644 --- a/lib/external/shapelib/dbfopen.c +++ b/lib/external/shapelib/dbfopen.c @@ -1,5 +1,4 @@ /****************************************************************************** - * $Id$ * * Project: Shapelib * Purpose: Implementation of .dbf access API documented in dbf_api.html. @@ -9,32 +8,10 @@ * Copyright (c) 1999, Frank Warmerdam * Copyright (c) 2012-2019, Even Rouault * - * This software is available under the following "MIT Style" license, - * or at the option of the licensee under the LGPL (see COPYING). This - * option is discussed in more detail in shapelib.html. - * - * -- - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. + * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later ******************************************************************************/ -#include "shapefil.h" +#include "shapefil_private.h" #include #include @@ -47,7 +24,9 @@ #include "cpl_string.h" #else -#if defined(WIN32) || defined(_WIN32) +#if defined(_MSC_VER) +#define STRCASECMP(a, b) (_stricmp(a, b)) +#elif defined(_WIN32) #define STRCASECMP(a, b) (stricmp(a, b)) #else #include @@ -58,7 +37,7 @@ #if _MSC_VER < 1900 #define snprintf _snprintf #endif -#elif defined(WIN32) || defined(_WIN32) +#elif defined(_WIN32) #ifndef snprintf #define snprintf _snprintf #endif @@ -68,8 +47,6 @@ #define CPLsnprintf snprintf #endif -SHP_CVSID("$Id$") - #ifndef FALSE #define FALSE 0 #define TRUE 1 @@ -91,33 +68,6 @@ CPL_INLINE static void CPL_IGNORE_RET_VAL_INT(CPL_UNUSED int unused) #define CPL_IGNORE_RET_VAL_INT(x) x #endif -#ifdef __cplusplus -#define STATIC_CAST(type, x) static_cast(x) -#define REINTERPRET_CAST(type, x) reinterpret_cast(x) -#define CONST_CAST(type, x) const_cast(x) -#define SHPLIB_NULLPTR nullptr -#else -#define STATIC_CAST(type, x) ((type)(x)) -#define REINTERPRET_CAST(type, x) ((type)(x)) -#define CONST_CAST(type, x) ((type)(x)) -#define SHPLIB_NULLPTR NULL -#endif - -/************************************************************************/ -/* SfRealloc() */ -/* */ -/* A realloc cover function that will access a NULL pointer as */ -/* a valid input. */ -/************************************************************************/ - -static void *SfRealloc(void *pMem, int nNewSize) -{ - if (pMem == SHPLIB_NULLPTR) - return malloc(nNewSize); - else - return realloc(pMem, nNewSize); -} - /************************************************************************/ /* DBFWriteHeader() */ /* */ @@ -137,9 +87,9 @@ static void DBFWriteHeader(DBFHandle psDBF) psDBF->bNoHeader = FALSE; /* -------------------------------------------------------------------- */ - /* Initialize the file header information. */ + /* Initialize the file header information. */ /* -------------------------------------------------------------------- */ - abyHeader[0] = 0x03; /* memo field? - just copying */ + abyHeader[0] = 0x03; /* memo field? - just copying */ /* write out update date */ abyHeader[1] = STATIC_CAST(unsigned char, psDBF->nUpdateYearSince1900); @@ -158,7 +108,7 @@ static void DBFWriteHeader(DBFHandle psDBF) /* -------------------------------------------------------------------- */ /* Write the initial 32 byte file header, and all the field */ - /* descriptions. */ + /* descriptions. */ /* -------------------------------------------------------------------- */ psDBF->sHooks.FSeek(psDBF->fp, 0, 0); psDBF->sHooks.FWrite(abyHeader, XBASE_FILEHDR_SZ, 1, psDBF->fp); @@ -344,7 +294,6 @@ void SHPAPI_CALL DBFSetLastModifiedDate(DBFHandle psDBF, int nYYSince1900, /************************************************************************/ DBFHandle SHPAPI_CALL DBFOpen(const char *pszFilename, const char *pszAccess) - { SAHooks sHooks; @@ -376,7 +325,7 @@ static int DBFGetLenWithoutExtension(const char *pszBasename) /************************************************************************/ DBFHandle SHPAPI_CALL DBFOpenLL(const char *pszFilename, const char *pszAccess, - SAHooks *psHooks) + const SAHooks *psHooks) { /* -------------------------------------------------------------------- */ /* We only allow the access strings "rb" and "r+". */ @@ -393,8 +342,8 @@ DBFHandle SHPAPI_CALL DBFOpenLL(const char *pszFilename, const char *pszAccess, pszAccess = "rb+"; /* -------------------------------------------------------------------- */ - /* Compute the base (layer) name. If there is any extension */ - /* on the passed in filename we will strip it off. */ + /* Compute the base (layer) name. If there is any extension */ + /* on the passed in filename we will strip it off. */ /* -------------------------------------------------------------------- */ const int nLenWithoutExtension = DBFGetLenWithoutExtension(pszFilename); char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5)); @@ -402,19 +351,20 @@ DBFHandle SHPAPI_CALL DBFOpenLL(const char *pszFilename, const char *pszAccess, memcpy(pszFullname + nLenWithoutExtension, ".dbf", 5); DBFHandle psDBF = STATIC_CAST(DBFHandle, calloc(1, sizeof(DBFInfo))); - psDBF->fp = psHooks->FOpen(pszFullname, pszAccess); + psDBF->fp = psHooks->FOpen(pszFullname, pszAccess, psHooks->pvUserData); memcpy(&(psDBF->sHooks), psHooks, sizeof(SAHooks)); if (psDBF->fp == SHPLIB_NULLPTR) { memcpy(pszFullname + nLenWithoutExtension, ".DBF", 5); - psDBF->fp = psDBF->sHooks.FOpen(pszFullname, pszAccess); + psDBF->fp = + psDBF->sHooks.FOpen(pszFullname, pszAccess, psHooks->pvUserData); } memcpy(pszFullname + nLenWithoutExtension, ".cpg", 5); - SAFile pfCPG = psHooks->FOpen(pszFullname, "r"); + SAFile pfCPG = psHooks->FOpen(pszFullname, "r", psHooks->pvUserData); if (pfCPG == SHPLIB_NULLPTR) { memcpy(pszFullname + nLenWithoutExtension, ".CPG", 5); - pfCPG = psHooks->FOpen(pszFullname, "r"); + pfCPG = psHooks->FOpen(pszFullname, "r", psHooks->pvUserData); } free(pszFullname); @@ -495,7 +445,7 @@ DBFHandle SHPAPI_CALL DBFOpenLL(const char *pszFilename, const char *pszAccess, /* -------------------------------------------------------------------- */ /* Read in Field Definitions */ /* -------------------------------------------------------------------- */ - pabyBuf = STATIC_CAST(unsigned char *, SfRealloc(pabyBuf, nHeadLen)); + pabyBuf = STATIC_CAST(unsigned char *, realloc(pabyBuf, nHeadLen)); psDBF->pszHeader = REINTERPRET_CAST(char *, pabyBuf); psDBF->sHooks.FSeek(psDBF->fp, XBASE_FILEHDR_SZ, 0); @@ -583,8 +533,8 @@ void SHPAPI_CALL DBFClose(DBFHandle psDBF) CPL_IGNORE_RET_VAL_INT(DBFFlushRecord(psDBF)); /* -------------------------------------------------------------------- */ - /* Update last access date, and number of records if we have */ - /* write access. */ + /* Update last access date, and number of records if we have */ + /* write access. */ /* -------------------------------------------------------------------- */ if (psDBF->bUpdated) DBFUpdateHeader(psDBF); @@ -645,11 +595,12 @@ DBFHandle SHPAPI_CALL DBFCreateEx(const char *pszFilename, /************************************************************************/ DBFHandle SHPAPI_CALL DBFCreateLL(const char *pszFilename, - const char *pszCodePage, SAHooks *psHooks) + const char *pszCodePage, + const SAHooks *psHooks) { /* -------------------------------------------------------------------- */ - /* Compute the base (layer) name. If there is any extension */ - /* on the passed in filename we will strip it off. */ + /* Compute the base (layer) name. If there is any extension */ + /* on the passed in filename we will strip it off. */ /* -------------------------------------------------------------------- */ const int nLenWithoutExtension = DBFGetLenWithoutExtension(pszFilename); char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5)); @@ -659,17 +610,7 @@ DBFHandle SHPAPI_CALL DBFCreateLL(const char *pszFilename, /* -------------------------------------------------------------------- */ /* Create the file. */ /* -------------------------------------------------------------------- */ - SAFile fp = psHooks->FOpen(pszFullname, "wb"); - if (fp == SHPLIB_NULLPTR) { - free(pszFullname); - return SHPLIB_NULLPTR; - } - - char chZero = '\0'; - psHooks->FWrite(&chZero, 1, 1, fp); - psHooks->FClose(fp); - - fp = psHooks->FOpen(pszFullname, "rb+"); + SAFile fp = psHooks->FOpen(pszFullname, "wb+", psHooks->pvUserData); if (fp == SHPLIB_NULLPTR) { free(pszFullname); return SHPLIB_NULLPTR; @@ -685,7 +626,8 @@ DBFHandle SHPAPI_CALL DBFCreateLL(const char *pszFilename, // a valid one } if (ldid < 0) { - SAFile fpCPG = psHooks->FOpen(pszFullname, "w"); + SAFile fpCPG = + psHooks->FOpen(pszFullname, "w", psHooks->pvUserData); psHooks->FWrite( CONST_CAST(void *, STATIC_CAST(const void *, pszCodePage)), strlen(pszCodePage), 1, fpCPG); @@ -693,13 +635,13 @@ DBFHandle SHPAPI_CALL DBFCreateLL(const char *pszFilename, } } if (pszCodePage == SHPLIB_NULLPTR || ldid >= 0) { - psHooks->Remove(pszFullname); + psHooks->Remove(pszFullname, psHooks->pvUserData); } free(pszFullname); /* -------------------------------------------------------------------- */ - /* Create the info structure. */ + /* Create the info structure. */ /* -------------------------------------------------------------------- */ DBFHandle psDBF = STATIC_CAST(DBFHandle, calloc(1, sizeof(DBFInfo))); @@ -829,23 +771,22 @@ int SHPAPI_CALL DBFAddNativeFieldType(DBFHandle psDBF, const char *pszFieldName, const int nOldHeaderLength = psDBF->nHeaderLength; /* -------------------------------------------------------------------- */ - /* SfRealloc all the arrays larger to hold the additional field */ + /* realloc all the arrays larger to hold the additional field */ /* information. */ /* -------------------------------------------------------------------- */ psDBF->nFields++; psDBF->panFieldOffset = STATIC_CAST( - int *, SfRealloc(psDBF->panFieldOffset, sizeof(int) * psDBF->nFields)); + int *, realloc(psDBF->panFieldOffset, sizeof(int) * psDBF->nFields)); psDBF->panFieldSize = STATIC_CAST( - int *, SfRealloc(psDBF->panFieldSize, sizeof(int) * psDBF->nFields)); + int *, realloc(psDBF->panFieldSize, sizeof(int) * psDBF->nFields)); - psDBF->panFieldDecimals = - STATIC_CAST(int *, SfRealloc(psDBF->panFieldDecimals, - sizeof(int) * psDBF->nFields)); + psDBF->panFieldDecimals = STATIC_CAST( + int *, realloc(psDBF->panFieldDecimals, sizeof(int) * psDBF->nFields)); psDBF->pachFieldType = STATIC_CAST( - char *, SfRealloc(psDBF->pachFieldType, sizeof(char) * psDBF->nFields)); + char *, realloc(psDBF->pachFieldType, sizeof(char) * psDBF->nFields)); /* -------------------------------------------------------------------- */ /* Assign the new field information fields. */ @@ -863,7 +804,7 @@ int SHPAPI_CALL DBFAddNativeFieldType(DBFHandle psDBF, const char *pszFieldName, psDBF->bUpdated = FALSE; psDBF->pszHeader = STATIC_CAST( - char *, SfRealloc(psDBF->pszHeader, psDBF->nFields * XBASE_FLDHDR_SZ)); + char *, realloc(psDBF->pszHeader, psDBF->nFields * XBASE_FLDHDR_SZ)); char *pszFInfo = psDBF->pszHeader + XBASE_FLDHDR_SZ * (psDBF->nFields - 1); @@ -887,7 +828,7 @@ int SHPAPI_CALL DBFAddNativeFieldType(DBFHandle psDBF, const char *pszFieldName, /* Make the current record buffer appropriately larger. */ /* -------------------------------------------------------------------- */ psDBF->pszCurrentRecord = STATIC_CAST( - char *, SfRealloc(psDBF->pszCurrentRecord, psDBF->nRecordLength)); + char *, realloc(psDBF->pszCurrentRecord, psDBF->nRecordLength)); /* we're done if dealing with new .dbf */ if (psDBF->bNoHeader) @@ -971,13 +912,13 @@ static void *DBFReadAttribute(DBFHandle psDBF, int hEntity, int iField, return SHPLIB_NULLPTR; /* -------------------------------------------------------------------- */ - /* Have we read the record? */ + /* Have we read the record? */ /* -------------------------------------------------------------------- */ if (!DBFLoadRecord(psDBF, hEntity)) return SHPLIB_NULLPTR; - unsigned char *pabyRec = - REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord); + const unsigned char *pabyRec = + REINTERPRET_CAST(const unsigned char *, psDBF->pszCurrentRecord); /* -------------------------------------------------------------------- */ /* Ensure we have room to extract the target field. */ @@ -993,7 +934,7 @@ static void *DBFReadAttribute(DBFHandle psDBF, int hEntity, int iField, } /* -------------------------------------------------------------------- */ - /* Extract the requested field. */ + /* Extract the requested field. */ /* -------------------------------------------------------------------- */ memcpy(psDBF->pszWorkField, REINTERPRET_CAST(const char *, pabyRec) + @@ -1085,7 +1026,6 @@ double SHPAPI_CALL DBFReadDoubleAttribute(DBFHandle psDBF, int iRecord, const char SHPAPI_CALL1(*) DBFReadStringAttribute(DBFHandle psDBF, int iRecord, int iField) - { return STATIC_CAST(const char *, DBFReadAttribute(psDBF, iRecord, iField, 'C')); @@ -1099,12 +1039,40 @@ const char SHPAPI_CALL1(*) const char SHPAPI_CALL1(*) DBFReadLogicalAttribute(DBFHandle psDBF, int iRecord, int iField) - { return STATIC_CAST(const char *, DBFReadAttribute(psDBF, iRecord, iField, 'L')); } +/************************************************************************/ +/* DBFReadDateAttribute() */ +/* */ +/* Read a date attribute. */ +/************************************************************************/ + +SHPDate SHPAPI_CALL DBFReadDateAttribute(DBFHandle psDBF, int iRecord, + int iField) +{ + const char *pdateValue = STATIC_CAST( + const char *, DBFReadAttribute(psDBF, iRecord, iField, 'D')); + + SHPDate date; + + if (pdateValue == SHPLIB_NULLPTR) { + date.year = 0; + date.month = 0; + date.day = 0; + } + else if (3 != sscanf(pdateValue, "%4d%2d%2d", &date.year, &date.month, + &date.day)) { + date.year = 0; + date.month = 0; + date.day = 0; + } + + return date; +} + /************************************************************************/ /* DBFIsValueNULL() */ /* */ @@ -1135,7 +1103,16 @@ static bool DBFIsValueNULL(char chType, const char *pszValue) case 'D': /* NULL date fields have value "00000000" */ - return strncmp(pszValue, "00000000", 8) == 0; + /* Some DBF files have fields filled with spaces */ + /* (trimmed by DBFReadStringAttribute) to indicate null */ + /* values for dates (#4265). */ + /* And others have ' 0': + * https://lists.osgeo.org/pipermail/gdal-dev/2023-November/058010.html + */ + /* And others just empty string: + * https://github.com/OSGeo/gdal/issues/10405 */ + return pszValue[0] == 0 || strncmp(pszValue, "00000000", 8) == 0 || + strcmp(pszValue, " ") == 0 || strcmp(pszValue, "0") == 0; case 'L': /* NULL boolean fields have value "?" */ @@ -1155,7 +1132,8 @@ static bool DBFIsValueNULL(char chType, const char *pszValue) /* Contributed by Jim Matthews. */ /************************************************************************/ -int SHPAPI_CALL DBFIsAttributeNULL(DBFHandle psDBF, int iRecord, int iField) +int SHPAPI_CALL DBFIsAttributeNULL(const DBFHandle psDBF, int iRecord, + int iField) { const char *pszValue = DBFReadStringAttribute(psDBF, iRecord, iField); @@ -1171,8 +1149,7 @@ int SHPAPI_CALL DBFIsAttributeNULL(DBFHandle psDBF, int iRecord, int iField) /* Return the number of fields in this table. */ /************************************************************************/ -int SHPAPI_CALL DBFGetFieldCount(DBFHandle psDBF) - +int SHPAPI_CALL DBFGetFieldCount(const DBFHandle psDBF) { return (psDBF->nFields); } @@ -1183,8 +1160,7 @@ int SHPAPI_CALL DBFGetFieldCount(DBFHandle psDBF) /* Return the number of records in this table. */ /************************************************************************/ -int SHPAPI_CALL DBFGetRecordCount(DBFHandle psDBF) - +int SHPAPI_CALL DBFGetRecordCount(const DBFHandle psDBF) { return (psDBF->nRecords); } @@ -1197,10 +1173,9 @@ int SHPAPI_CALL DBFGetRecordCount(DBFHandle psDBF) /* bytes long. */ /************************************************************************/ -DBFFieldType SHPAPI_CALL DBFGetFieldInfo(DBFHandle psDBF, int iField, +DBFFieldType SHPAPI_CALL DBFGetFieldInfo(const DBFHandle psDBF, int iField, char *pszFieldName, int *pnWidth, int *pnDecimals) - { if (iField < 0 || iField >= psDBF->nFields) return (FTInvalid); @@ -1230,10 +1205,9 @@ DBFFieldType SHPAPI_CALL DBFGetFieldInfo(DBFHandle psDBF, int iField, else if (psDBF->pachFieldType[iField] == 'N' || psDBF->pachFieldType[iField] == 'F') { - if (psDBF->panFieldDecimals[iField] > 0) { - /* || psDBF->panFieldSize[iField] >= 10 ) */ /* GDAL bug #809 */ + if (psDBF->panFieldDecimals[iField] > 0 || + psDBF->panFieldSize[iField] >= 10) return (FTDouble); - } else return (FTInteger); } @@ -1244,15 +1218,15 @@ DBFFieldType SHPAPI_CALL DBFGetFieldInfo(DBFHandle psDBF, int iField, /************************************************************************/ /* DBFWriteAttribute() */ -/* */ -/* Write an attribute record to the file. */ +/* */ +/* Write an attribute record to the file. */ /************************************************************************/ static bool DBFWriteAttribute(DBFHandle psDBF, int hEntity, int iField, void *pValue) { /* -------------------------------------------------------------------- */ - /* Is this a valid record? */ + /* Is this a valid record? */ /* -------------------------------------------------------------------- */ if (hEntity < 0 || hEntity > psDBF->nRecords) return false; @@ -1333,9 +1307,13 @@ static bool DBFWriteAttribute(DBFHandle psDBF, int hEntity, int iField, case 'L': if (psDBF->panFieldSize[iField] >= 1 && (*STATIC_CAST(char *, pValue) == 'F' || - *STATIC_CAST(char *, pValue) == 'T')) + *STATIC_CAST(char *, pValue) == 'T')) { *(pabyRec + psDBF->panFieldOffset[iField]) = *STATIC_CAST(char *, pValue); + } + else { + nRetResult = false; + } break; default: { @@ -1370,10 +1348,10 @@ static bool DBFWriteAttribute(DBFHandle psDBF, int hEntity, int iField, /************************************************************************/ int SHPAPI_CALL DBFWriteAttributeDirectly(DBFHandle psDBF, int hEntity, - int iField, void *pValue) + int iField, const void *pValue) { /* -------------------------------------------------------------------- */ - /* Is this a valid record? */ + /* Is this a valid record? */ /* -------------------------------------------------------------------- */ if (hEntity < 0 || hEntity > psDBF->nRecords) return (FALSE); @@ -1402,24 +1380,29 @@ int SHPAPI_CALL DBFWriteAttributeDirectly(DBFHandle psDBF, int hEntity, if (!DBFLoadRecord(psDBF, hEntity)) return FALSE; - unsigned char *pabyRec = - REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord); + if (iField >= 0) { + unsigned char *pabyRec = + REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord); - /* -------------------------------------------------------------------- */ - /* Assign all the record fields. */ - /* -------------------------------------------------------------------- */ - int j; - if (STATIC_CAST(int, strlen(STATIC_CAST(char *, pValue))) > - psDBF->panFieldSize[iField]) - j = psDBF->panFieldSize[iField]; - else { - memset(pabyRec + psDBF->panFieldOffset[iField], ' ', - psDBF->panFieldSize[iField]); - j = STATIC_CAST(int, strlen(STATIC_CAST(char *, pValue))); - } + /* -------------------------------------------------------------------- + */ + /* Assign all the record fields. */ + /* -------------------------------------------------------------------- + */ + int j; + if (STATIC_CAST(int, strlen(STATIC_CAST(const char *, pValue))) > + psDBF->panFieldSize[iField]) + j = psDBF->panFieldSize[iField]; + else { + memset(pabyRec + psDBF->panFieldOffset[iField], ' ', + psDBF->panFieldSize[iField]); + j = STATIC_CAST(int, strlen(STATIC_CAST(const char *, pValue))); + } - strncpy(REINTERPRET_CAST(char *, pabyRec + psDBF->panFieldOffset[iField]), + memcpy( + REINTERPRET_CAST(char *, pabyRec + psDBF->panFieldOffset[iField]), STATIC_CAST(const char *, pValue), j); + } psDBF->bCurrentRecordModified = TRUE; psDBF->bUpdated = TRUE; @@ -1443,7 +1426,7 @@ int SHPAPI_CALL DBFWriteDoubleAttribute(DBFHandle psDBF, int iRecord, /************************************************************************/ /* DBFWriteIntegerAttribute() */ /* */ -/* Write a integer attribute. */ +/* Write an integer attribute. */ /************************************************************************/ int SHPAPI_CALL DBFWriteIntegerAttribute(DBFHandle psDBF, int iRecord, @@ -1463,7 +1446,6 @@ int SHPAPI_CALL DBFWriteIntegerAttribute(DBFHandle psDBF, int iRecord, int SHPAPI_CALL DBFWriteStringAttribute(DBFHandle psDBF, int iRecord, int iField, const char *pszValue) - { return ( DBFWriteAttribute(psDBF, iRecord, iField, @@ -1473,11 +1455,10 @@ int SHPAPI_CALL DBFWriteStringAttribute(DBFHandle psDBF, int iRecord, /************************************************************************/ /* DBFWriteNULLAttribute() */ /* */ -/* Write a string attribute. */ +/* Write a NULL attribute. */ /************************************************************************/ int SHPAPI_CALL DBFWriteNULLAttribute(DBFHandle psDBF, int iRecord, int iField) - { return (DBFWriteAttribute(psDBF, iRecord, iField, SHPLIB_NULLPTR)); } @@ -1490,23 +1471,47 @@ int SHPAPI_CALL DBFWriteNULLAttribute(DBFHandle psDBF, int iRecord, int iField) int SHPAPI_CALL DBFWriteLogicalAttribute(DBFHandle psDBF, int iRecord, int iField, const char lValue) - { return ( DBFWriteAttribute(psDBF, iRecord, iField, STATIC_CAST(void *, CONST_CAST(char *, &lValue)))); } +/************************************************************************/ +/* DBFWriteDateAttribute() */ +/* */ +/* Write a date attribute. */ +/************************************************************************/ + +int SHPAPI_CALL DBFWriteDateAttribute(DBFHandle psDBF, int iRecord, int iField, + const SHPDate *lValue) +{ + if (SHPLIB_NULLPTR == lValue) + return false; + /* check for supported digit range, but do not check for valid date */ + if (lValue->year < 0 || lValue->year > 9999) + return false; + if (lValue->month < 0 || lValue->month > 99) + return false; + if (lValue->day < 0 || lValue->day > 99) + return false; + char dateValue[9]; /* "yyyyMMdd\0" */ + snprintf(dateValue, sizeof(dateValue), "%04d%02d%02d", lValue->year, + lValue->month, lValue->day); + return (DBFWriteAttributeDirectly(psDBF, iRecord, iField, dateValue)); +} + /************************************************************************/ /* DBFWriteTuple() */ -/* */ -/* Write an attribute record to the file. */ +/* */ +/* Write an attribute record to the file. */ /************************************************************************/ -int SHPAPI_CALL DBFWriteTuple(DBFHandle psDBF, int hEntity, void *pRawTuple) +int SHPAPI_CALL DBFWriteTuple(DBFHandle psDBF, int hEntity, + const void *pRawTuple) { /* -------------------------------------------------------------------- */ - /* Is this a valid record? */ + /* Is this a valid record? */ /* -------------------------------------------------------------------- */ if (hEntity < 0 || hEntity > psDBF->nRecords) return (FALSE); @@ -1554,7 +1559,6 @@ int SHPAPI_CALL DBFWriteTuple(DBFHandle psDBF, int hEntity, void *pRawTuple) /************************************************************************/ const char SHPAPI_CALL1(*) DBFReadTuple(DBFHandle psDBF, int hEntity) - { if (hEntity < 0 || hEntity >= psDBF->nRecords) return SHPLIB_NULLPTR; @@ -1566,14 +1570,17 @@ const char SHPAPI_CALL1(*) DBFReadTuple(DBFHandle psDBF, int hEntity) } /************************************************************************/ -/* DBFCloneEmpty() */ +/* DBFCloneEmpty() */ /* */ -/* Read one of the attribute fields of a record. */ +/* Create a new .dbf file with same code page and field */ +/* definitions as the given handle. */ /************************************************************************/ -DBFHandle SHPAPI_CALL DBFCloneEmpty(DBFHandle psDBF, const char *pszFilename) +DBFHandle SHPAPI_CALL DBFCloneEmpty(const DBFHandle psDBF, + const char *pszFilename) { - DBFHandle newDBF = DBFCreateEx(pszFilename, psDBF->pszCodePage); + DBFHandle newDBF = + DBFCreateLL(pszFilename, psDBF->pszCodePage, &psDBF->sHooks); if (newDBF == SHPLIB_NULLPTR) return SHPLIB_NULLPTR; @@ -1629,8 +1636,7 @@ DBFHandle SHPAPI_CALL DBFCloneEmpty(DBFHandle psDBF, const char *pszFilename) /* 'M' (Memo: 10 digits .DBT block ptr) */ /************************************************************************/ -char SHPAPI_CALL DBFGetNativeFieldType(DBFHandle psDBF, int iField) - +char SHPAPI_CALL DBFGetNativeFieldType(const DBFHandle psDBF, int iField) { if (iField >= 0 && iField < psDBF->nFields) return psDBF->pachFieldType[iField]; @@ -1646,7 +1652,8 @@ char SHPAPI_CALL DBFGetNativeFieldType(DBFHandle psDBF, int iField) /* Contributed by Jim Matthews. */ /************************************************************************/ -int SHPAPI_CALL DBFGetFieldIndex(DBFHandle psDBF, const char *pszFieldName) +int SHPAPI_CALL DBFGetFieldIndex(const DBFHandle psDBF, + const char *pszFieldName) { char name[XBASE_FLDNAME_LEN_READ + 1]; @@ -1665,7 +1672,7 @@ int SHPAPI_CALL DBFGetFieldIndex(DBFHandle psDBF, const char *pszFieldName) /* it returns FALSE. */ /************************************************************************/ -int SHPAPI_CALL DBFIsRecordDeleted(DBFHandle psDBF, int iShape) +int SHPAPI_CALL DBFIsRecordDeleted(const DBFHandle psDBF, int iShape) { /* -------------------------------------------------------------------- */ /* Verify selection. */ @@ -1674,7 +1681,7 @@ int SHPAPI_CALL DBFIsRecordDeleted(DBFHandle psDBF, int iShape) return TRUE; /* -------------------------------------------------------------------- */ - /* Have we read the record? */ + /* Have we read the record? */ /* -------------------------------------------------------------------- */ if (!DBFLoadRecord(psDBF, iShape)) return FALSE; @@ -1727,7 +1734,7 @@ int SHPAPI_CALL DBFMarkRecordDeleted(DBFHandle psDBF, int iShape, /* DBFGetCodePage */ /************************************************************************/ -const char SHPAPI_CALL1(*) DBFGetCodePage(DBFHandle psDBF) +const char SHPAPI_CALL1(*) DBFGetCodePage(const DBFHandle psDBF) { if (psDBF == SHPLIB_NULLPTR) return SHPLIB_NULLPTR; @@ -1768,17 +1775,16 @@ int SHPAPI_CALL DBFDeleteField(DBFHandle psDBF, int iField) psDBF->nFields--; psDBF->panFieldOffset = STATIC_CAST( - int *, SfRealloc(psDBF->panFieldOffset, sizeof(int) * psDBF->nFields)); + int *, realloc(psDBF->panFieldOffset, sizeof(int) * psDBF->nFields)); psDBF->panFieldSize = STATIC_CAST( - int *, SfRealloc(psDBF->panFieldSize, sizeof(int) * psDBF->nFields)); + int *, realloc(psDBF->panFieldSize, sizeof(int) * psDBF->nFields)); - psDBF->panFieldDecimals = - STATIC_CAST(int *, SfRealloc(psDBF->panFieldDecimals, - sizeof(int) * psDBF->nFields)); + psDBF->panFieldDecimals = STATIC_CAST( + int *, realloc(psDBF->panFieldDecimals, sizeof(int) * psDBF->nFields)); psDBF->pachFieldType = STATIC_CAST( - char *, SfRealloc(psDBF->pachFieldType, sizeof(char) * psDBF->nFields)); + char *, realloc(psDBF->pachFieldType, sizeof(char) * psDBF->nFields)); /* update header information */ psDBF->nHeaderLength -= XBASE_FLDHDR_SZ; @@ -1790,11 +1796,11 @@ int SHPAPI_CALL DBFDeleteField(DBFHandle psDBF, int iField) sizeof(char) * (psDBF->nFields - iField) * XBASE_FLDHDR_SZ); psDBF->pszHeader = STATIC_CAST( - char *, SfRealloc(psDBF->pszHeader, psDBF->nFields * XBASE_FLDHDR_SZ)); + char *, realloc(psDBF->pszHeader, psDBF->nFields * XBASE_FLDHDR_SZ)); /* update size of current record appropriately */ psDBF->pszCurrentRecord = STATIC_CAST( - char *, SfRealloc(psDBF->pszCurrentRecord, psDBF->nRecordLength)); + char *, realloc(psDBF->pszCurrentRecord, psDBF->nRecordLength)); /* we're done if we're dealing with not yet created .dbf */ if (psDBF->bNoHeader && psDBF->nRecords == 0) @@ -1866,7 +1872,7 @@ int SHPAPI_CALL DBFDeleteField(DBFHandle psDBF, int iField) /* code of DBFReorderFields. */ /************************************************************************/ -int SHPAPI_CALL DBFReorderFields(DBFHandle psDBF, int *panMap) +int SHPAPI_CALL DBFReorderFields(DBFHandle psDBF, const int *panMap) { if (psDBF->nFields == 0) return TRUE; @@ -1878,13 +1884,13 @@ int SHPAPI_CALL DBFReorderFields(DBFHandle psDBF, int *panMap) /* a simple malloc() would be enough, but calloc() helps clang static * analyzer */ int *panFieldOffsetNew = - STATIC_CAST(int *, calloc(sizeof(int), psDBF->nFields)); + STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int))); int *panFieldSizeNew = - STATIC_CAST(int *, calloc(sizeof(int), psDBF->nFields)); + STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int))); int *panFieldDecimalsNew = - STATIC_CAST(int *, calloc(sizeof(int), psDBF->nFields)); + STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int))); char *pachFieldTypeNew = - STATIC_CAST(char *, calloc(sizeof(char), psDBF->nFields)); + STATIC_CAST(char *, calloc(psDBF->nFields, sizeof(char))); char *pszHeaderNew = STATIC_CAST( char *, malloc(sizeof(char) * XBASE_FLDHDR_SZ * psDBF->nFields)); @@ -2050,7 +2056,7 @@ int SHPAPI_CALL DBFAlterFieldDefn(DBFHandle psDBF, int iField, psDBF->nRecordLength += nWidth - nOldWidth; psDBF->pszCurrentRecord = STATIC_CAST( - char *, SfRealloc(psDBF->pszCurrentRecord, psDBF->nRecordLength)); + char *, realloc(psDBF->pszCurrentRecord, psDBF->nRecordLength)); } /* we're done if we're dealing with not yet created .dbf */ @@ -2069,7 +2075,6 @@ int SHPAPI_CALL DBFAlterFieldDefn(DBFHandle psDBF, int iField, char *pszOldField = STATIC_CAST(char *, malloc(sizeof(char) * (nOldWidth + 1))); - /* cppcheck-suppress uninitdata */ pszOldField[nOldWidth] = 0; /* move records to their new positions */ @@ -2139,7 +2144,6 @@ int SHPAPI_CALL DBFAlterFieldDefn(DBFHandle psDBF, int iField, char *pszOldField = STATIC_CAST(char *, malloc(sizeof(char) * (nOldWidth + 1))); - /* cppcheck-suppress uninitdata */ pszOldField[nOldWidth] = 0; /* move records to their new positions */ diff --git a/lib/external/shapelib/safileio.c b/lib/external/shapelib/safileio.c index 3377642e4d4..e13f1f29acf 100644 --- a/lib/external/shapelib/safileio.c +++ b/lib/external/shapelib/safileio.c @@ -1,5 +1,4 @@ /****************************************************************************** - * $Id: safileio.c,v 1.6 2018-06-15 19:56:32 erouault Exp $ * * Project: Shapelib * Purpose: Default implementation of file io based on stdio. @@ -8,74 +7,19 @@ ****************************************************************************** * Copyright (c) 2007, Frank Warmerdam * - * This software is available under the following "MIT Style" license, - * or at the option of the licensee under the LGPL (see COPYING). This - * option is discussed in more detail in shapelib.html. - * - * -- - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. + * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later ****************************************************************************** * - * $Log: safileio.c,v $ - * Revision 1.6 2018-06-15 19:56:32 erouault - * * safileio.c: remove duplicate test. Patch by Jaroslav Fojtik. - * Fixes http://bugzilla.maptools.org/show_bug.cgi?id=2744 - * - * Revision 1.5 2016-12-05 12:44:05 erouault - * * Major overhaul of Makefile build system to use autoconf/automake. - * - * * Warning fixes in contrib/ - * - * Revision 1.4 2008-01-16 20:05:14 bram - * Add file hooks that accept UTF-8 encoded filenames on some platforms. Use - *SASetupUtf8Hooks tosetup the hooks and check SHPAPI_UTF8_HOOKS for its - *availability. Currently, this is only available on the Windows platform that - *decodes the UTF-8 filenames to wide character strings and feeds them to - *_wfopen and _wremove. - * - * Revision 1.3 2007/12/18 18:28:11 bram - * - create hook for client specific atof (bugzilla ticket 1615) - * - check for NULL handle before closing cpCPG file, and close after reading. - * - * Revision 1.2 2007/12/15 20:25:30 bram - * dbfopen.c now reads the Code Page information from the DBF file, and exports - * this information as a string through the DBFGetCodePage function. This is - * either the number from the LDID header field ("LDID/") or as the - * content of an accompanying .CPG file. When creating a DBF file, the code can - * be set using DBFCreateEx. - * - * Revision 1.1 2007/12/06 06:56:41 fwarmerdam - * new - * */ #include "shapefil.h" +#include #include #include -#include +#include #include #include -#include - -SHP_CVSID("$Id: safileio.c,v 1.6 2018-06-15 19:56:32 erouault Exp $") #ifdef SHPAPI_UTF8_HOOKS #ifdef SHPAPI_WINDOWS @@ -86,102 +30,64 @@ SHP_CVSID("$Id: safileio.c,v 1.6 2018-06-15 19:56:32 erouault Exp $") #endif #endif -/************************************************************************/ -/* SADFOpen() */ -/************************************************************************/ - -SAFile SADFOpen(const char *pszFilename, const char *pszAccess) - +static SAFile SADFOpen(const char *pszFilename, const char *pszAccess, + void *pvUserData) { + (void)pvUserData; return (SAFile)fopen(pszFilename, pszAccess); } -/************************************************************************/ -/* SADFRead() */ -/************************************************************************/ - -SAOffset SADFRead(void *p, SAOffset size, SAOffset nmemb, SAFile file) - +static SAOffset SADFRead(void *p, SAOffset size, SAOffset nmemb, SAFile file) { return (SAOffset)fread(p, (size_t)size, (size_t)nmemb, (FILE *)file); } -/************************************************************************/ -/* SADFWrite() */ -/************************************************************************/ - -SAOffset SADFWrite(void *p, SAOffset size, SAOffset nmemb, SAFile file) - +static SAOffset SADFWrite(const void *p, SAOffset size, SAOffset nmemb, + SAFile file) { return (SAOffset)fwrite(p, (size_t)size, (size_t)nmemb, (FILE *)file); } -/************************************************************************/ -/* SADFSeek() */ -/************************************************************************/ - -SAOffset SADFSeek(SAFile file, SAOffset offset, int whence) - +static SAOffset SADFSeek(SAFile file, SAOffset offset, int whence) { +#if defined(_MSC_VER) && _MSC_VER >= 1400 + return (SAOffset)_fseeki64((FILE *)file, (__int64)offset, whence); +#else return (SAOffset)fseek((FILE *)file, (long)offset, whence); +#endif } -/************************************************************************/ -/* SADFTell() */ -/************************************************************************/ - -SAOffset SADFTell(SAFile file) - +static SAOffset SADFTell(SAFile file) { +#if defined(_MSC_VER) && _MSC_VER >= 1400 + return (SAOffset)_ftelli64((FILE *)file); +#else return (SAOffset)ftell((FILE *)file); +#endif } -/************************************************************************/ -/* SADFFlush() */ -/************************************************************************/ - -int SADFFlush(SAFile file) - +static int SADFFlush(SAFile file) { return fflush((FILE *)file); } -/************************************************************************/ -/* SADFClose() */ -/************************************************************************/ - -int SADFClose(SAFile file) - +static int SADFClose(SAFile file) { return fclose((FILE *)file); } -/************************************************************************/ -/* SADFClose() */ -/************************************************************************/ - -int SADRemove(const char *filename) - +static int SADRemove(const char *filename, void *pvUserData) { + (void)pvUserData; return remove(filename); } -/************************************************************************/ -/* SADError() */ -/************************************************************************/ - -void SADError(const char *message) - +static void SADError(const char *message) { fprintf(stderr, "%s\n", message); } -/************************************************************************/ -/* SASetupDefaultHooks() */ -/************************************************************************/ - void SASetupDefaultHooks(SAHooks *psHooks) - { psHooks->FOpen = SADFOpen; psHooks->FRead = SADFRead; @@ -194,25 +100,20 @@ void SASetupDefaultHooks(SAHooks *psHooks) psHooks->Error = SADError; psHooks->Atof = atof; + psHooks->pvUserData = NULL; } #ifdef SHPAPI_WINDOWS -/************************************************************************/ -/* Utf8ToWideChar */ -/************************************************************************/ - -const wchar_t *Utf8ToWideChar(const char *pszFilename) +static wchar_t *Utf8ToWideChar(const char *pszFilename) { - int nMulti, nWide; - wchar_t *pwszFileName; - - nMulti = strlen(pszFilename) + 1; - nWide = MultiByteToWideChar(CP_UTF8, 0, pszFilename, nMulti, 0, 0); + const int nMulti = (int)strlen(pszFilename) + 1; + const int nWide = + MultiByteToWideChar(CP_UTF8, 0, pszFilename, nMulti, 0, 0); if (nWide == 0) { return NULL; } - pwszFileName = (wchar_t *)malloc(nWide * sizeof(wchar_t)); + wchar_t *pwszFileName = (wchar_t *)malloc(nWide * sizeof(wchar_t)); if (pwszFileName == NULL) { return NULL; } @@ -228,51 +129,44 @@ const wchar_t *Utf8ToWideChar(const char *pszFilename) /* SAUtf8WFOpen */ /************************************************************************/ -SAFile SAUtf8WFOpen(const char *pszFilename, const char *pszAccess) +static SAFile SAUtf8WFOpen(const char *pszFilename, const char *pszAccess, + void *pvUserData) { + (void)pvUserData; SAFile file = NULL; - const wchar_t *pwszFileName, *pwszAccess; - pwszFileName = Utf8ToWideChar(pszFilename); - pwszAccess = Utf8ToWideChar(pszAccess); + wchar_t *pwszFileName = Utf8ToWideChar(pszFilename); + wchar_t *pwszAccess = Utf8ToWideChar(pszAccess); if (pwszFileName != NULL && pwszAccess != NULL) { file = (SAFile)_wfopen(pwszFileName, pwszAccess); } - free((wchar_t *)pwszFileName); - free((wchar_t *)pwszAccess); + free(pwszFileName); + free(pwszAccess); return file; } -/************************************************************************/ -/* SAUtf8WRemove() */ -/************************************************************************/ - -int SAUtf8WRemove(const char *pszFilename) +static int SAUtf8WRemove(const char *pszFilename, void *pvUserData) { - const wchar_t *pwszFileName = Utf8ToWideChar(pszFilename); + (void)pvUserData; + wchar_t *pwszFileName = Utf8ToWideChar(pszFilename); int rc = -1; if (pwszFileName != NULL) { rc = _wremove(pwszFileName); } - free((wchar_t *)pwszFileName); + free(pwszFileName); return rc; } #endif #ifdef SHPAPI_UTF8_HOOKS - -/************************************************************************/ -/* SASetupUtf8Hooks() */ -/************************************************************************/ +#ifndef SHPAPI_WINDOWS +#error "no implementations of UTF-8 hooks available for this platform" +#endif void SASetupUtf8Hooks(SAHooks *psHooks) { -#ifdef SHPAPI_WINDOWS psHooks->FOpen = SAUtf8WFOpen; psHooks->Remove = SAUtf8WRemove; -#else -#error "no implementations of UTF-8 hooks available for this platform" -#endif psHooks->FRead = SADFRead; psHooks->FWrite = SADFWrite; psHooks->FSeek = SADFSeek; @@ -283,5 +177,4 @@ void SASetupUtf8Hooks(SAHooks *psHooks) psHooks->Error = SADError; psHooks->Atof = atof; } - #endif diff --git a/lib/external/shapelib/shapefil.h b/lib/external/shapelib/shapefil.h index cd4ef54aa56..c11632fa6ba 100644 --- a/lib/external/shapelib/shapefil.h +++ b/lib/external/shapelib/shapefil.h @@ -2,7 +2,6 @@ #define SHAPEFILE_H_INCLUDED /****************************************************************************** - * $Id$ * * Project: Shapelib * Purpose: Primary include file for Shapelib. @@ -12,29 +11,7 @@ * Copyright (c) 1999, Frank Warmerdam * Copyright (c) 2012-2016, Even Rouault * - * This software is available under the following "MIT Style" license, - * or at the option of the licensee under the LGPL (see COPYING). This - * option is discussed in more detail in shapelib.html. - * - * -- - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. + * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later ****************************************************************************** * */ @@ -49,6 +26,26 @@ extern "C" { #endif +/************************************************************************/ +/* Version related macros (added in 1.6.0) */ +/************************************************************************/ + +#define SHAPELIB_VERSION_MAJOR 1 +#define SHAPELIB_VERSION_MINOR 6 +#define SHAPELIB_VERSION_MICRO 0 + +#define SHAPELIB_MAKE_VERSION_NUMBER(major, minor, micro) \ + ((major) * 10000 + (minor) * 100 + (micro)) + +#define SHAPELIB_VERSION_NUMBER \ + SHAPELIB_MAKE_VERSION_NUMBER(SHAPELIB_VERSION_MAJOR, \ + SHAPELIB_VERSION_MINOR, \ + SHAPELIB_VERSION_MICRO) + +#define SHAPELIB_AT_LEAST(major, minor, micro) \ + (SHAPELIB_VERSION_NUMBER >= \ + SHAPELIB_MAKE_VERSION_NUMBER(major, minor, micro)) + /************************************************************************/ /* Configuration options. */ /************************************************************************/ @@ -73,7 +70,7 @@ extern "C" { /* various calling conventions on the Shapelib API. */ /* */ /* To force __stdcall conventions (needed to call Shapelib */ -/* from Visual Basic and/or Dephi I believe) the makefile could */ +/* from Visual Basic and/or Delphi I believe) the makefile could */ /* be modified to define: */ /* */ /* /DSHPAPI_CALL=__stdcall */ @@ -113,31 +110,11 @@ extern "C" { #define SHPAPI_CALL1(x) x SHPAPI_CALL #endif -/* -------------------------------------------------------------------- */ -/* Macros for controlling CVSID and ensuring they don't appear */ -/* as unreferenced variables resulting in lots of warnings. */ -/* -------------------------------------------------------------------- */ -#ifndef DISABLE_CVSID -#if defined(__GNUC__) && __GNUC__ >= 4 -#define SHP_CVSID(string) \ - static const char cpl_cvsid[] __attribute__((used)) = string; -#else -#define SHP_CVSID(string) \ - static const char cpl_cvsid[] = string; \ - static const char *cvsid_aw() \ - { \ - return (cvsid_aw() ? NULL : cpl_cvsid); \ - } -#endif -#else -#define SHP_CVSID(string) -#endif - /* -------------------------------------------------------------------- */ /* On some platforms, additional file IO hooks are defined that */ /* UTF-8 encoded filenames Unicode filenames */ /* -------------------------------------------------------------------- */ -#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +#if defined(_WIN32) #define SHPAPI_WINDOWS #define SHPAPI_UTF8_HOOKS #endif @@ -148,21 +125,27 @@ extern "C" { typedef int *SAFile; #ifndef SAOffset +#if defined(_MSC_VER) && _MSC_VER >= 1400 +typedef unsigned __int64 SAOffset; +#else typedef unsigned long SAOffset; #endif +#endif typedef struct { - SAFile (*FOpen)(const char *filename, const char *access); + SAFile (*FOpen)(const char *filename, const char *access, void *pvUserData); SAOffset (*FRead)(void *p, SAOffset size, SAOffset nmemb, SAFile file); - SAOffset (*FWrite)(void *p, SAOffset size, SAOffset nmemb, SAFile file); + SAOffset (*FWrite)(const void *p, SAOffset size, SAOffset nmemb, + SAFile file); SAOffset (*FSeek)(SAFile file, SAOffset offset, int whence); SAOffset (*FTell)(SAFile file); int (*FFlush)(SAFile file); int (*FClose)(SAFile file); - int (*Remove)(const char *filename); + int (*Remove)(const char *filename, void *pvUserData); void (*Error)(const char *message); double (*Atof)(const char *str); + void *pvUserData; } SAHooks; void SHPAPI_CALL SASetupDefaultHooks(SAHooks *psHooks); @@ -206,6 +189,12 @@ typedef struct { typedef SHPInfo *SHPHandle; +typedef struct { + int year; + int month; + int day; +} SHPDate; + /* -------------------------------------------------------------------- */ /* Shape types (nSHPType) */ /* -------------------------------------------------------------------- */ @@ -277,13 +266,13 @@ struct tagSHPObject { /* will be NULL as it is not necessary to keep the SHX file open */ SHPHandle SHPAPI_CALL SHPOpen(const char *pszShapeFile, const char *pszAccess); SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszShapeFile, const char *pszAccess, - SAHooks *psHooks); + const SAHooks *psHooks); SHPHandle SHPAPI_CALL SHPOpenLLEx(const char *pszShapeFile, - const char *pszAccess, SAHooks *psHooks, + const char *pszAccess, const SAHooks *psHooks, int bRestoreSHX); int SHPAPI_CALL SHPRestoreSHX(const char *pszShapeFile, const char *pszAccess, - SAHooks *psHooks); + const SAHooks *psHooks); /* If setting bFastMode = TRUE, the content of SHPReadObject() is owned by the * SHPHandle. */ @@ -296,12 +285,14 @@ void SHPAPI_CALL SHPSetFastModeReadObject(SHPHandle hSHP, int bFastMode); SHPHandle SHPAPI_CALL SHPCreate(const char *pszShapeFile, int nShapeType); SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszShapeFile, int nShapeType, - SAHooks *psHooks); -void SHPAPI_CALL SHPGetInfo(SHPHandle hSHP, int *pnEntities, int *pnShapeType, - double *padfMinBound, double *padfMaxBound); + const SAHooks *psHooks); +void SHPAPI_CALL SHPGetInfo(const SHPHandle hSHP, int *pnEntities, + int *pnShapeType, double *padfMinBound, + double *padfMaxBound); -SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle hSHP, int iShape); -int SHPAPI_CALL SHPWriteObject(SHPHandle hSHP, int iShape, SHPObject *psObject); +SHPObject SHPAPI_CALL1(*) SHPReadObject(const SHPHandle hSHP, int iShape); +int SHPAPI_CALL SHPWriteObject(SHPHandle hSHP, int iShape, + const SHPObject *psObject); void SHPAPI_CALL SHPDestroyObject(SHPObject *psObject); void SHPAPI_CALL SHPComputeExtents(SHPObject *psObject); @@ -314,7 +305,7 @@ SHPObject SHPAPI_CALL1(*) SHPCreateSimpleObject(int nSHPType, int nVertices, const double *padfX, const double *padfY, const double *padfZ); -int SHPAPI_CALL SHPRewindObject(SHPHandle hSHP, SHPObject *psObject); +int SHPAPI_CALL SHPRewindObject(const SHPHandle hSHP, SHPObject *psObject); void SHPAPI_CALL SHPClose(SHPHandle hSHP); void SHPAPI_CALL SHPWriteHeader(SHPHandle hSHP); @@ -360,21 +351,20 @@ typedef struct { SHPTree SHPAPI_CALL1(*) SHPCreateTree(SHPHandle hSHP, int nDimension, int nMaxDepth, - double *padfBoundsMin, double *padfBoundsMax); + const double *padfBoundsMin, const double *padfBoundsMax); void SHPAPI_CALL SHPDestroyTree(SHPTree *hTree); int SHPAPI_CALL SHPWriteTree(SHPTree *hTree, const char *pszFilename); int SHPAPI_CALL SHPTreeAddShapeId(SHPTree *hTree, SHPObject *psObject); -int SHPAPI_CALL SHPTreeRemoveShapeId(SHPTree *hTree, int nShapeId); void SHPAPI_CALL SHPTreeTrimExtraNodes(SHPTree *hTree); int SHPAPI_CALL1(*) - SHPTreeFindLikelyShapes(SHPTree *hTree, double *padfBoundsMin, + SHPTreeFindLikelyShapes(const SHPTree *hTree, double *padfBoundsMin, double *padfBoundsMax, int *); -int SHPAPI_CALL SHPCheckBoundsOverlap(double *, double *, double *, double *, - int); +int SHPAPI_CALL SHPCheckBoundsOverlap(const double *, const double *, + const double *, const double *, int); int SHPAPI_CALL1(*) SHPSearchDiskTree(FILE *fp, double *padfBoundsMin, double *padfBoundsMax, int *pnShapeCount); @@ -382,16 +372,17 @@ int SHPAPI_CALL1(*) SHPSearchDiskTree(FILE *fp, double *padfBoundsMin, typedef struct SHPDiskTreeInfo *SHPTreeDiskHandle; SHPTreeDiskHandle SHPAPI_CALL SHPOpenDiskTree(const char *pszQIXFilename, - SAHooks *psHooks); + const SAHooks *psHooks); void SHPAPI_CALL SHPCloseDiskTree(SHPTreeDiskHandle hDiskTree); int SHPAPI_CALL1(*) - SHPSearchDiskTreeEx(SHPTreeDiskHandle hDiskTree, double *padfBoundsMin, - double *padfBoundsMax, int *pnShapeCount); + SHPSearchDiskTreeEx(const SHPTreeDiskHandle hDiskTree, + double *padfBoundsMin, double *padfBoundsMax, + int *pnShapeCount); int SHPAPI_CALL SHPWriteTreeLL(SHPTree *hTree, const char *pszFilename, - SAHooks *psHooks); + const SAHooks *psHooks); /* -------------------------------------------------------------------- */ /* SBN Search API */ @@ -400,16 +391,16 @@ int SHPAPI_CALL SHPWriteTreeLL(SHPTree *hTree, const char *pszFilename, typedef struct SBNSearchInfo *SBNSearchHandle; SBNSearchHandle SHPAPI_CALL SBNOpenDiskTree(const char *pszSBNFilename, - SAHooks *psHooks); + const SAHooks *psHooks); void SHPAPI_CALL SBNCloseDiskTree(SBNSearchHandle hSBN); int SHPAPI_CALL1(*) - SBNSearchDiskTree(SBNSearchHandle hSBN, double *padfBoundsMin, - double *padfBoundsMax, int *pnShapeCount); + SBNSearchDiskTree(const SBNSearchHandle hSBN, const double *padfBoundsMin, + const double *padfBoundsMax, int *pnShapeCount); int SHPAPI_CALL1(*) - SBNSearchDiskTreeInteger(SBNSearchHandle hSBN, int bMinX, int bMinY, + SBNSearchDiskTreeInteger(const SBNSearchHandle hSBN, int bMinX, int bMinY, int bMaxX, int bMaxY, int *pnShapeCount); void SHPAPI_CALL SBNSearchFreeIds(int *panShapeId); @@ -485,15 +476,16 @@ typedef enum { DBFHandle SHPAPI_CALL DBFOpen(const char *pszDBFFile, const char *pszAccess); DBFHandle SHPAPI_CALL DBFOpenLL(const char *pszDBFFile, const char *pszAccess, - SAHooks *psHooks); + const SAHooks *psHooks); DBFHandle SHPAPI_CALL DBFCreate(const char *pszDBFFile); DBFHandle SHPAPI_CALL DBFCreateEx(const char *pszDBFFile, const char *pszCodePage); DBFHandle SHPAPI_CALL DBFCreateLL(const char *pszDBFFile, - const char *pszCodePage, SAHooks *psHooks); + const char *pszCodePage, + const SAHooks *psHooks); -int SHPAPI_CALL DBFGetFieldCount(DBFHandle psDBF); -int SHPAPI_CALL DBFGetRecordCount(DBFHandle psDBF); +int SHPAPI_CALL DBFGetFieldCount(const DBFHandle psDBF); +int SHPAPI_CALL DBFGetRecordCount(const DBFHandle psDBF); int SHPAPI_CALL DBFAddField(DBFHandle hDBF, const char *pszFieldName, DBFFieldType eType, int nWidth, int nDecimals); @@ -502,17 +494,18 @@ int SHPAPI_CALL DBFAddNativeFieldType(DBFHandle hDBF, const char *pszFieldName, int SHPAPI_CALL DBFDeleteField(DBFHandle hDBF, int iField); -int SHPAPI_CALL DBFReorderFields(DBFHandle psDBF, int *panMap); +int SHPAPI_CALL DBFReorderFields(DBFHandle psDBF, const int *panMap); int SHPAPI_CALL DBFAlterFieldDefn(DBFHandle psDBF, int iField, const char *pszFieldName, char chType, int nWidth, int nDecimals); -DBFFieldType SHPAPI_CALL DBFGetFieldInfo(DBFHandle psDBF, int iField, +DBFFieldType SHPAPI_CALL DBFGetFieldInfo(const DBFHandle psDBF, int iField, char *pszFieldName, int *pnWidth, int *pnDecimals); -int SHPAPI_CALL DBFGetFieldIndex(DBFHandle psDBF, const char *pszFieldName); +int SHPAPI_CALL DBFGetFieldIndex(const DBFHandle psDBF, + const char *pszFieldName); int SHPAPI_CALL DBFReadIntegerAttribute(DBFHandle hDBF, int iShape, int iField); double SHPAPI_CALL DBFReadDoubleAttribute(DBFHandle hDBF, int iShape, @@ -521,7 +514,10 @@ const char SHPAPI_CALL1(*) DBFReadStringAttribute(DBFHandle hDBF, int iShape, int iField); const char SHPAPI_CALL1(*) DBFReadLogicalAttribute(DBFHandle hDBF, int iShape, int iField); -int SHPAPI_CALL DBFIsAttributeNULL(DBFHandle hDBF, int iShape, int iField); +SHPDate SHPAPI_CALL DBFReadDateAttribute(DBFHandle hDBF, int iShape, + int iField); +int SHPAPI_CALL DBFIsAttributeNULL(const DBFHandle hDBF, int iShape, + int iField); int SHPAPI_CALL DBFWriteIntegerAttribute(DBFHandle hDBF, int iShape, int iField, int nFieldValue); @@ -533,22 +529,26 @@ int SHPAPI_CALL DBFWriteNULLAttribute(DBFHandle hDBF, int iShape, int iField); int SHPAPI_CALL DBFWriteLogicalAttribute(DBFHandle hDBF, int iShape, int iField, const char lFieldValue); +int SHPAPI_CALL DBFWriteDateAttribute(DBFHandle hDBF, int iShape, int iField, + const SHPDate *dateFieldValue); int SHPAPI_CALL DBFWriteAttributeDirectly(DBFHandle psDBF, int hEntity, - int iField, void *pValue); + int iField, const void *pValue); const char SHPAPI_CALL1(*) DBFReadTuple(DBFHandle psDBF, int hEntity); -int SHPAPI_CALL DBFWriteTuple(DBFHandle psDBF, int hEntity, void *pRawTuple); +int SHPAPI_CALL DBFWriteTuple(DBFHandle psDBF, int hEntity, + const void *pRawTuple); -int SHPAPI_CALL DBFIsRecordDeleted(DBFHandle psDBF, int iShape); +int SHPAPI_CALL DBFIsRecordDeleted(const DBFHandle psDBF, int iShape); int SHPAPI_CALL DBFMarkRecordDeleted(DBFHandle psDBF, int iShape, int bIsDeleted); -DBFHandle SHPAPI_CALL DBFCloneEmpty(DBFHandle psDBF, const char *pszFilename); +DBFHandle SHPAPI_CALL DBFCloneEmpty(const DBFHandle psDBF, + const char *pszFilename); void SHPAPI_CALL DBFClose(DBFHandle hDBF); void SHPAPI_CALL DBFUpdateHeader(DBFHandle hDBF); -char SHPAPI_CALL DBFGetNativeFieldType(DBFHandle hDBF, int iField); +char SHPAPI_CALL DBFGetNativeFieldType(const DBFHandle hDBF, int iField); -const char SHPAPI_CALL1(*) DBFGetCodePage(DBFHandle psDBF); +const char SHPAPI_CALL1(*) DBFGetCodePage(const DBFHandle psDBF); void SHPAPI_CALL DBFSetLastModifiedDate(DBFHandle psDBF, int nYYSince1900, int nMM, int nDD); diff --git a/lib/external/shapelib/shapefil_private.h b/lib/external/shapelib/shapefil_private.h new file mode 100644 index 00000000000..1bed49e7cce --- /dev/null +++ b/lib/external/shapelib/shapefil_private.h @@ -0,0 +1,115 @@ +#ifndef SHAPEFILE_PRIVATE_H_INCLUDED +#define SHAPEFILE_PRIVATE_H_INCLUDED + +/****************************************************************************** + * + * Project: Shapelib + * Purpose: Private include file for Shapelib. + * Author: Frank Warmerdam, warmerdam@pobox.com + * + ****************************************************************************** + * Copyright (c) 1999, Frank Warmerdam + * Copyright (c) 2012-2016, Even Rouault + * + * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later + ****************************************************************************** + * + */ + +#ifdef __cplusplus +#define STATIC_CAST(type, x) static_cast(x) +#define REINTERPRET_CAST(type, x) reinterpret_cast(x) +#define CONST_CAST(type, x) const_cast(x) +#define SHPLIB_NULLPTR nullptr +#else +#define STATIC_CAST(type, x) ((type)(x)) +#define REINTERPRET_CAST(type, x) ((type)(x)) +#define CONST_CAST(type, x) ((type)(x)) +#define SHPLIB_NULLPTR NULL +#endif + +#if !defined(SHP_BIG_ENDIAN) +#if defined(CPL_MSB) +#define SHP_BIG_ENDIAN 1 +#elif (defined(__GNUC__) && __GNUC__ >= 5) || \ + (defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 4 && \ + __GNUC_MINOR__ >= 6) +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SHP_BIG_ENDIAN 1 +#endif +#elif defined(__GLIBC__) +#if __BYTE_ORDER == __BIG_ENDIAN +#define SHP_BIG_ENDIAN 1 +#endif +#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +#define SHP_BIG_ENDIAN 1 +#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +#elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || \ + defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || \ + defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +#define SHP_BIG_ENDIAN 1 +#endif +#endif + +#include "shapefil.h" +#include +#include + +/************************************************************************/ +/* Little endian <==> big endian byte swap macros. */ +/************************************************************************/ + +#if (defined(__GNUC__) && __GNUC__ >= 5) || \ + (defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 4 && \ + __GNUC_MINOR__ >= 8) +#define _SHP_SWAP32(x) \ + STATIC_CAST(uint32_t, __builtin_bswap32(STATIC_CAST(uint32_t, x))) +#define _SHP_SWAP64(x) \ + STATIC_CAST(uint64_t, __builtin_bswap64(STATIC_CAST(uint64_t, x))) +#elif defined(_MSC_VER) +#define _SHP_SWAP32(x) \ + STATIC_CAST(uint32_t, _byteswap_ulong(STATIC_CAST(uint32_t, x))) +#define _SHP_SWAP64(x) \ + STATIC_CAST(uint64_t, _byteswap_uint64(STATIC_CAST(uint64_t, x))) +#else +#define _SHP_SWAP32(x) \ + STATIC_CAST(uint32_t, \ + ((STATIC_CAST(uint32_t, x) & 0x000000ffU) << 24) | \ + ((STATIC_CAST(uint32_t, x) & 0x0000ff00U) << 8) | \ + ((STATIC_CAST(uint32_t, x) & 0x00ff0000U) >> 8) | \ + ((STATIC_CAST(uint32_t, x) & 0xff000000U) >> 24)) +#define _SHP_SWAP64(x) \ + ((STATIC_CAST(uint64_t, _SHP_SWAP32(STATIC_CAST(uint32_t, x))) << 32) | \ + (STATIC_CAST(uint64_t, _SHP_SWAP32(STATIC_CAST( \ + uint32_t, STATIC_CAST(uint64_t, x) >> 32))))) + +#endif + +/* in-place uint32_t* swap */ +#define SHP_SWAP32(p) \ + *REINTERPRET_CAST(uint32_t *, p) = \ + _SHP_SWAP32(*REINTERPRET_CAST(uint32_t *, p)) +/* in-place uint64_t* swap */ +#define SHP_SWAP64(p) \ + *REINTERPRET_CAST(uint64_t *, p) = \ + _SHP_SWAP64(*REINTERPRET_CAST(uint64_t *, p)) +/* in-place double* swap */ +#define SHP_SWAPDOUBLE(x) \ + do { \ + uint64_t _n64; \ + void *_lx = x; \ + memcpy(&_n64, _lx, 8); \ + _n64 = _SHP_SWAP64(_n64); \ + memcpy(_lx, &_n64, 8); \ + } while (0) +/* copy double* swap*/ +#define SHP_SWAPDOUBLE_CPY(dst, src) \ + do { \ + uint64_t _n64; \ + const void *_ls = src; \ + void *_ld = dst; \ + memcpy(&_n64, _ls, 8); \ + _n64 = _SHP_SWAP64(_n64); \ + memcpy(_ld, &_n64, 8); \ + } while (0) +#endif /* ndef SHAPEFILE_PRIVATE_H_INCLUDED */ diff --git a/lib/external/shapelib/shpopen.c b/lib/external/shapelib/shpopen.c index e63cf768d25..b2eeb3614fb 100644 --- a/lib/external/shapelib/shpopen.c +++ b/lib/external/shapelib/shpopen.c @@ -8,52 +8,21 @@ * Copyright (c) 1999, 2001, Frank Warmerdam * Copyright (c) 2011-2019, Even Rouault * - * This software is available under the following "MIT Style" license, - * or at the option of the licensee under the LGPL (see COPYING). This - * option is discussed in more detail in shapelib.html. - * - * -- - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. + * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later ******************************************************************************/ -#include "shapefil.h" +#include "shapefil_private.h" #include #include #include #include #include +#include #include #include #include -SHP_CVSID("$Id$") - -typedef unsigned char uchar; - -#if UINT_MAX == 65535 -typedef unsigned long int32; -#else -typedef unsigned int int32; -#endif - #ifndef FALSE #define FALSE 0 #define TRUE 1 @@ -70,73 +39,18 @@ typedef unsigned int int32; #if _MSC_VER < 1900 #define snprintf _snprintf #endif -#elif defined(WIN32) || defined(_WIN32) +#elif defined(_WIN32) #ifndef snprintf #define snprintf _snprintf #endif #endif #endif -#ifndef CPL_UNUSED -#if defined(__GNUC__) && __GNUC__ >= 4 -#define CPL_UNUSED __attribute((__unused__)) -#else -#define CPL_UNUSED -#endif -#endif - -#if defined(CPL_LSB) -#define bBigEndian false -#elif defined(CPL_MSB) -#define bBigEndian true -#else -static bool bBigEndian; -#endif - -#ifdef __cplusplus -#define STATIC_CAST(type, x) static_cast(x) -#define SHPLIB_NULLPTR nullptr -#else -#define STATIC_CAST(type, x) ((type)(x)) -#define SHPLIB_NULLPTR NULL -#endif - -/************************************************************************/ -/* SwapWord() */ -/* */ -/* Swap a 2, 4 or 8 byte word. */ -/************************************************************************/ - -static void SwapWord(int length, void *wordP) -{ - for (int i = 0; i < length / 2; i++) { - const uchar temp = STATIC_CAST(uchar *, wordP)[i]; - STATIC_CAST(uchar *, wordP) - [i] = STATIC_CAST(uchar *, wordP)[length - i - 1]; - STATIC_CAST(uchar *, wordP)[length - i - 1] = temp; - } -} - -/************************************************************************/ -/* SfRealloc() */ -/* */ -/* A realloc cover function that will access a NULL pointer as */ -/* a valid input. */ -/************************************************************************/ - -static void *SfRealloc(void *pMem, int nNewSize) -{ - if (pMem == SHPLIB_NULLPTR) - return malloc(nNewSize); - else - return realloc(pMem, nNewSize); -} - /************************************************************************/ /* SHPWriteHeader() */ /* */ -/* Write out a header for the .shp and .shx files as well as the */ -/* contents of the index (.shx) file. */ +/* Write out a header for the .shp and .shx files as well as the */ +/* contents of the index (.shx) file. */ /************************************************************************/ void SHPAPI_CALL SHPWriteHeader(SHPHandle psSHP) @@ -150,64 +64,73 @@ void SHPAPI_CALL SHPWriteHeader(SHPHandle psSHP) /* Prepare header block for .shp file. */ /* -------------------------------------------------------------------- */ - uchar abyHeader[100] = {0}; + unsigned char abyHeader[100] = {0}; abyHeader[2] = 0x27; /* magic cookie */ abyHeader[3] = 0x0a; - int32 i32 = psSHP->nFileSize / 2; /* file size */ + uint32_t i32 = psSHP->nFileSize / 2; /* file size */ ByteCopy(&i32, abyHeader + 24, 4); - if (!bBigEndian) - SwapWord(4, abyHeader + 24); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(abyHeader + 24); +#endif i32 = 1000; /* version */ ByteCopy(&i32, abyHeader + 28, 4); - if (bBigEndian) - SwapWord(4, abyHeader + 28); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(abyHeader + 28); +#endif i32 = psSHP->nShapeType; /* shape type */ ByteCopy(&i32, abyHeader + 32, 4); - if (bBigEndian) - SwapWord(4, abyHeader + 32); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(abyHeader + 32); +#endif double dValue = psSHP->adBoundsMin[0]; /* set bounds */ ByteCopy(&dValue, abyHeader + 36, 8); - if (bBigEndian) - SwapWord(8, abyHeader + 36); - +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(abyHeader + 36); +#endif dValue = psSHP->adBoundsMin[1]; ByteCopy(&dValue, abyHeader + 44, 8); - if (bBigEndian) - SwapWord(8, abyHeader + 44); - +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(abyHeader + 44); +#endif dValue = psSHP->adBoundsMax[0]; ByteCopy(&dValue, abyHeader + 52, 8); - if (bBigEndian) - SwapWord(8, abyHeader + 52); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(abyHeader + 52); +#endif dValue = psSHP->adBoundsMax[1]; ByteCopy(&dValue, abyHeader + 60, 8); - if (bBigEndian) - SwapWord(8, abyHeader + 60); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(abyHeader + 60); +#endif dValue = psSHP->adBoundsMin[2]; /* z */ ByteCopy(&dValue, abyHeader + 68, 8); - if (bBigEndian) - SwapWord(8, abyHeader + 68); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(abyHeader + 68); +#endif dValue = psSHP->adBoundsMax[2]; ByteCopy(&dValue, abyHeader + 76, 8); - if (bBigEndian) - SwapWord(8, abyHeader + 76); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(abyHeader + 76); +#endif dValue = psSHP->adBoundsMin[3]; /* m */ ByteCopy(&dValue, abyHeader + 84, 8); - if (bBigEndian) - SwapWord(8, abyHeader + 84); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(abyHeader + 84); +#endif dValue = psSHP->adBoundsMax[3]; ByteCopy(&dValue, abyHeader + 92, 8); - if (bBigEndian) - SwapWord(8, abyHeader + 92); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(abyHeader + 92); +#endif /* -------------------------------------------------------------------- */ /* Write .shp file header. */ @@ -226,10 +149,11 @@ void SHPAPI_CALL SHPWriteHeader(SHPHandle psSHP) /* -------------------------------------------------------------------- */ /* Prepare, and write .shx file header. */ /* -------------------------------------------------------------------- */ - i32 = (psSHP->nRecords * 2 * sizeof(int32) + 100) / 2; /* file size */ + i32 = (psSHP->nRecords * 2 * sizeof(uint32_t) + 100) / 2; /* file size */ ByteCopy(&i32, abyHeader + 24, 4); - if (!bBigEndian) - SwapWord(4, abyHeader + 24); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(abyHeader + 24); +#endif if (psSHP->sHooks.FSeek(psSHP->fpSHX, 0, 0) != 0 || psSHP->sHooks.FWrite(abyHeader, 100, 1, psSHP->fpSHX) != 1) { @@ -246,8 +170,8 @@ void SHPAPI_CALL SHPWriteHeader(SHPHandle psSHP) /* -------------------------------------------------------------------- */ /* Write out the .shx contents. */ /* -------------------------------------------------------------------- */ - int32 *panSHX = - STATIC_CAST(int32 *, malloc(sizeof(int32) * 2 * psSHP->nRecords)); + uint32_t *panSHX = + STATIC_CAST(uint32_t *, malloc(sizeof(uint32_t) * 2 * psSHP->nRecords)); if (panSHX == SHPLIB_NULLPTR) { psSHP->sHooks.Error("Failure allocatin panSHX"); return; @@ -256,13 +180,13 @@ void SHPAPI_CALL SHPWriteHeader(SHPHandle psSHP) for (int i = 0; i < psSHP->nRecords; i++) { panSHX[i * 2] = psSHP->panRecOffset[i] / 2; panSHX[i * 2 + 1] = psSHP->panRecSize[i] / 2; - if (!bBigEndian) - SwapWord(4, panSHX + i * 2); - if (!bBigEndian) - SwapWord(4, panSHX + i * 2 + 1); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(panSHX + i * 2); + SHP_SWAP32(panSHX + i * 2 + 1); +#endif } - if (STATIC_CAST(int, psSHP->sHooks.FWrite(panSHX, sizeof(int32) * 2, + if (STATIC_CAST(int, psSHP->sHooks.FWrite(panSHX, sizeof(uint32_t) * 2, psSHP->nRecords, psSHP->fpSHX)) != psSHP->nRecords) { char szErrorMsg[200]; @@ -319,7 +243,7 @@ static int SHPGetLenWithoutExtension(const char *pszBasename) /************************************************************************/ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, - SAHooks *psHooks) + const SAHooks *psHooks) { /* -------------------------------------------------------------------- */ /* Ensure the access string is one of the legal ones. We */ @@ -336,23 +260,10 @@ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, pszAccess = "rb"; } -/* -------------------------------------------------------------------- */ -/* Establish the byte order on this machine. */ -/* -------------------------------------------------------------------- */ -#if !defined(bBigEndian) - { - int i = 1; - if (*((uchar *)&i) == 1) - bBigEndian = false; - else - bBigEndian = true; - } -#endif - /* -------------------------------------------------------------------- */ /* Initialize the info structure. */ /* -------------------------------------------------------------------- */ - SHPHandle psSHP = STATIC_CAST(SHPHandle, calloc(sizeof(SHPInfo), 1)); + SHPHandle psSHP = STATIC_CAST(SHPHandle, calloc(1, sizeof(SHPInfo))); psSHP->bUpdated = FALSE; memcpy(&(psSHP->sHooks), psHooks, sizeof(SAHooks)); @@ -365,18 +276,21 @@ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5)); memcpy(pszFullname, pszLayer, nLenWithoutExtension); memcpy(pszFullname + nLenWithoutExtension, ".shp", 5); - psSHP->fpSHP = psSHP->sHooks.FOpen(pszFullname, pszAccess); + psSHP->fpSHP = + psSHP->sHooks.FOpen(pszFullname, pszAccess, psSHP->sHooks.pvUserData); if (psSHP->fpSHP == SHPLIB_NULLPTR) { memcpy(pszFullname + nLenWithoutExtension, ".SHP", 5); - psSHP->fpSHP = psSHP->sHooks.FOpen(pszFullname, pszAccess); + psSHP->fpSHP = psSHP->sHooks.FOpen(pszFullname, pszAccess, + psSHP->sHooks.pvUserData); } if (psSHP->fpSHP == SHPLIB_NULLPTR) { const size_t nMessageLen = strlen(pszFullname) * 2 + 256; char *pszMessage = STATIC_CAST(char *, malloc(nMessageLen)); pszFullname[nLenWithoutExtension] = 0; - snprintf(pszMessage, nMessageLen, "Unable to open %s.shp or %s.SHP.", - pszFullname, pszFullname); + snprintf(pszMessage, nMessageLen, + "Unable to open %s.shp or %s.SHP in %s mode.", pszFullname, + pszFullname, pszAccess); psHooks->Error(pszMessage); free(pszMessage); @@ -387,10 +301,12 @@ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, } memcpy(pszFullname + nLenWithoutExtension, ".shx", 5); - psSHP->fpSHX = psSHP->sHooks.FOpen(pszFullname, pszAccess); + psSHP->fpSHX = + psSHP->sHooks.FOpen(pszFullname, pszAccess, psSHP->sHooks.pvUserData); if (psSHP->fpSHX == SHPLIB_NULLPTR) { memcpy(pszFullname + nLenWithoutExtension, ".SHX", 5); - psSHP->fpSHX = psSHP->sHooks.FOpen(pszFullname, pszAccess); + psSHP->fpSHX = psSHP->sHooks.FOpen(pszFullname, pszAccess, + psSHP->sHooks.pvUserData); } if (psSHP->fpSHX == SHPLIB_NULLPTR) { @@ -416,7 +332,7 @@ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, /* -------------------------------------------------------------------- */ /* Read the file size from the SHP file. */ /* -------------------------------------------------------------------- */ - uchar *pabyBuf = STATIC_CAST(uchar *, malloc(100)); + unsigned char *pabyBuf = STATIC_CAST(unsigned char *, malloc(100)); if (psSHP->sHooks.FRead(pabyBuf, 100, 1, psSHP->fpSHP) != 1) { psSHP->sHooks.Error(".shp file is unreadable, or corrupt."); psSHP->sHooks.FClose(psSHP->fpSHP); @@ -489,43 +405,51 @@ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, /* -------------------------------------------------------------------- */ double dValue; - if (bBigEndian) - SwapWord(8, pabyBuf + 36); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyBuf + 36); +#endif memcpy(&dValue, pabyBuf + 36, 8); psSHP->adBoundsMin[0] = dValue; - if (bBigEndian) - SwapWord(8, pabyBuf + 44); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyBuf + 44); +#endif memcpy(&dValue, pabyBuf + 44, 8); psSHP->adBoundsMin[1] = dValue; - if (bBigEndian) - SwapWord(8, pabyBuf + 52); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyBuf + 52); +#endif memcpy(&dValue, pabyBuf + 52, 8); psSHP->adBoundsMax[0] = dValue; - if (bBigEndian) - SwapWord(8, pabyBuf + 60); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyBuf + 60); +#endif memcpy(&dValue, pabyBuf + 60, 8); psSHP->adBoundsMax[1] = dValue; - if (bBigEndian) - SwapWord(8, pabyBuf + 68); /* z */ +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyBuf + 68); /* z */ +#endif memcpy(&dValue, pabyBuf + 68, 8); psSHP->adBoundsMin[2] = dValue; - if (bBigEndian) - SwapWord(8, pabyBuf + 76); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyBuf + 76); +#endif memcpy(&dValue, pabyBuf + 76, 8); psSHP->adBoundsMax[2] = dValue; - if (bBigEndian) - SwapWord(8, pabyBuf + 84); /* z */ +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyBuf + 84); /* z */ +#endif memcpy(&dValue, pabyBuf + 84, 8); psSHP->adBoundsMin[3] = dValue; - if (bBigEndian) - SwapWord(8, pabyBuf + 92); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyBuf + 92); +#endif memcpy(&dValue, pabyBuf + 92, 8); psSHP->adBoundsMax[3] = dValue; @@ -546,7 +470,8 @@ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, if (bLazySHXLoading) pabyBuf = SHPLIB_NULLPTR; else - pabyBuf = STATIC_CAST(uchar *, malloc(8 * MAX(1, psSHP->nRecords))); + pabyBuf = + STATIC_CAST(unsigned char *, malloc(8 * MAX(1, psSHP->nRecords))); if (psSHP->panRecOffset == SHPLIB_NULLPTR || psSHP->panRecSize == SHPLIB_NULLPTR || @@ -612,13 +537,15 @@ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, for (int i = 0; i < psSHP->nRecords; i++) { unsigned int nOffset; memcpy(&nOffset, pabyBuf + i * 8, 4); - if (!bBigEndian) - SwapWord(4, &nOffset); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nOffset); +#endif unsigned int nLength; memcpy(&nLength, pabyBuf + i * 8 + 4, 4); - if (!bBigEndian) - SwapWord(4, &nLength); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nLength); +#endif if (nOffset > STATIC_CAST(unsigned int, INT_MAX)) { char str[128]; @@ -657,7 +584,7 @@ SHPHandle SHPAPI_CALL SHPOpenLL(const char *pszLayer, const char *pszAccess, /************************************************************************/ SHPHandle SHPAPI_CALL SHPOpenLLEx(const char *pszLayer, const char *pszAccess, - SAHooks *psHooks, int bRestoreSHX) + const SAHooks *psHooks, int bRestoreSHX) { if (!bRestoreSHX) return SHPOpenLL(pszLayer, pszAccess, psHooks); @@ -678,7 +605,7 @@ SHPHandle SHPAPI_CALL SHPOpenLLEx(const char *pszLayer, const char *pszAccess, /************************************************************************/ int SHPAPI_CALL SHPRestoreSHX(const char *pszLayer, const char *pszAccess, - SAHooks *psHooks) + const SAHooks *psHooks) { /* -------------------------------------------------------------------- */ /* Ensure the access string is one of the legal ones. We */ @@ -693,19 +620,6 @@ int SHPAPI_CALL SHPRestoreSHX(const char *pszLayer, const char *pszAccess, pszAccess = "rb"; } -/* -------------------------------------------------------------------- */ -/* Establish the byte order on this machine. */ -/* -------------------------------------------------------------------- */ -#if !defined(bBigEndian) - { - int i = 1; - if (*((uchar *)&i) == 1) - bBigEndian = false; - else - bBigEndian = true; - } -#endif - /* -------------------------------------------------------------------- */ /* Open the .shp file. Note that files pulled from */ /* a PC to Unix with upper case filenames won't work! */ @@ -714,10 +628,10 @@ int SHPAPI_CALL SHPRestoreSHX(const char *pszLayer, const char *pszAccess, char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5)); memcpy(pszFullname, pszLayer, nLenWithoutExtension); memcpy(pszFullname + nLenWithoutExtension, ".shp", 5); - SAFile fpSHP = psHooks->FOpen(pszFullname, pszAccess); + SAFile fpSHP = psHooks->FOpen(pszFullname, pszAccess, psHooks->pvUserData); if (fpSHP == SHPLIB_NULLPTR) { memcpy(pszFullname + nLenWithoutExtension, ".SHP", 5); - fpSHP = psHooks->FOpen(pszFullname, pszAccess); + fpSHP = psHooks->FOpen(pszFullname, pszAccess, psHooks->pvUserData); } if (fpSHP == SHPLIB_NULLPTR) { @@ -738,7 +652,7 @@ int SHPAPI_CALL SHPRestoreSHX(const char *pszLayer, const char *pszAccess, /* -------------------------------------------------------------------- */ /* Read the file size from the SHP file. */ /* -------------------------------------------------------------------- */ - uchar *pabyBuf = STATIC_CAST(uchar *, malloc(100)); + unsigned char *pabyBuf = STATIC_CAST(unsigned char *, malloc(100)); if (psHooks->FRead(pabyBuf, 100, 1, fpSHP) != 1) { psHooks->Error(".shp file is unreadable, or corrupt."); psHooks->FClose(fpSHP); @@ -759,7 +673,8 @@ int SHPAPI_CALL SHPRestoreSHX(const char *pszLayer, const char *pszAccess, memcpy(pszFullname + nLenWithoutExtension, ".shx", 5); const char pszSHXAccess[] = "w+b"; - SAFile fpSHX = psHooks->FOpen(pszFullname, pszSHXAccess); + SAFile fpSHX = + psHooks->FOpen(pszFullname, pszSHXAccess, psHooks->pvUserData); if (fpSHX == SHPLIB_NULLPTR) { size_t nMessageLen = strlen(pszFullname) * 2 + 256; char *pszMessage = STATIC_CAST(char *, malloc(nMessageLen)); @@ -789,25 +704,70 @@ int SHPAPI_CALL SHPRestoreSHX(const char *pszLayer, const char *pszAccess, // unsigned int nCurrentRecordOffset = 0; unsigned int nCurrentSHPOffset = 100; unsigned int nRealSHXContentSize = 100; - unsigned int niRecord = 0; - unsigned int nRecordLength = 0; + int nRetCode = TRUE; unsigned int nRecordOffset = 50; - char abyReadRecord[8]; while (nCurrentSHPOffset < nSHPFilesize) { + unsigned int niRecord = 0; + unsigned int nRecordLength = 0; + int nSHPType; + if (psHooks->FRead(&niRecord, 4, 1, fpSHP) == 1 && - psHooks->FRead(&nRecordLength, 4, 1, fpSHP) == 1) { - if (!bBigEndian) - SwapWord(4, &nRecordOffset); - memcpy(abyReadRecord, &nRecordOffset, 4); + psHooks->FRead(&nRecordLength, 4, 1, fpSHP) == 1 && + psHooks->FRead(&nSHPType, 4, 1, fpSHP) == 1) { + char abyReadRecord[8]; + unsigned int nRecordOffsetBE = nRecordOffset; + +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nRecordOffsetBE); +#endif + memcpy(abyReadRecord, &nRecordOffsetBE, 4); memcpy(abyReadRecord + 4, &nRecordLength, 4); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nRecordLength); +#endif +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nSHPType); +#endif + + // Sanity check on record length + if (nRecordLength < 1 || + nRecordLength > (nSHPFilesize - (nCurrentSHPOffset + 8)) / 2) { + char szErrorMsg[200]; + snprintf(szErrorMsg, sizeof(szErrorMsg), + "Error parsing .shp to restore .shx. " + "Invalid record length = %u at record starting at " + "offset %u", + nRecordLength, nCurrentSHPOffset); + psHooks->Error(szErrorMsg); + + nRetCode = FALSE; + break; + } + + // Sanity check on record type + if (nSHPType != SHPT_NULL && nSHPType != SHPT_POINT && + nSHPType != SHPT_ARC && nSHPType != SHPT_POLYGON && + nSHPType != SHPT_MULTIPOINT && nSHPType != SHPT_POINTZ && + nSHPType != SHPT_ARCZ && nSHPType != SHPT_POLYGONZ && + nSHPType != SHPT_MULTIPOINTZ && nSHPType != SHPT_POINTM && + nSHPType != SHPT_ARCM && nSHPType != SHPT_POLYGONM && + nSHPType != SHPT_MULTIPOINTM && nSHPType != SHPT_MULTIPATCH) { + char szErrorMsg[200]; + snprintf(szErrorMsg, sizeof(szErrorMsg), + "Error parsing .shp to restore .shx. " + "Invalid shape type = %d at record starting at " + "offset %u", + nSHPType, nCurrentSHPOffset); + psHooks->Error(szErrorMsg); + + nRetCode = FALSE; + break; + } + psHooks->FWrite(abyReadRecord, 8, 1, fpSHX); - if (!bBigEndian) - SwapWord(4, &nRecordOffset); - if (!bBigEndian) - SwapWord(4, &nRecordLength); nRecordOffset += nRecordLength + 4; // nCurrentRecordOffset += 8; nCurrentSHPOffset += 8 + nRecordLength * 2; @@ -816,21 +776,30 @@ int SHPAPI_CALL SHPRestoreSHX(const char *pszLayer, const char *pszAccess, nRealSHXContentSize += 8; } else { - psHooks->Error("Error parsing .shp to restore .shx"); - - psHooks->FClose(fpSHX); - psHooks->FClose(fpSHP); - - free(pabySHXHeader); - free(pszFullname); + char szErrorMsg[200]; + snprintf(szErrorMsg, sizeof(szErrorMsg), + "Error parsing .shp to restore .shx. " + "Cannot read first bytes of record starting at " + "offset %u", + nCurrentSHPOffset); + psHooks->Error(szErrorMsg); - return (0); + nRetCode = FALSE; + break; } } + if (nRetCode && nCurrentSHPOffset != nSHPFilesize) { + psHooks->Error("Error parsing .shp to restore .shx. " + "Not expected number of bytes"); + + nRetCode = FALSE; + } nRealSHXContentSize /= 2; // Bytes counted -> WORDs - if (!bBigEndian) - SwapWord(4, &nRealSHXContentSize); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nRealSHXContentSize); +#endif + psHooks->FSeek(fpSHX, 24, 0); psHooks->FWrite(&nRealSHXContentSize, 4, 1, fpSHX); @@ -840,13 +809,13 @@ int SHPAPI_CALL SHPRestoreSHX(const char *pszLayer, const char *pszAccess, free(pszFullname); free(pabySHXHeader); - return (1); + return nRetCode; } /************************************************************************/ /* SHPClose() */ -/* */ -/* Close the .shp and .shx files. */ +/* */ +/* Close the .shp and .shx files. */ /************************************************************************/ void SHPAPI_CALL SHPClose(SHPHandle psSHP) @@ -855,7 +824,7 @@ void SHPAPI_CALL SHPClose(SHPHandle psSHP) return; /* -------------------------------------------------------------------- */ - /* Update the header if we have modified anything. */ + /* Update the header if we have modified anything. */ /* -------------------------------------------------------------------- */ if (psSHP->bUpdated) SHPWriteHeader(psSHP); @@ -914,8 +883,9 @@ void SHPAPI_CALL SHPSetFastModeReadObject(SHPHandle hSHP, int bFastMode) /* Fetch general information about the shape file. */ /************************************************************************/ -void SHPAPI_CALL SHPGetInfo(SHPHandle psSHP, int *pnEntities, int *pnShapeType, - double *padfMinBound, double *padfMaxBound) +void SHPAPI_CALL SHPGetInfo(const SHPHandle psSHP, int *pnEntities, + int *pnShapeType, double *padfMinBound, + double *padfMaxBound) { if (psSHP == SHPLIB_NULLPTR) return; @@ -958,21 +928,8 @@ SHPHandle SHPAPI_CALL SHPCreate(const char *pszLayer, int nShapeType) /************************************************************************/ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, - SAHooks *psHooks) + const SAHooks *psHooks) { -/* -------------------------------------------------------------------- */ -/* Establish the byte order on this system. */ -/* -------------------------------------------------------------------- */ -#if !defined(bBigEndian) - { - int i = 1; - if (*((uchar *)&i) == 1) - bBigEndian = false; - else - bBigEndian = true; - } -#endif - /* -------------------------------------------------------------------- */ /* Open the two files so we can write their headers. */ /* -------------------------------------------------------------------- */ @@ -980,7 +937,7 @@ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5)); memcpy(pszFullname, pszLayer, nLenWithoutExtension); memcpy(pszFullname + nLenWithoutExtension, ".shp", 5); - SAFile fpSHP = psHooks->FOpen(pszFullname, "wb"); + SAFile fpSHP = psHooks->FOpen(pszFullname, "w+b", psHooks->pvUserData); if (fpSHP == SHPLIB_NULLPTR) { char szErrorMsg[200]; snprintf(szErrorMsg, sizeof(szErrorMsg), "Failed to create file %s: %s", @@ -988,11 +945,11 @@ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, psHooks->Error(szErrorMsg); free(pszFullname); - return NULL; + return SHPLIB_NULLPTR; } memcpy(pszFullname + nLenWithoutExtension, ".shx", 5); - SAFile fpSHX = psHooks->FOpen(pszFullname, "wb"); + SAFile fpSHX = psHooks->FOpen(pszFullname, "w+b", psHooks->pvUserData); if (fpSHX == SHPLIB_NULLPTR) { char szErrorMsg[200]; snprintf(szErrorMsg, sizeof(szErrorMsg), "Failed to create file %s: %s", @@ -1001,7 +958,7 @@ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, free(pszFullname); psHooks->FClose(fpSHP); - return NULL; + return SHPLIB_NULLPTR; } free(pszFullname); @@ -1010,26 +967,29 @@ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, /* -------------------------------------------------------------------- */ /* Prepare header block for .shp file. */ /* -------------------------------------------------------------------- */ - uchar abyHeader[100]; + unsigned char abyHeader[100]; memset(abyHeader, 0, sizeof(abyHeader)); abyHeader[2] = 0x27; /* magic cookie */ abyHeader[3] = 0x0a; - int32 i32 = 50; /* file size */ + uint32_t i32 = 50; /* file size */ ByteCopy(&i32, abyHeader + 24, 4); - if (!bBigEndian) - SwapWord(4, abyHeader + 24); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(abyHeader + 24); +#endif i32 = 1000; /* version */ ByteCopy(&i32, abyHeader + 28, 4); - if (bBigEndian) - SwapWord(4, abyHeader + 28); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(abyHeader + 28); +#endif i32 = nShapeType; /* shape type */ ByteCopy(&i32, abyHeader + 32, 4); - if (bBigEndian) - SwapWord(4, abyHeader + 32); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(abyHeader + 32); +#endif double dValue = 0.0; /* set bounds */ ByteCopy(&dValue, abyHeader + 36, 8); @@ -1051,7 +1011,7 @@ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, free(pszFullname); psHooks->FClose(fpSHP); psHooks->FClose(fpSHX); - return NULL; + return SHPLIB_NULLPTR; } /* -------------------------------------------------------------------- */ @@ -1059,8 +1019,9 @@ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, /* -------------------------------------------------------------------- */ i32 = 50; /* file size */ ByteCopy(&i32, abyHeader + 24, 4); - if (!bBigEndian) - SwapWord(4, abyHeader + 24); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(abyHeader + 24); +#endif if (psHooks->FWrite(abyHeader, 100, 1, fpSHX) != 1) { char szErrorMsg[200]; @@ -1073,16 +1034,37 @@ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, free(pszFullname); psHooks->FClose(fpSHP); psHooks->FClose(fpSHX); - return NULL; + return SHPLIB_NULLPTR; } - /* -------------------------------------------------------------------- */ - /* Close the files, and then open them as regular existing files. */ - /* -------------------------------------------------------------------- */ - psHooks->FClose(fpSHP); - psHooks->FClose(fpSHX); + SHPHandle psSHP = STATIC_CAST(SHPHandle, calloc(1, sizeof(SHPInfo))); - return (SHPOpenLL(pszLayer, "r+b", psHooks)); + psSHP->bUpdated = FALSE; + memcpy(&(psSHP->sHooks), psHooks, sizeof(SAHooks)); + + psSHP->fpSHP = fpSHP; + psSHP->fpSHX = fpSHX; + psSHP->nShapeType = nShapeType; + psSHP->nFileSize = 100; + psSHP->panRecOffset = + STATIC_CAST(unsigned int *, malloc(sizeof(unsigned int))); + psSHP->panRecSize = + STATIC_CAST(unsigned int *, malloc(sizeof(unsigned int))); + + if (psSHP->panRecOffset == SHPLIB_NULLPTR || + psSHP->panRecSize == SHPLIB_NULLPTR) { + psSHP->sHooks.Error("Not enough memory to allocate requested memory"); + psSHP->sHooks.FClose(psSHP->fpSHP); + psSHP->sHooks.FClose(psSHP->fpSHX); + if (psSHP->panRecOffset) + free(psSHP->panRecOffset); + if (psSHP->panRecSize) + free(psSHP->panRecSize); + free(psSHP); + return SHPLIB_NULLPTR; + } + + return psSHP; } /************************************************************************/ @@ -1092,19 +1074,19 @@ SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszLayer, int nShapeType, /* indicated location in the record. */ /************************************************************************/ -static void _SHPSetBounds(uchar *pabyRec, SHPObject *psShape) +static void _SHPSetBounds(unsigned char *pabyRec, const SHPObject *psShape) { ByteCopy(&(psShape->dfXMin), pabyRec + 0, 8); ByteCopy(&(psShape->dfYMin), pabyRec + 8, 8); ByteCopy(&(psShape->dfXMax), pabyRec + 16, 8); ByteCopy(&(psShape->dfYMax), pabyRec + 24, 8); - if (bBigEndian) { - SwapWord(8, pabyRec + 0); - SwapWord(8, pabyRec + 8); - SwapWord(8, pabyRec + 16); - SwapWord(8, pabyRec + 24); - } +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + 0); + SHP_SWAP64(pabyRec + 8); + SHP_SWAP64(pabyRec + 16); + SHP_SWAP64(pabyRec + 24); +#endif } /************************************************************************/ @@ -1159,7 +1141,7 @@ SHPObject SHPAPI_CALL1(*) psObject->bMeasureIsUsed = FALSE; /* -------------------------------------------------------------------- */ - /* Establish whether this shape type has M, and Z values. */ + /* Establish whether this shape type has M, and Z values. */ /* -------------------------------------------------------------------- */ bool bHasM; bool bHasZ; @@ -1191,7 +1173,7 @@ SHPObject SHPAPI_CALL1(*) psObject->nParts = MAX(1, nParts); psObject->panPartStart = - STATIC_CAST(int *, calloc(sizeof(int), psObject->nParts)); + STATIC_CAST(int *, calloc(psObject->nParts, sizeof(int))); psObject->panPartType = STATIC_CAST(int *, malloc(sizeof(int) * psObject->nParts)); @@ -1208,8 +1190,7 @@ SHPObject SHPAPI_CALL1(*) psObject->panPartType[i] = SHPP_RING; } - if (psObject->panPartStart[0] != 0) - psObject->panPartStart[0] = 0; + psObject->panPartStart[0] = 0; } /* -------------------------------------------------------------------- */ @@ -1219,16 +1200,16 @@ SHPObject SHPAPI_CALL1(*) const size_t nSize = sizeof(double) * nVertices; psObject->padfX = STATIC_CAST(double *, padfX ? malloc(nSize) - : calloc(sizeof(double), nVertices)); + : calloc(nVertices, sizeof(double))); psObject->padfY = STATIC_CAST(double *, padfY ? malloc(nSize) - : calloc(sizeof(double), nVertices)); + : calloc(nVertices, sizeof(double))); psObject->padfZ = STATIC_CAST( double *, - padfZ &&bHasZ ? malloc(nSize) : calloc(sizeof(double), nVertices)); + padfZ &&bHasZ ? malloc(nSize) : calloc(nVertices, sizeof(double))); psObject->padfM = STATIC_CAST( double *, - padfM &&bHasM ? malloc(nSize) : calloc(sizeof(double), nVertices)); + padfM &&bHasM ? malloc(nSize) : calloc(nVertices, sizeof(double))); if (padfX != SHPLIB_NULLPTR) memcpy(psObject->padfX, padfX, nSize); if (padfY != SHPLIB_NULLPTR) @@ -1273,7 +1254,7 @@ SHPObject SHPAPI_CALL1(*) /************************************************************************/ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, - SHPObject *psObject) + const SHPObject *psObject) { psSHP->bUpdated = TRUE; @@ -1298,22 +1279,32 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, /* Add the new entity to the in memory index. */ /* -------------------------------------------------------------------- */ if (nShapeId == -1 && psSHP->nRecords + 1 > psSHP->nMaxRecords) { + /* This cannot overflow given that we check that the file size does + * not grow over 4 GB, and the minimum size of a record is 12 bytes, + * hence the maximm value for nMaxRecords is 357,913,941 + */ int nNewMaxRecords = psSHP->nMaxRecords + psSHP->nMaxRecords / 3 + 100; unsigned int *panRecOffsetNew; unsigned int *panRecSizeNew; panRecOffsetNew = STATIC_CAST( - unsigned int *, SfRealloc(psSHP->panRecOffset, - sizeof(unsigned int) * nNewMaxRecords)); - if (panRecOffsetNew == SHPLIB_NULLPTR) + unsigned int *, realloc(psSHP->panRecOffset, + sizeof(unsigned int) * nNewMaxRecords)); + if (panRecOffsetNew == SHPLIB_NULLPTR) { + psSHP->sHooks.Error("Failed to write shape object. " + "Memory allocation error."); return -1; + } psSHP->panRecOffset = panRecOffsetNew; panRecSizeNew = STATIC_CAST( - unsigned int *, SfRealloc(psSHP->panRecSize, - sizeof(unsigned int) * nNewMaxRecords)); - if (panRecSizeNew == SHPLIB_NULLPTR) + unsigned int *, + realloc(psSHP->panRecSize, sizeof(unsigned int) * nNewMaxRecords)); + if (panRecSizeNew == SHPLIB_NULLPTR) { + psSHP->sHooks.Error("Failed to write shape object. " + "Memory allocation error."); return -1; + } psSHP->panRecSize = panRecSizeNew; psSHP->nMaxRecords = nNewMaxRecords; @@ -1322,14 +1313,28 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, /* -------------------------------------------------------------------- */ /* Initialize record. */ /* -------------------------------------------------------------------- */ - uchar *pabyRec = - STATIC_CAST(uchar *, malloc(psObject->nVertices * 4 * sizeof(double) + - psObject->nParts * 8 + 128)); - if (pabyRec == SHPLIB_NULLPTR) + + /* The following computation cannot overflow on 32-bit platforms given that + * the user had to allocate arrays of at least that size. */ + size_t nRecMaxSize = + psObject->nVertices * 4 * sizeof(double) + psObject->nParts * 8; + /* But the following test could trigger on 64-bit platforms on huge + * geometries. */ + const unsigned nExtraSpaceForGeomHeader = 128; + if (nRecMaxSize > UINT_MAX - nExtraSpaceForGeomHeader) { + psSHP->sHooks.Error("Failed to write shape object. Too big geometry."); return -1; + } + nRecMaxSize += nExtraSpaceForGeomHeader; + unsigned char *pabyRec = STATIC_CAST(unsigned char *, malloc(nRecMaxSize)); + if (pabyRec == SHPLIB_NULLPTR) { + psSHP->sHooks.Error("Failed to write shape object. " + "Memory allocation error."); + return -1; + } /* -------------------------------------------------------------------- */ - /* Extract vertices for a Polygon or Arc. */ + /* Extract vertices for a Polygon or Arc. */ /* -------------------------------------------------------------------- */ unsigned int nRecordSize = 0; const bool bFirstFeature = psSHP->nRecords == 0; @@ -1339,15 +1344,15 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, psObject->nSHPType == SHPT_POLYGONM || psObject->nSHPType == SHPT_ARC || psObject->nSHPType == SHPT_ARCZ || psObject->nSHPType == SHPT_ARCM || psObject->nSHPType == SHPT_MULTIPATCH) { - int32 nPoints = psObject->nVertices; - int32 nParts = psObject->nParts; + uint32_t nPoints = psObject->nVertices; + uint32_t nParts = psObject->nParts; _SHPSetBounds(pabyRec + 12, psObject); - if (bBigEndian) - SwapWord(4, &nPoints); - if (bBigEndian) - SwapWord(4, &nParts); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nPoints); + SHP_SWAP32(&nParts); +#endif ByteCopy(&nPoints, pabyRec + 40 + 8, 4); ByteCopy(&nParts, pabyRec + 36 + 8, 4); @@ -1360,8 +1365,9 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, ByteCopy(psObject->panPartStart, pabyRec + 44 + 8, 4 * psObject->nParts); for (int i = 0; i < psObject->nParts; i++) { - if (bBigEndian) - SwapWord(4, pabyRec + 44 + 8 + 4 * i); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(pabyRec + 44 + 8 + 4 * i); +#endif nRecordSize += 4; } @@ -1372,8 +1378,9 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, memcpy(pabyRec + nRecordSize, psObject->panPartType, 4 * psObject->nParts); for (int i = 0; i < psObject->nParts; i++) { - if (bBigEndian) - SwapWord(4, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(pabyRec + nRecordSize); +#endif nRecordSize += 4; } } @@ -1385,11 +1392,10 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, ByteCopy(psObject->padfX + i, pabyRec + nRecordSize, 8); ByteCopy(psObject->padfY + i, pabyRec + nRecordSize + 8, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); - - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize + 8); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); + SHP_SWAP64(pabyRec + nRecordSize + 8); +#endif nRecordSize += 2 * 8; } @@ -1401,19 +1407,22 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, psObject->nSHPType == SHPT_ARCZ || psObject->nSHPType == SHPT_MULTIPATCH) { ByteCopy(&(psObject->dfZMin), pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; ByteCopy(&(psObject->dfZMax), pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; for (int i = 0; i < psObject->nVertices; i++) { ByteCopy(psObject->padfZ + i, pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; } } @@ -1430,65 +1439,72 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, || psObject->nSHPType == SHPT_POLYGONZ || psObject->nSHPType == SHPT_ARCZ)) { ByteCopy(&(psObject->dfMMin), pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; ByteCopy(&(psObject->dfMMax), pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; for (int i = 0; i < psObject->nVertices; i++) { ByteCopy(psObject->padfM + i, pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; } } } /* -------------------------------------------------------------------- */ - /* Extract vertices for a MultiPoint. */ + /* Extract vertices for a MultiPoint. */ /* -------------------------------------------------------------------- */ else if (psObject->nSHPType == SHPT_MULTIPOINT || psObject->nSHPType == SHPT_MULTIPOINTZ || psObject->nSHPType == SHPT_MULTIPOINTM) { - int32 nPoints = psObject->nVertices; + uint32_t nPoints = psObject->nVertices; _SHPSetBounds(pabyRec + 12, psObject); - if (bBigEndian) - SwapWord(4, &nPoints); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nPoints); +#endif ByteCopy(&nPoints, pabyRec + 44, 4); for (int i = 0; i < psObject->nVertices; i++) { ByteCopy(psObject->padfX + i, pabyRec + 48 + i * 16, 8); ByteCopy(psObject->padfY + i, pabyRec + 48 + i * 16 + 8, 8); - if (bBigEndian) - SwapWord(8, pabyRec + 48 + i * 16); - if (bBigEndian) - SwapWord(8, pabyRec + 48 + i * 16 + 8); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + 48 + i * 16); + SHP_SWAP64(pabyRec + 48 + i * 16 + 8); +#endif } nRecordSize = 48 + 16 * psObject->nVertices; if (psObject->nSHPType == SHPT_MULTIPOINTZ) { ByteCopy(&(psObject->dfZMin), pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; ByteCopy(&(psObject->dfZMax), pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; for (int i = 0; i < psObject->nVertices; i++) { ByteCopy(psObject->padfZ + i, pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; } } @@ -1497,26 +1513,29 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, (psObject->nSHPType == SHPT_MULTIPOINTZ || psObject->nSHPType == SHPT_MULTIPOINTM)) { ByteCopy(&(psObject->dfMMin), pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; ByteCopy(&(psObject->dfMMax), pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; for (int i = 0; i < psObject->nVertices; i++) { ByteCopy(psObject->padfM + i, pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; } } } /* -------------------------------------------------------------------- */ - /* Write point. */ + /* Write point. */ /* -------------------------------------------------------------------- */ else if (psObject->nSHPType == SHPT_POINT || psObject->nSHPType == SHPT_POINTZ || @@ -1524,25 +1543,27 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, ByteCopy(psObject->padfX, pabyRec + 12, 8); ByteCopy(psObject->padfY, pabyRec + 20, 8); - if (bBigEndian) - SwapWord(8, pabyRec + 12); - if (bBigEndian) - SwapWord(8, pabyRec + 20); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + 12); + SHP_SWAP64(pabyRec + 20); +#endif nRecordSize = 28; if (psObject->nSHPType == SHPT_POINTZ) { ByteCopy(psObject->padfZ, pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; } if (psObject->bMeasureIsUsed && (psObject->nSHPType == SHPT_POINTZ || psObject->nSHPType == SHPT_POINTM)) { ByteCopy(psObject->padfM, pabyRec + nRecordSize, 8); - if (bBigEndian) - SwapWord(8, pabyRec + nRecordSize); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP64(pabyRec + nRecordSize); +#endif nRecordSize += 8; } } @@ -1598,20 +1619,23 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, /* -------------------------------------------------------------------- */ /* Set the shape type, record number, and record size. */ /* -------------------------------------------------------------------- */ - int32 i32 = + uint32_t i32 = (nShapeId < 0) ? psSHP->nRecords + 1 : nShapeId + 1; /* record # */ - if (!bBigEndian) - SwapWord(4, &i32); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&i32); +#endif ByteCopy(&i32, pabyRec, 4); i32 = (nRecordSize - 8) / 2; /* record size */ - if (!bBigEndian) - SwapWord(4, &i32); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&i32); +#endif ByteCopy(&i32, pabyRec + 4, 4); i32 = psObject->nSHPType; /* shape type */ - if (bBigEndian) - SwapWord(4, &i32); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&i32); +#endif ByteCopy(&i32, pabyRec + 8, 4); /* -------------------------------------------------------------------- */ @@ -1666,7 +1690,7 @@ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, psSHP->panRecSize[nShapeId] = nRecordSize - 8; /* -------------------------------------------------------------------- */ - /* Expand file wide bounds based on this shape. */ + /* Expand file wide bounds based on this shape. */ /* -------------------------------------------------------------------- */ if (bFirstFeature) { if (psObject->nSHPType == SHPT_NULL || psObject->nVertices == 0) { @@ -1754,11 +1778,11 @@ static unsigned char *SHPReallocObjectBufIfNecessary(SHPHandle psSHP, /************************************************************************/ /* SHPReadObject() */ /* */ -/* Read the vertices, parts, and other non-attribute information */ -/* for one shape. */ +/* Read the vertices, parts, and other non-attribute information */ +/* for one shape. */ /************************************************************************/ -SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) +SHPObject SHPAPI_CALL1(*) SHPReadObject(const SHPHandle psSHP, int hEntity) { /* -------------------------------------------------------------------- */ /* Validate the record/entity number. */ @@ -1786,10 +1810,10 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) psSHP->sHooks.Error(str); return SHPLIB_NULLPTR; } - if (!bBigEndian) - SwapWord(4, &nOffset); - if (!bBigEndian) - SwapWord(4, &nLength); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nOffset); + SHP_SWAP32(&nLength); +#endif if (nOffset > STATIC_CAST(unsigned int, INT_MAX)) { char str[128]; @@ -1859,8 +1883,8 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) } } - uchar *pabyRecNew = - STATIC_CAST(uchar *, SfRealloc(psSHP->pabyRec, nNewBufSize)); + unsigned char *pabyRecNew = + STATIC_CAST(unsigned char *, realloc(psSHP->pabyRec, nNewBufSize)); if (pabyRecNew == SHPLIB_NULLPTR) { char szErrorMsg[160]; snprintf(szErrorMsg, sizeof(szErrorMsg), @@ -1919,8 +1943,9 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* Do a sanity check */ int nSHPContentLength; memcpy(&nSHPContentLength, psSHP->pabyRec + 4, 4); - if (!bBigEndian) - SwapWord(4, &(nSHPContentLength)); +#if !defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&(nSHPContentLength)); +#endif if (nSHPContentLength < 0 || nSHPContentLength > INT_MAX / 2 - 4 || 2 * nSHPContentLength + 8 != nBytesRead) { char str[128]; @@ -1962,11 +1987,12 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) int nSHPType; memcpy(&nSHPType, psSHP->pabyRec + 8, 4); - if (bBigEndian) - SwapWord(4, &(nSHPType)); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&(nSHPType)); +#endif /* -------------------------------------------------------------------- */ - /* Allocate and minimally initialize the object. */ + /* Allocate and minimally initialize the object. */ /* -------------------------------------------------------------------- */ SHPObject *psShape; if (psSHP->bFastModeReadObject) { @@ -1988,7 +2014,7 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) psShape->bFastModeReadObject = psSHP->bFastModeReadObject; /* ==================================================================== */ - /* Extract vertices for a Polygon or Arc. */ + /* Extract vertices for a Polygon or Arc. */ /* ==================================================================== */ if (psShape->nSHPType == SHPT_POLYGON || psShape->nSHPType == SHPT_ARC || psShape->nSHPType == SHPT_POLYGONZ || @@ -2007,22 +2033,20 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) } /* -------------------------------------------------------------------- */ - /* Get the X/Y bounds. */ + /* Get the X/Y bounds. */ /* -------------------------------------------------------------------- */ - memcpy(&(psShape->dfXMin), psSHP->pabyRec + 8 + 4, 8); - memcpy(&(psShape->dfYMin), psSHP->pabyRec + 8 + 12, 8); - memcpy(&(psShape->dfXMax), psSHP->pabyRec + 8 + 20, 8); - memcpy(&(psShape->dfYMax), psSHP->pabyRec + 8 + 28, 8); - - if (bBigEndian) - SwapWord(8, &(psShape->dfXMin)); - if (bBigEndian) - SwapWord(8, &(psShape->dfYMin)); - if (bBigEndian) - SwapWord(8, &(psShape->dfXMax)); - if (bBigEndian) - SwapWord(8, &(psShape->dfYMax)); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(&psShape->dfXMin, psSHP->pabyRec + 8 + 4); + SHP_SWAPDOUBLE_CPY(&psShape->dfYMin, psSHP->pabyRec + 8 + 12); + SHP_SWAPDOUBLE_CPY(&psShape->dfXMax, psSHP->pabyRec + 8 + 20); + SHP_SWAPDOUBLE_CPY(&psShape->dfYMax, psSHP->pabyRec + 8 + 28); +#else + memcpy(&psShape->dfXMin, psSHP->pabyRec + 8 + 4, 8); + memcpy(&psShape->dfYMin, psSHP->pabyRec + 8 + 12, 8); + memcpy(&psShape->dfXMax, psSHP->pabyRec + 8 + 20, 8); + memcpy(&psShape->dfYMax, psSHP->pabyRec + 8 + 28, 8); +#endif /* -------------------------------------------------------------------- */ @@ -2030,15 +2054,15 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* to proper size. */ /* -------------------------------------------------------------------- */ - int32 nPoints; + uint32_t nPoints; memcpy(&nPoints, psSHP->pabyRec + 40 + 8, 4); - int32 nParts; + uint32_t nParts; memcpy(&nParts, psSHP->pabyRec + 36 + 8, 4); - if (bBigEndian) - SwapWord(4, &nPoints); - if (bBigEndian) - SwapWord(4, &nParts); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nPoints); + SHP_SWAP32(&nParts); +#endif /* nPoints and nParts are unsigned */ if (/* nPoints < 0 || nParts < 0 || */ @@ -2121,7 +2145,7 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) return SHPLIB_NULLPTR; } - for (int i = 0; STATIC_CAST(int32, i) < nParts; i++) + for (int i = 0; STATIC_CAST(uint32_t, i) < nParts; i++) psShape->panPartType[i] = SHPP_RING; /* -------------------------------------------------------------------- @@ -2130,9 +2154,10 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* -------------------------------------------------------------------- */ memcpy(psShape->panPartStart, psSHP->pabyRec + 44 + 8, 4 * nParts); - for (int i = 0; STATIC_CAST(int32, i) < nParts; i++) { - if (bBigEndian) - SwapWord(4, psShape->panPartStart + i); + for (int i = 0; STATIC_CAST(uint32_t, i) < nParts; i++) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(psShape->panPartStart + i); +#endif /* We check that the offset is inside the vertex array */ if (psShape->panPartStart[i] < 0 || @@ -2174,9 +2199,10 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) */ if (psShape->nSHPType == SHPT_MULTIPATCH) { memcpy(psShape->panPartType, psSHP->pabyRec + nOffset, 4 * nParts); - for (int i = 0; STATIC_CAST(int32, i) < nParts; i++) { - if (bBigEndian) - SwapWord(4, psShape->panPartType + i); + for (int i = 0; STATIC_CAST(uint32_t, i) < nParts; i++) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(psShape->panPartType + i); +#endif } nOffset += 4 * nParts; @@ -2187,16 +2213,17 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* Copy out the vertices from the record. */ /* -------------------------------------------------------------------- */ - for (int i = 0; STATIC_CAST(int32, i) < nPoints; i++) { + for (int i = 0; STATIC_CAST(uint32_t, i) < nPoints; i++) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfX + i, + psSHP->pabyRec + nOffset + i * 16); + SHP_SWAPDOUBLE_CPY(psShape->padfY + i, + psSHP->pabyRec + nOffset + i * 16 + 8); +#else memcpy(psShape->padfX + i, psSHP->pabyRec + nOffset + i * 16, 8); - memcpy(psShape->padfY + i, psSHP->pabyRec + nOffset + i * 16 + 8, 8); - - if (bBigEndian) - SwapWord(8, psShape->padfX + i); - if (bBigEndian) - SwapWord(8, psShape->padfY + i); +#endif } nOffset += 16 * nPoints; @@ -2209,19 +2236,23 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) if (psShape->nSHPType == SHPT_POLYGONZ || psShape->nSHPType == SHPT_ARCZ || psShape->nSHPType == SHPT_MULTIPATCH) { - memcpy(&(psShape->dfZMin), psSHP->pabyRec + nOffset, 8); - memcpy(&(psShape->dfZMax), psSHP->pabyRec + nOffset + 8, 8); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(&psShape->dfZMin, psSHP->pabyRec + nOffset); + SHP_SWAPDOUBLE_CPY(&psShape->dfZMax, psSHP->pabyRec + nOffset + 8); +#else + memcpy(&psShape->dfZMin, psSHP->pabyRec + nOffset, 8); + memcpy(&psShape->dfZMax, psSHP->pabyRec + nOffset + 8, 8); - if (bBigEndian) - SwapWord(8, &(psShape->dfZMin)); - if (bBigEndian) - SwapWord(8, &(psShape->dfZMax)); +#endif - for (int i = 0; STATIC_CAST(int32, i) < nPoints; i++) { + for (int i = 0; STATIC_CAST(uint32_t, i) < nPoints; i++) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfZ + i, + psSHP->pabyRec + nOffset + 16 + i * 8); +#else memcpy(psShape->padfZ + i, psSHP->pabyRec + nOffset + 16 + i * 8, 8); - if (bBigEndian) - SwapWord(8, psShape->padfZ + i); +#endif } nOffset += 16 + 8 * nPoints; @@ -2239,19 +2270,22 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* -------------------------------------------------------------------- */ if (nEntitySize >= STATIC_CAST(int, nOffset + 16 + 8 * nPoints)) { - memcpy(&(psShape->dfMMin), psSHP->pabyRec + nOffset, 8); - memcpy(&(psShape->dfMMax), psSHP->pabyRec + nOffset + 8, 8); - - if (bBigEndian) - SwapWord(8, &(psShape->dfMMin)); - if (bBigEndian) - SwapWord(8, &(psShape->dfMMax)); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(&psShape->dfMMin, psSHP->pabyRec + nOffset); + SHP_SWAPDOUBLE_CPY(&psShape->dfMMax, psSHP->pabyRec + nOffset + 8); +#else + memcpy(&psShape->dfMMin, psSHP->pabyRec + nOffset, 8); + memcpy(&psShape->dfMMax, psSHP->pabyRec + nOffset + 8, 8); +#endif - for (int i = 0; STATIC_CAST(int32, i) < nPoints; i++) { + for (int i = 0; STATIC_CAST(uint32_t, i) < nPoints; i++) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfM + i, + psSHP->pabyRec + nOffset + 16 + i * 8); +#else memcpy(psShape->padfM + i, psSHP->pabyRec + nOffset + 16 + i * 8, 8); - if (bBigEndian) - SwapWord(8, psShape->padfM + i); +#endif } psShape->bMeasureIsUsed = TRUE; } @@ -2261,7 +2295,7 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) } /* ==================================================================== */ - /* Extract vertices for a MultiPoint. */ + /* Extract vertices for a MultiPoint. */ /* ==================================================================== */ else if (psShape->nSHPType == SHPT_MULTIPOINT || psShape->nSHPType == SHPT_MULTIPOINTM || @@ -2276,11 +2310,12 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) SHPDestroyObject(psShape); return SHPLIB_NULLPTR; } - int32 nPoints; + uint32_t nPoints; memcpy(&nPoints, psSHP->pabyRec + 44, 4); - if (bBigEndian) - SwapWord(4, &nPoints); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAP32(&nPoints); +#endif /* nPoints is unsigned */ if (/* nPoints < 0 || */ nPoints > 50 * 1000 * 1000) { @@ -2346,36 +2381,36 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) return SHPLIB_NULLPTR; } - for (int i = 0; STATIC_CAST(int32, i) < nPoints; i++) { + for (int i = 0; STATIC_CAST(uint32_t, i) < nPoints; i++) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfX + i, + psSHP->pabyRec + 48 + 16 * i); + SHP_SWAPDOUBLE_CPY(psShape->padfY + i, + psSHP->pabyRec + 48 + 16 * i + 8); +#else memcpy(psShape->padfX + i, psSHP->pabyRec + 48 + 16 * i, 8); memcpy(psShape->padfY + i, psSHP->pabyRec + 48 + 16 * i + 8, 8); - - if (bBigEndian) - SwapWord(8, psShape->padfX + i); - if (bBigEndian) - SwapWord(8, psShape->padfY + i); +#endif } int nOffset = 48 + 16 * nPoints; /* -------------------------------------------------------------------- */ - /* Get the X/Y bounds. */ + /* Get the X/Y bounds. */ /* -------------------------------------------------------------------- */ - memcpy(&(psShape->dfXMin), psSHP->pabyRec + 8 + 4, 8); - memcpy(&(psShape->dfYMin), psSHP->pabyRec + 8 + 12, 8); - memcpy(&(psShape->dfXMax), psSHP->pabyRec + 8 + 20, 8); - memcpy(&(psShape->dfYMax), psSHP->pabyRec + 8 + 28, 8); - - if (bBigEndian) - SwapWord(8, &(psShape->dfXMin)); - if (bBigEndian) - SwapWord(8, &(psShape->dfYMin)); - if (bBigEndian) - SwapWord(8, &(psShape->dfXMax)); - if (bBigEndian) - SwapWord(8, &(psShape->dfYMax)); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(&psShape->dfXMin, psSHP->pabyRec + 8 + 4); + SHP_SWAPDOUBLE_CPY(&psShape->dfYMin, psSHP->pabyRec + 8 + 12); + SHP_SWAPDOUBLE_CPY(&psShape->dfXMax, psSHP->pabyRec + 8 + 20); + SHP_SWAPDOUBLE_CPY(&psShape->dfYMax, psSHP->pabyRec + 8 + 28); +#else + memcpy(&psShape->dfXMin, psSHP->pabyRec + 8 + 4, 8); + memcpy(&psShape->dfYMin, psSHP->pabyRec + 8 + 12, 8); + memcpy(&psShape->dfXMax, psSHP->pabyRec + 8 + 20, 8); + memcpy(&psShape->dfYMax, psSHP->pabyRec + 8 + 28, 8); +#endif /* -------------------------------------------------------------------- */ @@ -2383,19 +2418,22 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* -------------------------------------------------------------------- */ if (psShape->nSHPType == SHPT_MULTIPOINTZ) { - memcpy(&(psShape->dfZMin), psSHP->pabyRec + nOffset, 8); - memcpy(&(psShape->dfZMax), psSHP->pabyRec + nOffset + 8, 8); - - if (bBigEndian) - SwapWord(8, &(psShape->dfZMin)); - if (bBigEndian) - SwapWord(8, &(psShape->dfZMax)); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(&psShape->dfZMin, psSHP->pabyRec + nOffset); + SHP_SWAPDOUBLE_CPY(&psShape->dfZMax, psSHP->pabyRec + nOffset + 8); +#else + memcpy(&psShape->dfZMin, psSHP->pabyRec + nOffset, 8); + memcpy(&psShape->dfZMax, psSHP->pabyRec + nOffset + 8, 8); +#endif - for (int i = 0; STATIC_CAST(int32, i) < nPoints; i++) { + for (int i = 0; STATIC_CAST(uint32_t, i) < nPoints; i++) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfZ + i, + psSHP->pabyRec + nOffset + 16 + i * 8); +#else memcpy(psShape->padfZ + i, psSHP->pabyRec + nOffset + 16 + i * 8, 8); - if (bBigEndian) - SwapWord(8, psShape->padfZ + i); +#endif } nOffset += 16 + 8 * nPoints; @@ -2412,19 +2450,22 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* -------------------------------------------------------------------- */ if (nEntitySize >= STATIC_CAST(int, nOffset + 16 + 8 * nPoints)) { - memcpy(&(psShape->dfMMin), psSHP->pabyRec + nOffset, 8); - memcpy(&(psShape->dfMMax), psSHP->pabyRec + nOffset + 8, 8); - - if (bBigEndian) - SwapWord(8, &(psShape->dfMMin)); - if (bBigEndian) - SwapWord(8, &(psShape->dfMMax)); +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(&psShape->dfMMin, psSHP->pabyRec + nOffset); + SHP_SWAPDOUBLE_CPY(&psShape->dfMMax, psSHP->pabyRec + nOffset + 8); +#else + memcpy(&psShape->dfMMin, psSHP->pabyRec + nOffset, 8); + memcpy(&psShape->dfMMax, psSHP->pabyRec + nOffset + 8, 8); +#endif - for (int i = 0; STATIC_CAST(int32, i) < nPoints; i++) { + for (int i = 0; STATIC_CAST(uint32_t, i) < nPoints; i++) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfM + i, + psSHP->pabyRec + nOffset + 16 + i * 8); +#else memcpy(psShape->padfM + i, psSHP->pabyRec + nOffset + 16 + i * 8, 8); - if (bBigEndian) - SwapWord(8, psShape->padfM + i); +#endif } psShape->bMeasureIsUsed = TRUE; } @@ -2465,13 +2506,13 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) SHPDestroyObject(psShape); return SHPLIB_NULLPTR; } +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfX, psSHP->pabyRec + 12); + SHP_SWAPDOUBLE_CPY(psShape->padfY, psSHP->pabyRec + 20); +#else memcpy(psShape->padfX, psSHP->pabyRec + 12, 8); memcpy(psShape->padfY, psSHP->pabyRec + 20, 8); - - if (bBigEndian) - SwapWord(8, psShape->padfX); - if (bBigEndian) - SwapWord(8, psShape->padfY); +#endif int nOffset = 20 + 8; @@ -2481,10 +2522,11 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* -------------------------------------------------------------------- */ if (psShape->nSHPType == SHPT_POINTZ) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfZ, psSHP->pabyRec + nOffset); +#else memcpy(psShape->padfZ, psSHP->pabyRec + nOffset, 8); - - if (bBigEndian) - SwapWord(8, psShape->padfZ); +#endif nOffset += 8; } @@ -2498,10 +2540,11 @@ SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) /* -------------------------------------------------------------------- */ if (nEntitySize >= nOffset + 8) { +#if defined(SHP_BIG_ENDIAN) + SHP_SWAPDOUBLE_CPY(psShape->padfM, psSHP->pabyRec + nOffset); +#else memcpy(psShape->padfM, psSHP->pabyRec + nOffset, 8); - - if (bBigEndian) - SwapWord(8, psShape->padfM); +#endif psShape->bMeasureIsUsed = TRUE; } @@ -2739,8 +2782,9 @@ static int SHPRewindIsInnerRing(const SHPObject *psObject, int iOpRing, /* specification. */ /************************************************************************/ -int SHPAPI_CALL SHPRewindObject(CPL_UNUSED SHPHandle hSHP, SHPObject *psObject) +int SHPAPI_CALL SHPRewindObject(const SHPHandle hSHP, SHPObject *psObject) { + (void)hSHP; /* -------------------------------------------------------------------- */ /* Do nothing if this is not a polygon object. */ /* -------------------------------------------------------------------- */ diff --git a/lib/gis/testsuite/test_parser_json.py b/lib/gis/testsuite/test_parser_json.py index 918fb453d04..74ff9673c7f 100644 --- a/lib/gis/testsuite/test_parser_json.py +++ b/lib/gis/testsuite/test_parser_json.py @@ -10,11 +10,14 @@ import subprocess from grass.gunittest.case import TestCase +from grass.gunittest.utils import xfail_windows from grass.script import decode import json class TestParserJson(TestCase): + + @xfail_windows def test_r_slope_aspect_json(self): args = [ "r.slope.aspect", @@ -58,6 +61,7 @@ def test_r_slope_aspect_json(self): self.assertEqual(json_code["inputs"], inputs) self.assertEqual(json_code["outputs"], outputs) + @xfail_windows def test_v_out_ascii(self): args = [ "v.out.ascii", @@ -91,6 +95,7 @@ def test_v_out_ascii(self): self.assertEqual(json_code["inputs"], inputs) self.assertEqual(json_code["outputs"], outputs) + @xfail_windows def test_v_info(self): args = ["v.info", "map=hospitals@PERMANENT", "-c", "--json"] diff --git a/lib/imagery/testsuite/test_imagery_sigfile.py b/lib/imagery/testsuite/test_imagery_sigfile.py index 104cd5a59b4..8d0e288561d 100644 --- a/lib/imagery/testsuite/test_imagery_sigfile.py +++ b/lib/imagery/testsuite/test_imagery_sigfile.py @@ -9,10 +9,10 @@ for details """ -import os import stat import ctypes import shutil +from pathlib import Path from grass.gunittest.case import TestCase from grass.gunittest.main import test @@ -80,7 +80,7 @@ def test_roundtrip_signature_v1_norgb_one_label(self): # Write signatures to file p_new_sigfile = I_fopen_signature_file_new(self.sig_name) - sig_stat = os.stat(f"{self.sig_dir}/sig") + sig_stat = Path(self.sig_dir, "sig").stat() self.assertTrue(stat.S_ISREG(sig_stat.st_mode)) I_write_signatures(p_new_sigfile, ctypes.byref(So)) self.libc.fclose(p_new_sigfile) @@ -136,7 +136,7 @@ def test_broken_signature_v1_norgb(self): # Write signatures to file p_new_sigfile = I_fopen_signature_file_new(self.sig_name) - sig_stat = os.stat(f"{self.sig_dir}/sig") + sig_stat = Path(self.sig_dir, "sig").stat() self.assertTrue(stat.S_ISREG(sig_stat.st_mode)) I_write_signatures(p_new_sigfile, ctypes.byref(So)) self.libc.fclose(p_new_sigfile) @@ -188,7 +188,7 @@ def test_roundtrip_signature_v1_norgb_two_labelss(self): # Write signatures to file p_new_sigfile = I_fopen_signature_file_new(self.sig_name) - sig_stat = os.stat(f"{self.sig_dir}/sig") + sig_stat = Path(self.sig_dir, "sig").stat() self.assertTrue(stat.S_ISREG(sig_stat.st_mode)) I_write_signatures(p_new_sigfile, ctypes.byref(So)) self.libc.fclose(p_new_sigfile) @@ -277,7 +277,7 @@ def test_roundtrip_signature_v2_norgb_two_labels_oclass(self): # Write signatures to file p_new_sigfile = I_fopen_signature_file_new(self.sig_name) - sig_stat = os.stat(f"{self.sig_dir}/sig") + sig_stat = Path(self.sig_dir, "sig").stat() self.assertTrue(stat.S_ISREG(sig_stat.st_mode)) I_write_signatures(p_new_sigfile, ctypes.byref(So)) self.libc.fclose(p_new_sigfile) diff --git a/lib/imagery/testsuite/test_imagery_signature_management.py b/lib/imagery/testsuite/test_imagery_signature_management.py index 7b1dc8bdc57..42c55c45123 100644 --- a/lib/imagery/testsuite/test_imagery_signature_management.py +++ b/lib/imagery/testsuite/test_imagery_signature_management.py @@ -15,6 +15,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows from grass.script.core import tempname import grass.script as gs @@ -44,16 +45,19 @@ class GetSignaturesDirTestCase(TestCase): + @xfail_windows def test_get_sig(self): cdir = ctypes.create_string_buffer(GNAME_MAX) I_get_signatures_dir(cdir, I_SIGFILE_TYPE_SIG) self.assertEqual(utils.decode(cdir.value), f"signatures{HOST_DIRSEP}sig") + @xfail_windows def test_get_sigset(self): cdir = ctypes.create_string_buffer(GNAME_MAX) I_get_signatures_dir(cdir, I_SIGFILE_TYPE_SIGSET) self.assertEqual(utils.decode(cdir.value), f"signatures{HOST_DIRSEP}sigset") + @xfail_windows def test_get_libsvm(self): elem = ctypes.create_string_buffer(GNAME_MAX) I_get_signatures_dir(elem, I_SIGFILE_TYPE_LIBSVM) diff --git a/lib/imagery/testsuite/test_imagery_sigsetfile.py b/lib/imagery/testsuite/test_imagery_sigsetfile.py index b8bcb566520..d526e920885 100644 --- a/lib/imagery/testsuite/test_imagery_sigsetfile.py +++ b/lib/imagery/testsuite/test_imagery_sigsetfile.py @@ -9,10 +9,10 @@ for details """ -import os import stat import ctypes import shutil +from pathlib import Path from grass.gunittest.case import TestCase from grass.gunittest.main import test @@ -84,7 +84,7 @@ def test_roundtrip_sigset_v1_one_label(self): # Write signatures to file p_new_sigfile = I_fopen_sigset_file_new(self.sig_name) - sig_stat = os.stat(f"{self.sig_dir}/sig") + sig_stat = Path(self.sig_dir, "sig").stat() self.assertTrue(stat.S_ISREG(sig_stat.st_mode)) I_WriteSigSet(p_new_sigfile, ctypes.byref(So)) self.libc.fclose(p_new_sigfile) @@ -139,7 +139,7 @@ def test_read_fail_sigset_v1_one_label(self): # Write signatures to file p_new_sigfile = I_fopen_sigset_file_new(self.sig_name) - sig_stat = os.stat(f"{self.sig_dir}/sig") + sig_stat = Path(self.sig_dir, "sig").stat() self.assertTrue(stat.S_ISREG(sig_stat.st_mode)) I_WriteSigSet(p_new_sigfile, ctypes.byref(So)) self.libc.fclose(p_new_sigfile) @@ -183,7 +183,7 @@ def test_roundtrip_sigset_v1_two_labels(self): # Write signatures to file p_new_sigfile = I_fopen_sigset_file_new(self.sig_name) - sig_stat = os.stat(f"{self.sig_dir}/sig") + sig_stat = Path(self.sig_dir, "sig").stat() self.assertTrue(stat.S_ISREG(sig_stat.st_mode)) I_WriteSigSet(p_new_sigfile, ctypes.byref(So)) self.libc.fclose(p_new_sigfile) diff --git a/lib/init/grass.py b/lib/init/grass.py index 30c0a1a68ba..233bcf53460 100755 --- a/lib/init/grass.py +++ b/lib/init/grass.py @@ -400,7 +400,7 @@ def create_grass_config_dir(): os.makedirs(directory) except OSError as e: # Can happen as a race condition - if not e.errno == errno.EEXIST or not os.path.isdir(directory): + if e.errno != errno.EEXIST or not os.path.isdir(directory): fatal( _( "Failed to create configuration directory '{}' with error: {}" @@ -496,7 +496,7 @@ def create_gisrc(tmpdir, gisrcrc): def read_gisrc(filename): kv = {} try: - f = open(filename, "r") + f = open(filename) except OSError: return kv @@ -522,7 +522,7 @@ def write_gisrcrc(gisrcrc, gisrc, skip_variable=None): """Reads gisrc file and write to gisrcrc""" debug("Reading %s" % gisrc) number = 0 - with open(gisrc, "r") as f: + with open(gisrc) as f: lines = f.readlines() for line in lines: if skip_variable in line: @@ -535,7 +535,7 @@ def write_gisrcrc(gisrcrc, gisrc, skip_variable=None): def read_env_file(path): kv = {} - f = open(path, "r") + f = open(path) for line in f: k, v = line.split(":", 1) kv[k.strip()] = v.strip() @@ -620,7 +620,7 @@ def create_initial_gisrc(filename): LOCATION_NAME: MAPSET: """ - % os.getcwd() + % Path.cwd() ) writefile(filename, s) @@ -789,7 +789,7 @@ def set_mapset( # non-empty element as the last element (which is good for both mapset # and location split) if arg == ".": - arg = os.getcwd() + arg = str(Path.cwd()) elif not os.path.isabs(arg): arg = os.path.abspath(arg) if arg.endswith(os.path.sep): @@ -1098,7 +1098,7 @@ def set_language(grass_config_dir): # Override value is stored in wxGUI preferences file. try: - with open(os.path.join(grass_config_dir, "wx.json"), "r") as json_file: + with open(os.path.join(grass_config_dir, "wx.json")) as json_file: try: language = json.load(json_file)["language"]["locale"]["lc_all"] except KeyError: @@ -1281,65 +1281,6 @@ def set_language(grass_config_dir): gettext.install("grasslibs", gpath("locale")) -def lock_mapset(mapset_path, force_gislock_removal, user): - """Lock the mapset and return name of the lock file - - Behavior on error must be changed somehow; now it fatals but GUI case is - unresolved. - """ - if not os.path.exists(mapset_path): - fatal(_("Path '%s' doesn't exist") % mapset_path) - if not os.access(mapset_path, os.W_OK): - error = _("Path '%s' not accessible.") % mapset_path - stat_info = os.stat(mapset_path) - mapset_uid = stat_info.st_uid - if mapset_uid != os.getuid(): - # GTC %s is mapset's folder path - error = "%s\n%s" % ( - error, - _("You are not the owner of '%s'.") % mapset_path, - ) - fatal(error) - # Check for concurrent use - lockfile = os.path.join(mapset_path, ".gislock") - ret = call([gpath("etc", "lock"), lockfile, "%d" % os.getpid()]) - msg = None - if ret == 2: - if not force_gislock_removal: - msg = _( - "%(user)s is currently running GRASS in selected mapset" - " (file %(file)s found). Concurrent use not allowed.\n" - "You can force launching GRASS using -f flag" - " (note that you need permission for this operation)." - " Have another look in the processor " - "manager just to be sure..." - ) % {"user": user, "file": lockfile} - - else: - try_remove(lockfile) - message( - _( - "%(user)s is currently running GRASS in selected mapset" - " (file %(file)s found). Forcing to launch GRASS..." - ) - % {"user": user, "file": lockfile} - ) - elif ret != 0: - msg = ( - _("Unable to properly access '%s'.\nPlease notify system personnel.") - % lockfile - ) - - if msg: - raise Exception(msg) - debug( - "Mapset <{mapset}> locked using '{lockfile}'".format( - mapset=mapset_path, lockfile=lockfile - ) - ) - return lockfile - - # TODO: the gisrcrc here does not make sense, remove it from load_gisrc def unlock_gisrc_mapset(gisrc, gisrcrc): """Unlock mapset from the gisrc file""" @@ -1690,7 +1631,7 @@ def sh_like_startup(location, location_name, grass_env_file, sh): # save command history in mapset dir and remember more # bash history file handled in specific_addition - if not sh == "bash": + if sh != "bash": os.environ["HISTFILE"] = os.path.join(location, sh_history) # instead of changing $HOME, start bash with: @@ -2421,14 +2362,16 @@ def main(): location = mapset_settings.full_mapset + from grass.app.data import lock_mapset, MapsetLockingException + try: # check and create .gislock file lock_mapset( - mapset_settings.full_mapset, - user=user, - force_gislock_removal=params.force_gislock_removal, + mapset_path=mapset_settings.full_mapset, + force_lock_removal=params.force_gislock_removal, + message_callback=message, ) - except Exception as e: + except MapsetLockingException as e: fatal(e.args[0]) sys.exit(_("Exiting...")) diff --git a/lib/init/testsuite/test_grass_tmp_mapset.py b/lib/init/testsuite/test_grass_tmp_mapset.py index 7a488dadb5f..9379957cd3f 100644 --- a/lib/init/testsuite/test_grass_tmp_mapset.py +++ b/lib/init/testsuite/test_grass_tmp_mapset.py @@ -18,6 +18,7 @@ import os import shutil import subprocess +from grass.gunittest.utils import xfail_windows # Note that unlike rest of GRASS GIS, here we are using unittest package @@ -43,6 +44,7 @@ def tearDown(self): """Deletes the location""" shutil.rmtree(self.location, ignore_errors=True) + @xfail_windows def test_command_runs(self): """Check that correct parameters are accepted""" return_code = subprocess.call( @@ -57,6 +59,7 @@ def test_command_runs(self): ), ) + @xfail_windows def test_command_fails_without_location(self): """Check that the command fails with a nonexistent location""" return_code = subprocess.call( @@ -78,6 +81,7 @@ def test_command_fails_without_location(self): ), ) + @xfail_windows def test_mapset_metadata_correct(self): """Check that metadata is readable and have expected value (XY CRS)""" output = subprocess.check_output( @@ -91,6 +95,7 @@ def test_mapset_metadata_correct(self): ), ) + @xfail_windows def test_mapset_deleted(self): """Check that mapset is deleted at the end of execution""" subprocess.check_call( diff --git a/lib/raster/mask_info.c b/lib/raster/mask_info.c index 792510879dc..1a11da972f8 100644 --- a/lib/raster/mask_info.c +++ b/lib/raster/mask_info.c @@ -70,3 +70,13 @@ int Rast__mask_info(char *name, char *mapset) return 1; } + +/** + * @brief Check presence of 2D raster mask + * + * @return true if mask is present, false otherwise + */ +bool Rast_mask_is_present(void) +{ + return G_find_raster("MASK", G_mapset()) != NULL; +} diff --git a/locale/grass_po_stats.py b/locale/grass_po_stats.py index b5f15026691..0c49e44bc8d 100644 --- a/locale/grass_po_stats.py +++ b/locale/grass_po_stats.py @@ -21,10 +21,12 @@ import subprocess import sys +from pathlib import Path + def read_po_files(inputdirpath): """Return a dictionary with for each language the list of *.po files""" - originalpath = os.getcwd() + originalpath = Path.cwd() os.chdir(inputdirpath) languages = {} for pofile in sorted(glob.glob("*.po")): @@ -144,7 +146,7 @@ def writejson(stats, outfile): fout.close() try: os.remove("messages.mo") - except: + except OSError: pass diff --git a/man/build_class_graphical.py b/man/build_class_graphical.py index 35b2a731cfe..554c950d3a8 100644 --- a/man/build_class_graphical.py +++ b/man/build_class_graphical.py @@ -91,10 +91,7 @@ def file_matches(filename, patterns): - for pattern in patterns: - if fnmatch.fnmatch(filename, pattern): - return True - return False + return any(fnmatch.fnmatch(filename, pattern) for pattern in patterns) def starts_with_module(string, module) -> bool: diff --git a/man/build_full_index.py b/man/build_full_index.py index ead5167cbb1..7d2ce03e342 100644 --- a/man/build_full_index.py +++ b/man/build_full_index.py @@ -9,6 +9,8 @@ import sys import os +from operator import itemgetter + from build_html import ( html_dir, grass_version, @@ -50,7 +52,7 @@ prefix = cmd.split(".")[0] if prefix not in [item[0] for item in classes]: classes.append((prefix, class_labels.get(prefix, prefix))) -classes.sort(key=lambda tup: tup[0]) +classes.sort(key=itemgetter(0)) # begin full index: filename = "full_index.html" diff --git a/man/build_html.py b/man/build_html.py index daa38b557f4..c89df29e290 100644 --- a/man/build_html.py +++ b/man/build_html.py @@ -393,7 +393,7 @@ def check_for_desc_override(basename): def read_file(name): - f = open(name, "r") + f = open(name) s = f.read() f.close() return s @@ -476,7 +476,7 @@ def write_html_footer(f, index_url, year=None): def get_desc(cmd): - f = open(cmd, "r") + f = open(cmd) while True: line = f.readline() if not line: diff --git a/man/build_keywords.py b/man/build_keywords.py index 6070fad1a28..b0dfe95e0e3 100644 --- a/man/build_keywords.py +++ b/man/build_keywords.py @@ -158,7 +158,7 @@ def get_module_man_html_file_path(module): for k in sorted(char_list.keys()): test_length += 1 # toc += '

  • %s
  • ' % (char_list[k], k) - if test_length % 4 == 0 and not test_length == all_keys: + if test_length % 4 == 0 and test_length != all_keys: toc += '\n%s, ' % (char_list[k], k) elif test_length % 4 == 0 and test_length == all_keys: toc += '\n%s' % (char_list[k], k) diff --git a/man/build_manual_gallery.py b/man/build_manual_gallery.py index 3d381e27877..51a6fbcd312 100755 --- a/man/build_manual_gallery.py +++ b/man/build_manual_gallery.py @@ -102,10 +102,7 @@ def img_in_html(filename, imagename) -> bool: def file_matches(filename, patterns): - for pattern in patterns: - if fnmatch.fnmatch(filename, pattern): - return True - return False + return any(fnmatch.fnmatch(filename, pattern) for pattern in patterns) def get_files(directory, patterns, exclude_patterns): diff --git a/man/build_rest.py b/man/build_rest.py index ea1dd7692d2..473827fcc86 100644 --- a/man/build_rest.py +++ b/man/build_rest.py @@ -271,7 +271,7 @@ def check_for_desc_override(basename): def read_file(name): - f = open(name, "r") + f = open(name) s = f.read() f.close() return s @@ -337,7 +337,7 @@ def write_rest_footer(f, index_url): def get_desc(cmd): - f = open(cmd, "r") + f = open(cmd) while True: line = f.readline() if not line: diff --git a/ps/ps.map/ps_vpoints.c b/ps/ps.map/ps_vpoints.c index 80b29a349b1..cd320b46f11 100644 --- a/ps/ps.map/ps_vpoints.c +++ b/ps/ps.map/ps_vpoints.c @@ -268,5 +268,6 @@ int PS_vpoints_plot(struct Map_info *P_map, int vec) } /* for (line) */ fprintf(PS.fp, "\n"); + Vect_destroy_cats_struct(Cats); return 0; } diff --git a/pyproject.toml b/pyproject.toml index 649e309a73f..c6cfac4185b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -145,7 +145,6 @@ ignore = [ "FBT001", # boolean-type-hint-positional-argument "FBT002", # boolean-default-value-positional-argument "FBT003", # boolean-positional-value-in-call - "FURB118", # reimplemented-operator "I001", # unsorted-imports "ISC003", # explicit-string-concatenation "PERF203", # try-except-in-loop @@ -195,12 +194,10 @@ ignore = [ "PTH106", # os-rmdir "PTH107", # os-remove "PTH108", # os-unlink - "PTH109", # os-getcwd "PTH110", # os-path-exists "PTH111", # os-path-expanduser "PTH112", # os-path-isdir "PTH113", # os-path-isfile - "PTH116", # os-stat "PTH117", # os-path-isabs "PTH118", # os-path-join "PTH119", # os-path-basename @@ -246,17 +243,12 @@ ignore = [ "S606", # start-process-with-no-shell "S607", # start-process-with-partial-path "S608", # hardcoded-sql-expression - "SIM101", # duplicate-isinstance-call "SIM102", # collapsible-if "SIM105", # suppressible-exception "SIM108", # if-else-block-instead-of-if-exp - "SIM109", # compare-with-tuple - "SIM110", # reimplemented-builtin "SIM113", # enumerate-for-loop - "SIM114", # if-with-same-arms "SIM116", # if-else-block-instead-of-dict-lookup "SIM118", # in-dict-keys - "SIM201", # negate-equal-op "SIM223", # expr-and-false "SIM401", # if-else-block-instead-of-dict-get "SLF001", # private-member-access @@ -266,7 +258,6 @@ ignore = [ "TRY201", # verbose-raise "TRY300", # try-consider-else "TRY301", # raise-within-try - "UP015", # redundant-open-modes "UP030", # format-literals "UP031", # printf-string-formatting "UP032", # f-string diff --git a/python/grass/app/data.py b/python/grass/app/data.py index 439a6c3c4d1..10997cd59e8 100644 --- a/python/grass/app/data.py +++ b/python/grass/app/data.py @@ -15,8 +15,12 @@ import os import tempfile import getpass +import subprocess import sys from shutil import copytree, ignore_patterns +from pathlib import Path + +import grass.script as gs import grass.grassdb.config as cfg from grass.grassdb.checks import is_location_valid @@ -162,3 +166,69 @@ def ensure_default_data_hierarchy(): mapset_path = os.path.join(gisdbase, location, mapset) return gisdbase, location, mapset, mapset_path + + +class MapsetLockingException(Exception): + pass + + +def lock_mapset(mapset_path, force_lock_removal, message_callback): + """Acquire a lock for a mapset and return name of new lock file + + Raises MapsetLockingException when it is not possible to acquire a lock for the + given mapset either because of existing lock or due to insufficient permissions. + A corresponding localized message is given in the exception. + + A *message_callback* is a function which will be called to report messages about + certain states. Specifically, the function is called when forcibly unlocking the + mapset. + + Assumes that the runtime is set up (specifically that GISBASE is in + the environment). + """ + if not os.path.exists(mapset_path): + raise MapsetLockingException(_("Path '{}' doesn't exist").format(mapset_path)) + if not os.access(mapset_path, os.W_OK): + error = _("Path '{}' not accessible.").format(mapset_path) + stat_info = Path(mapset_path).stat() + mapset_uid = stat_info.st_uid + if mapset_uid != os.getuid(): + error = "{error}\n{detail}".format( + error=error, + detail=_("You are not the owner of '{}'.").format(mapset_path), + ) + raise MapsetLockingException(error) + # Check for concurrent use + lockfile = os.path.join(mapset_path, ".gislock") + locker_path = os.path.join(os.environ["GISBASE"], "etc", "lock") + ret = subprocess.run( + [locker_path, lockfile, "%d" % os.getpid()], check=False + ).returncode + msg = None + if ret == 2: + if not force_lock_removal: + msg = _( + "{user} is currently running GRASS in selected mapset" + " (file {file} found). Concurrent use of one mapset not allowed.\n" + "You can force launching GRASS using -f flag" + " (assuming your have sufficient access permissions)." + " Confirm in a process manager " + "that there is no other process using the mapset." + ).format(user=Path(lockfile).owner(), file=lockfile) + else: + message_callback( + _( + "{user} is currently running GRASS in selected mapset" + " (file {file} found), but forcing to launch GRASS anyway..." + ).format(user=Path(lockfile).owner(), file=lockfile) + ) + gs.try_remove(lockfile) + elif ret != 0: + msg = _( + "Unable to properly access lock file '{name}'.\n" + "Please resolve this with your system administrator." + ).format(name=lockfile) + + if msg: + raise MapsetLockingException(msg) + return lockfile diff --git a/python/grass/grassdb/checks.py b/python/grass/grassdb/checks.py index 4de65a4cba3..6916ea2748a 100644 --- a/python/grass/grassdb/checks.py +++ b/python/grass/grassdb/checks.py @@ -113,7 +113,7 @@ def is_current_user_mapset_owner(mapset_path): # Mapset needs to be owned by user. if sys.platform == "win32": return True - stat_info = os.stat(mapset_path) + stat_info = Path(mapset_path).stat() mapset_uid = stat_info.st_uid return mapset_uid == os.getuid() @@ -159,7 +159,7 @@ def is_first_time_user(): genv = gisenv() if "LAST_MAPSET_PATH" in genv.keys(): return genv["LAST_MAPSET_PATH"] == os.path.join( - os.getcwd(), cfg.unknown_location, cfg.unknown_mapset + Path.cwd(), cfg.unknown_location, cfg.unknown_mapset ) return False diff --git a/python/grass/grassdb/history.py b/python/grass/grassdb/history.py index 60b5459700d..d7c22eb36fc 100644 --- a/python/grass/grassdb/history.py +++ b/python/grass/grassdb/history.py @@ -81,9 +81,7 @@ def _read_from_plain_text(history_path): stores only executed commands.""" content_list = [] try: - with open( - history_path, encoding="utf-8", mode="r", errors="replace" - ) as file_history: + with open(history_path, encoding="utf-8", errors="replace") as file_history: content_list = [ {"command": line.strip(), "command_info": None} for line in file_history ] @@ -287,7 +285,7 @@ def _add_entry_to_JSON(history_path, entry): :param dict entry: entry consisting of 'command' and 'command_info' keys """ try: - with open(history_path, encoding="utf-8", mode="r") as file_history: + with open(history_path, encoding="utf-8") as file_history: existing_data = json.load(file_history) except (OSError, ValueError): existing_data = [] diff --git a/python/grass/grassdb/testsuite/test_manage.py b/python/grass/grassdb/testsuite/test_manage.py index a2788d4f1a8..43b8945bd31 100644 --- a/python/grass/grassdb/testsuite/test_manage.py +++ b/python/grass/grassdb/testsuite/test_manage.py @@ -18,6 +18,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.gmodules import call_module from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows class TestMapsetPath(TestCase): @@ -38,6 +39,7 @@ def test_mapset_from_path_object(self): self.assertEqual(mapset_path.mapset, mapset_name) self.assertEqual(mapset_path.path, Path(path) / location_name / mapset_name) + @xfail_windows def test_mapset_from_str(self): """Check with path from str and database directory as Path""" path = "does/not/exist" @@ -60,6 +62,7 @@ def test_mapset_from_str(self): class TestSplitMapsetPath(TestCase): """Check that split works with different parameters""" + @xfail_windows def test_split_path(self): """Check that pathlib.Path is correctly split""" ref_db = "does/not/exist" @@ -71,6 +74,7 @@ def test_split_path(self): self.assertEqual(new_location, ref_location) self.assertEqual(new_mapset, ref_mapset) + @xfail_windows def test_split_str(self): """Check that path as str is correctly split""" ref_db = "does/not/exist" @@ -82,6 +86,7 @@ def test_split_str(self): self.assertEqual(new_location, ref_location) self.assertEqual(new_mapset, ref_mapset) + @xfail_windows def test_split_str_trailing_slash(self): """Check that path as str with a trailing slash is correctly split""" ref_db = "does/not/exist" diff --git a/python/grass/gunittest/case.py b/python/grass/gunittest/case.py index da2c7486d16..b5563e47e9e 100644 --- a/python/grass/gunittest/case.py +++ b/python/grass/gunittest/case.py @@ -695,7 +695,7 @@ def assertFileMd5(self, filename, md5, text=False, msg=None): actual = text_file_md5(filename) else: actual = file_md5(filename) - if not actual == md5: + if actual != md5: standardMsg = ( "File <{name}> does not have the right MD5 sum.\n" "Expected is <{expected}>," diff --git a/python/grass/gunittest/checkers.py b/python/grass/gunittest/checkers.py index fb46a1deb66..c63f6d08ff0 100644 --- a/python/grass/gunittest/checkers.py +++ b/python/grass/gunittest/checkers.py @@ -638,7 +638,7 @@ def text_file_md5( if prepend_lines: for line in prepend_lines: hasher.update(encode(line)) - with open(filename, "r") as f: + with open(filename) as f: for line in f: # replace platform newlines by standard newline if os.linesep != "\n": diff --git a/python/grass/gunittest/reporters.py b/python/grass/gunittest/reporters.py index 650480a234c..9535e07f871 100644 --- a/python/grass/gunittest/reporters.py +++ b/python/grass/gunittest/reporters.py @@ -51,7 +51,7 @@ def replace_in_file(file_path, pattern, repl): """ # using tmp file to store the replaced content tmp_file_path = file_path + ".tmp" - with open(file_path, "r") as old_file, open(tmp_file_path, "w") as new_file: + with open(file_path) as old_file, open(tmp_file_path, "w") as new_file: for line in old_file: new_file.write(re.sub(pattern=pattern, string=line, repl=repl)) # remove old file since it must not exist for rename/move diff --git a/python/grass/gunittest/testsuite/test_assertions.py b/python/grass/gunittest/testsuite/test_assertions.py index 3af538132f3..ed27c0e54fb 100644 --- a/python/grass/gunittest/testsuite/test_assertions.py +++ b/python/grass/gunittest/testsuite/test_assertions.py @@ -11,6 +11,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows class TestTextAssertions(TestCase): @@ -34,6 +35,7 @@ def test_assertLooksLike(self): def test_assertLooksLike_multiline(self): self.assertLooksLike("a=123\nb=456\nc=789", "a=...\nb=...\nc=...") + @xfail_windows def test_assertLooksLike_multiline_platform_dependent(self): self.assertLooksLike( "a=123\nb=456\nc=789", "a=...{nl}b=...{nl}c=...".format(nl=os.linesep) @@ -384,6 +386,7 @@ def test_assertFileExists_empty_file(self): self.failureException, self.assertFileExists, filename=self.emtpy_file ) + @xfail_windows def test_assertFileMd5(self): self.assertFileMd5(filename=self.file_with_md5, md5=self.file_md5) self.assertRaises( diff --git a/python/grass/gunittest/testsuite/test_assertions_vect.py b/python/grass/gunittest/testsuite/test_assertions_vect.py index 8b3288ad2ca..0a73580ba58 100644 --- a/python/grass/gunittest/testsuite/test_assertions_vect.py +++ b/python/grass/gunittest/testsuite/test_assertions_vect.py @@ -5,6 +5,7 @@ from grass.exceptions import CalledModuleError from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows V_UNIVAR_SCHOOLS_WIDTH_SUBSET = """n=144 @@ -281,6 +282,7 @@ def test_assertVectorAsciiEqualsVectorAscii_diff_content(self): self.assertFileExists(self.simple_base_file) self.assertFileExists(self.simple_modified_file) + @xfail_windows def test_assertVectorEqualsAscii_by_import(self): amap = "simple_vector_map_imported_base" self.runModule( diff --git a/python/grass/gunittest/testsuite/test_checkers.py b/python/grass/gunittest/testsuite/test_checkers.py index d35ffcb19d8..b11b25778a3 100644 --- a/python/grass/gunittest/testsuite/test_checkers.py +++ b/python/grass/gunittest/testsuite/test_checkers.py @@ -24,6 +24,7 @@ file_md5, text_file_md5, ) +from grass.gunittest.utils import xfail_windows class TestValuesEqual(TestCase): @@ -386,6 +387,7 @@ def tearDownClass(cls): try_remove(cls.correct_file_name_unix_nl) try_remove(cls.wrong_file_name) + @xfail_windows def test_text_file_binary(self): r"""File with ``\n`` (LF) newlines as binary (MD5 has ``\n``).""" self.assertEqual( diff --git a/python/grass/gunittest/utils.py b/python/grass/gunittest/utils.py index c1afea3d5ad..5313dbb9bc8 100644 --- a/python/grass/gunittest/utils.py +++ b/python/grass/gunittest/utils.py @@ -14,6 +14,8 @@ from pathlib import Path import shutil import sys +from unittest import expectedFailure +import warnings def ensure_dir(directory): @@ -80,3 +82,17 @@ def safe_repr(obj, short=False): if not short or len(result) < _MAX_LENGTH: return result return result[:_MAX_LENGTH] + " [truncated]..." + + +def xfail_windows(test_item): + """Marks a test as an expected failure or error only on Windows + Equivalent to applying @unittest.expectedFailure only when running + on Windows. + """ + if not sys.platform.startswith("win"): + return lambda func: func + warnings.warn( + "Once the test is fixed and passing, remove the @xfail_windows decorator", + stacklevel=2, + ) + return expectedFailure(test_item) diff --git a/python/grass/imaging/images2ims.py b/python/grass/imaging/images2ims.py index 31088a22f2e..662e3eb46e0 100644 --- a/python/grass/imaging/images2ims.py +++ b/python/grass/imaging/images2ims.py @@ -31,6 +31,7 @@ """ import os +from operator import itemgetter try: import numpy as np @@ -214,7 +215,7 @@ def readIms(filename, asNumpy=True): images.append((im.copy(), nr)) # Sort images - images.sort(key=lambda x: x[1]) + images.sort(key=itemgetter(1)) images = [im[0] for im in images] # Convert to numpy if needed diff --git a/python/grass/jupyter/baseseriesmap.py b/python/grass/jupyter/baseseriesmap.py index 634e172f3c5..4497b16593d 100644 --- a/python/grass/jupyter/baseseriesmap.py +++ b/python/grass/jupyter/baseseriesmap.py @@ -25,7 +25,7 @@ import grass.script as gs from .map import Map -from .utils import get_number_of_cores +from .utils import get_number_of_cores, save_gif class BaseSeriesMap: @@ -210,3 +210,49 @@ def change_image(index): width="100%", display="inline-flex", flex_flow="row wrap" ) return widgets.HBox([play, slider, out_img], layout=layout) + + def save( + self, + filename, + duration=500, + label=True, + font=None, + text_size=12, + text_color="gray", + ): + """ + Creates a GIF animation of rendered layers. + + Text color must be in a format accepted by PIL ImageColor module. For supported + formats, visit: + https://pillow.readthedocs.io/en/stable/reference/ImageColor.html#color-names + + param str filename: name of output GIF file + param int duration: time to display each frame; milliseconds + param bool label: include label on each frame + param str font: font file + param int text_size: size of label text + param str text_color: color to use for the text. + """ + + # Render images if they have not been already + if not self._layers_rendered: + self.render() + + input_files = [] + for index in self._indices: + input_files.append(self._base_filename_dict[index]) + + save_gif( + input_files, + filename, + duration=duration, + label=label, + labels=self._labels, + font=font, + text_size=text_size, + text_color=text_color, + ) + + # Display the GIF + return filename diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index 085c2843fe7..d843ac3b6b6 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -178,7 +178,7 @@ def add_to(self, interactive_map): else: import ipyleaflet # pylint: disable=import-outside-toplevel - with open(self._filename, "r", encoding="utf-8") as file: + with open(self._filename, encoding="utf-8") as file: data = json.load(file) # allow using opacity directly to keep interface # consistent for both backends diff --git a/python/grass/jupyter/seriesmap.py b/python/grass/jupyter/seriesmap.py index 8615ad488fb..ae6bde911b6 100644 --- a/python/grass/jupyter/seriesmap.py +++ b/python/grass/jupyter/seriesmap.py @@ -20,7 +20,6 @@ from .map import Map from .region import RegionManagerForSeries -from .utils import save_gif from .baseseriesmap import BaseSeriesMap @@ -165,49 +164,3 @@ def render(self): ) tasks = [(i,) for i in range(self.baseseries)] self._render(tasks) - - def save( - self, - filename, - duration=500, - label=True, - font=None, - text_size=12, - text_color="gray", - ): - """ - Creates a GIF animation of rendered layers. - - Text color must be in a format accepted by PIL ImageColor module. For supported - formats, visit: - https://pillow.readthedocs.io/en/stable/reference/ImageColor.html#color-names - - param str filename: name of output GIF file - param int duration: time to display each frame; milliseconds - param bool label: include label on each frame - param str font: font file - param int text_size: size of label text - param str text_color: color to use for the text - """ - - # Render images if they have not been already - if not self._layers_rendered: - self.render() - - tmp_files = [] - for file in self._base_filename_dict.values(): - tmp_files.append(file) - - save_gif( - tmp_files, - filename, - duration=duration, - label=label, - labels=self._labels, - font=font, - text_size=text_size, - text_color=text_color, - ) - - # Display the GIF - return filename diff --git a/python/grass/jupyter/tests/grass_jupyter_session_test.py b/python/grass/jupyter/tests/grass_jupyter_session_test.py index 7b6bc89e4ed..17b6e7fbf5d 100644 --- a/python/grass/jupyter/tests/grass_jupyter_session_test.py +++ b/python/grass/jupyter/tests/grass_jupyter_session_test.py @@ -27,8 +27,8 @@ def test_init_finish(tmp_path): import os import grass.script as gs import grass.jupyter as gj -gs.core._create_location_xy("{tmp_path}", "{location}") -session = gj.init("{tmp_path / location}") +gs.core._create_location_xy(r"{tmp_path}", r"{location}") +session = gj.init(r"{tmp_path / location}") gs.read_command("g.region", flags="p") print(os.environ["GISRC"]) session.finish() @@ -49,8 +49,8 @@ def test_init_with_auto_finish(tmp_path): import os import grass.script as gs import grass.jupyter as gj -gs.core._create_location_xy("{tmp_path}", "{location}") -session = gj.init("{tmp_path / location}") +gs.core._create_location_xy(r"{tmp_path}", r"{location}") +session = gj.init(r"{tmp_path / location}") print(os.environ["GISRC"]) """ diff --git a/python/grass/jupyter/testsuite/interactivemap_test.py b/python/grass/jupyter/testsuite/interactivemap_test.py index 6b8a7379548..493578596b5 100644 --- a/python/grass/jupyter/testsuite/interactivemap_test.py +++ b/python/grass/jupyter/testsuite/interactivemap_test.py @@ -103,24 +103,26 @@ def test_query_button(self): # Create InteractiveMap with ipyleaflet backend interactive_map = gj.InteractiveMap(map_backend="ipyleaflet") interactive_map.add_raster("elevation") - interactive_map.add_vector("roadsmajor") - interactive_map.add_query_button() - self.assertIsNotNone(interactive_map.map) - self.assertTrue(interactive_map.query_mode is False) - # Toggle query button to activate - interactive_map.query_mode = True - self.assertTrue(interactive_map.query_mode) - # Toggle query button to deactivate - interactive_map.query_mode = False - self.assertFalse(interactive_map.query_mode) + button = interactive_map.setup_query_interface() + self.assertIsNotNone(interactive_map._controllers[button].query_raster((0, 0))) + + @unittest.skipIf(not can_import_ipyleaflet(), "Cannot import ipyleaflet") + def test_draw(self): + """Test the draw_computational_region method.""" + # Create InteractiveMap + interactive_map = gj.InteractiveMap(map_backend="ipyleaflet") + button = interactive_map.setup_drawing_interface() + interactive_map._controllers[button].activate() + self.assertIsNotNone(interactive_map._controllers[button].save_button_control) @unittest.skipIf(not can_import_ipyleaflet(), "Cannot import ipyleaflet") def test_draw_computational_region(self): """Test the draw_computational_region method.""" # Create InteractiveMap - interactive_map = gj.InteractiveMap() - interactive_map.draw_computational_region() - self.assertTrue(callable(interactive_map.draw_computational_region)) + interactive_map = gj.InteractiveMap(map_backend="ipyleaflet") + button = interactive_map.setup_computational_region_interface() + interactive_map._controllers[button].activate() + self.assertIsNotNone(interactive_map._controllers[button].save_button_control) if __name__ == "__main__": diff --git a/python/grass/jupyter/testsuite/map3d_test.py b/python/grass/jupyter/testsuite/map3d_test.py index b28b6a03a5b..5f88fad24e7 100644 --- a/python/grass/jupyter/testsuite/map3d_test.py +++ b/python/grass/jupyter/testsuite/map3d_test.py @@ -26,6 +26,7 @@ import grass.jupyter as gj from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows def can_import_ipython(): @@ -81,12 +82,14 @@ def tearDown(self): else: file.unlink(missing_ok=True) + @xfail_windows def test_defaults(self): """Check that default settings work""" renderer = gj.Map3D() renderer.render(elevation_map="elevation", color_map="elevation") self.assertFileExists(renderer.filename) + @xfail_windows def test_filename(self): """Check that custom filename works""" custom_filename = "test_filename.png" @@ -96,12 +99,14 @@ def test_filename(self): renderer.render(elevation_map="elevation", color_map="elevation") self.assertFileExists(custom_filename) + @xfail_windows def test_hw(self): """Check that custom width and height works""" renderer = gj.Map3D(width=200, height=400) renderer.render(elevation_map="elevation", color_map="elevation") self.assertFileExists(renderer.filename) + @xfail_windows def test_overlay(self): """Check that overlay works""" renderer = gj.Map3D() diff --git a/python/grass/jupyter/timeseriesmap.py b/python/grass/jupyter/timeseriesmap.py index 3ac94bea93e..2acfa575126 100644 --- a/python/grass/jupyter/timeseriesmap.py +++ b/python/grass/jupyter/timeseriesmap.py @@ -20,7 +20,6 @@ from .map import Map from .region import RegionManagerForTimeSeries -from .utils import save_gif from .baseseriesmap import BaseSeriesMap @@ -312,49 +311,3 @@ def render(self): filename = os.path.join(self._tmpdir.name, f"{layer}.png") tasks.append((date, layer, filename)) self._render(tasks) - - def save( - self, - filename, - duration=500, - label=True, - font="DejaVuSans.ttf", - text_size=12, - text_color="gray", - ): - """ - Creates a GIF animation of rendered layers. - - Text color must be in a format accepted by PIL ImageColor module. For supported - formats, visit: - https://pillow.readthedocs.io/en/stable/reference/ImageColor.html#color-names - - param str filename: name of output GIF file - param int duration: time to display each frame; milliseconds - param bool label: include date/time stamp on each frame - param str font: font file - param int text_size: size of date/time text - param str text_color: color to use for the text. - """ - - # Render images if they have not been already - if not self._layers_rendered: - self.render() - - input_files = [] - for date in self._labels: - input_files.append(self._base_filename_dict[date]) - - save_gif( - input_files, - filename, - duration=duration, - label=label, - labels=self._labels, - font=font, - text_size=text_size, - text_color=text_color, - ) - - # Display the GIF - return filename diff --git a/python/grass/pygrass/gis/__init__.py b/python/grass/pygrass/gis/__init__.py index 3f380368d28..d31bc31b972 100644 --- a/python/grass/pygrass/gis/__init__.py +++ b/python/grass/pygrass/gis/__init__.py @@ -430,7 +430,7 @@ def __iter__(self): def read(self): """Return the mapsets in the search path""" try: - with open(self.spath, "r") as f: + with open(self.spath) as f: lines = f.readlines() if lines: return [line.strip() for line in lines] diff --git a/python/grass/pygrass/gis/region.py b/python/grass/pygrass/gis/region.py index 98c75a15631..892a3664b49 100644 --- a/python/grass/pygrass/gis/region.py +++ b/python/grass/pygrass/gis/region.py @@ -331,10 +331,7 @@ def __eq__(self, reg): "zone", "proj", ] - for attr in attrs: - if getattr(self, attr) != getattr(reg, attr): - return False - return True + return all(getattr(self, attr) == getattr(reg, attr) for attr in attrs) def __ne__(self, other): return not self == other diff --git a/python/grass/pygrass/modules/grid/grid.py b/python/grass/pygrass/modules/grid/grid.py index fb338ca973a..e0dbd309656 100644 --- a/python/grass/pygrass/modules/grid/grid.py +++ b/python/grass/pygrass/modules/grid/grid.py @@ -121,7 +121,7 @@ def read_gisrc(gisrc): ... genv['GISDBASE'])) True """ - with open(gisrc, "r") as gfile: + with open(gisrc) as gfile: gis = dict( [(k.strip(), v.strip()) for k, v in [row.split(":", 1) for row in gfile]] ) diff --git a/python/grass/pygrass/modules/interface/env.py b/python/grass/pygrass/modules/interface/env.py index ad6e4c4800e..1f0519d23c5 100644 --- a/python/grass/pygrass/modules/interface/env.py +++ b/python/grass/pygrass/modules/interface/env.py @@ -13,7 +13,7 @@ def get_env(): gisrc = os.environ.get("GISRC") if gisrc is None: raise RuntimeError("You are not in a GRASS session, GISRC not found.") - with open(gisrc, mode="r") as grc: + with open(gisrc) as grc: return dict( [ (k.strip(), v.strip()) diff --git a/python/grass/pygrass/modules/interface/parameter.py b/python/grass/pygrass/modules/interface/parameter.py index 798efde56b7..3b58ea2b372 100644 --- a/python/grass/pygrass/modules/interface/parameter.py +++ b/python/grass/pygrass/modules/interface/parameter.py @@ -216,7 +216,7 @@ def __init__(self, xparameter=None, diz=None): # if "gisprompt" in diz and diz["gisprompt"]: self.typedesc = diz["gisprompt"].get("prompt", "") - self.input = not diz["gisprompt"]["age"] == "new" + self.input = diz["gisprompt"]["age"] != "new" else: self.input = True diff --git a/python/grass/pygrass/raster/category.py b/python/grass/pygrass/raster/category.py index cfeb84ddde2..f439d51dcdf 100644 --- a/python/grass/pygrass/raster/category.py +++ b/python/grass/pygrass/raster/category.py @@ -299,7 +299,7 @@ def read_rules(self, filename, sep=":"): """ self.reset() - with open(filename, "r") as f: + with open(filename) as f: for row in f: cat = row.strip().split(sep) if len(cat) == 2: diff --git a/python/grass/pygrass/raster/history.py b/python/grass/pygrass/raster/history.py index 5ec855cf1bb..8f227affabc 100644 --- a/python/grass/pygrass/raster/history.py +++ b/python/grass/pygrass/raster/history.py @@ -59,10 +59,7 @@ def __del__(self): """Rast_free_history""" def __eq__(self, hist): - for attr in self.attrs: - if getattr(self, attr) != getattr(hist, attr): - return False - return True + return all(getattr(self, attr) == getattr(hist, attr) for attr in self.attrs) def __len__(self): return self.length() diff --git a/python/grass/pygrass/raster/testsuite/test_category.py b/python/grass/pygrass/raster/testsuite/test_category.py index 527c43b894f..113264c82f6 100644 --- a/python/grass/pygrass/raster/testsuite/test_category.py +++ b/python/grass/pygrass/raster/testsuite/test_category.py @@ -6,6 +6,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows from grass.pygrass.raster import RasterRow from grass.pygrass.raster.category import Category @@ -76,6 +77,7 @@ def testFirstCat(self): self.assertEqual(cats[7], cat7) self.assertEqual(cats[15], cat15) + @xfail_windows def testWrite(self): tmpfile = tempfile(False) cats = Category(self.name) diff --git a/python/grass/pygrass/raster/testsuite/test_numpy.py b/python/grass/pygrass/raster/testsuite/test_numpy.py index 5f0b2309544..b23926d8ba7 100644 --- a/python/grass/pygrass/raster/testsuite/test_numpy.py +++ b/python/grass/pygrass/raster/testsuite/test_numpy.py @@ -6,6 +6,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows from numpy.random import default_rng from grass.pygrass.raster import raster2numpy, numpy2raster, RasterRow @@ -48,6 +49,7 @@ def test_len(self): self.assertTrue(len(self.numpy_obj), 40) self.assertTrue(len(self.numpy_obj[0]), 60) + @xfail_windows def test_write(self): rng = default_rng() numpy2raster(rng.random([40, 60]), "FCELL", self.name, True) diff --git a/python/grass/pygrass/raster/testsuite/test_raster_img.py b/python/grass/pygrass/raster/testsuite/test_raster_img.py index bbc1cb2dabd..b873b16a86b 100644 --- a/python/grass/pygrass/raster/testsuite/test_raster_img.py +++ b/python/grass/pygrass/raster/testsuite/test_raster_img.py @@ -3,6 +3,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows from grass.pygrass.raster import raster2numpy_img from grass.pygrass.gis.region import Region @@ -149,6 +150,7 @@ def test_resampling_to_numpy_img_1(self): self.assertEqual(len(a), region.rows * region.cols * 4) + @xfail_windows def test_resampling_to_numpy_img_2(self): region = Region() region.ewres = 1 @@ -159,6 +161,7 @@ def test_resampling_to_numpy_img_2(self): self.assertEqual(len(a), region.rows * region.cols * 4) + @xfail_windows def test_resampling_to_numpy_img_3(self): region = Region() region.ewres = 0.4 @@ -169,6 +172,7 @@ def test_resampling_to_numpy_img_3(self): self.assertEqual(len(a), region.rows * region.cols * 1) + @xfail_windows def test_resampling_to_numpy_img_4(self): region = Region() region.ewres = 0.1 diff --git a/python/grass/pygrass/tests/benchmark.py b/python/grass/pygrass/tests/benchmark.py index 75e32541a59..fa7f0c01a65 100644 --- a/python/grass/pygrass/tests/benchmark.py +++ b/python/grass/pygrass/tests/benchmark.py @@ -12,11 +12,11 @@ import copy import cProfile import sys -import os from jinja2 import Template +from pathlib import Path -sys.path.append(os.getcwd()) -sys.path.append("%s/.." % (os.getcwd())) +sys.path.append(str(Path.cwd())) +sys.path.append("%s/.." % (str(Path.cwd()))) import grass.lib.gis as libgis import grass.lib.raster as libraster diff --git a/python/grass/pygrass/tests/set_mapset.py b/python/grass/pygrass/tests/set_mapset.py index f9d4a96fabf..105735efa5a 100644 --- a/python/grass/pygrass/tests/set_mapset.py +++ b/python/grass/pygrass/tests/set_mapset.py @@ -13,7 +13,7 @@ def read_gisrc(gisrcpath): - gisrc = open(gisrcpath, "r") + gisrc = open(gisrcpath) diz = {} for row in gisrc: key, val = row.split(":") diff --git a/python/grass/pygrass/vector/table.py b/python/grass/pygrass/vector/table.py index 4b7e99c0a83..634c440a583 100644 --- a/python/grass/pygrass/vector/table.py +++ b/python/grass/pygrass/vector/table.py @@ -791,10 +791,7 @@ def __eq__(self, link): False """ attrs = ["layer", "name", "table_name", "key", "driver"] - for attr in attrs: - if getattr(self, attr) != getattr(link, attr): - return False - return True + return all(getattr(self, attr) == getattr(link, attr) for attr in attrs) def __ne__(self, other): return not self == other diff --git a/python/grass/script/core.py b/python/grass/script/core.py index a86738926f0..ee90a41d4ef 100644 --- a/python/grass/script/core.py +++ b/python/grass/script/core.py @@ -1066,7 +1066,7 @@ def _text_to_key_value_dict( {'a': ['Hello'], 'c': [1, 2, 3, 4, 5], 'b': [1.0], 'd': ['hello', 8, 0.1]} """ - text = open(filename, "r").readlines() + text = open(filename).readlines() kvdict = KeyValue() for line in text: @@ -1276,7 +1276,7 @@ def region_env(region3d=False, flags=None, env=None, **kwargs): windfile = os.path.join( gis_env["GISDBASE"], gis_env["LOCATION_NAME"], gis_env["MAPSET"], "WIND" ) - with open(windfile, "r") as fd: + with open(windfile) as fd: grass_region = "" for line in fd: key, value = (x.strip() for x in line.split(":", 1)) @@ -1894,7 +1894,7 @@ def _create_location_xy(database, location): :param database: GRASS database where to create new location :param location: location name """ - cur_dir = os.getcwd() + cur_dir = Path.cwd() try: os.chdir(database) os.mkdir(location) diff --git a/python/grass/script/task.py b/python/grass/script/task.py index 35234ba65e6..8e1f0a12844 100644 --- a/python/grass/script/task.py +++ b/python/grass/script/task.py @@ -247,11 +247,7 @@ def get_options(self): def has_required(self): """Check if command has at least one required parameter""" - for p in self.params: - if p.get("required", False): - return True - - return False + return any(p.get("required", False) for p in self.params) def set_param(self, aParam, aValue, element="value"): """Set param value/values.""" diff --git a/python/grass/script/testsuite/test_start_command_functions.py b/python/grass/script/testsuite/test_start_command_functions.py index 414a68cd270..368669263a8 100644 --- a/python/grass/script/testsuite/test_start_command_functions.py +++ b/python/grass/script/testsuite/test_start_command_functions.py @@ -4,6 +4,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows from grass.script.core import start_command, PIPE, run_command, write_command from grass.script.core import read_command, find_program @@ -85,6 +86,7 @@ def setUpClass(cls): def tearDownClass(cls): cls.runModule("g.remove", type="raster", name=cls.raster, flags="f") + @xfail_windows def test_write_labels_unicode(self): """This tests if Python module works""" find_program("ls", "--version") @@ -99,6 +101,7 @@ def test_write_labels_unicode(self): self.assertEqual(res, "1:kůň\n2:kráva\n3:ovečka\n4:býk") self.assertIsInstance(res, str) + @xfail_windows def test_write_labels_bytes(self): """This tests if Python module works""" write_command( diff --git a/python/grass/script/testsuite/test_utils.py b/python/grass/script/testsuite/test_utils.py index db12041d66d..67d2c59dd69 100644 --- a/python/grass/script/testsuite/test_utils.py +++ b/python/grass/script/testsuite/test_utils.py @@ -2,6 +2,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows from grass.script import utils @@ -39,6 +40,7 @@ def test_bytes(self): def test_unicode(self): self.assertEqual(b"text", utils.encode("text")) + @xfail_windows def test_bytes_garbage_in_out(self): """If the input is bytes we should not touch it for encoding""" self.assertEqual( diff --git a/python/grass/script/utils.py b/python/grass/script/utils.py index 13d049412a4..aa7d53375dd 100644 --- a/python/grass/script/utils.py +++ b/python/grass/script/utils.py @@ -28,6 +28,8 @@ import random import string +from pathlib import Path + def float_or_dms(s): """Convert DMS to float. @@ -89,8 +91,8 @@ def diff_files(filename_a, filename_b): import difflib differ = difflib.Differ() - fh_a = open(filename_a, "r") - fh_b = open(filename_b, "r") + fh_a = open(filename_a) + fh_b = open(filename_b) return list(differ.compare(fh_a.readlines(), fh_b.readlines())) @@ -371,10 +373,9 @@ def get_lib_path(modname, libname=None): getenv("GRASS_ADDON_BASE") and libname and isdir(join(getenv("GRASS_ADDON_BASE"), "etc", modname, libname)) - ): - path = join(getenv("GRASS_ADDON_BASE"), "etc", modname) - elif getenv("GRASS_ADDON_BASE") and isdir( - join(getenv("GRASS_ADDON_BASE"), "etc", modname) + ) or ( + getenv("GRASS_ADDON_BASE") + and isdir(join(getenv("GRASS_ADDON_BASE"), "etc", modname)) ): path = join(getenv("GRASS_ADDON_BASE"), "etc", modname) elif getenv("GRASS_ADDON_BASE") and isdir( @@ -383,7 +384,7 @@ def get_lib_path(modname, libname=None): path = join(os.getenv("GRASS_ADDON_BASE"), modname, modname) else: # used by g.extension compilation process - cwd = os.getcwd() + cwd = str(Path.cwd()) idx = cwd.find(modname) if idx < 0: return None @@ -463,10 +464,10 @@ def set_path(modulename, dirname=None, path="."): import sys # TODO: why dirname is checked first - the logic should be revised - pathlib = None + _pathlib = None if dirname: - pathlib = os.path.join(path, dirname) - if pathlib and os.path.exists(pathlib): + _pathlib = os.path.join(path, dirname) + if _pathlib and os.path.exists(_pathlib): # we are running the script from the script directory, therefore # we add the path to sys.path to reach the directory (dirname) sys.path.append(os.path.abspath(path)) @@ -477,7 +478,7 @@ def set_path(modulename, dirname=None, path="."): pathname = os.path.join(modulename, dirname) if dirname else modulename raise ImportError( "Not able to find the path '%s' directory " - "(current dir '%s')." % (pathname, os.getcwd()) + "(current dir '%s')." % (pathname, Path.cwd()) ) sys.path.insert(0, path) diff --git a/python/grass/temporal/metadata.py b/python/grass/temporal/metadata.py index 6094bbbe146..369fadc03cf 100644 --- a/python/grass/temporal/metadata.py +++ b/python/grass/temporal/metadata.py @@ -251,6 +251,26 @@ def get_max(self): min = property(fget=get_min, fset=set_min) max = property(fget=get_max, fset=set_max) + def print_info(self): + """Print information about this class in human readable style""" + self._print_info_body(shell=False) + + def print_shell_info(self): + """Print information about this class in shell style""" + self._print_info_body(shell=True) + + def _print_info_head(self, shell=False): + """Print information about this class (head part). + + No header printed in shell style mode. + + :param bool shell: True for human readable style otherwise shell style + """ + if not shell: + print( + " +-------------------- Metadata information ----------------------------------+" # noqa: E501 + ) + def _print_info_body(self, shell=False): """Print information about this class (body part). @@ -331,6 +351,7 @@ class RasterMetadata(RasterMetadataBase): | East-west resolution:....... 0.1 | Minimum value:.............. 0.0 | Maximum value:.............. 100.0 + | Semantic label:............. None >>> meta.print_shell_info() datatype=CELL cols=100 @@ -340,6 +361,7 @@ class RasterMetadata(RasterMetadataBase): ewres=0.1 min=0.0 max=100.0 + semantic_label=None """ @@ -384,17 +406,19 @@ def get_semantic_label(self): semantic_label = property(fget=get_semantic_label, fset=set_semantic_label) - def _print_info_body(self, shell=False): - """Print information about this class (body part). + def print_info(self): + """Print information about this class.""" + self._print_info_head(shell=False) + self._print_info_body(shell=False) + # semantic label section (raster specific only) + print(" | Semantic label:............. " + str(self.get_semantic_label())) - :param bool shell: True for human readable style otherwise shell style - """ - super()._print_info_body(shell) + def print_shell_info(self): + """Print information about this class in shell style""" + self._print_info_head(shell=True) + self._print_info_body(shell=True) # semantic label section (raster specific only) - if shell: - print("semantic_label=" + str(self.get_semantic_label())) - else: - print(" | Semantic label:............. " + str(self.get_semantic_label())) + print("semantic_label=" + str(self.get_semantic_label())) ############################################################################### @@ -539,18 +563,19 @@ def get_tbres(self): depths = property(fget=get_depths, fset=set_depths) tbres = property(fget=get_tbres, fset=set_tbres) - def _print_info_body(self, shell=False): - """Print information about this class (body part). + def print_info(self): + """Print information about this class.""" + self._print_info_head(shell=False) + self._print_info_body(shell=False) + print(" | Number of depths:........... " + str(self.get_depths())) + print(" | Top-Bottom resolution:...... " + str(self.get_tbres())) - :param bool shell: True for human readable style otherwise shell style - """ - super()._print_info_body(shell) - if shell: - print("depths=" + str(self.get_depths())) - print("tbres=" + str(self.get_tbres())) - else: - print(" | Number of depths:........... " + str(self.get_depths())) - print(" | Top-Bottom resolution:...... " + str(self.get_tbres())) + def print_shell_info(self): + """Print information about this class in shell style""" + self._print_info_head(shell=True) + self._print_info_body(shell=True) + print("depths=" + str(self.get_depths())) + print("tbres=" + str(self.get_tbres())) ############################################################################### @@ -870,53 +895,40 @@ def get_number_of_volumes(self): number_of_holes = property(fget=get_number_of_holes, fset=set_number_of_holes) number_of_volumes = property(fget=get_number_of_volumes, fset=set_number_of_volumes) - def _print_info_body(self, shell=False): - """Print information about this class (body part). + def print_info(self): + """Print information about this class in human readable style""" + print( + " +-------------------- Metadata information ----------------------------------+" # noqa: E501 + ) + print(" | Is map 3d .................. " + str(self.get_3d_info())) + print(" | Number of points ........... " + str(self.get_number_of_points())) + print(" | Number of lines ............ " + str(self.get_number_of_lines())) + print(" | Number of boundaries ....... " + str(self.get_number_of_boundaries())) + print(" | Number of centroids ........ " + str(self.get_number_of_centroids())) + print(" | Number of faces ............ " + str(self.get_number_of_faces())) + print(" | Number of kernels .......... " + str(self.get_number_of_kernels())) + print(" | Number of primitives ....... " + str(self.get_number_of_primitives())) + print(" | Number of nodes ............ " + str(self.get_number_of_nodes())) + print(" | Number of areas ............ " + str(self.get_number_of_areas())) + print(" | Number of islands .......... " + str(self.get_number_of_islands())) + print(" | Number of holes ............ " + str(self.get_number_of_holes())) + print(" | Number of volumes .......... " + str(self.get_number_of_volumes())) - :param bool shell: True for human readable style otherwise shell style - """ - if shell: - print("is_3d=" + str(self.get_3d_info())) - print("points=" + str(self.get_number_of_points())) - print("lines=" + str(self.get_number_of_lines())) - print("boundaries=" + str(self.get_number_of_boundaries())) - print("centroids=" + str(self.get_number_of_centroids())) - print("faces=" + str(self.get_number_of_faces())) - print("kernels=" + str(self.get_number_of_kernels())) - print("primitives=" + str(self.get_number_of_primitives())) - print("nodes=" + str(self.get_number_of_nodes())) - print("areas=" + str(self.get_number_of_areas())) - print("islands=" + str(self.get_number_of_islands())) - print("holes=" + str(self.get_number_of_holes())) - print("volumes=" + str(self.get_number_of_volumes())) - else: - print(" | Is map 3d .................. " + str(self.get_3d_info())) - print(" | Number of points ........... " + str(self.get_number_of_points())) - print(" | Number of lines ............ " + str(self.get_number_of_lines())) - print( - " | Number of boundaries ....... " - + str(self.get_number_of_boundaries()) - ) - print( - " | Number of centroids ........ " + str(self.get_number_of_centroids()) - ) - print(" | Number of faces ............ " + str(self.get_number_of_faces())) - print( - " | Number of kernels .......... " + str(self.get_number_of_kernels()) - ) - print( - " | Number of primitives ....... " - + str(self.get_number_of_primitives()) - ) - print(" | Number of nodes ............ " + str(self.get_number_of_nodes())) - print(" | Number of areas ............ " + str(self.get_number_of_areas())) - print( - " | Number of islands .......... " + str(self.get_number_of_islands()) - ) - print(" | Number of holes ............ " + str(self.get_number_of_holes())) - print( - " | Number of volumes .......... " + str(self.get_number_of_volumes()) - ) + def print_shell_info(self): + """Print information about this class in shell style""" + print("is_3d=" + str(self.get_3d_info())) + print("points=" + str(self.get_number_of_points())) + print("lines=" + str(self.get_number_of_lines())) + print("boundaries=" + str(self.get_number_of_boundaries())) + print("centroids=" + str(self.get_number_of_centroids())) + print("faces=" + str(self.get_number_of_faces())) + print("kernels=" + str(self.get_number_of_kernels())) + print("primitives=" + str(self.get_number_of_primitives())) + print("nodes=" + str(self.get_number_of_nodes())) + print("areas=" + str(self.get_number_of_areas())) + print("islands=" + str(self.get_number_of_islands())) + print("holes=" + str(self.get_number_of_holes())) + print("volumes=" + str(self.get_number_of_volumes())) ############################################################################### @@ -1034,13 +1046,11 @@ def get_number_of_maps(self): def print_info(self): """Print information about this class in human readable style""" - self._print_info_head(shell=False) self._print_info_body(shell=False) self._print_info_tail(shell=False) def print_shell_info(self): """Print information about this class in shell style""" - self._print_info_head(shell=True) self._print_info_body(shell=True) self._print_info_tail(shell=True) @@ -1056,6 +1066,12 @@ def _print_info_head(self, shell=False): " +-------------------- Metadata information ----------------------------------+" # noqa: E501 ) + def _print_info_body(self, shell=False): + """Print information about this class (body part). + + :param bool shell: True for human readable style otherwise shell style + """ + def _print_info_tail(self, shell=False): """Print information about this class (tail part). @@ -1476,6 +1492,16 @@ def get_semantic_labels(self): number_of_semantic_labels = property(fget=get_number_of_semantic_labels) semantic_labels = property(fget=get_semantic_labels) + def print_info(self): + """Print information about this class in human readable style""" + self._print_info_head(shell=False) + super().print_info() + + def print_shell_info(self): + """Print information about this class in shell style""" + self._print_info_head(shell=True) + super().print_shell_info() + def _print_info_body(self, shell=False): """Print information about this class (body part). @@ -1562,6 +1588,8 @@ class STR3DSMetadata(STDSRasterMetadataBase): | Command history: >>> meta.print_shell_info() raster3d_register=None + tbres_min=None + tbres_max=None nsres_min=None nsres_max=None ewres_min=None @@ -1570,8 +1598,6 @@ class STR3DSMetadata(STDSRasterMetadataBase): min_max=None max_min=None max_max=None - tbres_min=None - tbres_max=None aggregation_type=None number_of_maps=None @@ -1624,6 +1650,16 @@ def get_tbres_max(self): tbres_min = property(fget=get_tbres_min) tbres_max = property(fget=get_tbres_max) + def print_info(self): + """Print information about this class in human readable style""" + self._print_info_head(shell=False) + super().print_info() + + def print_shell_info(self): + """Print information about this class in shell style""" + self._print_info_head(shell=True) + super().print_shell_info() + def _print_info_body(self, shell=False): """Print information about this class (body part). @@ -1889,6 +1925,16 @@ def get_number_of_volumes(self): number_of_holes = property(fget=get_number_of_holes) number_of_volumes = property(fget=get_number_of_volumes) + def print_info(self): + """Print information about this class in human readable style""" + self._print_info_head(shell=False) + super().print_info() + + def print_shell_info(self): + """Print information about this class in shell style""" + self._print_info_head(shell=True) + super().print_shell_info() + def _print_info_body(self, shell=False): """Print information about this class (body part). diff --git a/python/grass/temporal/register.py b/python/grass/temporal/register.py index 0cd339946b9..8b7852ab557 100644 --- a/python/grass/temporal/register.py +++ b/python/grass/temporal/register.py @@ -161,7 +161,7 @@ def register_maps_in_space_time_dataset( if hasattr(file, "readline"): fd = file else: - fd = open(file, "r") + fd = open(file) line = True while True: diff --git a/python/grass/temporal/spatial_extent.py b/python/grass/temporal/spatial_extent.py index 329a0a4a684..26498e0385e 100644 --- a/python/grass/temporal/spatial_extent.py +++ b/python/grass/temporal/spatial_extent.py @@ -875,7 +875,7 @@ def cover_2d(self, extent) -> bool: if eS > S and eS < N: edge_count += 1 - return not edge_count == 0 + return edge_count != 0 def cover(self, extent) -> bool: """Return True if this extent covers the provided spatial @@ -956,7 +956,7 @@ def cover(self, extent) -> bool: if eB > B and eB < T: edge_count += 1 - return not edge_count == 0 + return edge_count != 0 def covered_2d(self, extent): """Return True if this extent is covered by the provided spatial diff --git a/python/grass/temporal/stds_export.py b/python/grass/temporal/stds_export.py index ff2731b102a..a01b94e1071 100644 --- a/python/grass/temporal/stds_export.py +++ b/python/grass/temporal/stds_export.py @@ -348,7 +348,7 @@ def export_stds( """ # Save current working directory path - old_cwd = os.getcwd() + old_cwd = Path.cwd() # Create the temporary directory and jump into it new_cwd = tempfile.mkdtemp(dir=directory) diff --git a/python/grass/temporal/stds_import.py b/python/grass/temporal/stds_import.py index ae587cadb0f..006ee6a387e 100644 --- a/python/grass/temporal/stds_import.py +++ b/python/grass/temporal/stds_import.py @@ -286,7 +286,7 @@ def import_stds( # We use a new list file name for map registration new_list_file_name = list_file_name + "_new" # Save current working directory path - old_cwd = os.getcwd() + old_cwd = Path.cwd() # Switch into the data directory os.chdir(directory) @@ -372,7 +372,7 @@ def import_stds( fs = "|" maplist = [] mapset = get_current_mapset() - list_file = open(list_file_name, "r") + list_file = open(list_file_name) new_list_file = open(new_list_file_name, "w") # get number of lines to correctly form the suffix @@ -426,7 +426,7 @@ def import_stds( # Read the init file fs = "=" init = {} - init_file = open(init_file_name, "r") + init_file = open(init_file_name) while True: line = init_file.readline() if not line: diff --git a/python/grass/temporal/temporal_algebra.py b/python/grass/temporal/temporal_algebra.py index 1b9a126c635..1cf1cfd7366 100644 --- a/python/grass/temporal/temporal_algebra.py +++ b/python/grass/temporal/temporal_algebra.py @@ -1275,15 +1275,9 @@ def check_stds(self, input, clear=False, stds_type=None, check_type=True): self.temporaltype = "absolute" elif map_i.is_time_relative() and self.temporaltype is None: self.temporaltype = "relative" - elif map_i.is_time_absolute() and self.temporaltype == "relative": - self.msgr.fatal( - _( - "Wrong temporal type of space time dataset " - "<%s> <%s> time is required" - ) - % (id_input, self.temporaltype) - ) - elif map_i.is_time_relative() and self.temporaltype == "absolute": + elif ( + map_i.is_time_absolute() and self.temporaltype == "relative" + ) or (map_i.is_time_relative() and self.temporaltype == "absolute"): self.msgr.fatal( _( "Wrong temporal type of space time dataset " @@ -1299,13 +1293,9 @@ def check_stds(self, input, clear=False, stds_type=None, check_type=True): maplist = input # Create map_value as empty list item. for map_i in maplist: - if "map_value" not in dir(map_i): + if ("map_value" not in dir(map_i)) or clear: map_i.map_value = [] - elif clear: - map_i.map_value = [] - if "condition_value" not in dir(map_i): - map_i.condition_value = [] - elif clear: + if ("condition_value" not in dir(map_i)) or clear: map_i.condition_value = [] else: self.msgr.fatal(_("Wrong type of input " + str(input))) diff --git a/python/grass/temporal/testsuite/unittests_temporal_raster_algebra_equal_ts.py b/python/grass/temporal/testsuite/unittests_temporal_raster_algebra_equal_ts.py index 92397cd825c..f96ed12899f 100644 --- a/python/grass/temporal/testsuite/unittests_temporal_raster_algebra_equal_ts.py +++ b/python/grass/temporal/testsuite/unittests_temporal_raster_algebra_equal_ts.py @@ -12,6 +12,7 @@ import grass.temporal as tgis from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows class TestTemporalRasterAlgebraImplicitAggregation(TestCase): @@ -64,6 +65,7 @@ def tearDownClass(cls): cls.runModule("t.unregister", maps="singletmap", quiet=True) cls.del_temp_region() + @xfail_windows def test_simple_operator(self): """Test implicit aggregation @@ -149,6 +151,7 @@ def test_single_map_complex_operator(self): self.assertEqual(D.check_temporal_topology(), True) self.assertEqual(D.get_granularity(), None) + @xfail_windows def test_single_map_simple_operator(self): """Test implicit aggregation diff --git a/raster/r.contour/testsuite/test_r_contour.py b/raster/r.contour/testsuite/test_r_contour.py index 9fc0fc17469..c9ee4c9876a 100644 --- a/raster/r.contour/testsuite/test_r_contour.py +++ b/raster/r.contour/testsuite/test_r_contour.py @@ -62,7 +62,7 @@ def test_raster_contour(self): self.assertModule("v.db.select", map=self.output, file="testReport") self.assertFileExists("testReport", msg="testReport file was not created") if os.path.isfile("testReport"): - file = open("testReport", "r") + file = open("testReport") fileData = file.read() self.assertMultiLineEqual(fileData, self.test_ref_str) file.close() @@ -87,7 +87,7 @@ def test_raster_contour_cut(self): self.assertModule("v.db.select", map=self.output + "_cut", file="testReportCut") self.assertFileExists("testReportCut", msg="testReportCut file was not created") if os.path.isfile("testReportCut"): - file = open("testReportCut", "r") + file = open("testReportCut") fileData = file.read() self.assertMultiLineEqual(fileData, self.test_ref_str) file.close() diff --git a/raster/r.fill.dir/filldir.c b/raster/r.fill.dir/filldir.c index 55380b7dfc5..cfe0ae69586 100644 --- a/raster/r.fill.dir/filldir.c +++ b/raster/r.fill.dir/filldir.c @@ -145,14 +145,20 @@ void filldir(int fe, int fd, int nl, struct band3 *bnd) CELL *dir; /* fill single-cell depressions, except on outer rows and columns */ - lseek(fe, 0, SEEK_SET); + if (lseek(fe, 0, SEEK_SET) == -1) { + G_fatal_error(_("Unable to seek: %s"), strerror(errno)); + } advance_band3(fe, bnd); advance_band3(fe, bnd); for (i = 1; i < nl - 1; i += 1) { - lseek(fe, (off_t)(i + 1) * bnd->sz, SEEK_SET); + if (lseek(fe, (off_t)(i + 1) * bnd->sz, SEEK_SET) == -1) { + G_fatal_error(_("Unable to seek: %s"), strerror(errno)); + } advance_band3(fe, bnd); if (fill_row(nl, bnd->ns, bnd)) { - lseek(fe, (off_t)i * bnd->sz, SEEK_SET); + if (lseek(fe, (off_t)i * bnd->sz, SEEK_SET) == -1) { + G_fatal_error(_("Unable to seek: %s"), strerror(errno)); + } if (write(fe, bnd->b[1], bnd->sz) < 0) G_fatal_error(_("File writing error in %s() %d:%s"), __func__, errno, strerror(errno)); @@ -172,8 +178,12 @@ void filldir(int fe, int fd, int nl, struct band3 *bnd) dir = G_calloc(bnd->ns, sizeof(CELL)); bufsz = bnd->ns * sizeof(CELL); - lseek(fe, 0, SEEK_SET); - lseek(fd, 0, SEEK_SET); + if (lseek(fe, 0, SEEK_SET) == -1) { + G_fatal_error(_("Unable to seek: %s"), strerror(errno)); + } + if (lseek(fd, 0, SEEK_SET) == -1) { + G_fatal_error(_("Unable to seek: %s"), strerror(errno)); + } advance_band3(fe, bnd); for (i = 0; i < nl; i += 1) { advance_band3(fe, bnd); diff --git a/raster/r.in.gdal/testsuite/test_r_in_gdal.py b/raster/r.in.gdal/testsuite/test_r_in_gdal.py index 355e9b74eda..477ffca89c8 100644 --- a/raster/r.in.gdal/testsuite/test_r_in_gdal.py +++ b/raster/r.in.gdal/testsuite/test_r_in_gdal.py @@ -4,6 +4,7 @@ """ import unittest +import sys from subprocess import check_output @@ -306,12 +307,13 @@ def test_netCDF_3d_5(self): test_gdal_import_map.0000000105 """ - text_from_file = open("map_names_file.txt", "r").read() + text_from_file = open("map_names_file.txt").read() self.assertLooksLike(map_list, text_from_file) @unittest.skipIf( - tuple( + not sys.platform.startswith("win") + and tuple( map( int, check_output(["gdal-config", "--version"]) diff --git a/raster/r.kappa/testsuite/test_r_kappa.py b/raster/r.kappa/testsuite/test_r_kappa.py index 56e356ca20b..620a4f4979b 100644 --- a/raster/r.kappa/testsuite/test_r_kappa.py +++ b/raster/r.kappa/testsuite/test_r_kappa.py @@ -21,6 +21,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test from grass.gunittest.checkers import keyvalue_equals +from grass.gunittest.utils import xfail_windows class MatrixCorrectnessTest(TestCase): @@ -474,6 +475,7 @@ def test_stdout(self): keyvalue_equals(self.expected_outputs[i], json_out, precision=4) ) + @xfail_windows def test_file(self): for i in range(len(self.references)): f = NamedTemporaryFile() diff --git a/raster/r.mfilter/main.c b/raster/r.mfilter/main.c index 17ecd2182be..ad0a9dd35ae 100644 --- a/raster/r.mfilter/main.c +++ b/raster/r.mfilter/main.c @@ -132,8 +132,8 @@ int main(int argc, char **argv) "threads setting.")); nprocs = 1; #endif - if (nprocs > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (nprocs > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); nprocs = 1; } out_name = opt2->answer; diff --git a/raster/r.mfilter/testsuite/test_r_mfilter.py b/raster/r.mfilter/testsuite/test_r_mfilter.py index 3d1764b4894..c18aa7c9273 100644 --- a/raster/r.mfilter/testsuite/test_r_mfilter.py +++ b/raster/r.mfilter/testsuite/test_r_mfilter.py @@ -1,6 +1,7 @@ from tempfile import NamedTemporaryFile from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows class TestNeighbors(TestCase): @@ -202,6 +203,7 @@ def tearDownClass(cls): "g.remove", flags="f", type="raster", name=",".join(cls.to_remove) ) + @xfail_windows def test_sequential(self): """Test output with sequential filter type.""" test_case = "test_sequential" @@ -235,6 +237,7 @@ def test_sequential(self): precision=1e-5, ) + @xfail_windows def test_parallel(self): """Test output with parallel filter type.""" test_case = "test_parallel" @@ -268,6 +271,7 @@ def test_parallel(self): precision=1e-5, ) + @xfail_windows def test_sequential_null(self): """Test output with sequential filter type with null mode enabled.""" test_case = "test_sequential_null" @@ -301,6 +305,7 @@ def test_sequential_null(self): precision=1e-5, ) + @xfail_windows def test_parallel_null(self): """Test output with parallel filter type with null mode enabled.""" test_case = "test_parallel_null" @@ -361,6 +366,7 @@ def test_parallel_null(self): precision=1e-5, ) + @xfail_windows def test_multiple_filters(self): """Test output with multiple filters.""" test_case = "test_multiple_filters" @@ -394,6 +400,7 @@ def test_multiple_filters(self): precision=1e-5, ) + @xfail_windows def test_repeated_filters(self): """Test output with repeated filters.""" test_case = "test_repeated_filters" diff --git a/raster/r.neighbors/main.c b/raster/r.neighbors/main.c index 93cbaa9e3da..8885460a564 100644 --- a/raster/r.neighbors/main.c +++ b/raster/r.neighbors/main.c @@ -310,8 +310,8 @@ int main(int argc, char *argv[]) "threads setting.")); ncb.threads = 1; #endif - if (ncb.threads > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (ncb.threads > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); ncb.threads = 1; } if (strcmp(parm.weighting_function->answer, "none") && flag.circle->answer) diff --git a/raster/r.patch/main.c b/raster/r.patch/main.c index 3835e287440..c54482db13a 100644 --- a/raster/r.patch/main.c +++ b/raster/r.patch/main.c @@ -113,8 +113,8 @@ int main(int argc, char *argv[]) "threads setting.")); nprocs = 1; #endif - if (nprocs > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (nprocs > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); nprocs = 1; } diff --git a/raster/r.report/testsuite/test_r_report.py b/raster/r.report/testsuite/test_r_report.py index 53e49fdc174..86a16c27adb 100644 --- a/raster/r.report/testsuite/test_r_report.py +++ b/raster/r.report/testsuite/test_r_report.py @@ -17,6 +17,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows class TestRasterreport(TestCase): @@ -294,6 +295,7 @@ def test_json(self): data = json.loads(module.outputs.stdout) self._assert_report_equal(reference, data) + @xfail_windows def test_json2(self): """Test JSON format with more options""" reference = { diff --git a/raster/r.resamp.filter/main.c b/raster/r.resamp.filter/main.c index b1f6ed3e530..e74f3598629 100644 --- a/raster/r.resamp.filter/main.c +++ b/raster/r.resamp.filter/main.c @@ -494,8 +494,8 @@ int main(int argc, char *argv[]) "threads setting.")); nprocs = 1; #endif - if (nprocs > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (nprocs > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active make.")); nprocs = 1; } if (parm.radius->answer) { diff --git a/raster/r.resamp.interp/main.c b/raster/r.resamp.interp/main.c index 734f3306bf8..2319400b0fc 100644 --- a/raster/r.resamp.interp/main.c +++ b/raster/r.resamp.interp/main.c @@ -132,8 +132,8 @@ int main(int argc, char *argv[]) "threads setting.")); threads = 1; #endif - if (threads > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (threads > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); threads = 1; } bufrows = atoi(memory->answer) * (((1 << 20) / sizeof(DCELL)) / dst_w.cols); diff --git a/raster/r.series/main.c b/raster/r.series/main.c index bfecbf117c0..ade93d8f185 100644 --- a/raster/r.series/main.c +++ b/raster/r.series/main.c @@ -227,8 +227,8 @@ int main(int argc, char *argv[]) "threads setting.")); nprocs = 1; #endif - if (nprocs > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (nprocs > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); nprocs = 1; } lo = -INFINITY; diff --git a/raster/r.sim/r.sim.sediment/Makefile b/raster/r.sim/r.sim.sediment/Makefile index 4bc07478ec9..13ca19d09dc 100644 --- a/raster/r.sim/r.sim.sediment/Makefile +++ b/raster/r.sim/r.sim.sediment/Makefile @@ -4,8 +4,8 @@ PGM=r.sim.sediment EXTRA_CLEAN_DIRS=doxygenhtml -LIBES = $(SIMLIB) $(GMATHLIB) $(GISLIB) $(OPENMP_LIBPATH) $(OPENMP_LIB) -DEPENDENCIES = $(SIMDEP) $(GMATHDEP) $(GISDEP) +LIBES = $(SIMLIB) $(GMATHLIB) $(GISLIB) $(RASTERLIB) $(OPENMP_LIBPATH) $(OPENMP_LIB) +DEPENDENCIES = $(SIMDEP) $(GMATHDEP) $(GISDEP) $(RASTERDEP) EXTRA_INC = $(OPENMP_INCPATH) $(VECT_INC) EXTRA_CFLAGS = -I ../simlib $(VECT_CFLAGS) $(OPENMP_CFLAGS) diff --git a/raster/r.sim/r.sim.sediment/main.c b/raster/r.sim/r.sim.sediment/main.c index fcdd9e98f89..dd082fe0f81 100644 --- a/raster/r.sim/r.sim.sediment/main.c +++ b/raster/r.sim/r.sim.sediment/main.c @@ -70,6 +70,7 @@ #endif #include #include +#include #include #include #include @@ -380,8 +381,8 @@ int main(int argc, char *argv[]) #else threads = 1; #endif - if (threads > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (threads > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); threads = 1; } G_message(_("Number of threads: %d"), threads); diff --git a/raster/r.sim/r.sim.water/Makefile b/raster/r.sim/r.sim.water/Makefile index 36121ebf82e..47037823fae 100644 --- a/raster/r.sim/r.sim.water/Makefile +++ b/raster/r.sim/r.sim.water/Makefile @@ -4,8 +4,8 @@ PGM=r.sim.water EXTRA_CLEAN_DIRS=doxygenhtml -LIBES = $(SIMLIB) $(GMATHLIB) $(GISLIB) $(OPENMP_LIBPATH) $(OPENMP_LIB) -DEPENDENCIES = $(SIMDEP) $(GMATHDEP) $(GISDEP) +LIBES = $(SIMLIB) $(GMATHLIB) $(GISLIB) $(RASTERLIB) $(OPENMP_LIBPATH) $(OPENMP_LIB) +DEPENDENCIES = $(SIMDEP) $(GMATHDEP) $(GISDEP) $(RASTERDEP) EXTRA_INC = $(VECT_INC) $(OPENMP_INCPATH) EXTRA_CFLAGS = -I ../simlib $(VECT_CFLAGS) $(OPENMP_CFLAGS) diff --git a/raster/r.sim/r.sim.water/main.c b/raster/r.sim/r.sim.water/main.c index 3998e5524ac..61f128f5504 100644 --- a/raster/r.sim/r.sim.water/main.c +++ b/raster/r.sim/r.sim.water/main.c @@ -77,6 +77,7 @@ #endif #include #include +#include #include #include #include @@ -407,8 +408,8 @@ int main(int argc, char *argv[]) #else threads = 1; #endif - if (threads > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (threads > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); threads = 1; } G_message(_("Number of threads: %d"), threads); diff --git a/raster/r.slope.aspect/main.c b/raster/r.slope.aspect/main.c index 86b4d5f5607..d302e02b22c 100644 --- a/raster/r.slope.aspect/main.c +++ b/raster/r.slope.aspect/main.c @@ -305,8 +305,8 @@ int main(int argc, char *argv[]) "threads setting.")); nprocs = 1; #endif - if (nprocs > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (nprocs > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); nprocs = 1; } radians_to_degrees = 180.0 / M_PI; diff --git a/raster/r.sun/main.c b/raster/r.sun/main.c index 9e45e184dac..e325cb637f0 100644 --- a/raster/r.sun/main.c +++ b/raster/r.sun/main.c @@ -591,8 +591,8 @@ int main(int argc, char *argv[]) #else threads = 1; #endif - if (threads > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (threads > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); threads = 1; } G_message(_("Number of threads <%d>"), threads); diff --git a/raster/r.thin/io.c b/raster/r.thin/io.c index d5ba76245c3..3b2d0bd8aa3 100644 --- a/raster/r.thin/io.c +++ b/raster/r.thin/io.c @@ -67,13 +67,17 @@ int put_a_row(int row, CELL *buf) static int read_row(int file, void *buf, int row, int buf_len) { - lseek(file, ((off_t)row) * buf_len, 0); + if (lseek(file, ((off_t)row) * buf_len, 0) == -1) { + G_fatal_error(_("Unable to seek: %s"), strerror(errno)); + } return (read(file, buf, buf_len) == buf_len); } static int write_row(int file, const void *buf, int row, int buf_len) { - lseek(file, ((off_t)row) * buf_len, 0); + if (lseek(file, ((off_t)row) * buf_len, 0) == -1) { + G_fatal_error(_("Unable to seek: %s"), strerror(errno)); + } return (write(file, buf, buf_len) == buf_len); } diff --git a/raster/r.univar/r.univar_main.c b/raster/r.univar/r.univar_main.c index 805acfff27b..e56301a1364 100644 --- a/raster/r.univar/r.univar_main.c +++ b/raster/r.univar/r.univar_main.c @@ -192,8 +192,8 @@ int main(int argc, char *argv[]) sscanf(param.nprocs->answer, "%d", &nprocs); if (nprocs < 1) G_fatal_error(_("<%d> is not valid number of nprocs."), nprocs); - if (nprocs > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (nprocs > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); nprocs = 1; } #if defined(_OPENMP) diff --git a/raster/r.what/testsuite/test_r_what.py b/raster/r.what/testsuite/test_r_what.py index fcc312e6284..68bfdeea376 100644 --- a/raster/r.what/testsuite/test_r_what.py +++ b/raster/r.what/testsuite/test_r_what.py @@ -510,7 +510,7 @@ def test_raster_what_csv(self): ) self.assertFileExists(filename="result.csv", msg="CSV file was not created") if os.path.isfile("result.csv"): - file = open("result.csv", "r") + file = open("result.csv") fileData = file.read() self.assertLooksLike( actual=fileData, diff --git a/raster3d/r3.in.v5d/v5d.c b/raster3d/r3.in.v5d/v5d.c index 445d6f29b9f..54f75f2361a 100644 --- a/raster3d/r3.in.v5d/v5d.c +++ b/raster3d/r3.in.v5d/v5d.c @@ -63,6 +63,9 @@ #include #include #include +#include +#include + #include "binio.h" #include "v5d.h" #include "vis5d.h" @@ -1232,7 +1235,10 @@ static int read_comp_header(int f, v5dstruct *v) unsigned int id; /* reset file position to start of file */ - lseek(f, 0, SEEK_SET); + if (lseek(f, 0, SEEK_SET) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } /* read file ID */ read_int4(f, (int *)&id); @@ -1334,8 +1340,8 @@ static int read_comp_header(int f, v5dstruct *v) /* skip ahead by 'gridsize' bytes */ if (lseek(f, gridsize, SEEK_CUR) == -1) { - printf("Error: Unexpected end of file, "); - printf("file may be corrupted.\n"); + G_warning(_("Error: Unexpected end of file, file may be " + "corrupted.")); return 0; } min = -(125.0 + gb) / ga; @@ -1478,7 +1484,10 @@ static int read_comp_grid(v5dstruct *v, int time, int var, float *ga, float *gb, /* move to position in file */ pos = grid_position(v, time, var); - lseek(f, pos, SEEK_SET); + if (lseek(f, pos, SEEK_SET) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } if (v->FileFormat == 0x80808083) { /* read McIDAS grid and file numbers */ @@ -1551,7 +1560,13 @@ static int read_comp_grid(v5dstruct *v, int time, int var, float *ga, float *gb, */ static int read_v5d_header(v5dstruct *v) { -#define SKIP(N) lseek(f, N, SEEK_CUR) +#define SKIP(N) \ + do { \ + if (lseek(f, N, SEEK_CUR) == -1) { \ + G_warning(_("Unable to seek: %s"), strerror(errno)); \ + return 0; \ + } \ + } while (0) int end_of_header = 0; unsigned int id; int idlen, var, numargs; @@ -1870,13 +1885,19 @@ static int read_v5d_header(v5dstruct *v) case TAG_END: /* end of header */ end_of_header = 1; - lseek(f, length, SEEK_CUR); + if (lseek(f, length, SEEK_CUR) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } break; default: /* unknown tag, skip to next tag */ printf("Unknown tag: %d length=%d\n", tag, length); - lseek(f, length, SEEK_CUR); + if (lseek(f, length, SEEK_CUR) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } break; } } @@ -1966,7 +1987,10 @@ int v5dReadCompressedGrid(v5dstruct *v, int time, int var, float *ga, float *gb, /* move to position in file */ pos = grid_position(v, time, var); - lseek(v->FileDesc, pos, SEEK_SET); + if (lseek(v->FileDesc, pos, SEEK_SET) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } /* read ga, gb arrays */ read_float4_array(v->FileDesc, ga, v->Nl[var]); @@ -2118,7 +2142,10 @@ static int write_v5d_header(v5dstruct *v) } /* set file pointer to start of file */ - lseek(f, 0, SEEK_SET); + if (lseek(f, 0, SEEK_SET) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } v->CurPos = 0; /* @@ -2222,7 +2249,10 @@ static int write_v5d_header(v5dstruct *v) /* We're writing to a brand new file. Reserve 10000 bytes */ /* for future header growth. */ WRITE_TAG(v, TAG_END, 10000); - lseek(f, 10000, SEEK_CUR); + if (lseek(f, 10000, SEEK_CUR) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } /* Let file pointer indicate where first grid is stored */ v->FirstGridPos = ltell(f); @@ -2336,7 +2366,7 @@ int v5dWriteCompressedGrid(const v5dstruct *v, int time, int var, pos = grid_position(v, time, var); if (lseek(v->FileDesc, pos, SEEK_SET) < 0) { /* lseek failed, return error */ - printf("Error in v5dWrite[Compressed]Grid: seek failed, disk full?\n"); + G_warning(_("Unable to seek: %s"), strerror(errno)); return 0; } @@ -2452,9 +2482,15 @@ int v5dCloseFile(v5dstruct *v) if (v->Mode == 'w') { /* rewrite header because writing grids updates the minval and */ /* maxval fields */ - lseek(v->FileDesc, 0, SEEK_SET); + if (lseek(v->FileDesc, 0, SEEK_SET) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } status = write_v5d_header(v); - lseek(v->FileDesc, 0, SEEK_END); + if (lseek(v->FileDesc, 0, SEEK_END) == -1) { + G_warning(_("Unable to seek: %s"), strerror(errno)); + return 0; + } close(v->FileDesc); } else if (v->Mode == 'r') { diff --git a/scripts/d.correlate/d.correlate.py b/scripts/d.correlate/d.correlate.py index af681cce545..1068733a11b 100755 --- a/scripts/d.correlate/d.correlate.py +++ b/scripts/d.correlate/d.correlate.py @@ -73,7 +73,7 @@ def main(): gcore.run_command("r.stats", flags="cnA", input=(i, j), stdout=ofile) ofile.close() - ifile = open(tmpfile, "r") + ifile = open(tmpfile) first = True for line in ifile: f = line.rstrip("\r\n").split(" ") @@ -95,7 +95,7 @@ def main(): p = gcore.feed_command("d.graph", color=color) ofile = p.stdin - ifile = open(tmpfile, "r") + ifile = open(tmpfile) for line in ifile: f = line.rstrip("\r\n").split(" ") x = float(f[0]) diff --git a/scripts/d.frame/d.frame.py b/scripts/d.frame/d.frame.py index e9b44b21650..ff77316f0ec 100755 --- a/scripts/d.frame/d.frame.py +++ b/scripts/d.frame/d.frame.py @@ -91,7 +91,7 @@ def check_monitor(): def read_monitor_file(monitor, ftype="env"): mfile = check_monitor_file(monitor, ftype) try: - fd = open(mfile, "r") + fd = open(mfile) except OSError as e: fatal(_("Unable to get monitor info. %s"), e) diff --git a/scripts/db.univar/db.univar.py b/scripts/db.univar/db.univar.py index f941be42140..97dfbdb7dbe 100755 --- a/scripts/db.univar/db.univar.py +++ b/scripts/db.univar/db.univar.py @@ -77,7 +77,7 @@ def cleanup(): def sortfile(infile, outfile): - inf = open(infile, "r") + inf = open(infile) outf = open(outfile, "w") if gs.find_program("sort", "--help"): diff --git a/scripts/g.extension.all/g.extension.all.py b/scripts/g.extension.all/g.extension.all.py index 7f0b36f4dc6..1b0a2a88108 100644 --- a/scripts/g.extension.all/g.extension.all.py +++ b/scripts/g.extension.all/g.extension.all.py @@ -64,7 +64,7 @@ def get_extensions(): return [] # read XML file - fo = open(fXML, "r") + fo = open(fXML) try: tree = ET.fromstring(fo.read()) except Exception as e: @@ -104,7 +104,7 @@ def download_modules_xml_file(url, response_format, *args, **kwargs): try: response = urlopen(url, *args, **kwargs) - if not response.code == 200: + if response.code != 200: index = HTTP_STATUS_CODES.index(response.code) desc = HTTP_STATUS_CODES[index].description gs.fatal( diff --git a/scripts/g.extension/testsuite/test_addons_modules.py b/scripts/g.extension/testsuite/test_addons_modules.py index c1cab5c954a..9b99e7ac2ae 100644 --- a/scripts/g.extension/testsuite/test_addons_modules.py +++ b/scripts/g.extension/testsuite/test_addons_modules.py @@ -15,7 +15,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test from grass.gunittest.gmodules import SimpleModule -from grass.gunittest.utils import silent_rmtree +from grass.gunittest.utils import silent_rmtree, xfail_windows from grass.script.utils import decode import os @@ -52,6 +52,7 @@ class TestModulesMetadata(TestCase): url = "file://" + os.path.abspath("data") + @xfail_windows def test_listing(self): """List individual extensions/modules/addons""" module = SimpleModule("g.extension", flags="l", url=self.url) @@ -90,6 +91,7 @@ def tearDown(self): """Remove created files""" silent_rmtree(self.install_prefix) + @xfail_windows def test_directory_install(self): """Test installing extension from directory""" self.assertModule( @@ -102,6 +104,7 @@ def test_directory_install(self): for file in self.files: self.assertFileExists(file) + @xfail_windows def test_targz_install(self): """Test installing extension from local .tar.gz""" self.assertModule( @@ -113,6 +116,7 @@ def test_targz_install(self): for file in self.files: self.assertFileExists(file) + @xfail_windows def test_remote_targz_without_dir_install(self): """Test installing extension from (remote) .tar.gz without main dir""" self.assertModule( @@ -125,6 +129,7 @@ def test_remote_targz_without_dir_install(self): for file in self.files: self.assertFileExists(file) + @xfail_windows def test_remote_zip_install(self): """Test installing extension from .zip specified by URL (local)""" self.assertModule( diff --git a/scripts/g.extension/testsuite/test_addons_toolboxes.py b/scripts/g.extension/testsuite/test_addons_toolboxes.py index 5cbc0454a41..17d7e4b5b74 100644 --- a/scripts/g.extension/testsuite/test_addons_toolboxes.py +++ b/scripts/g.extension/testsuite/test_addons_toolboxes.py @@ -15,9 +15,11 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows import os + FULL_TOOLBOXES_OUTPUT = """\ Hydrology (HY) * r.stream.basins @@ -39,6 +41,7 @@ class TestToolboxesMetadata(TestCase): url = "file://" + os.path.abspath("data") + @xfail_windows def test_listing(self): """List toolboxes and their content""" module = SimpleModule("g.extension", flags="lt", url=self.url) diff --git a/scripts/g.search.modules/g.search.modules.py b/scripts/g.search.modules/g.search.modules.py index 85247517898..cb40cbdced5 100755 --- a/scripts/g.search.modules/g.search.modules.py +++ b/scripts/g.search.modules/g.search.modules.py @@ -66,6 +66,8 @@ import os import sys +from operator import itemgetter + from grass.script import core as grass from grass.exceptions import CalledModuleError @@ -199,7 +201,7 @@ def _search_module( WXGUIDIR = os.path.join(os.getenv("GISBASE"), "gui", "wxpython") filename = os.path.join(WXGUIDIR, "xml", "module_items.xml") - menudata_file = open(filename, "r") + menudata_file = open(filename) menudata = ET.parse(menudata_file) menudata_file.close() @@ -210,7 +212,7 @@ def _search_module( if os.getenv("GRASS_ADDON_BASE"): filename_addons = os.path.join(os.getenv("GRASS_ADDON_BASE"), "modules.xml") if os.path.isfile(filename_addons): - addon_menudata_file = open(filename_addons, "r") + addon_menudata_file = open(filename_addons) addon_menudata = ET.parse(addon_menudata_file) addon_menudata_file.close() addon_items = addon_menudata.findall("task") @@ -219,7 +221,7 @@ def _search_module( # add system-wide installed addons to modules list filename_addons_s = os.path.join(os.getenv("GISBASE"), "modules.xml") if os.path.isfile(filename_addons_s): - addon_menudata_file_s = open(filename_addons_s, "r") + addon_menudata_file_s = open(filename_addons_s) addon_menudata_s = ET.parse(addon_menudata_file_s) addon_menudata_file_s.close() addon_items_s = addon_menudata_s.findall("task") @@ -283,7 +285,7 @@ def _search_module( } ) - return sorted(found_modules, key=lambda k: k["name"]) + return sorted(found_modules, key=itemgetter("name")) def _basic_search(pattern, name, description, module_keywords) -> bool: @@ -311,10 +313,7 @@ def _exact_search(keyword, module_keywords): :param module_keywords: comma separated list of keywords """ module_keywords = module_keywords.split(",") - for current in module_keywords: - if keyword == current: - return True - return False + return keyword in module_keywords def _manpage_search(pattern, name): diff --git a/scripts/g.search.modules/testsuite/test_g_search_modules.py b/scripts/g.search.modules/testsuite/test_g_search_modules.py index d280ffc1441..75537eb8d78 100644 --- a/scripts/g.search.modules/testsuite/test_g_search_modules.py +++ b/scripts/g.search.modules/testsuite/test_g_search_modules.py @@ -15,6 +15,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows from grass.script.utils import decode import unittest @@ -65,6 +66,7 @@ def test_colored_terminal(self): stdout = decode(module.outputs.stdout).split() self.assertEqual(stdout[0], termcolor.colored("r.basins.fill", attrs=["bold"])) + @xfail_windows def test_manual_pages(self): module = SimpleModule("g.search.modules", keyword="kapri", flags="gm") self.assertModule(module) diff --git a/scripts/r.in.wms/wms_drv.py b/scripts/r.in.wms/wms_drv.py index 186157ad6db..dd666573cfe 100644 --- a/scripts/r.in.wms/wms_drv.py +++ b/scripts/r.in.wms/wms_drv.py @@ -736,9 +736,7 @@ def _findTileMats(self, tile_mats, region, bbox): best_diff = best_scale_den - scale_den mat_diff = mat_scale_den - scale_den - if (best_diff < mat_diff and mat_diff < 0) or ( - best_diff > mat_diff and best_diff > 0 - ): + if (best_diff < mat_diff < 0) or (best_diff > mat_diff and best_diff > 0): best_t_mat = t_mat best_scale_den = mat_scale_den @@ -1020,9 +1018,7 @@ def _parseTilePattern(self, group_t_patts, bbox, region): best_diff = best_res - res[comp_res] tile_diff = t_res[comp_res] - res[comp_res] - if (best_diff < tile_diff and tile_diff < 0) or ( - best_diff > tile_diff and best_diff > 0 - ): + if (best_diff < tile_diff < 0) or (best_diff > tile_diff and best_diff > 0): best_res = t_res[comp_res] best_patt = pattern diff --git a/scripts/r.in.wms/wms_gdal_drv.py b/scripts/r.in.wms/wms_gdal_drv.py index 69a49ca5429..830be2b593e 100644 --- a/scripts/r.in.wms/wms_gdal_drv.py +++ b/scripts/r.in.wms/wms_gdal_drv.py @@ -155,7 +155,7 @@ def _download(self): xml_file = self._createXML() # print xml file content for debug level 1 - file = open(xml_file, "r") + file = open(xml_file) gs.debug("WMS request XML:\n%s" % file.read(), 1) file.close() diff --git a/scripts/r.pack/r.pack.py b/scripts/r.pack/r.pack.py index eb11547322c..5029f93c6d2 100644 --- a/scripts/r.pack/r.pack.py +++ b/scripts/r.pack/r.pack.py @@ -37,6 +37,8 @@ import atexit import tarfile +from pathlib import Path + from grass.script.utils import try_rmdir, try_remove from grass.script import core as grass @@ -80,7 +82,7 @@ def main(): grass.message(_("Packing <%s> to <%s>...") % (gfile["fullname"], outfile)) basedir = os.path.sep.join(os.path.normpath(gfile["file"]).split(os.path.sep)[:-2]) - olddir = os.getcwd() + olddir = Path.cwd() # copy elements info = grass.parse_command("r.info", flags="e", map=infile) @@ -93,7 +95,7 @@ def main(): if map_file["file"]: vrt = os.path.join(map_file["file"], "vrt") if os.path.exists(vrt): - with open(vrt, "r") as f: + with open(vrt) as f: for r in f: map, mapset = r.split("@") map_basedir = os.path.sep.join( diff --git a/scripts/r.semantic.label/testsuite/test_r_semantic_label.py b/scripts/r.semantic.label/testsuite/test_r_semantic_label.py index b7456e22633..990954dbb5f 100644 --- a/scripts/r.semantic.label/testsuite/test_r_semantic_label.py +++ b/scripts/r.semantic.label/testsuite/test_r_semantic_label.py @@ -27,7 +27,7 @@ def read_semantic_label(self): return rast.info.semantic_label def test_semantic_label_assign_not_current_mapset(self): - if not self.mapset == "PERMANENT": + if self.mapset != "PERMANENT": self.mapset.name = "PERMANENT" a_map = self.mapset.glist(type="raster")[0] module = SimpleModule( diff --git a/scripts/r.tileset/testsuite/test_r_tileset.py b/scripts/r.tileset/testsuite/test_r_tileset.py index 7f1c62ed1f5..cba9908d900 100644 --- a/scripts/r.tileset/testsuite/test_r_tileset.py +++ b/scripts/r.tileset/testsuite/test_r_tileset.py @@ -7,6 +7,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows from grass.script.utils import decode @@ -36,6 +37,7 @@ def tearDownClass(cls): """!Remove the temporary region""" cls.del_temp_region() + @xfail_windows def test_tiling(self): """Produce tiling test""" module = SimpleModule( diff --git a/scripts/v.centroids/testsuite/test_v_centroids.py b/scripts/v.centroids/testsuite/test_v_centroids.py index 4b0654748a5..a7f56464ef6 100644 --- a/scripts/v.centroids/testsuite/test_v_centroids.py +++ b/scripts/v.centroids/testsuite/test_v_centroids.py @@ -12,37 +12,47 @@ class TestVCentroids(TestCase): """Test v.centroids script""" - mapName = "busroute11" - outRouteMap = "busroute11_boundary" - fromType = "line" - toType = "boundary" - outAreaMap = "busroute11_area" + region_line = "region_line" + region_boundary = "region_boundary" + region_area = "region_area" + output = "output" @classmethod def setUpClass(cls): """Create an area from a closed line""" + cls.runModule("v.in.region", output=cls.region_line, type="line") + cls.runModule("v.in.region", output=cls.region_area, type="area") cls.runModule( "v.type", - input=cls.mapName, - output=cls.outRouteMap, - from_type=cls.fromType, - to_type=cls.toType, + input=cls.region_line, + output=cls.region_boundary, + from_type="line", + to_type="boundary", ) @classmethod def tearDownClass(cls): """Remove the generated maps""" cls.runModule( - "g.remove", flags="f", type="vector", name=(cls.outRouteMap, cls.outAreaMap) + "g.remove", + flags="f", + type="vector", + name=(cls.region_line, cls.region_area, cls.region_boundary), ) + def tearDown(self): + """Remove the generated maps""" + self.runModule("g.remove", flags="f", type="vector", name=self.output) + def test_area(self): """Adds missing centroids to closed boundaries test""" module = SimpleModule( - "v.centroids", input=self.outRouteMap, output=self.outAreaMap + "v.centroids", input=self.region_boundary, output=self.output ) self.assertModule(module) - self.assertVectorExists(self.outAreaMap) + self.assertVectorInfoEqualsVectorInfo( + self.output, self.region_area, precision=1e-6 + ) if __name__ == "__main__": diff --git a/scripts/v.pack/v.pack.py b/scripts/v.pack/v.pack.py index cd8d96b86cd..4468c888bcb 100755 --- a/scripts/v.pack/v.pack.py +++ b/scripts/v.pack/v.pack.py @@ -38,6 +38,8 @@ import tarfile import atexit +from pathlib import Path + from grass.script.utils import try_rmdir, try_remove from grass.script import core as grass from grass.script import vector @@ -128,7 +130,7 @@ def main(): tar.add(path, "PROJ_" + support) tar.close() - grass.message(_("Pack file <%s> created") % os.path.join(os.getcwd(), outfile)) + grass.message(_("Pack file <%s> created") % Path(outfile).resolve()) if __name__ == "__main__": diff --git a/scripts/v.rast.stats/testsuite/test_v_rast_stats.py b/scripts/v.rast.stats/testsuite/test_v_rast_stats.py index ca3111d14d8..56acbce5964 100644 --- a/scripts/v.rast.stats/testsuite/test_v_rast_stats.py +++ b/scripts/v.rast.stats/testsuite/test_v_rast_stats.py @@ -5,6 +5,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows from grass.pygrass.vector import VectorTopo from grass.pygrass.vector.geometry import Line from grass.pygrass.vector.geometry import Boundary @@ -71,6 +72,7 @@ def setUp(self): vt.table.conn.commit() vt.close() + @xfail_windows def test_1(self): # Output of v.rast.stats univar_string = """cat|value|label|a_minimum|a_maximum|a_sum @@ -91,6 +93,7 @@ def test_1(self): self.runModule(v_db_select) self.assertLooksLike(univar_string, str(v_db_select.outputs.stdout)) + @xfail_windows def test_line_d(self): output_str = """cat|name|a_median|a_number|a_range 1|first|192|3|1 @@ -109,6 +112,7 @@ def test_line_d(self): self.runModule(v_db_select) self.assertLooksLike(output_str, str(v_db_select.outputs.stdout)) + @xfail_windows def test_line(self): output_str = """cat|name|a_median|a_number|a_range 1|first|192|5|2 @@ -128,6 +132,7 @@ def test_line(self): self.runModule(v_db_select) self.assertLooksLike(output_str, str(v_db_select.outputs.stdout)) + @xfail_windows def test_zone_all(self): # Output of v.rast.stats univar_string = """cat|value|label|a_number|a_null_cells|a_minimum|a_maximum|a_range|a_average|a_stddev|a_variance|a_coeff_var|a_sum|a_first_quartile|a_median|a_third_quartile|a_percentile_90 @@ -143,6 +148,7 @@ def test_zone_all(self): self.runModule(v_db_select) self.assertLooksLike(univar_string, str(v_db_select.outputs.stdout)) + @xfail_windows def test_small_area_with_centroid(self): # Output of v.rast.stats univar_string = """cat|name|a_number|a_null_cells|a_minimum|a_maximum|a_range|a_average|a_stddev|a_variance|a_coeff_var|a_sum|a_first_quartile|a_median|a_third_quartile|a_percentile_90 diff --git a/scripts/v.report/v.report.py b/scripts/v.report/v.report.py index 18752a697cb..9da9a8b1de7 100755 --- a/scripts/v.report/v.report.py +++ b/scripts/v.report/v.report.py @@ -55,6 +55,8 @@ import sys import os +from operator import itemgetter + import grass.script as gs from grass.script.utils import separator, decode @@ -134,7 +136,7 @@ def main(): if p.returncode != 0: sys.exit(1) - records1.sort(key=lambda r: r[catcol]) + records1.sort(key=itemgetter(catcol)) if len(records1) == 0: try: diff --git a/scripts/v.unpack/v.unpack.py b/scripts/v.unpack/v.unpack.py index 419e133b195..ea75d443b57 100644 --- a/scripts/v.unpack/v.unpack.py +++ b/scripts/v.unpack/v.unpack.py @@ -218,7 +218,7 @@ def main(): todb = dbconn["database"] # return the list of old connection for extract layer number and key - dbln = open(os.path.join(new_dir, "dbln"), "r") + dbln = open(os.path.join(new_dir, "dbln")) dbnlist = dbln.readlines() dbln.close() # check if dbf or sqlite directory exists diff --git a/scripts/v.what.vect/testsuite/test_v_what_vect.py b/scripts/v.what.vect/testsuite/test_v_what_vect.py index 09ade33668f..2385a71c400 100644 --- a/scripts/v.what.vect/testsuite/test_v_what_vect.py +++ b/scripts/v.what.vect/testsuite/test_v_what_vect.py @@ -9,7 +9,6 @@ from grass.gunittest.gmodules import SimpleModule from grass.script.core import run_command -from grass.script.utils import decode class TestVWhatVect(TestCase): @@ -29,20 +28,20 @@ def tearDownClass(cls): def test_what_vect(self): """Uploads vector values""" - run_command("v.db.addcolumn", map=self.mapName, columns="urb_name varchar(25)") + run_command("v.db.addcolumn", map=self.mapName, columns="geology_cat integer") module = SimpleModule( "v.what.vect", map=self.mapName, - query_map="urbanarea", - column="urb_name", - query_column="NAME", + query_map="geology", + column="geology_cat", + query_column="cat", ) self.assertModule(module) - - m = SimpleModule("v.db.select", map=self.mapName) - self.assertModule(m) - self.assertRegex(decode(m.outputs.stdout), "urb_name") + minmax = "min=11\nmax=1810" + self.assertVectorFitsUnivar( + map=self.mapName, column="geology_cat", reference=minmax + ) if __name__ == "__main__": diff --git a/temporal/t.rast.gapfill/testsuite/test_gapfill.py b/temporal/t.rast.gapfill/testsuite/test_gapfill.py index 63cd2b83c26..7b083617d08 100644 --- a/temporal/t.rast.gapfill/testsuite/test_gapfill.py +++ b/temporal/t.rast.gapfill/testsuite/test_gapfill.py @@ -12,6 +12,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows class TestRasterToVector(TestCase): @@ -75,6 +76,7 @@ def tearDown(self): """Remove generated data""" self.runModule("t.remove", flags="df", type="strds", inputs="A") + @xfail_windows def test_simple_2procs(self): self.assertModule( "t.rast.gapfill", @@ -125,6 +127,7 @@ def test_simple_2procs(self): self.assertModule(rast_list) self.assertLooksLike(text, rast_list.outputs.stdout) + @xfail_windows def test_simple_where(self): self.assertModule( "t.rast.gapfill", @@ -173,6 +176,7 @@ def test_simple_where(self): self.assertModule(rast_list) self.assertLooksLike(text, rast_list.outputs.stdout) + @xfail_windows def test_simple_where_2(self): self.assertModule( "t.rast.gapfill", @@ -216,6 +220,7 @@ def test_simple_where_2(self): self.assertModule(rast_list) self.assertLooksLike(text, rast_list.outputs.stdout) + @xfail_windows def test_simple_empty(self): self.assertModule( "t.rast.gapfill", @@ -302,6 +307,7 @@ def test_simple_gran(self): self.assertModule(rast_list) self.assertLooksLike(text, rast_list.outputs.stdout) + @xfail_windows def test_simple_gran(self): self.assertModule( "t.rast.gapfill", diff --git a/temporal/t.rast.series/testsuite/test_series.py b/temporal/t.rast.series/testsuite/test_series.py index cfb17a338cd..99fe69d4352 100644 --- a/temporal/t.rast.series/testsuite/test_series.py +++ b/temporal/t.rast.series/testsuite/test_series.py @@ -14,6 +14,7 @@ import grass.temporal as tgis from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows class TestSnapAbsoluteSTRDS(TestCase): @@ -146,6 +147,7 @@ def test_minimum_where(self): map="series_minimum_2", refmin=300, refmax=300, msg="Minimum must be 300" ) + @xfail_windows def test_quantile(self): self.assertModule( "t.rast.series", diff --git a/temporal/t.rast.univar/testsuite/test_t_rast_univar.py b/temporal/t.rast.univar/testsuite/test_t_rast_univar.py index 11a91614b6c..dca870d6a98 100644 --- a/temporal/t.rast.univar/testsuite/test_t_rast_univar.py +++ b/temporal/t.rast.univar/testsuite/test_t_rast_univar.py @@ -11,6 +11,7 @@ from pathlib import Path from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows class TestRasterUnivar(TestCase): @@ -152,6 +153,7 @@ def tearDownClass(cls): cls.del_temp_region() + @xfail_windows def test_with_all_maps(self): t_rast_univar = SimpleModule( "t.rast.univar", @@ -177,6 +179,7 @@ def test_with_all_maps(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_with_subset_of_maps(self): t_rast_univar = SimpleModule( "t.rast.univar", @@ -201,6 +204,7 @@ def test_with_subset_of_maps(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_coarser_resolution(self): t_rast_univar = SimpleModule( "t.rast.univar", @@ -316,6 +320,7 @@ def test_error_handling_no_input(self): # No input self.assertModuleFail("t.rast.univar", output="out.txt") + @xfail_windows def test_with_zones(self): """Test use of zones""" @@ -353,6 +358,7 @@ def test_with_zones(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_with_semantic_label(self): """Test semantic labels""" t_rast_univar = SimpleModule( @@ -379,6 +385,7 @@ def test_with_semantic_label(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_with_semantic_label_parallel(self): """Test semantic labels""" t_rast_univar = SimpleModule( @@ -406,6 +413,7 @@ def test_with_semantic_label_parallel(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_with_spatial_filter_intersects(self): """Test spatial filter overlaps""" t_rast_univar = SimpleModule( @@ -434,6 +442,7 @@ def test_with_spatial_filter_intersects(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_with_spatial_filter_contains(self): """Test spatial filter contains""" t_rast_univar = SimpleModule( @@ -460,6 +469,7 @@ def test_with_spatial_filter_contains(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_with_spatial_filter_is_contained(self): """Test spatial filter is_contained""" t_rast_univar = SimpleModule( diff --git a/temporal/t.rast.what/t.rast.what.py b/temporal/t.rast.what/t.rast.what.py index 39e63180abf..b3cec73a538 100755 --- a/temporal/t.rast.what/t.rast.what.py +++ b/temporal/t.rast.what/t.rast.what.py @@ -389,7 +389,7 @@ def one_point_per_row_output( file_name = output_files[count] gs.verbose(_("Transforming r.what output file %s") % (file_name)) map_list = output_time_list[count] - in_file = open(file_name, "r") + in_file = open(file_name) for line in in_file: line = line.split(separator) if vcat: @@ -466,7 +466,7 @@ def one_point_per_col_output( for count in range(len(output_files)): file_name = output_files[count] gs.verbose(_("Transforming r.what output file %s") % (file_name)) - in_file = open(file_name, "r") + in_file = open(file_name) lines = in_file.readlines() matrix = [] @@ -563,7 +563,7 @@ def one_point_per_timerow_output( file_name = output_files[count] gs.verbose("Transforming r.what output file %s" % (file_name)) map_list = output_time_list[count] - in_file = open(file_name, "r") + in_file = open(file_name) if write_header: if first is True: diff --git a/temporal/t.rast.what/testsuite/test_what.py b/temporal/t.rast.what/testsuite/test_what.py index 96432eff4e1..18c7af63807 100644 --- a/temporal/t.rast.what/testsuite/test_what.py +++ b/temporal/t.rast.what/testsuite/test_what.py @@ -10,6 +10,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows class TestRasterWhat(TestCase): @@ -218,6 +219,7 @@ def test_timerow_output_coords(self): "out_timerow_coords.txt", "ca4ee0e7e4aaca170d6034e0d57d292d", text=True ) + @xfail_windows def test_row_stdout_where_parallel(self): t_rast_what = SimpleModule( "t.rast.what", @@ -245,6 +247,7 @@ def test_row_stdout_where_parallel(self): """ self.assertLooksLike(text, str(t_rast_what.outputs.stdout)) + @xfail_windows def test_row_stdout_where_parallel_cat(self): t_rast_what = SimpleModule( "t.rast.what", @@ -272,6 +275,7 @@ def test_row_stdout_where_parallel_cat(self): """ self.assertLooksLike(text, str(t_rast_what.outputs.stdout)) + @xfail_windows def test_row_stdout_where_parallel2(self): """Here without output definition, the default is used then""" @@ -385,6 +389,7 @@ def tearDownClass(cls): cls.runModule("t.remove", flags="df", type="strds", inputs="A") cls.del_temp_region() + @xfail_windows def test_null_value(self): """Test setting the null value""" diff --git a/temporal/t.rast3d.univar/testsuite/test_t_rast3d_univar.py b/temporal/t.rast3d.univar/testsuite/test_t_rast3d_univar.py index 7c8ab1e4100..4ca64e88442 100644 --- a/temporal/t.rast3d.univar/testsuite/test_t_rast3d_univar.py +++ b/temporal/t.rast3d.univar/testsuite/test_t_rast3d_univar.py @@ -11,6 +11,7 @@ from pathlib import Path from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows class TestRasterUnivar(TestCase): @@ -58,6 +59,7 @@ def tearDownClass(cls): cls.runModule("g.remove", flags="f", type="raster_3d", name="zones") cls.del_temp_region() + @xfail_windows def test_with_all_maps(self): t_rast3d_univar = SimpleModule( "t.rast3d.univar", @@ -82,6 +84,7 @@ def test_with_all_maps(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_with_subset_of_maps(self): t_rast3d_univar = SimpleModule( "t.rast3d.univar", @@ -166,6 +169,7 @@ def test_error_handling_no_input(self): # No input self.assertModuleFail("t.rast3d.univar", output="out.txt") + @xfail_windows def test_with_zones(self): """Test use of zones""" @@ -203,6 +207,7 @@ def test_with_zones(self): res_line = res.split("|", 1)[1] self.assertLooksLike(ref_line, res_line) + @xfail_windows def test_with_zones_parallel(self): """Test use of zones""" diff --git a/temporal/t.remove/t.remove.py b/temporal/t.remove/t.remove.py index b5c1164cef4..91fa58dde7a 100755 --- a/temporal/t.remove/t.remove.py +++ b/temporal/t.remove/t.remove.py @@ -102,7 +102,7 @@ def main(): # Read the dataset list from file if file: - fd = open(file, "r") + fd = open(file) line = True while True: diff --git a/temporal/t.unregister/t.unregister.py b/temporal/t.unregister/t.unregister.py index 7aef44ed12b..b39b7b9819f 100755 --- a/temporal/t.unregister/t.unregister.py +++ b/temporal/t.unregister/t.unregister.py @@ -108,7 +108,7 @@ def main(): # Read the map list from file if file: - fd = open(file, "r") + fd = open(file) line = True while True: diff --git a/utils/g.html2man/rest.py b/utils/g.html2man/rest.py index 2df87db8e27..c320f18e397 100644 --- a/utils/g.html2man/rest.py +++ b/utils/g.html2man/rest.py @@ -1,4 +1,5 @@ import sys +from operator import itemgetter def match(node, tag, attr=None, val=None): @@ -26,8 +27,7 @@ def find(node, tag, attr=None, val=None): raise ValueError("child not found") -def children(node): - return node[2] +children = itemgetter(2) def text(node): diff --git a/utils/gitlog2changelog.py b/utils/gitlog2changelog.py index 43aedcfb491..5ad459aa2b1 100755 --- a/utils/gitlog2changelog.py +++ b/utils/gitlog2changelog.py @@ -78,14 +78,12 @@ dateFound = True except Exception as e: print(f"Could not parse dateList = '{line}'. Error: {e!s}") - # The Fossil-IDs are ignored: - elif line.startswith((" Fossil-ID:", " [[SVN:")): - continue - # The svn-id lines are ignored - elif " git-svn-id:" in line: - continue - # The sign off line is ignored too - elif "Signed-off-by" in line: + # The Fossil-IDs, svn-id, ad sign off lines are ignored: + elif ( + line.startswith((" Fossil-ID:", " [[SVN:")) + or " git-svn-id:" in line + or "Signed-off-by" in line + ): continue # Extract the actual commit message for this commit elif authorFound & dateFound & messageFound is False: diff --git a/utils/mkhtml.py b/utils/mkhtml.py index d36ea78663d..f64f6d7d97a 100644 --- a/utils/mkhtml.py +++ b/utils/mkhtml.py @@ -159,7 +159,7 @@ def download_git_commit(url, response_format, *args, **kwargs): """ try: response = urlopen(url, *args, **kwargs) - if not response.code == 200: + if response.code != 200: index = HTTP_STATUS_CODES.index(response.code) desc = HTTP_STATUS_CODES[index].description gs.fatal( @@ -366,7 +366,7 @@ def has_src_code_git(src_dir, is_addon): if core module or addon source code has Git """ - actual_dir = os.getcwd() + actual_dir = Path.cwd() if is_addon: os.chdir(src_dir) else: diff --git a/vector/v.colors/scan_attr.c b/vector/v.colors/scan_attr.c index 18c51635f81..e805cd74a7f 100644 --- a/vector/v.colors/scan_attr.c +++ b/vector/v.colors/scan_attr.c @@ -42,6 +42,8 @@ int scan_attr(struct Map_info *Map, int layer, const char *column_name, &cvarr); if (nrec < 1) { G_important_message(_("No data selected")); + Vect_destroy_field_info(fi); + db_close_database(driver); return 0; } @@ -100,6 +102,7 @@ int scan_attr(struct Map_info *Map, int layer, const char *column_name, } db_close_database(driver); + Vect_destroy_field_info(fi); return is_fp; } diff --git a/vector/v.generalize/displacement.c b/vector/v.generalize/displacement.c index bc7a3f15170..a4d599080e4 100644 --- a/vector/v.generalize/displacement.c +++ b/vector/v.generalize/displacement.c @@ -310,6 +310,8 @@ int snakes_displacement(struct Map_info *In, struct Map_info *Out, matrix_free(&fy); matrix_free(&dx_old); matrix_free(&dy_old); + Vect_destroy_cats_struct(Cats); + Vect_destroy_line_struct(Points); return 0; } diff --git a/vector/v.out.lidar/testsuite/test_v_out_lidar.py b/vector/v.out.lidar/testsuite/test_v_out_lidar.py index 9be4f57d3b6..613cb14b61a 100644 --- a/vector/v.out.lidar/testsuite/test_v_out_lidar.py +++ b/vector/v.out.lidar/testsuite/test_v_out_lidar.py @@ -12,6 +12,7 @@ import os from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.utils import xfail_windows class BasicTest(TestCase): @@ -60,6 +61,7 @@ def test_module_runs_output_created(self): self.assertModule("v.out.lidar", input=self.vector_points, output=self.las_file) self.assertFileExists(self.las_file) + @xfail_windows def test_output_identical(self): """Test to see if the standard outputs are created diff --git a/vector/v.select/testsuite/test_v_select.py b/vector/v.select/testsuite/test_v_select.py index c74cdb53761..b8564505a6a 100644 --- a/vector/v.select/testsuite/test_v_select.py +++ b/vector/v.select/testsuite/test_v_select.py @@ -13,22 +13,9 @@ class TestRasterReport(TestCase): - binput = "bridges" + binput = "zipcodes" ainput = "geology" output = "testvselect" - overlap = "geonames_wake" - disjoint = "schools_wake" - equals = "streets_wake" - touches = "zipcodes_wake" - within = "geonames_wake" - - @classmethod - def setUpClass(cls): - cls.use_temp_region() - - @classmethod - def tearDownClass(cls): - cls.del_temp_region() def tearDown(cls): cls.runModule("g.remove", type="vector", flags="f", name=cls.output) @@ -42,8 +29,8 @@ def test_opo(self): output=self.output, operator="overlap", ) - topology = {"points": 1088, "lines": 0, "areas": 0} - self.assertVectorFitsTopoInfo(self.overlap, topology) + topology = {"areas": 97} + self.assertVectorFitsTopoInfo(self.output, topology) def test_opd(self): """Testign operator disjoint""" @@ -54,8 +41,8 @@ def test_opd(self): output=self.output, operator="disjoint", ) - topology = {"points": 167, "lines": 0, "areas": 0} - self.assertVectorFitsTopoInfo(self.disjoint, topology) + topology = {"areas": 1770} + self.assertVectorFitsTopoInfo(self.output, topology) def test_ope(self): """Testing operator equals""" @@ -66,8 +53,7 @@ def test_ope(self): output=self.output, operator="equals", ) - topology = {"points": 0, "lines": 49746, "areas": 0} - self.assertVectorFitsTopoInfo(self.equals, topology) + self.assertVectorDoesNotExist(self.output) def test_opt(self): """Testing operator touches""" @@ -78,8 +64,7 @@ def test_opt(self): output=self.output, operator="touches", ) - topology = {"points": 0, "lines": 0, "areas": 48} - self.assertVectorFitsTopoInfo(self.touches, topology) + self.assertVectorDoesNotExist(self.output) def test_opw(self): """Testing operator within""" @@ -90,8 +75,8 @@ def test_opw(self): output=self.output, operator="within", ) - topology = {"points": 1088, "lines": 0, "areas": 0} - self.assertVectorFitsTopoInfo(self.within, topology) + topology = {"areas": 17} + self.assertVectorFitsTopoInfo(self.output, topology) if __name__ == "__main__": diff --git a/vector/v.surf.rst/Makefile b/vector/v.surf.rst/Makefile index 3b5d8eca836..414ebbd85a0 100644 --- a/vector/v.surf.rst/Makefile +++ b/vector/v.surf.rst/Makefile @@ -4,9 +4,9 @@ PGM=v.surf.rst EXTRA_CLEAN_DIRS=doxygenhtml -LIBES = $(INTERPFLLIB) $(QTREELIB) $(INTERPDATALIB) $(GMATHLIB) $(VECTORLIB) $(DBMILIB) $(GISLIB) $(MATHLIB) +LIBES = $(INTERPFLLIB) $(QTREELIB) $(INTERPDATALIB) $(GMATHLIB) $(RASTERLIB) $(VECTORLIB) $(DBMILIB) $(GISLIB) $(MATHLIB) EXTRA_LIBS = $(OPENMP_LIBPATH) $(OPENMP_LIB) -DEPENDENCIES = $(INTERPFLDEP) $(QTREEDEP) $(INTERPDATADEP) $(GMATHDEP) $(VECTORDEP) $(DBMIDEP) $(GISDEP) +DEPENDENCIES = $(INTERPFLDEP) $(QTREEDEP) $(INTERPDATADEP) $(GMATHDEP) $(RASTERDEP) $(VECTORDEP) $(DBMIDEP) $(GISDEP) EXTRA_INC = $(VECT_INC) $(OPENMP_INCPATH) EXTRA_CFLAGS = $(VECT_CFLAGS) $(OPENMP_CFLAGS) diff --git a/vector/v.surf.rst/main.c b/vector/v.surf.rst/main.c index c4581737eed..7b42e269d0f 100644 --- a/vector/v.surf.rst/main.c +++ b/vector/v.surf.rst/main.c @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -419,8 +420,8 @@ int main(int argc, char *argv[]) G_warning(_("GRASS GIS is not compiled with OpenMP support, parallel " "computation is disabled.")); #endif - if (threads > 1 && G_find_raster("MASK", G_mapset()) != NULL) { - G_warning(_("Parallel processing disabled due to active MASK.")); + if (threads > 1 && Rast_mask_is_present()) { + G_warning(_("Parallel processing disabled due to active mask.")); threads = 1; } if (devi) { diff --git a/vector/v.transform/main.c b/vector/v.transform/main.c index 400cd3ded91..06a7d105881 100644 --- a/vector/v.transform/main.c +++ b/vector/v.transform/main.c @@ -188,8 +188,17 @@ int main(int argc, char *argv[]) if (G_parser(argc, argv)) exit(EXIT_FAILURE); - strcpy(Current.name, vold->answer); - strcpy(Trans.name, vnew->answer); + if (G_strlcpy(Current.name, vold->answer, sizeof(Current.name)) >= + sizeof(Current.name)) { + G_fatal_error(_("Input vector map name <%s> is too long"), + vold->answer); + } + + if (G_strlcpy(Trans.name, vnew->answer, sizeof(Trans.name)) >= + sizeof(Trans.name)) { + G_fatal_error(_("Output vector map name <%s> is too long"), + vnew->answer); + } Vect_check_input_output_name(vold->answer, vnew->answer, G_FATAL_EXIT); diff --git a/vector/v.univar/testsuite/test_v_univar.py b/vector/v.univar/testsuite/test_v_univar.py index 766871d951c..e5fc2685cac 100644 --- a/vector/v.univar/testsuite/test_v_univar.py +++ b/vector/v.univar/testsuite/test_v_univar.py @@ -15,9 +15,11 @@ from grass.gunittest.case import TestCase from grass.gunittest.main import test from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.utils import xfail_windows class TestProfiling(TestCase): + @xfail_windows def test_flagg(self): """Testing flag g with map lakes""" output_str = """n=15279 @@ -40,6 +42,7 @@ def test_flagg(self): v_univar.run() self.assertLooksLike(actual=v_univar.outputs.stdout, reference=output_str) + @xfail_windows def test_flage(self): """Testing flag e with map geology""" output_str = """number of features with non NULL attribute: 1832 @@ -68,6 +71,7 @@ def test_flage(self): v_univar.run() self.assertLooksLike(actual=v_univar.outputs.stdout, reference=output_str) + @xfail_windows def test_flagw(self): """Testing flag w with map lakes""" output_str = """number of features with non NULL attribute: 15279 @@ -83,6 +87,7 @@ def test_flagw(self): v_univar.run() self.assertLooksLike(actual=v_univar.outputs.stdout, reference=output_str) + @xfail_windows def test_flagd(self): """Testing flag d with map hospitals""" univar_string = """number of primitives: 160 diff --git a/vector/v.vol.rst/user1.c b/vector/v.vol.rst/user1.c index f47324dff36..6a1c2f441e6 100644 --- a/vector/v.vol.rst/user1.c +++ b/vector/v.vol.rst/user1.c @@ -220,6 +220,7 @@ int INPUT(struct Map_info *In, char *column, char *scol, char *wheresql) if (a < 0) { G_warning(_("Can't insert %lf,%lf,%lf,%lf,%lf a=%d"), x, y, z, w, sm, a); + Vect_destroy_field_info(Fi); return -1; } @@ -322,6 +323,7 @@ int INPUT(struct Map_info *In, char *column, char *scol, char *wheresql) } else { fprintf(stderr, "ERROR: zero points in the given region!\n"); + Vect_destroy_field_info(Fi); return -1; } } @@ -332,6 +334,7 @@ int INPUT(struct Map_info *In, char *column, char *scol, char *wheresql) KMIN, KMAX); fprintf(stderr, "for smooth connection of segments, npmin > segmax " "(see manual) \n"); + Vect_destroy_field_info(Fi); return -1; } @@ -382,6 +385,7 @@ int INPUT(struct Map_info *In, char *column, char *scol, char *wheresql) } G_message(_("Bitmap mask created")); } + Vect_destroy_field_info(Fi); return 1; }