├── .circleci
├── config.yml
└── images
│ └── Dockerfile
├── .dockerignore
├── .env.example
├── .github
├── images
│ └── Dockerfile
├── pull_request_template.md
└── workflows
│ ├── release.yml
│ ├── test.yml
│ └── update_major_tag.yml
├── .gitignore
├── .gitmodules
├── .goreleaser.yaml
├── .idea
├── .gitignore
├── inspectionProfiles
│ └── Project_Default.xml
├── mob.xml
├── modules.xml
├── unity-meta-checker.iml
└── vcs.xml
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── action.yml
├── aqua.yaml
├── build.Dockerfile
├── docs
└── images
│ └── github-pr-comment-screenshot.png
├── filecollector
├── aggregator.go
├── aggregator_stub.go
├── aggregator_test.go
├── entry.go
├── filecollector.go
├── filecollector_test.go
├── options.go
└── repofinder
│ ├── compose.go
│ ├── find.go
│ ├── find_stub.go
│ ├── find_test.go
│ └── foundrepo.go
├── git
├── config.go
├── config_test.go
├── lsfiles.go
├── lsfiles_stub.go
├── lsfiles_test.go
├── revparse.go
└── revparse_test.go
├── go.mod
├── go.sum
├── ignore
├── file.go
├── file_test.go
└── testdata
│ └── Assets
│ ├── Bar
│ └── Baz
│ └── Foo
├── main.go
├── main_test.go
├── options
├── argparser.go
├── env.go
├── ignorefile.go
├── ignorefile_stub.go
├── ignorefile_test.go
├── options.go
├── options_test.go
├── rootdir.go
├── rootdir_stub.go
├── rootdir_test.go
├── testdata
│ └── ignorefile
│ │ ├── ProjectDoesNotHaveMetaCheckIgnore
│ │ └── .gitempty
│ │ └── ProjectHasMetaCheckIgnore
│ │ └── .meta-check-ignore
├── unityproject.go
└── unityproject_stub.go
├── report
├── parser.go
├── parser_test.go
└── printer.go
├── resultfilter
├── resultfilter.go
└── resultfilter_test.go
├── test
├── compat
├── compat-revisions
└── perf-compat
├── tool
├── gh-action
│ ├── action-yaml-gen
│ │ ├── cmd
│ │ │ ├── cmd.go
│ │ │ └── cmd_test.go
│ │ ├── main.go
│ │ ├── testdata
│ │ │ └── action.yml
│ │ └── yaml
│ │ │ ├── inputs.go
│ │ │ ├── inputs_test.go
│ │ │ ├── metadata.go
│ │ │ ├── runs.go
│ │ │ ├── runs_test.go
│ │ │ ├── yaml.go
│ │ │ └── yaml_test.go
│ ├── cmd
│ │ ├── cmd.go
│ │ └── cmd_test.go
│ ├── inputs
│ │ ├── env.go
│ │ ├── event.go
│ │ ├── event_stub.go
│ │ ├── event_test.go
│ │ ├── json.go
│ │ ├── json_test.go
│ │ └── testdata
│ │ │ ├── inputs-example.json
│ │ │ ├── pr-event-payload-example.json
│ │ │ └── push-event-payload-example.json
│ ├── main.go
│ ├── options
│ │ ├── options.go
│ │ └── options_test.go
│ └── runner
│ │ ├── options.go
│ │ ├── options_test.go
│ │ ├── runner.go
│ │ ├── runner_test.go
│ │ └── testdata
│ │ ├── InvalidProject
│ │ └── Packages
│ │ │ └── manifest.json
│ │ ├── InvalidSubDir
│ │ ├── dangling.meta
│ │ └── missing
│ │ ├── ValidProject
│ │ └── Packages
│ │ │ └── manifest.json
│ │ └── ValidSubDir
│ │ ├── ok
│ │ └── ok.meta
├── unity-meta-autofix
│ ├── autofix
│ │ ├── autofix.go
│ │ ├── autofix_stub.go
│ │ ├── autofix_test.go
│ │ ├── create.go
│ │ ├── create_stub.go
│ │ ├── create_test.go
│ │ ├── metatype.go
│ │ ├── metatype_stub.go
│ │ ├── metatype_test.go
│ │ ├── options.go
│ │ ├── options_stub.go
│ │ ├── remover.go
│ │ ├── remover_stub.go
│ │ └── testdata
│ │ │ ├── InvalidProject
│ │ │ └── Assets
│ │ │ │ ├── Dangling.meta
│ │ │ │ └── Missing
│ │ │ └── ValidProject
│ │ │ └── Assets
│ │ │ ├── OK
│ │ │ └── OK.meta
│ ├── cmd
│ │ ├── cmd.go
│ │ ├── cmd_test.go
│ │ └── testdata
│ │ │ ├── InvalidProject
│ │ │ └── Assets
│ │ │ │ ├── Dangling.meta
│ │ │ │ └── Missing
│ │ │ └── ValidProject
│ │ │ └── Assets
│ │ │ ├── OK
│ │ │ └── OK.meta
│ ├── main.go
│ └── options
│ │ ├── options.go
│ │ └── options_test.go
├── unity-meta-check-github-pr-comment
│ ├── cmd
│ │ ├── cmd.go
│ │ └── cmd_test.go
│ ├── github
│ │ ├── api.go
│ │ ├── api_test.go
│ │ ├── http.go
│ │ ├── send.go
│ │ ├── send_stub.go
│ │ └── types.go
│ ├── l10n
│ │ ├── lang.go
│ │ ├── template.go
│ │ ├── template_stub.go
│ │ └── template_test.go
│ ├── main.go
│ ├── markdown
│ │ ├── markdown.go
│ │ └── markdown_test.go
│ └── options
│ │ ├── options.go
│ │ ├── options_test.go
│ │ └── testdata
│ │ └── example-template.json
├── unity-meta-check-junit
│ ├── cmd
│ │ ├── cmd.go
│ │ └── cmd_test.go
│ ├── junit
│ │ ├── junit.go
│ │ ├── junit_stub.go
│ │ └── junit_test.go
│ ├── main.go
│ └── options
│ │ └── options.go
└── unity-meta-check-meta-audit
│ └── main.go
├── unity
├── checker
│ ├── checker.go
│ ├── checker_stub.go
│ ├── checker_test.go
│ ├── chkworker.go
│ ├── chkworker_test.go
│ ├── options.go
│ ├── result.go
│ ├── strategy.go
│ └── testdata
│ │ ├── EmptyManifest
│ │ └── Packages
│ │ │ └── manifest.json
│ │ ├── LocalsManifest
│ │ ├── LocalPackages
│ │ │ └── com.example.local
│ │ │ │ └── .gitempty
│ │ └── Packages
│ │ │ └── manifest.json
│ │ └── NoLocalsManifest
│ │ └── Packages
│ │ └── manifest.json
├── manifest.go
├── manifest_test.go
├── meta
│ ├── defaultimportergen.go
│ ├── defaultimportergen_test.go
│ ├── foldergen.go
│ ├── foldergen_test.go
│ ├── gen.go
│ ├── guid.go
│ ├── guid_stub.go
│ ├── guid_test.go
│ ├── guidgen.go
│ ├── guidgen_stub.go
│ ├── monoimportergen.go
│ ├── monoimportergen_test.go
│ ├── textscriptimportergen.go
│ └── textscriptimportergen_test.go
├── metanecessity.go
├── metanecessity_stub.go
├── metanecessity_test.go
├── package.go
├── package_test.go
└── testdata
│ ├── EmptyManifest
│ └── Packages
│ │ └── manifest.json
│ ├── LocalsManifest
│ ├── LocalPackages
│ │ └── com.example.local
│ │ │ └── .gitempty
│ └── Packages
│ │ └── manifest.json
│ └── NoLocalsManifest
│ └── Packages
│ ├── com.example.exists
│ └── .gitempty
│ └── manifest.json
├── util
├── chanutil
│ ├── fromslice.go
│ └── toslice.go
├── cli
│ ├── cmd.go
│ ├── env.go
│ ├── env_stub.go
│ ├── loglevel.go
│ ├── opt
│ │ ├── flag.go
│ │ ├── flag
│ │ │ ├── flag.go
│ │ │ └── flag_test.go
│ │ ├── flag_test.go
│ │ ├── ghactions
│ │ │ ├── ghactions.go
│ │ │ └── ghactions_test.go
│ │ └── markdown
│ │ │ ├── markdown.go
│ │ │ └── markdown_test.go
│ ├── processinout.go
│ ├── processinout_stub.go
│ └── prof.go
├── cstrset
│ ├── cistrset.go
│ ├── csstrset.go
│ └── cstrset.go
├── errutil
│ └── errors.go
├── globs
│ ├── glob.go
│ └── glob_test.go
├── logging
│ ├── logger.go
│ └── logger_stub.go
├── ostestable
│ ├── getwd.go
│ ├── getwd_stub.go
│ ├── isdir.go
│ └── isdir_stub.go
├── pathutil
│ ├── ancestors.go
│ ├── ancestors_test.go
│ ├── set.go
│ ├── split.go
│ ├── split_test.go
│ ├── tree.go
│ └── tree_test.go
├── prefix
│ ├── writer.go
│ └── writer_test.go
├── testutil
│ ├── env.go
│ ├── readcloser_stub.go
│ ├── writecloser_spy.go
│ └── writecloser_stub.go
└── typedpath
│ ├── basename.go
│ ├── raw.go
│ ├── raw_test.go
│ └── slash.go
└── version
└── version.go
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | go_get:
4 | docker:
5 | - image: cimg/go:1.22.2
6 | steps:
7 | - run:
8 | name: Test go get
9 | command: |
10 | go get github.com/DeNA/unity-meta-check
11 | - run:
12 | name: Test
13 | command: unity-meta-check --version
14 |
15 | workflows:
16 | version: 2
17 |
18 | main:
19 | jobs:
20 | - go_get:
21 | filters:
22 | branches:
23 | only: master
24 |
--------------------------------------------------------------------------------
/.circleci/images/Dockerfile:
--------------------------------------------------------------------------------
1 | # image-name: ghcr.io/dena/unity-meta-check/unity-meta-check-circleci
2 | FROM ghcr.io/dena/unity-meta-check/unity-meta-check-builder:latest as builder
3 |
4 | FROM debian:bookworm-slim
5 | # https://circleci.com/docs/2.0/custom-images/#required-tools-for-primary-containers
6 | RUN apt-get update \
7 | && apt-get install --yes --no-install-recommends git openssh-server tar gzip ca-certificates \
8 | && apt-get clean \
9 | && rm -rf /var/lib/apt/lists/*
10 | COPY --from=builder /go/src/unity-meta-check/out/* /usr/bin/
11 | ENTRYPOINT ["unity-meta-check"]
12 | CMD ["-help"]
13 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | /.git
2 | /.gitignore
3 | /out
4 | /test
5 | /tmp
6 | /Dockerfile
7 | testdata
8 | .env
9 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | UNITY_META_CHECK_GITHUB_API_ENDPOINT=https://github.example.com/api/v3
2 | UNITY_META_CHECK_GITHUB_OWNER=owner
3 | UNITY_META_CHECK_GITHUB_REPO=repo
4 | UNITY_META_CHECK_GITHUB_PULL_NUMBER=1
5 | UNITY_META_CHECK_GITHUB_TOKEN=****************************************
6 |
--------------------------------------------------------------------------------
/.github/images/Dockerfile:
--------------------------------------------------------------------------------
1 | # image-name: ghcr.io/dena/unity-meta-check/unity-meta-check-gh-action
2 | FROM ghcr.io/dena/unity-meta-check/unity-meta-check-builder:latest as builder
3 |
4 | FROM debian:bookworm-slim
5 | RUN apt-get update \
6 | && apt-get install --yes --no-install-recommends git ca-certificates \
7 | && apt-get clean \
8 | && rm -rf /var/lib/apt/lists/*
9 | COPY --from=builder /go/src/unity-meta-check/out/gh-action /usr/bin/
10 | ENTRYPOINT ["gh-action"]
11 | CMD ["-help"]
12 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ---
4 |
5 | ### Contribution License Agreement
6 |
7 | - [ ] By placing an "x" in the box, I hereby understand, accept and agree to be bound by the terms and conditions of the [Contribution License Agreement](https://dena.github.io/cla/).
8 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Pull Request Tests
2 | on:
3 | pull_request:
4 | paths-ignore:
5 | - '**/*.md'
6 |
7 | jobs:
8 | test:
9 | name: Test
10 | runs-on: ${{ matrix.os }}
11 | permissions:
12 | contents: read
13 | strategy:
14 | matrix:
15 | os:
16 | - ubuntu-latest
17 | - windows-latest
18 | - macos-latest
19 | steps:
20 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
21 | with:
22 | submodules: recursive
23 | - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
24 | with:
25 | go-version-file: go.mod
26 | - run: go test -v ./...
27 |
--------------------------------------------------------------------------------
/.github/workflows/update_major_tag.yml:
--------------------------------------------------------------------------------
1 | name: Update major version and latest tag
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | tag:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | steps:
14 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
15 | - uses: nowsprinting/check-version-format-action@98485692a883d962227b09f40f29a63de0771299 # v4.0.2
16 | id: version
17 | with:
18 | prefix: v
19 | - name: Update major version and latest tag
20 | run: |
21 | git push --force origin 'HEAD:refs/heads/${{ steps.version.outputs.major_prerelease }}'
22 | git push --force origin 'HEAD:refs/heads/latest'
23 | if: steps.version.outputs.is_valid == 'true'
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | tmp
2 | dist
3 | out
4 | .env
5 | # Created by https://www.toptal.com/developers/gitignore/api/go
6 | # Edit at https://www.toptal.com/developers/gitignore?templates=go
7 |
8 | ### Go ###
9 | # Binaries for programs and plugins
10 | *.exe
11 | *.exe~
12 | *.dll
13 | *.so
14 | *.dylib
15 |
16 | # Test binary, built with `go test -c`
17 | *.test
18 |
19 | # Output of the go coverage tool, specifically when used with LiteIDE
20 | *.out
21 |
22 | # Dependency directories (remove the comment below to include it)
23 | # vendor/
24 |
25 | ### Go Patch ###
26 | /vendor/
27 | /Godeps/
28 |
29 | # End of https://www.toptal.com/developers/gitignore/api/go
30 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "testdata/ValidProject"]
2 | path = testdata/ValidProject
3 | url = https://github.com/DeNA/unity-meta-check-ValidProject
4 | [submodule "testdata/InvalidProject"]
5 | path = testdata/InvalidProject
6 | url = https://github.com/DeNA/unity-meta-check-InvalidProject
7 | [submodule "tool/gh-action/cmd/testdata/ValidProject"]
8 | path = tool/gh-action/cmd/testdata/ValidProject
9 | url = https://github.com/DeNA/unity-meta-check-ValidProject
10 | [submodule "tool/gh-action/cmd/testdata/InvalidProject"]
11 | path = tool/gh-action/cmd/testdata/InvalidProject
12 | url = https://github.com/DeNA/unity-meta-check-InvalidProject
13 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | builds:
4 | - id: unity-meta-check
5 | binary: unity-meta-check
6 | main: ./main.go
7 | env:
8 | - CGO_ENABLED=0
9 | goos:
10 | - linux
11 | - windows
12 | - darwin
13 | goarch:
14 | - amd64
15 | - arm64
16 | - id: unity-meta-check-junit
17 | binary: unity-meta-check-junit
18 | main: ./tool/unity-meta-check-junit
19 | env:
20 | - CGO_ENABLED=0
21 | goos:
22 | - linux
23 | - windows
24 | - darwin
25 | goarch:
26 | - amd64
27 | - arm64
28 | - id: gh-action
29 | binary: gh-action
30 | main: ./tool/gh-action
31 | env:
32 | - CGO_ENABLED=0
33 | goos:
34 | - linux
35 | goarch:
36 | - amd64
37 | - arm64
38 | - id: gh-action-yaml-gen
39 | binary: gh-action-yaml-gen
40 | main: ./tool/gh-action/action-yaml-gen
41 | env:
42 | - CGO_ENABLED=0
43 | goos:
44 | - linux
45 | - windows
46 | - darwin
47 | goarch:
48 | - amd64
49 | - arm64
50 | - id: unity-meta-autofix
51 | binary: unity-meta-autofix
52 | main: ./tool/unity-meta-autofix
53 | env:
54 | - CGO_ENABLED=0
55 | goos:
56 | - linux
57 | - windows
58 | - darwin
59 | goarch:
60 | - amd64
61 | - arm64
62 | - id: unity-meta-check-github-pr-comment
63 | binary: unity-meta-check-github-pr-comment
64 | main: ./tool/unity-meta-check-github-pr-comment
65 | env:
66 | - CGO_ENABLED=0
67 | goos:
68 | - linux
69 | - windows
70 | - darwin
71 | goarch:
72 | - amd64
73 | - arm64
74 |
75 | archives:
76 | - format: binary
77 | name_template: >-
78 | {{ .Binary }}-
79 | {{- if eq .Os "Darwin" }}darwin-
80 | {{- else if eq .Os "Linux" }}linux-
81 | {{- else }}{{ .Os }}{{ end }}-{{ .Arch }}
82 | # https://goreleaser.com/errors/multiple-binaries-archive/
83 | allow_different_binary_count: true
84 |
85 | checksum:
86 | name_template: "checksums.txt"
87 |
88 | release:
89 | prerelease: auto
90 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 | # GitHub Copilot persisted chat sessions
10 | /copilot/chatSessions
11 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/mob.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/unity-meta-checker.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | How to run all tests
2 | ====================
3 |
4 | 1. Copy `.env.example` to `.env`
5 | 2. Fill `.env`
6 | 3. Run `dotenv go test ./...`
7 |
8 |
9 |
10 | How to deploy
11 | =============
12 |
13 | ```console
14 | $ cd path/to/unity-meta-check
15 |
16 | $ # Write the new version to deploy into version.go
17 | $ edit ./version/version.go
18 | $ go run ./tool/gh-action/action-yaml-gen/main.go ./action.yml
19 | $ git add ./version/version.go ./action.yml
20 | $ git commit -m "Bump to $(./scripts/print-version)"
21 | $ git push
22 |
23 | $ # Deploy to GitHub releases and Docker registry and unity-meta-check-bins:
24 | $ scripts/deploy
25 | ```
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # image-name: ghcr.io/dena/unity-meta-check/unity-meta-check
2 | FROM ghcr.io/dena/unity-meta-check/unity-meta-check-builder:latest as builder
3 |
4 | FROM debian:bookworm-slim
5 | RUN apt-get update \
6 | && apt-get install --yes --no-install-recommends git \
7 | && apt-get clean \
8 | && rm -rf /var/lib/apt/lists/*
9 | COPY --from=builder /go/src/unity-meta-check/out/* /usr/bin/
10 | ENTRYPOINT ["unity-meta-check"]
11 | CMD ["-help"]
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 DeNA Co., Ltd.
2 |
3 | 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:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | 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.
8 |
--------------------------------------------------------------------------------
/aqua.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # aqua - Declarative CLI Version Manager
3 | # https://aquaproj.github.io/
4 | # checksum:
5 | # enabled: true
6 | # require_checksum: true
7 | # supported_envs:
8 | # - all
9 | registries:
10 | - type: standard
11 | ref: v4.192.0 # renovate: depName=aquaproj/aqua-registry
12 | packages:
13 | - name: goreleaser/goreleaser@v2.0.0
14 | - name: cli/cli@v2.50.0
15 |
--------------------------------------------------------------------------------
/build.Dockerfile:
--------------------------------------------------------------------------------
1 | # image-name: ghcr.io/dena/unity-meta-check/unity-meta-check-builder
2 | FROM golang:1.22.2-bookworm as builder
3 | ARG TARGETARCH
4 | WORKDIR /go/src/unity-meta-check
5 | COPY . .
6 | RUN make -j$(nproc) out/unity-meta-check-linux-${TARGETARCH} \
7 | out/unity-meta-check-junit-linux-${TARGETARCH} \
8 | out/unity-meta-check-github-pr-comment-linux-${TARGETARCH} \
9 | out/unity-meta-autofix-linux-${TARGETARCH} \
10 | out/gh-action-linux-${TARGETARCH} && \
11 | mv ./out/unity-meta-check-linux-${TARGETARCH} ./out/unity-meta-check && \
12 | mv ./out/unity-meta-check-junit-linux-${TARGETARCH} ./out/unity-meta-check-junit && \
13 | mv ./out/unity-meta-check-github-pr-comment-linux-${TARGETARCH} ./out/unity-meta-check-github-pr-comment && \
14 | mv ./out/unity-meta-autofix-linux-${TARGETARCH} ./out/unity-meta-autofix && \
15 | mv ./out/gh-action-linux-${TARGETARCH} ./out/gh-action
16 |
--------------------------------------------------------------------------------
/docs/images/github-pr-comment-screenshot.png:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/filecollector/aggregator.go:
--------------------------------------------------------------------------------
1 | package filecollector
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/filecollector/repofinder"
6 | "github.com/DeNA/unity-meta-check/git"
7 | "github.com/DeNA/unity-meta-check/util/errutil"
8 | "github.com/DeNA/unity-meta-check/util/logging"
9 | "github.com/DeNA/unity-meta-check/util/typedpath"
10 | "sync"
11 | )
12 |
13 | type FileAggregator func(rootDirAbs typedpath.RawPath, opts *Options, ch chan<- Entry) error
14 |
15 | func NewFileAggregator(gitLsFiles git.LsFiles, findRepo repofinder.RepoFinder, logger logging.Logger) FileAggregator {
16 | collectFiles := New(gitLsFiles, logger)
17 |
18 | return func(rootDirAbs typedpath.RawPath, opts *Options, ch chan<- Entry) error {
19 | var errsMutex sync.Mutex
20 | errs := make([]error, 0)
21 |
22 | foundRepos, err := findRepo()
23 | if err != nil {
24 | return err
25 | }
26 |
27 | var wg sync.WaitGroup
28 | for _, foundRepo := range foundRepos {
29 | logger.Info(fmt.Sprintf("repository found: %q (submodule=%t)", foundRepo.RawPath, foundRepo.Type))
30 |
31 | wg.Add(1)
32 | go func(foundRepo repofinder.FoundRepo) {
33 | defer wg.Done()
34 | ch <- Entry{Path: foundRepo.RawPath.ToSlash(), IsDir: true}
35 | }(foundRepo)
36 |
37 | wg.Add(1)
38 | go func(foundRepo repofinder.FoundRepo) {
39 | defer wg.Done()
40 | if err := collectFiles(rootDirAbs, foundRepo.RawPath, opts, ch); err != nil {
41 | errsMutex.Lock()
42 | errs = append(errs, err)
43 | errsMutex.Unlock()
44 | return
45 | }
46 | }(foundRepo)
47 | }
48 |
49 | if err = collectFiles(rootDirAbs, ".", opts, ch); err != nil {
50 | errsMutex.Lock()
51 | errs = append(errs, err)
52 | errsMutex.Unlock()
53 | }
54 | wg.Wait()
55 |
56 | if len(errs) > 0 {
57 | return errutil.NewErrors(errs)
58 | }
59 | return nil
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/filecollector/aggregator_stub.go:
--------------------------------------------------------------------------------
1 | package filecollector
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | func StubFileAggregator(result []Entry, err error) FileAggregator {
8 | return func(rootDirAbs typedpath.RawPath, opts *Options, ch chan<- Entry) error {
9 | for _, path := range result {
10 | ch <- path
11 | }
12 | return err
13 | }
14 | }
15 |
16 | func StubSuccessfulFileAggregator(result []Entry) FileAggregator {
17 | return StubFileAggregator(result, nil)
18 | }
19 |
--------------------------------------------------------------------------------
/filecollector/entry.go:
--------------------------------------------------------------------------------
1 | package filecollector
2 |
3 | import "github.com/DeNA/unity-meta-check/util/typedpath"
4 |
5 | type Entry struct {
6 | Path typedpath.SlashPath
7 | IsDir bool
8 | }
9 |
--------------------------------------------------------------------------------
/filecollector/filecollector.go:
--------------------------------------------------------------------------------
1 | package filecollector
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/git"
7 | "github.com/DeNA/unity-meta-check/util/logging"
8 | "github.com/DeNA/unity-meta-check/util/pathutil"
9 | "github.com/DeNA/unity-meta-check/util/typedpath"
10 | "os"
11 | "sync"
12 | )
13 |
14 | type FileCollector func(projRootAbs typedpath.RawPath, targetRel typedpath.RawPath, opts *Options, writer chan<- Entry) error
15 |
16 | func New(gitLsFiles git.LsFiles, logger logging.Logger) FileCollector {
17 | return func(projRootAbs typedpath.RawPath, targetRel typedpath.RawPath, opts *Options, writer chan<- Entry) error {
18 | targetAbs := projRootAbs.JoinRawPath(targetRel)
19 | targetRelSlash := targetRel.ToSlash()
20 |
21 | logger.Debug(fmt.Sprintf("searching: %q", targetAbs))
22 | dirSet := pathutil.NewPathSetWithSize(opts.IgnoreCase, 50000)
23 |
24 | stdoutReader, stdoutWriter, err := os.Pipe()
25 | if err != nil {
26 | return err
27 | }
28 |
29 | var wg sync.WaitGroup
30 | wg.Add(1)
31 | go func() {
32 | defer wg.Done()
33 | dirSet.Add(targetRelSlash) // XXX: To prevent endless loop.
34 |
35 | scanner := bufio.NewScanner(stdoutReader)
36 | for scanner.Scan() {
37 | var filePath typedpath.SlashPath
38 | if targetRelSlash == "." {
39 | filePath = typedpath.SlashPath(scanner.Text())
40 | } else {
41 | filePath = targetRelSlash.JoinSlashPath(typedpath.SlashPath(scanner.Text()))
42 | }
43 | writer <- Entry{
44 | Path: filePath,
45 | // NOTE: git ls-files list only files.
46 | IsDir: false,
47 | }
48 |
49 | dirname := filePath.Dir()
50 | for {
51 | // NOTE: Do not list duplicated entries.
52 | if dirSet.Has(dirname) {
53 | break
54 | }
55 |
56 | writer <- Entry{
57 | Path: dirname,
58 | IsDir: true,
59 | }
60 | dirSet.Add(dirname)
61 | dirname = dirname.Dir()
62 | }
63 | }
64 | }()
65 |
66 | if err := gitLsFiles(targetAbs, []string{}, stdoutWriter); err != nil {
67 | return err
68 | }
69 | wg.Wait()
70 | logger.Debug(fmt.Sprintf("length of set for collected path footprints: %d", dirSet.Len()))
71 | return nil
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/filecollector/filecollector_test.go:
--------------------------------------------------------------------------------
1 | package filecollector
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/git"
5 | "github.com/DeNA/unity-meta-check/util/chanutil"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "github.com/google/go-cmp/cmp"
8 | "reflect"
9 | "sort"
10 | "sync"
11 | "testing"
12 | )
13 |
14 | func TestNewOnlyTracked(t *testing.T) {
15 | spyLogger := logging.SpyLogger()
16 |
17 | // NOTE: git returns a slash separated path on Windows.
18 | gitLsFiles := git.StubRawLsFiles(`dir1/fileB
19 | fileA
20 | `, nil)
21 | opts := &Options{IgnoreCase: false}
22 | onlyTracked := New(gitLsFiles, spyLogger)
23 |
24 | spyCh := make(chan Entry)
25 | actual := make([]Entry, 0)
26 |
27 | var wg sync.WaitGroup
28 | wg.Add(1)
29 | go func() {
30 | actual = chanutil.ToSlice(spyCh)
31 | sort.Slice(actual, func(i, j int) bool {
32 | return actual[i].Path < actual[j].Path
33 | })
34 | defer wg.Done()
35 | }()
36 |
37 | if err := onlyTracked("/path/to/repo", ".", opts, spyCh); err != nil {
38 | t.Log(spyLogger.Logs.String())
39 | t.Errorf("want nil, got %#v", err)
40 | return
41 | }
42 | close(spyCh)
43 | wg.Wait()
44 |
45 | expected := []Entry{
46 | // NOTE: Directory should be added automatically (git ls-files does not print any directories)
47 | {Path: "dir1", IsDir: true},
48 | {Path: "dir1/fileB", IsDir: false},
49 | {Path: "fileA", IsDir: false},
50 | }
51 | if !reflect.DeepEqual(actual, expected) {
52 | t.Log(spyLogger.Logs.String())
53 | t.Error(cmp.Diff(expected, actual))
54 | return
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/filecollector/options.go:
--------------------------------------------------------------------------------
1 | package filecollector
2 |
3 | type Options struct {
4 | IgnoreCase bool
5 | }
6 |
--------------------------------------------------------------------------------
/filecollector/repofinder/compose.go:
--------------------------------------------------------------------------------
1 | package repofinder
2 |
3 | func Compose(repoFinders []RepoFinder) RepoFinder {
4 | return func() ([]FoundRepo, error) {
5 | result := make([]FoundRepo, 0)
6 | for _, repoFinder := range repoFinders {
7 | foundRepo, err := repoFinder()
8 | if err != nil {
9 | return nil, err
10 | }
11 | result = append(result, foundRepo...)
12 | }
13 | return result, nil
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/filecollector/repofinder/find.go:
--------------------------------------------------------------------------------
1 | package repofinder
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | type RepositoryType bool
10 |
11 | const (
12 | RepositoryTypeIsSubmodule RepositoryType = true
13 | RepositoryTypeIsNested RepositoryType = false
14 | )
15 |
16 | type RepoFinder func() ([]FoundRepo, error)
17 |
18 | func New(rootDirAbs typedpath.RawPath, targetDirRel typedpath.RawPath) RepoFinder {
19 | return func() ([]FoundRepo, error) {
20 | var targetDirAbs typedpath.RawPath
21 | if targetDirRel == "." {
22 | targetDirAbs = rootDirAbs
23 | } else {
24 | targetDirAbs = rootDirAbs.JoinRawPath(targetDirRel)
25 | }
26 |
27 | result := make([]FoundRepo, 0)
28 |
29 | if err := filepath.Walk(string(targetDirAbs), func(path string, info os.FileInfo, err error) error {
30 | if info == nil {
31 | return err
32 | }
33 |
34 | // NOTE: This matches both submodules and nested repositories. The "nested repository" means a repository
35 | // that is cloned into other repository with no any submodule commands. The nested repositories are
36 | // known as anti-pattern, but it is frequently needed for game developers.
37 | if info.Name() == ".git" {
38 | relPath, err := rootDirAbs.Rel(typedpath.RawPath(path))
39 | if err != nil {
40 | return err
41 | }
42 | isDir := info.IsDir()
43 |
44 | // NOTE: Ignore the repository at the rootDirAbs.
45 | if relPath != ".git" {
46 | result = append(result, FoundRepo{
47 | Type: RepositoryType(!isDir),
48 | RawPath: relPath.Dir(),
49 | })
50 | }
51 | // NOTE: Skip finding into .git if it is a directory (this is a nested repository). But if it is a file
52 | // (this is a submodule) skipping will skip unexpectedly the parent directory instead of the file.
53 | if isDir {
54 | return filepath.SkipDir
55 | }
56 | return nil
57 | }
58 | return nil
59 | }); err != nil {
60 | return result, err
61 | }
62 | return result, nil
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/filecollector/repofinder/find_stub.go:
--------------------------------------------------------------------------------
1 | package repofinder
2 |
3 | func StubRepoFinder(result []FoundRepo, err error) RepoFinder {
4 | return func() ([]FoundRepo, error) {
5 | return result, err
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/filecollector/repofinder/find_test.go:
--------------------------------------------------------------------------------
1 | package repofinder
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "github.com/google/go-cmp/cmp"
6 | "os"
7 | "path/filepath"
8 | "reflect"
9 | "testing"
10 | )
11 |
12 | func TestFind(t *testing.T) {
13 | testDir := setUpTestDir()
14 |
15 | findNested := New(testDir, ".")
16 |
17 | actual, err := findNested()
18 | if err != nil {
19 | t.Errorf("want nil, got %#v", err)
20 | return
21 | }
22 |
23 | expected := []FoundRepo{
24 | {RepositoryTypeIsNested, "nested1"},
25 | {RepositoryTypeIsNested, typedpath.NewRawPath("nested1", "nestedInNested1")},
26 | {RepositoryTypeIsSubmodule, "nested2"},
27 | {RepositoryTypeIsSubmodule, typedpath.NewRawPath("nested2", "nestedInNested2")},
28 | }
29 | if !reflect.DeepEqual(actual, expected) {
30 | t.Error(cmp.Diff(expected, actual))
31 | return
32 | }
33 | }
34 |
35 | func TestFindOnRel(t *testing.T) {
36 | testDir := setUpTestDir()
37 |
38 | findNested := New(testDir, "nested1")
39 |
40 | actual, err := findNested()
41 | if err != nil {
42 | t.Errorf("want nil, got %#v", err)
43 | return
44 | }
45 |
46 | expected := []FoundRepo{
47 | {RepositoryTypeIsNested, "nested1"},
48 | {RepositoryTypeIsNested, typedpath.NewRawPath("nested1", "nestedInNested1")},
49 | }
50 | if !reflect.DeepEqual(actual, expected) {
51 | t.Error(cmp.Diff(expected, actual))
52 | return
53 | }
54 | }
55 |
56 | func setUpTestDir() typedpath.RawPath {
57 | workDir, err := os.MkdirTemp(os.TempDir(), "unity-meta-check-tests.")
58 | if err != nil {
59 | panic(err.Error())
60 | }
61 |
62 | mkdirp(workDir, ".git")
63 | mkdirp(workDir, "nested1")
64 | mkdirp(workDir, "nested1", ".git")
65 | mkdirp(workDir, "nested1", "nestedInNested1")
66 | mkdirp(workDir, "nested1", "nestedInNested1", ".git")
67 | mkdirp(workDir, "nested2")
68 | touch(workDir, "nested2", ".git") // Means submodule.
69 | mkdirp(workDir, "nested2", "nestedInNested2")
70 | touch(workDir, "nested2", "nestedInNested2", ".git") // Means submodule.
71 | mkdirp(workDir, "others")
72 |
73 | return typedpath.NewRawPathUnsafe(workDir)
74 | }
75 |
76 | func mkdirp(cwd string, path ...string) {
77 | path = append([]string{cwd}, path...)
78 | if err := os.Mkdir(filepath.Join(path...), 0755); err != nil {
79 | panic(err.Error())
80 | }
81 | }
82 |
83 | func touch(cwd string, path ...string) {
84 | path = append([]string{cwd}, path...)
85 | file, err := os.Create(filepath.Join(path...))
86 | if err != nil {
87 | panic(err.Error())
88 | }
89 | if err := file.Close(); err != nil {
90 | panic(err.Error())
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/filecollector/repofinder/foundrepo.go:
--------------------------------------------------------------------------------
1 | package repofinder
2 |
3 | import "github.com/DeNA/unity-meta-check/util/typedpath"
4 |
5 | type FoundRepo struct {
6 | Type RepositoryType
7 | RawPath typedpath.RawPath
8 | }
9 |
--------------------------------------------------------------------------------
/git/config.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "io"
8 | "os/exec"
9 | "sync"
10 | )
11 |
12 | type GlobalConfig func(stdoutWriter io.WriteCloser, args ...string) error
13 |
14 | func NewGlobalConfig(logger logging.Logger) GlobalConfig {
15 | return func(stdoutWriter io.WriteCloser, args ...string) error {
16 | configWithOpts := append([]string{"config", "--global"}, args...)
17 | cmd := exec.Command("git", configWithOpts...)
18 | cmd.Stdout = stdoutWriter
19 | defer func() { _ = stdoutWriter.Close() }()
20 |
21 | stderr, err := cmd.StderrPipe()
22 | if err != nil {
23 | return err
24 | }
25 |
26 | var wg sync.WaitGroup
27 | wg.Add(1)
28 | go func() {
29 | defer wg.Done()
30 | scanner := bufio.NewScanner(stderr)
31 | for scanner.Scan() {
32 | logger.Debug(fmt.Sprintf("stderr: %s", scanner.Text()))
33 | }
34 | }()
35 |
36 | err = cmd.Run()
37 | wg.Wait()
38 | return err
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/git/config_test.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/logging"
5 | "github.com/DeNA/unity-meta-check/util/testutil"
6 | "testing"
7 | )
8 |
9 | func TestGlobalConfig(t *testing.T) {
10 | spy := testutil.SpyWriteCloser(nil)
11 | spyLogger := logging.SpyLogger()
12 |
13 | globalConfig := NewGlobalConfig(spyLogger)
14 |
15 | if err := globalConfig(spy, "--default", "", "--get", "safe.directory"); err != nil {
16 | t.Errorf("want nil, got %#v", err)
17 | t.Log(spyLogger.Logs.String())
18 | return
19 | }
20 |
21 | if !spy.IsClosed {
22 | t.Error("want true, but false")
23 | return
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/git/lsfiles.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "github.com/DeNA/unity-meta-check/util/typedpath"
8 | "io"
9 | "os/exec"
10 | "strings"
11 | "sync"
12 | )
13 |
14 | type LsFiles func(repoDir typedpath.RawPath, options []string, stdoutWriter io.WriteCloser) error
15 |
16 | func NewLsFiles(logger logging.Logger) LsFiles {
17 | return func(repoDir typedpath.RawPath, options []string, stdoutWriter io.WriteCloser) error {
18 | subcmdWithOpts := append([]string{"-c", "core.quotepath=false", "ls-files"}, options...)
19 | logger.Debug(fmt.Sprintf("exec: git %s (on %q)", strings.Join(subcmdWithOpts, " "), repoDir))
20 |
21 | cmd := exec.Command("git", subcmdWithOpts...)
22 | cmd.Dir = string(repoDir)
23 | cmd.Stdout = stdoutWriter
24 | defer func(){ _ = stdoutWriter.Close() }()
25 |
26 | stderr, err := cmd.StderrPipe()
27 | if err != nil {
28 | return err
29 | }
30 |
31 | var wg sync.WaitGroup
32 | wg.Add(1)
33 | go func() {
34 | defer wg.Done()
35 | scanner := bufio.NewScanner(stderr)
36 | for scanner.Scan() {
37 | logger.Debug(fmt.Sprintf("stderr: %s", scanner.Text()))
38 | }
39 | }()
40 |
41 | err = cmd.Run()
42 | wg.Wait()
43 | return err
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/git/lsfiles_stub.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "io"
6 | "strings"
7 | )
8 |
9 | func FakeLsFiles(lsFiles func(repoDir typedpath.RawPath)([]string, error)) LsFiles {
10 | return func(repoDir typedpath.RawPath, options []string, stdoutWriter io.WriteCloser) error {
11 | files, err := lsFiles(repoDir)
12 | if err != nil {
13 | return err
14 | }
15 | _, _ = io.WriteString(stdoutWriter, strings.Join(files, "\n"))
16 | _ = stdoutWriter.Close()
17 | return err
18 | }
19 | }
20 |
21 | func StubLsFiles(files []string, err error) LsFiles {
22 | return StubRawLsFiles(strings.Join(files, "\n") + "\n", err)
23 | }
24 |
25 | func StubRawLsFiles(stdout string, err error) LsFiles {
26 | return func(repoDir typedpath.RawPath, options []string, stdoutWriter io.WriteCloser) error {
27 | _, _ = io.WriteString(stdoutWriter, stdout)
28 | _ = stdoutWriter.Close()
29 | return err
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/git/lsfiles_test.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/logging"
5 | "github.com/DeNA/unity-meta-check/util/testutil"
6 | "testing"
7 | )
8 |
9 | func TestLsFiles(t *testing.T) {
10 | spy := testutil.SpyWriteCloser(nil)
11 | spyLogger := logging.SpyLogger()
12 |
13 | lsFiles := NewLsFiles(spyLogger)
14 |
15 | if err := lsFiles(".", []string{}, spy); err != nil {
16 | t.Errorf("want nil, got %#v", err)
17 | return
18 | }
19 |
20 | stdout := spy.Captured.String()
21 | if len(stdout) == 0 {
22 | t.Log(spyLogger.Logs.String())
23 | t.Error("want stdout > 0, but == 0")
24 | return
25 | }
26 | if !spy.IsClosed {
27 | t.Error("want true, but false")
28 | return
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/git/revparse.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "github.com/pkg/errors"
8 | "os/exec"
9 | "strings"
10 | )
11 |
12 | type RevParse func(repoDir string, options ...string) (string, error)
13 |
14 | func NewRevParse(logger logging.Logger) RevParse {
15 | return func(repoDir string, options ...string) (string, error) {
16 | subcmdWithOpts := append([]string{"-c", "core.quotepath=false", "rev-parse"}, options...)
17 | logger.Debug(fmt.Sprintf("exec: git %s (on %q)", strings.Join(subcmdWithOpts, " "), repoDir))
18 |
19 | stdoutBuf := &bytes.Buffer{}
20 | stderrBuf := &bytes.Buffer{}
21 |
22 | cmd := exec.Command("git", subcmdWithOpts...)
23 | cmd.Dir = repoDir
24 | cmd.Stdout = stdoutBuf
25 | cmd.Stderr = stderrBuf
26 |
27 | if err := cmd.Run(); err != nil {
28 | return "", errors.Wrapf(err, "failed to run git rev-parse:\nstderr:%s", stderrBuf.String())
29 | }
30 |
31 | return strings.TrimSpace(stdoutBuf.String()), nil
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/git/revparse_test.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/logging"
5 | "path/filepath"
6 | "testing"
7 | )
8 |
9 | func TestNewRevParse(t *testing.T) {
10 | spyLogger := logging.SpyLogger()
11 |
12 | revParse := NewRevParse(spyLogger)
13 |
14 | actual, err := revParse(".", "--show-toplevel")
15 |
16 | if err != nil {
17 | t.Log(spyLogger.Logs.String())
18 | t.Errorf("want nil, got %#v", err)
19 | return
20 | }
21 |
22 | if !filepath.IsAbs(actual) {
23 | t.Log(spyLogger.Logs.String())
24 | t.Errorf("want an absolute file path, got %s", actual)
25 | return
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/DeNA/unity-meta-check
2 |
3 | go 1.22.2
4 |
5 | require (
6 | github.com/google/go-cmp v0.5.5
7 | github.com/pkg/errors v0.9.1
8 | github.com/scylladb/go-set v1.0.2
9 | )
10 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
2 | github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
3 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
4 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
5 | github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
6 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
7 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
8 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
9 | github.com/scylladb/go-set v1.0.2 h1:SkvlMCKhP0wyyct6j+0IHJkBkSZL+TDzZ4E7f7BCcRE=
10 | github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs=
11 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
12 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
13 |
--------------------------------------------------------------------------------
/ignore/file.go:
--------------------------------------------------------------------------------
1 | package ignore
2 |
3 | import (
4 | "bufio"
5 | "github.com/DeNA/unity-meta-check/util/globs"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | "io"
8 | "os"
9 | "strings"
10 | )
11 |
12 | func ReadFile(path typedpath.RawPath) ([]globs.Glob, error) {
13 | file, err := os.Open(string(path))
14 | if err != nil {
15 | return nil, err
16 | }
17 | defer func(){ _ = file.Close() }()
18 | return Read(file)
19 | }
20 |
21 | func Read(reader io.Reader) ([]globs.Glob, error) {
22 | result := make([]globs.Glob, 0)
23 | scanner := bufio.NewScanner(reader)
24 | for scanner.Scan() {
25 | line := scanner.Text()
26 | elementsRaw := strings.SplitN(line, "#", 2)
27 | globPattern := strings.Trim(strings.TrimSpace(elementsRaw[0]), "/")
28 | if globPattern != "" {
29 | result = append(result, globs.Glob(globPattern))
30 | }
31 | }
32 | return result, nil
33 | }
34 |
--------------------------------------------------------------------------------
/ignore/file_test.go:
--------------------------------------------------------------------------------
1 | package ignore
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/globs"
5 | "github.com/google/go-cmp/cmp"
6 | "reflect"
7 | "strings"
8 | "testing"
9 | )
10 |
11 | func TestRead(t *testing.T) {
12 | ignoreFile := `
13 | # this is a comment
14 | foo
15 | bar/path # also this is a comment
16 | baz/ # this will be trimmed
17 | /qux # also this will be trimmed
18 | `
19 |
20 | reader := strings.NewReader(ignoreFile)
21 | actual, err := Read(reader)
22 |
23 | if err != nil {
24 | t.Errorf("want nil, got %#v", err)
25 | return
26 | }
27 |
28 | expected := []globs.Glob{"foo", "bar/path", "baz", "qux"}
29 | if !reflect.DeepEqual(actual, expected) {
30 | t.Error(cmp.Diff(expected, actual))
31 | return
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ignore/testdata/Assets/Bar/Baz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/ignore/testdata/Assets/Bar/Baz
--------------------------------------------------------------------------------
/ignore/testdata/Assets/Foo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/ignore/testdata/Assets/Foo
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/git"
7 | "github.com/DeNA/unity-meta-check/options"
8 | "github.com/DeNA/unity-meta-check/report"
9 | "github.com/DeNA/unity-meta-check/resultfilter"
10 | "github.com/DeNA/unity-meta-check/unity"
11 | "github.com/DeNA/unity-meta-check/unity/checker"
12 | "github.com/DeNA/unity-meta-check/util/cli"
13 | "github.com/DeNA/unity-meta-check/util/logging"
14 | "github.com/DeNA/unity-meta-check/util/ostestable"
15 | "github.com/DeNA/unity-meta-check/version"
16 | "os"
17 | )
18 |
19 | func main() {
20 | var cmd cli.Command
21 | profModeSwitch := os.Getenv("UNITY_META_CHECK_PROFILE")
22 | if profModeSwitch == "cpu" {
23 | cmd = cli.NewCommandWithCPUProfile(NewMain())
24 | } else if profModeSwitch == "heap" {
25 | cmd = cli.NewCommandWithHeapProfile(NewMain())
26 | } else if profModeSwitch != "" {
27 | println(fmt.Sprintf("unsupported profile mode: %q", profModeSwitch))
28 | os.Exit(1)
29 | } else {
30 | cmd = NewMain()
31 | }
32 |
33 | exitStatus := cmd(os.Args[1:], cli.GetProcessInout(), cli.NewEnv())
34 | os.Exit(int(exitStatus))
35 | }
36 |
37 | func NewMain() cli.Command {
38 | return func(args []string, procInout cli.ProcessInout, env cli.Env) cli.ExitStatus {
39 | opts, err := options.ParseArgs(args, procInout)
40 | if err != nil {
41 | if err != flag.ErrHelp {
42 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
43 | }
44 | return cli.ExitAbnormal
45 | }
46 |
47 | if opts.Version {
48 | _, _ = fmt.Fprintln(procInout.Stdout, version.Version)
49 | return cli.ExitNormal
50 | }
51 |
52 | logger := logging.NewLogger(opts.LogLevel, procInout.Stderr)
53 |
54 | check := checker.NewChecker(
55 | checker.NewStrategySelector(
56 | unity.NewFindPackages(logger),
57 | git.NewLsFiles(logger),
58 | logger,
59 | ),
60 | logger,
61 | )
62 | result, err := check(
63 | opts.RootDirAbs,
64 | &checker.Options{
65 | IgnoreCase: opts.IgnoreCase,
66 | IgnoreSubmodulesAndNested: opts.IgnoreSubmodulesAndNested,
67 | TargetType: opts.TargetType,
68 | },
69 | )
70 | if err != nil {
71 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
72 | return cli.ExitAbnormal
73 | }
74 |
75 | filterFunc := resultfilter.NewFilter(ostestable.NewGetwd(), logger)
76 | filtered, err := filterFunc(result, &resultfilter.Options{
77 | IgnoreDangling: opts.IgnoreDangling,
78 | IgnoredGlobs: opts.IgnoredPaths,
79 | })
80 | if err != nil {
81 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
82 | return cli.ExitAbnormal
83 | }
84 |
85 | if err := report.WriteResult(procInout.Stdout, filtered); err != nil {
86 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
87 | return cli.ExitAbnormal
88 | }
89 |
90 | if !filtered.Empty() {
91 | return cli.ExitAbnormal
92 | }
93 | return cli.ExitNormal
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/options/env.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | const GitHubTokenEnv = "GITHUB_TOKEN"
4 |
--------------------------------------------------------------------------------
/options/ignorefile.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/ignore"
5 | "github.com/DeNA/unity-meta-check/util/globs"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "github.com/DeNA/unity-meta-check/util/typedpath"
8 | "os"
9 | )
10 |
11 | const IgnoreFileBasename typedpath.BaseName = ".meta-check-ignore"
12 |
13 | type IgnoredGlobsBuilder func(ignoreFilePath typedpath.RawPath, rootDirAbs typedpath.RawPath) ([]globs.Glob, error)
14 |
15 | func NewIgnoredGlobsBuilder(logger logging.Logger) IgnoredGlobsBuilder {
16 | return func(ignoreFilePath typedpath.RawPath, rootDirAbs typedpath.RawPath) ([]globs.Glob, error) {
17 | ignoredGlobs, err := ignore.ReadFile(getIgnoreFilePath(ignoreFilePath, rootDirAbs))
18 | if err != nil {
19 | // NOTE: If it is a default value, missing .meta-check-ignore is allowed because it is optional.
20 | // Otherwise, treat as an error if specified ignoreFilePath is missing.
21 | if ignoreFilePath != "" || !os.IsNotExist(err) {
22 | return nil, err
23 | }
24 | logger.Info("no .meta-check-ignore, so ignored paths are empty")
25 | ignoredGlobs = []globs.Glob{}
26 | }
27 | return ignoredGlobs, nil
28 | }
29 | }
30 |
31 | func getIgnoreFilePath(path typedpath.RawPath, rootDirAbs typedpath.RawPath) typedpath.RawPath {
32 | if path == "" {
33 | return rootDirAbs.JoinBaseName(IgnoreFileBasename)
34 | } else {
35 | return path
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/options/ignorefile_stub.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/globs"
5 | "github.com/DeNA/unity-meta-check/util/typedpath"
6 | )
7 |
8 | func StubIgnoredPathBuilder(result []globs.Glob, err error) IgnoredGlobsBuilder {
9 | return func(typedpath.RawPath, typedpath.RawPath) ([]globs.Glob, error) {
10 | return result, err
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/options/ignorefile_test.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/globs"
5 | "github.com/DeNA/unity-meta-check/util/logging"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | "github.com/google/go-cmp/cmp"
8 | "reflect"
9 | "testing"
10 | )
11 |
12 | func TestNewIgnoredPathsBuilderSpecifiedAndIgnoreFileExists(t *testing.T) {
13 | spyLogger := logging.SpyLogger()
14 | buildIgnorePaths := NewIgnoredGlobsBuilder(spyLogger)
15 |
16 | actual, err := buildIgnorePaths(
17 | typedpath.NewRawPath("testdata", "ignorefile", "ProjectHasMetaCheckIgnore", ".meta-check-ignore"),
18 | "/path/to/any",
19 | )
20 |
21 | if err != nil {
22 | t.Errorf("want nil, got %#v", err)
23 | return
24 | }
25 |
26 | expected := []globs.Glob{
27 | "path/to/ignored",
28 | }
29 | if !reflect.DeepEqual(actual, expected) {
30 | t.Log(spyLogger.Logs.String())
31 | t.Error(cmp.Diff(expected, actual))
32 | return
33 | }
34 | }
35 |
36 | func TestNewIgnoredPathsBuilderSpecifiedButIgnoreFileDoesNotExist(t *testing.T) {
37 | spyLogger := logging.SpyLogger()
38 | buildIgnorePaths := NewIgnoredGlobsBuilder(spyLogger)
39 |
40 | _, err := buildIgnorePaths(
41 | typedpath.NewRawPath("testdata", "ignorefile", "ProjectDoesNotHaveMetaCheckIgnore", ".meta-check-ignore"),
42 | "/path/to/any",
43 | )
44 |
45 | if err == nil {
46 | t.Log(spyLogger.Logs.String())
47 | t.Error("want error, got nil")
48 | return
49 | }
50 | }
51 |
52 | func TestNewIgnoredPathsBuilderOmitButIgnoreFileExists(t *testing.T) {
53 | spyLogger := logging.SpyLogger()
54 | buildIgnorePaths := NewIgnoredGlobsBuilder(spyLogger)
55 |
56 | actual, err := buildIgnorePaths(
57 | "",
58 | typedpath.NewRawPath("testdata", "ignorefile", "ProjectHasMetaCheckIgnore"),
59 | )
60 |
61 | if err != nil {
62 | t.Log(spyLogger.Logs.String())
63 | t.Errorf("want nil, got %#v", err)
64 | return
65 | }
66 |
67 | expected := []globs.Glob{
68 | "path/to/ignored",
69 | }
70 | if !reflect.DeepEqual(actual, expected) {
71 | t.Log(spyLogger.Logs.String())
72 | t.Error(cmp.Diff(expected, actual))
73 | return
74 | }
75 | }
76 |
77 | func TestNewIgnoredPathsBuilderOmitAndIgnoreFileDoesNotExist(t *testing.T) {
78 | spyLogger := logging.SpyLogger()
79 | buildIgnorePaths := NewIgnoredGlobsBuilder(spyLogger)
80 |
81 | actual, err := buildIgnorePaths(
82 | "",
83 | typedpath.NewRawPath("testdata", "meta-check-ignore", "ProjectDoesNotHaveMetaCheckIgnore"),
84 | )
85 |
86 | if err != nil {
87 | t.Log(spyLogger.Logs.String())
88 | t.Errorf("want nil, got %#v", err)
89 | return
90 | }
91 |
92 | expected := make([]globs.Glob, 0)
93 | if !reflect.DeepEqual(actual, expected) {
94 | t.Log(spyLogger.Logs.String())
95 | t.Error(cmp.Diff(expected, actual))
96 | return
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/options/options.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/unity/checker"
5 | "github.com/DeNA/unity-meta-check/util/globs"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "github.com/DeNA/unity-meta-check/util/typedpath"
8 | )
9 |
10 | type Options struct {
11 | Version bool
12 | LogLevel logging.Severity
13 | TargetType checker.TargetType
14 | IgnoreDangling bool
15 | IgnoreCase bool
16 | IgnoreSubmodulesAndNested bool
17 | IgnoredPaths []globs.Glob
18 | RootDirAbs typedpath.RawPath
19 | }
20 |
--------------------------------------------------------------------------------
/options/rootdir.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/git"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "github.com/DeNA/unity-meta-check/util/ostestable"
8 | "github.com/DeNA/unity-meta-check/util/typedpath"
9 | "github.com/pkg/errors"
10 | "path/filepath"
11 | )
12 |
13 | type RootDirCompletion func() (typedpath.RawPath, error)
14 |
15 | func NewRootDirCompletion(gitRevParse git.RevParse, logger logging.Logger) RootDirCompletion {
16 | return func() (typedpath.RawPath, error) {
17 | assumedRootDir, err := gitRevParse(".", "--show-toplevel")
18 | if err != nil {
19 | return "", errors.Wrap(err, "rootDir not specified and seems not being in any git repositories")
20 | }
21 |
22 | logger.Debug(fmt.Sprintf("rootDir not specified, so assumed by $(git rev-parse --show-toplevel): %q", assumedRootDir))
23 | return typedpath.NewRawPathUnsafe(assumedRootDir), nil
24 | }
25 | }
26 |
27 | type RootDirAbsValidator func(unsafeRootDir typedpath.RawPath) (typedpath.RawPath, error)
28 |
29 | func NewRootDirValidator(isDir ostestable.IsDir) RootDirAbsValidator {
30 | return func(unsafeRootDir typedpath.RawPath) (typedpath.RawPath, error) {
31 | rootDirAbsStr, err := filepath.Abs(string(unsafeRootDir))
32 | if err != nil {
33 | return "", err
34 | }
35 |
36 | rootDirAbs := typedpath.NewRawPathUnsafe(rootDirAbsStr)
37 |
38 | ok, err := isDir(rootDirAbs)
39 | if err != nil {
40 | return "", errors.Wrapf(err, "cannot check directory: %q", rootDirAbs)
41 | }
42 | if !ok {
43 | return "", fmt.Errorf("root directory must be a directory: %s", rootDirAbsStr)
44 | }
45 |
46 | return rootDirAbs, nil
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/options/rootdir_stub.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "strings"
6 | )
7 |
8 | func StubRootDirCompletion(path typedpath.RawPath, err error) RootDirCompletion {
9 | return func() (typedpath.RawPath, error) {
10 | return path, err
11 | }
12 | }
13 |
14 | func StubRootDirValidator(path typedpath.RawPath, err error) RootDirAbsValidator {
15 | return func(_ typedpath.RawPath) (typedpath.RawPath, error) {
16 | return path, err
17 | }
18 | }
19 |
20 | func FakeRootDirValidator(cwd typedpath.RawPath) RootDirAbsValidator {
21 | return func(unsafeRootDir typedpath.RawPath) (typedpath.RawPath, error) {
22 | if strings.HasPrefix(string(unsafeRootDir), "/") {
23 | return unsafeRootDir, nil
24 | }
25 |
26 | return cwd.JoinRawPath(unsafeRootDir), nil
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/options/rootdir_test.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "testing"
6 | )
7 |
8 | func TestFakeRootDirValidator(t *testing.T) {
9 | cases := map[string]struct{
10 | Cwd typedpath.SlashPath
11 | RootDir typedpath.SlashPath
12 | Expected typedpath.SlashPath
13 | } {
14 | "already absolute": {
15 | RootDir: "/already/absolute",
16 | Expected: "/already/absolute",
17 | },
18 | "only .": {
19 | Cwd: "/path/to/cwd",
20 | RootDir: ".",
21 | Expected: "/path/to/cwd",
22 | },
23 | "relative start with ./": {
24 | Cwd: "/path/to/cwd",
25 | RootDir: "./rel",
26 | Expected: "/path/to/cwd/rel",
27 | },
28 | "relative not start with ./": {
29 | Cwd: "/path/to/cwd",
30 | RootDir: "rel",
31 | Expected: "/path/to/cwd/rel",
32 | },
33 | }
34 |
35 | for desc, c := range cases {
36 | t.Run(desc, func(t *testing.T) {
37 | validateRootDir := FakeRootDirValidator(c.Cwd.ToRaw())
38 | actual, err := validateRootDir(c.RootDir.ToRaw())
39 | if err != nil {
40 | t.Errorf("want nil, got %#v", err)
41 | return
42 | }
43 |
44 | if actual.ToSlash() != c.Expected {
45 | t.Errorf("want %q, got %q", c.Expected, actual.ToSlash())
46 | }
47 | })
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/options/testdata/ignorefile/ProjectDoesNotHaveMetaCheckIgnore/.gitempty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/options/testdata/ignorefile/ProjectDoesNotHaveMetaCheckIgnore/.gitempty
--------------------------------------------------------------------------------
/options/testdata/ignorefile/ProjectHasMetaCheckIgnore/.meta-check-ignore:
--------------------------------------------------------------------------------
1 | path/to/ignored
--------------------------------------------------------------------------------
/options/unityproject.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/unity"
6 | "github.com/DeNA/unity-meta-check/unity/checker"
7 | "github.com/DeNA/unity-meta-check/util/logging"
8 | "github.com/DeNA/unity-meta-check/util/typedpath"
9 | "github.com/pkg/errors"
10 | "os"
11 | )
12 |
13 | type UnityProjectDetector func(rootDirAbs typedpath.RawPath) (checker.TargetType, error)
14 |
15 | func NewUnityProjectDetector(logger logging.Logger) UnityProjectDetector {
16 | return func(rootDirAbs typedpath.RawPath) (checker.TargetType, error) {
17 | isUnityProj, err := hasUnityProjectSpecificDirectory(rootDirAbs, logger)
18 | if err != nil {
19 | return "", errors.Wrap(err, "automatic check mode detection was failed")
20 | }
21 |
22 | if isUnityProj {
23 | return checker.TargetTypeIsUnityProjectRootDirectory, nil
24 | }
25 | return checker.TargetTypeIsUnityProjectSubDirectory, nil
26 | }
27 | }
28 |
29 | func hasUnityProjectSpecificDirectory(rootDirAbs typedpath.RawPath, logger logging.Logger) (bool, error) {
30 | assetsDir := rootDirAbs.JoinBaseName(unity.AssetsDirBaseName)
31 | _, err := os.Stat(string(assetsDir))
32 | if err != nil {
33 | if os.IsNotExist(err) {
34 | logger.Debug(fmt.Sprintf("seems a not Unity Project because %s is not found.", assetsDir))
35 | return false, nil
36 | }
37 | return false, err
38 | }
39 | logger.Debug(fmt.Sprintf("seems an Unity Project because %s is found.", assetsDir))
40 | return true, nil
41 | }
42 |
--------------------------------------------------------------------------------
/options/unityproject_stub.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/unity/checker"
5 | "github.com/DeNA/unity-meta-check/util/typedpath"
6 | )
7 |
8 | func StubUnityProjectDetector(result checker.TargetType, err error) UnityProjectDetector {
9 | return func(_ typedpath.RawPath) (checker.TargetType, error) {
10 | return result, err
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/report/parser.go:
--------------------------------------------------------------------------------
1 | package report
2 |
3 | import (
4 | "bufio"
5 | "github.com/DeNA/unity-meta-check/unity/checker"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | "io"
8 | "strings"
9 | )
10 |
11 | type Parser func(reader io.Reader) *checker.CheckResult
12 |
13 | func NewParser() Parser {
14 | return func(reader io.Reader) *checker.CheckResult {
15 | scanner := bufio.NewScanner(reader)
16 |
17 | missingMeta := make([]typedpath.SlashPath, 0)
18 | danglingMeta := make([]typedpath.SlashPath, 0)
19 |
20 | for scanner.Scan() {
21 | line := scanner.Text()
22 |
23 | if strings.HasPrefix(line, MissingMetaLinePrefix) {
24 | missingMeta = append(missingMeta, typedpath.NewSlashPathUnsafe(strings.TrimPrefix(line, MissingMetaLinePrefix)))
25 | continue
26 | }
27 |
28 | if strings.HasPrefix(line, DanglingMetaPrefix) {
29 | danglingMeta = append(danglingMeta, typedpath.NewSlashPathUnsafe(strings.TrimPrefix(line, DanglingMetaPrefix)))
30 | continue
31 | }
32 | }
33 |
34 | return &checker.CheckResult{
35 | MissingMeta: missingMeta,
36 | DanglingMeta: danglingMeta,
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/report/parser_test.go:
--------------------------------------------------------------------------------
1 | package report
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/unity/checker"
7 | "github.com/DeNA/unity-meta-check/util/typedpath"
8 | "github.com/google/go-cmp/cmp"
9 | "reflect"
10 | "testing"
11 | )
12 |
13 | func TestNewLineReporterParser(t *testing.T) {
14 | cases := []*checker.CheckResult{
15 | checker.NewCheckResult([]typedpath.SlashPath{}, []typedpath.SlashPath{}),
16 | checker.NewCheckResult([]typedpath.SlashPath{
17 | "path/to/missing1.meta",
18 | "path/to/missing2.meta",
19 | }, []typedpath.SlashPath{}),
20 | checker.NewCheckResult([]typedpath.SlashPath{}, []typedpath.SlashPath{
21 | "path/to/dangling1.meta",
22 | "path/to/dangling2.meta",
23 | }),
24 | checker.NewCheckResult(
25 | []typedpath.SlashPath{
26 | "path/to/missing1.meta",
27 | "path/to/missing2.meta",
28 | },
29 | []typedpath.SlashPath{
30 | "path/to/dangling1.meta",
31 | "path/to/dangling2.meta",
32 | },
33 | ),
34 | }
35 |
36 | for _, result := range cases {
37 | t.Run(fmt.Sprintf("%v", result), func(t *testing.T) {
38 | buf := &bytes.Buffer{}
39 |
40 | if err := WriteResult(buf, result); err != nil {
41 | t.Errorf("want nil, got %#v", err)
42 | return
43 | }
44 |
45 | parse := NewParser()
46 | actual := parse(buf)
47 | expected := result
48 |
49 | if !reflect.DeepEqual(actual, expected) {
50 | t.Error(cmp.Diff(expected, actual))
51 | return
52 | }
53 | })
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/report/printer.go:
--------------------------------------------------------------------------------
1 | package report
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/unity/checker"
6 | "io"
7 | )
8 |
9 | const (
10 | MissingMetaLinePrefix = "missing "
11 | DanglingMetaPrefix = "dangling "
12 | )
13 |
14 | func WriteResult(writer io.Writer, result *checker.CheckResult) error {
15 | if len(result.MissingMeta) > 0 {
16 | for _, missing := range result.MissingMeta {
17 | if _, err := io.WriteString(writer, fmt.Sprintf("%s%s\n", MissingMetaLinePrefix, missing)); err != nil {
18 | return err
19 | }
20 | }
21 | }
22 |
23 | if len(result.DanglingMeta) > 0 {
24 | for _, dangling := range result.DanglingMeta {
25 | if _, err := io.WriteString(writer, fmt.Sprintf("%s%s\n", DanglingMetaPrefix, dangling)); err != nil {
26 | return err
27 | }
28 | }
29 | }
30 |
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/resultfilter/resultfilter.go:
--------------------------------------------------------------------------------
1 | package resultfilter
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/unity/checker"
6 | "github.com/DeNA/unity-meta-check/util/globs"
7 | "github.com/DeNA/unity-meta-check/util/logging"
8 | "github.com/DeNA/unity-meta-check/util/ostestable"
9 | "github.com/DeNA/unity-meta-check/util/typedpath"
10 | )
11 |
12 | type Options struct {
13 | IgnoreDangling bool
14 | IgnoredGlobs []globs.Glob
15 | IgnoreCase bool
16 | }
17 |
18 | type Filter func(result *checker.CheckResult, opts *Options) (*checker.CheckResult, error)
19 |
20 | func NewFilter(getwd ostestable.Getwd, logger logging.Logger) Filter {
21 | return func(result *checker.CheckResult, opts *Options) (*checker.CheckResult, error) {
22 | var ignored bool
23 | var matched globs.Glob
24 | rawWd, err := getwd()
25 | if err != nil {
26 | return nil, err
27 | }
28 | wd := rawWd.ToSlash()
29 |
30 | newMissingMeta := make([]typedpath.SlashPath, 0)
31 | for _, missingMeta := range result.MissingMeta {
32 | ignored, matched, err = globs.MatchAny(missingMeta, opts.IgnoredGlobs, wd)
33 | if err != nil {
34 | return nil, err
35 | }
36 | if ignored {
37 | logger.Debug(fmt.Sprintf("ignored missing: %q by %q", missingMeta, matched))
38 | continue
39 | }
40 | newMissingMeta = append(newMissingMeta, missingMeta)
41 | }
42 |
43 | newDanglingMeta := make([]typedpath.SlashPath, 0)
44 | if !opts.IgnoreDangling {
45 | for _, danglingMeta := range result.DanglingMeta {
46 | ignored, matched, err = globs.MatchAny(danglingMeta, opts.IgnoredGlobs, wd)
47 | if err != nil {
48 | return nil, err
49 | }
50 | if ignored {
51 | logger.Debug(fmt.Sprintf("ignored dangling: %q by %q", danglingMeta, matched))
52 | continue
53 | }
54 | newDanglingMeta = append(newDanglingMeta, danglingMeta)
55 | }
56 | }
57 |
58 | return checker.NewCheckResult(newMissingMeta, newDanglingMeta), nil
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/test/compat:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 |
5 | throw() {
6 | local message="$*"
7 | printf "error: %s\n" "$message" 1>&2
8 | false
9 | }
10 |
11 |
12 | error_help() {
13 | local message="$*"
14 | printf "error: %s\n" "$message" 1>&2
15 | usage
16 | false
17 | }
18 |
19 |
20 | usage() {
21 | echo 'usage: compat '
22 | echo
23 | echo 'ARGUMENTS'
24 | echo ' absolute path to old unity-meta-check executable'
25 | echo ' absolute path to new unity-meta-check executable'
26 | echo ' path to directory where compatibility tests running on'
27 | echo ' arguments for unity-meta-check to give to both old and new'
28 | echo
29 | echo 'EXAMPLES'
30 | echo ' compat path/to/old/unity-meta-check path/to/new-unity-meta-check path/to/Unity/Project'
31 | }
32 |
33 |
34 | main() {
35 | local old_bin="${1:-}"
36 | local new_bin="${2:-}"
37 | local work_dir="${3:-}"
38 | shift 3
39 |
40 | "$old_bin" -version >/dev/null || error_help "old binary does not exist: $old_bin"
41 | "$new_bin" -version >/dev/null || error_help "new binary does not exist: $new_bin"
42 |
43 | [[ -d "$work_dir" ]] || error_help "working directory does not exist: $work_dir"
44 | (cd "$work_dir"
45 | git rev-parse --is-inside-work-tree >/dev/null || error_help "working directory is not in a git repository"
46 |
47 | local tmpdir
48 | tmpdir="$(mktemp -d "/tmp/unity-meta-check-compat.XXXXXX")"
49 |
50 | set +e
51 | "$old_bin" -silent "$@" > "${tmpdir}/old" 2>&1
52 | local old_status=$?
53 |
54 | "$new_bin" -silent "$@" > "${tmpdir}/new" 2>&1
55 | local new_status=$?
56 | set -e
57 |
58 | if [[ $old_status -eq $new_status ]]; then
59 | echo "ok: match exit status (old: $old_status == new: $new_status)"
60 | rm -r "$tmpdir"
61 | else
62 | echo "ng: exit status mismatch (old: $old_status != new: $new_status)"
63 | diff -u "${tmpdir}/old" "${tmpdir}/new"
64 | false
65 | fi
66 | )
67 | }
68 |
69 |
70 | case "$*" in
71 | --help | -h)
72 | usage
73 | false ;;
74 | *)
75 | main "$@" ;;
76 | esac
77 |
--------------------------------------------------------------------------------
/test/compat-revisions:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | BASE_DIR="$(cd "$(dirname "$0")"; pwd)"
5 |
6 |
7 | throw() {
8 | local message="$*"
9 | printf "error: %s\n" "$message" 1>&2
10 | false
11 | }
12 |
13 |
14 | error_help() {
15 | local message="$*"
16 | printf "error: %s\n" "$message" 1>&2
17 | usage
18 | false
19 | }
20 |
21 |
22 | usage() {
23 | echo 'usage: compat-revisions []'
24 | echo
25 | echo 'ARGUMENTS'
26 | echo ' absolute path to old unity-meta-check executable'
27 | echo ' absolute path to new unity-meta-check executable'
28 | echo ' path to directory where compatibility tests running on'
29 | echo ' number of revisions from HEAD to test (default: 10)'
30 | echo ' arguments for unity-meta-check to give to both old and new'
31 | echo
32 | echo 'EXAMPLES'
33 | echo ' compat-revisions path/to/old/unity-meta-check path/to/new-unity-meta-check path/to/Unity/Project 100'
34 | }
35 |
36 |
37 | main() {
38 | local old_bin="${1:-}"
39 | local new_bin="${2:-}"
40 | local work_dir="${3:-}"
41 | local num="${4:-10}"
42 | shift 4
43 |
44 | "$old_bin" -version >/dev/null || error_help "old binary does not exist: $old_bin"
45 | "$new_bin" -version >/dev/null || error_help "new binary does not exist: $new_bin"
46 |
47 | [[ -d "$work_dir" ]] || error_help "working directory does not exist: $work_dir"
48 | (cd "$work_dir"
49 | git rev-parse --is-inside-work-tree >/dev/null || error_help "working directory is not in a git repository"
50 |
51 | local failed=0
52 | while read -r rev; do
53 | git checkout --quiet "$rev"
54 | git submodule sync --quiet
55 | git submodule update --quiet --init --recursive
56 | printf "%s: " "$rev"
57 |
58 | if ! "${BASE_DIR}/compat" "$old_bin" "$new_bin" "$work_dir" "$@"; then
59 | failed=1
60 | fi
61 | done < <(git rev-list -n "$num" HEAD)
62 |
63 | [[ $failed -eq 0 ]]
64 | )
65 | }
66 |
67 |
68 | case "$*" in
69 | --help | -h)
70 | usage
71 | false ;;
72 | *)
73 | main "$@" ;;
74 | esac
75 |
--------------------------------------------------------------------------------
/test/perf-compat:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 |
5 | throw() {
6 | local message="$*"
7 | printf "error: %s\n" "$message" 1>&2
8 | false
9 | }
10 |
11 |
12 | error_help() {
13 | local message="$*"
14 | printf "error: %s\n" "$message" 1>&2
15 | usage
16 | false
17 | }
18 |
19 |
20 | usage() {
21 | echo 'usage: compat '
22 | echo
23 | echo 'ARGUMENTS'
24 | echo ' path to old unity-meta-check executable'
25 | echo ' path to new unity-meta-check executable'
26 | echo ' path to directory where compatibility tests running on'
27 | echo
28 | echo 'EXAMPLES'
29 | echo ' compat path/to/old/unity-meta-check path/to/new-unity-meta-check path/to/Unity/Project'
30 | }
31 |
32 |
33 | main() {
34 | local old_bin="${1:-}"
35 | local new_bin="${2:-}"
36 | local work_dir="${3:-}"
37 |
38 | "$old_bin" -version >/dev/null || error_help "old binary does not exist: $old_bin"
39 | "$new_bin" -version >/dev/null || error_help "new binary does not exist: $new_bin"
40 |
41 | [[ -d "$work_dir" ]] || error_help "working directory does not exist: $work_dir"
42 | (cd "$work_dir"
43 | git rev-parse --is-inside-work-tree >/dev/null || error_help "working directory is not in a git repository"
44 |
45 | set +e
46 | "$old_bin" -silent >/dev/null 2>&1
47 | echo -n "old:"
48 | time "$old_bin" -silent 2>&1
49 |
50 | "$new_bin" -silent >/dev/null 2>&1
51 | echo -n "new:"
52 | time "$new_bin" -silent 2>&1
53 | set -e
54 | )
55 | }
56 |
57 |
58 | main "$@"
59 |
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/cmd/cmd.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/tool/gh-action/action-yaml-gen/yaml"
7 | "github.com/DeNA/unity-meta-check/util/cli"
8 | "github.com/pkg/errors"
9 | "io"
10 | "os"
11 | )
12 |
13 | func Main(args []string, procInout cli.ProcessInout, _ cli.Env) cli.ExitStatus {
14 | if len(args) != 1 {
15 | _, _ = fmt.Fprintf(procInout.Stderr, "error: must specify 1 argument, but come %d arguments\n", len(args))
16 | return cli.ExitAbnormal
17 | }
18 |
19 | flags := flag.NewFlagSet("gh-action-yaml-gen", flag.ContinueOnError)
20 | flags.SetOutput(io.Discard)
21 |
22 | if err := flags.Parse(args); err != nil {
23 | _, _ = fmt.Fprintln(procInout.Stderr, "usage: gh-action-yaml-gen ")
24 | return cli.ExitAbnormal
25 | }
26 |
27 | actionYAMLPath := args[0]
28 | if err := writeYAML(actionYAMLPath); err != nil {
29 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
30 | return cli.ExitAbnormal
31 | }
32 |
33 | return cli.ExitNormal
34 | }
35 |
36 | func writeYAML(actionYAMLPath string) error {
37 | f, err := os.OpenFile(actionYAMLPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
38 | if err != nil {
39 | return errors.Wrapf(err, "cannot open: %q", actionYAMLPath)
40 | }
41 | defer func(f *os.File) {
42 | _ = f.Close()
43 | }(f)
44 |
45 | if _, err := yaml.WriteTo(f); err != nil {
46 | return errors.Wrapf(err, "cannot write YAML to: %q", actionYAMLPath)
47 | }
48 |
49 | return nil
50 | }
51 |
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/cmd/cmd_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/cli"
5 | "github.com/DeNA/unity-meta-check/util/testutil"
6 | "io"
7 | "io/ioutil"
8 | "os"
9 | "path"
10 | "strings"
11 | "testing"
12 | )
13 |
14 | func TestNewMain(t *testing.T) {
15 | tmpDir, err := ioutil.TempDir(os.TempDir(), "action-yaml-gen-test.*")
16 | if err != nil {
17 | t.Errorf("want nil, got %#v", err)
18 | return
19 | }
20 |
21 | stdin := io.NopCloser(strings.NewReader(""))
22 | stdout := testutil.SpyWriteCloser(nil)
23 | stderr := testutil.SpyWriteCloser(nil)
24 | procInput := cli.ProcessInout{
25 | Stdin: stdin,
26 | Stdout: stdout,
27 | Stderr: stderr,
28 | }
29 | env := cli.StubEnv(map[string]string{})
30 |
31 | actual := Main([]string{path.Join(tmpDir, "action.yml")}, procInput, env)
32 |
33 | if actual != cli.ExitNormal {
34 | t.Log(stdout.Captured.String())
35 | t.Log(stderr.Captured.String())
36 | t.Errorf("want %d, got %d", cli.ExitAbnormal, actual)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/tool/gh-action/action-yaml-gen/cmd"
5 | "github.com/DeNA/unity-meta-check/util/cli"
6 | "os"
7 | )
8 |
9 | func main() {
10 | exitStatus := cmd.Main(os.Args[1:], cli.GetProcessInout(), cli.NewEnv())
11 | os.Exit(int(exitStatus))
12 | }
13 |
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/testdata/action.yml:
--------------------------------------------------------------------------------
1 | ../../../../action.yml
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/yaml/inputs.go:
--------------------------------------------------------------------------------
1 | package yaml
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type (
8 | Name string
9 | Desc string
10 |
11 | InputDef interface {
12 | Name() Name
13 | Desc() Desc
14 | Required() bool
15 | DefaultValueAsYAML() string
16 | }
17 | )
18 |
19 | type StringInputDef struct {
20 | name Name
21 | desc Desc
22 | required bool
23 | defaultValue string
24 | }
25 |
26 | func (s StringInputDef) Name() Name {
27 | return s.name
28 | }
29 |
30 | func (s StringInputDef) Desc() Desc {
31 | return s.desc
32 | }
33 |
34 | func (s StringInputDef) Required() bool {
35 | return s.required
36 | }
37 |
38 | func (s StringInputDef) DefaultValueAsYAML() string {
39 | return fmt.Sprintf("%q", s.defaultValue)
40 | }
41 |
42 | type BoolInputDef struct {
43 | name Name
44 | desc Desc
45 | required bool
46 | defaultValue bool
47 | }
48 |
49 | func (b BoolInputDef) Name() Name {
50 | return b.name
51 | }
52 |
53 | func (b BoolInputDef) Desc() Desc {
54 | return b.desc
55 | }
56 |
57 | func (b BoolInputDef) Required() bool {
58 | return b.required
59 | }
60 |
61 | func (b BoolInputDef) DefaultValueAsYAML() string {
62 | if b.defaultValue {
63 | return `"true"`
64 | }
65 | return `"false"`
66 | }
67 |
68 | type IntInputDef struct {
69 | name Name
70 | desc Desc
71 | required bool
72 | defaultValue int
73 | }
74 |
75 | func (u IntInputDef) Name() Name {
76 | return u.name
77 | }
78 |
79 | func (u IntInputDef) Desc() Desc {
80 | return u.desc
81 | }
82 |
83 | func (u IntInputDef) Required() bool {
84 | return u.required
85 | }
86 |
87 | func (u IntInputDef) DefaultValueAsYAML() string {
88 | return fmt.Sprintf("%q", fmt.Sprintf("%d", u.defaultValue))
89 | }
90 |
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/yaml/inputs_test.go:
--------------------------------------------------------------------------------
1 | package yaml
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | var _ InputDef = StringInputDef{}
8 | var _ InputDef = BoolInputDef{}
9 | var _ InputDef = IntInputDef{}
10 |
11 | func TestInputDefs(t *testing.T) {
12 | for i, inputDef := range BuildMetadata().Inputs {
13 | if inputDef.Name() == "" {
14 | t.Errorf("InputDefs[%d] must have a non-empty name", i)
15 | }
16 |
17 | if inputDef.Desc() == "" {
18 | t.Errorf("%q must have a non-empty description", inputDef.Name())
19 | }
20 |
21 | if !inputDef.Required() && inputDef.DefaultValueAsYAML() == "" {
22 | t.Errorf("optional input %q must have a non-empty default value representation for YAML", inputDef.Name())
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/yaml/runs.go:
--------------------------------------------------------------------------------
1 | package yaml
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | const ImageName = "ghcr.io/dena/unity-meta-check/unity-meta-check-gh-action"
8 |
9 | func ImageWithTag(version string) DockerImage {
10 | return DockerImage(fmt.Sprintf("%s:%s", ImageName, version))
11 | }
12 |
13 | type (
14 | DockerImage string
15 | BrandingIcon string
16 | BrandingColor string
17 |
18 | DockerAction struct {
19 | // Image is the Docker image to use as the container to run the action. The value can be the Docker base image name,
20 | // a local Dockerfile in your repository, or a public image in Docker Hub or another registry.
21 | // To reference a Dockerfile local to your repository, the file must be named Dockerfile and you must use a path
22 | // relative to your action metadata file. The docker application will execute this file.
23 | // SEE: https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runsimage
24 | Image DockerImage
25 |
26 | // Env specifies a key/value map of environment variables to set in the container environment.
27 | // SEE: https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runsenv
28 | Env map[string]string
29 |
30 | // Args is an array of strings that define the inputs for a Docker container. Inputs can include hardcoded strings.
31 | // GitHub passes the args to the container's ENTRYPOINT when the container starts up.
32 | // The args are used in place of the CMD instruction in a Dockerfile. If you use CMD in your Dockerfile, use the guidelines ordered by preference:
33 | // 1. Document required arguments in the action's README and omit them from the CMD instruction.
34 | // 2. Use defaults that allow using the action without specifying any args.
35 | // 3. If the action exposes a --help flag, or something similar, use that to make your action self-documenting.
36 | // If you need to pass environment variables into an action, make sure your action runs a command shell to perform variable substitution.
37 | // For example, if your entrypoint attribute is set to "sh -c", args will be run in a command shell.
38 | // Alternatively, if your Dockerfile uses an ENTRYPOINT to run the same command ("sh -c"),
39 | // args will execute in a command shell.
40 | //
41 | // For more information about using the CMD instruction with GitHub Actions, see "Dockerfile support for GitHub Actions."
42 | // SEE: https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runsargs
43 | Args []string
44 | }
45 | )
46 |
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/yaml/runs_test.go:
--------------------------------------------------------------------------------
1 | package yaml
2 |
3 | import (
4 | "bufio"
5 | "errors"
6 | "fmt"
7 | "os"
8 | "strings"
9 | "testing"
10 | )
11 |
12 | const tagNamePrefix = "# image-name: "
13 |
14 | func TestImage(t *testing.T) {
15 | expected, err := getActualImageName("../../../../.github/images/Dockerfile")
16 | if err != nil {
17 | t.Errorf("want nil, got %#v", err)
18 | return
19 | }
20 |
21 | if ImageName != expected {
22 | t.Errorf("want %q, got %q", expected, ImageName)
23 | }
24 | }
25 |
26 | func getActualImageName(dockerfilePath string) (DockerImage, error) {
27 | dockerfile, err := os.OpenFile(dockerfilePath, os.O_RDONLY, 0)
28 | if err != nil {
29 | return "", err
30 | }
31 | defer func(dockerfile *os.File) {
32 | _ = dockerfile.Close()
33 | }(dockerfile)
34 |
35 | scanner := bufio.NewScanner(dockerfile)
36 | if !scanner.Scan() {
37 | return "", errors.New("want read a first line, got no lines")
38 | }
39 |
40 | firstLine := scanner.Text()
41 |
42 | if !strings.HasPrefix(firstLine, tagNamePrefix) {
43 | return "", fmt.Errorf("want a tag name prefix comment at first line, got no prefix: %q", firstLine)
44 | }
45 |
46 | return DockerImage(strings.TrimPrefix(firstLine, tagNamePrefix)), nil
47 | }
48 |
--------------------------------------------------------------------------------
/tool/gh-action/action-yaml-gen/yaml/yaml_test.go:
--------------------------------------------------------------------------------
1 | package yaml
2 |
3 | import (
4 | "bytes"
5 | "github.com/DeNA/unity-meta-check/version"
6 | "github.com/google/go-cmp/cmp"
7 | "os"
8 | "strings"
9 | "testing"
10 | )
11 |
12 | // NOTE: This test is fragile, but we can use like Golden Testing.
13 | func TestRecentActionYAML(t *testing.T) {
14 | actualRawBytes, err := os.ReadFile("../testdata/action.yml")
15 | if err != nil {
16 | t.Errorf("want nil, got %v", err)
17 | return
18 | }
19 |
20 | // NOTE: On Windows, Git may replace \n to \r\n on specific configurations.
21 | actual := strings.NewReplacer("\r", "", "%VERSION%", version.Version).Replace(string(actualRawBytes))
22 |
23 | buf := &bytes.Buffer{}
24 | if _, err := WriteTo(buf); err != nil {
25 | t.Errorf("want nil, got %#v", err)
26 | return
27 | }
28 |
29 | if buf.String() != actual {
30 | t.Error(cmp.Diff(buf.String(), actual))
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tool/gh-action/inputs/env.go:
--------------------------------------------------------------------------------
1 | package inputs
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/options"
6 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/github"
7 | "github.com/DeNA/unity-meta-check/util/cli"
8 | "github.com/DeNA/unity-meta-check/util/typedpath"
9 | "strings"
10 | )
11 |
12 | type ActionEnv struct {
13 | GitHubToken github.Token
14 | EventPath typedpath.RawPath
15 | Workspace typedpath.RawPath
16 | APIURL string
17 | }
18 |
19 | // SEE: https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables
20 | const (
21 | // GitHubEventPath is the path of the file with the complete webhook event payload. For example, /github/workflow/event.json.
22 | GitHubEventPath = "GITHUB_EVENT_PATH"
23 |
24 | // GitHubWorkspace is the GitHub workspace directory path. The workspace directory is a copy of your repository
25 | // if your workflow uses the actions/checkout action. If you don't use the actions/checkout action,
26 | // the directory will be empty. For example, /home/runner/work/my-repo-name/my-repo-name.
27 | GitHubWorkspace = "GITHUB_WORKSPACE"
28 |
29 | // GitHubAPIURL returns the API URL. For example: https://api.github.com.
30 | GitHubAPIURL = "GITHUB_API_URL"
31 | )
32 |
33 | func GetActionEnv(env cli.Env) ActionEnv {
34 | gitHubToken := env(options.GitHubTokenEnv)
35 | eventPath := env(GitHubEventPath)
36 | workspace := env(GitHubWorkspace)
37 | apiURL := env(GitHubAPIURL)
38 |
39 | return ActionEnv{
40 | GitHubToken: github.Token(gitHubToken),
41 | EventPath: typedpath.NewRawPathUnsafe(eventPath),
42 | Workspace: typedpath.NewRawPathUnsafe(workspace),
43 | APIURL: apiURL,
44 | }
45 | }
46 |
47 | func MaskedActionEnv(env ActionEnv) string {
48 | return fmt.Sprintf(`
49 | TOKEN=%q (len=%d)
50 | GITHUB_EVENT_PATH=%q
51 | GITHUB_WORKSPACE=%q
52 | GITHUB_API_URL=%q`[1:], strings.Repeat("*", len(env.GitHubToken)), len(env.GitHubToken), env.EventPath, env.Workspace, env.APIURL)
53 | }
54 |
--------------------------------------------------------------------------------
/tool/gh-action/inputs/event.go:
--------------------------------------------------------------------------------
1 | package inputs
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/github"
7 | "github.com/DeNA/unity-meta-check/util/logging"
8 | "github.com/DeNA/unity-meta-check/util/typedpath"
9 | "github.com/pkg/errors"
10 | "os"
11 | )
12 |
13 | type ReadEventPayloadFunc func(path typedpath.RawPath) (*PushOrPullRequestEventPayload, error)
14 |
15 | func NewReadEventPayload(logger logging.Logger) ReadEventPayloadFunc {
16 | return func(path typedpath.RawPath) (*PushOrPullRequestEventPayload, error) {
17 | payloadBytes, err := os.ReadFile(string(path))
18 | if err != nil {
19 | return nil, errors.Wrapf(err, "cannot read file: %q", path)
20 | }
21 |
22 | logger.Debug(fmt.Sprintf("event=%s", string(payloadBytes)))
23 |
24 | var payload PushOrPullRequestEventPayload
25 | if err := json.Unmarshal(payloadBytes, &payload); err != nil {
26 | return nil, errors.Wrapf(err, "cannot decode file: %q\n%s", path, string(payloadBytes))
27 | }
28 |
29 | return &payload, nil
30 | }
31 | }
32 |
33 | // PushOrPullRequestEventPayload is a payload for pull request events.
34 | // SEE: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request
35 | type PushOrPullRequestEventPayload struct {
36 | PullRequest *PullRequest `json:"pull_request,omitempty"`
37 | Repository *Repository `json:"repository,omitempty"`
38 | }
39 |
40 | // PullRequest is a payload for pull requests.
41 | // SEE: https://docs.github.com/en/rest/reference/pulls#get-a-pull-request
42 | type PullRequest struct {
43 | Number github.PullNumber `json:"number"`
44 | }
45 |
46 | // Repository is a payload for repository.
47 | // SEE: https://docs.github.com/en/rest/reference/repos#get-a-repository
48 | type Repository struct {
49 | Name github.Repo `json:"name"`
50 | Owner User `json:"owner"`
51 | }
52 |
53 | // User is a payload for users.
54 | type User struct {
55 | Login github.Owner `json:"login"`
56 | }
57 |
--------------------------------------------------------------------------------
/tool/gh-action/inputs/event_stub.go:
--------------------------------------------------------------------------------
1 | package inputs
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | func StubReadEventPayload(payload *PushOrPullRequestEventPayload, err error) ReadEventPayloadFunc {
8 | return func(_ typedpath.RawPath) (*PushOrPullRequestEventPayload, error) {
9 | return payload, err
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tool/gh-action/inputs/event_test.go:
--------------------------------------------------------------------------------
1 | package inputs
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/google/go-cmp/cmp"
6 | "os"
7 | "reflect"
8 | "testing"
9 | )
10 |
11 | func TestEventPayload(t *testing.T) {
12 | cases := map[string]struct {
13 | Path string
14 | Expected PushOrPullRequestEventPayload
15 | }{
16 | "pull request": {
17 | Path: "./testdata/pr-event-payload-example.json",
18 | Expected: PushOrPullRequestEventPayload{
19 | PullRequest: &PullRequest{
20 | Number: 2,
21 | },
22 | Repository: &Repository{
23 | Name: "Hello-World",
24 | Owner: User{Login: "Codertocat"},
25 | },
26 | },
27 | },
28 | "push": {
29 | Path: "./testdata/push-event-payload-example.json",
30 | Expected: PushOrPullRequestEventPayload{
31 | PullRequest: nil,
32 | Repository: &Repository{
33 | Name: "Hello-World",
34 | Owner: User{Login: "Codertocat"},
35 | },
36 | },
37 | },
38 | }
39 |
40 | for desc, c := range cases {
41 | t.Run(desc, func(t *testing.T) {
42 | jsonBytes, err := os.ReadFile(c.Path)
43 | if err != nil {
44 | t.Errorf("want nil, got %#v", err)
45 | return
46 | }
47 |
48 | var payload PushOrPullRequestEventPayload
49 | if err := json.Unmarshal(jsonBytes, &payload); err != nil {
50 | t.Errorf("want nil, got %#v", err)
51 | return
52 | }
53 |
54 | if !reflect.DeepEqual(payload, c.Expected) {
55 | t.Error(cmp.Diff(c.Expected, payload))
56 | }
57 | })
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tool/gh-action/inputs/testdata/inputs-example.json:
--------------------------------------------------------------------------------
1 | {
2 | "log_level": "DEBUG",
3 | "ignored_file_path": ".meta-check-ignore",
4 | "enable_autofix": "true",
5 | "autofix_globs": ".",
6 | "enable_junit": "true",
7 | "junit_xml_path": "junit.xml",
8 | "enable_pr_comment": "true",
9 | "pr_comment_lang": "ja",
10 | "pr_comment_send_success": "true",
11 | "target_path": "/home/runner/work/unity-meta-check-playground/unity-meta-check-playground",
12 | "target_type": "auto-detect",
13 | "ignore_dangling": "false",
14 | "ignore_case": "false",
15 | "ignore_submodules_and_nested": "false",
16 | "pr_comment_tmpl_file": "",
17 | "pr_comment_pull_number": "123",
18 | "pr_comment_event_path": "/home/runner/work/_temp/_github_workflow/event.json"
19 | }
--------------------------------------------------------------------------------
/tool/gh-action/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/tool/gh-action/cmd"
5 | "github.com/DeNA/unity-meta-check/util/cli"
6 | "os"
7 | )
8 |
9 | func main() {
10 | exitStatus := cmd.Main(os.Args[1:], cli.GetProcessInout(), cli.NewEnv())
11 | os.Exit(int(exitStatus))
12 | }
13 |
--------------------------------------------------------------------------------
/tool/gh-action/options/options.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "fmt"
7 | "github.com/DeNA/unity-meta-check/tool/gh-action/inputs"
8 | "github.com/DeNA/unity-meta-check/util/cli"
9 | "github.com/pkg/errors"
10 | )
11 |
12 | type Options struct {
13 | Version bool
14 | Inputs inputs.Inputs
15 | Env inputs.ActionEnv
16 | }
17 |
18 | type Parser func(args []string, procInout cli.ProcessInout, env cli.Env) (*Options, error)
19 |
20 | func NewParser() Parser {
21 | return func(args []string, procInout cli.ProcessInout, env cli.Env) (*Options, error) {
22 | flags := flag.NewFlagSet("gh-action", flag.ContinueOnError)
23 | flags.SetOutput(procInout.Stderr)
24 | flags.Usage = func() {
25 | _, _ = fmt.Fprintln(flags.Output(), "usage: gh-action -inputs-json ")
26 | flags.PrintDefaults()
27 | }
28 |
29 | version := flags.Bool("version", false, "print version")
30 | inputsJSON := flags.String("inputs-json", "", `JSON string of "inputs" context value of GitHub Actions`)
31 |
32 | if err := flags.Parse(args); err != nil {
33 | return nil, err
34 | }
35 |
36 | if *version {
37 | return &Options{Version: *version}, nil
38 | }
39 |
40 | if flags.NArg() > 0 {
41 | return nil, fmt.Errorf("0 arguments required, but come %d arguments: %#v", flags.NArg(), flags.Args())
42 | }
43 |
44 | var unsafeInputs inputs.Inputs
45 | if err := json.Unmarshal([]byte(*inputsJSON), &unsafeInputs); err != nil {
46 | return nil, errors.Wrapf(err, "malformed JSON of inputs:\n%q", *inputsJSON)
47 | }
48 |
49 | return &Options{
50 | Inputs: unsafeInputs,
51 | Env: inputs.GetActionEnv(env),
52 | }, nil
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tool/gh-action/options/options_test.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/DeNA/unity-meta-check/tool/gh-action/inputs"
6 | "github.com/DeNA/unity-meta-check/util/cli"
7 | "github.com/DeNA/unity-meta-check/util/testutil"
8 | "github.com/DeNA/unity-meta-check/util/typedpath"
9 | "github.com/google/go-cmp/cmp"
10 | "io"
11 | "reflect"
12 | "strings"
13 | "testing"
14 | )
15 |
16 | func TestParse(t *testing.T) {
17 | in := inputs.Inputs{}
18 | inputsJson, err := json.Marshal(in)
19 | if err != nil {
20 | t.Errorf("want nil, got %#v", err)
21 | return
22 | }
23 |
24 | cases := map[string]struct {
25 | Args []string
26 | Env map[string]string
27 | Expected *Options
28 | }{
29 | "easiest case": {
30 | Args: []string{"-inputs-json", string(inputsJson)},
31 | Env: map[string]string{
32 | "GITHUB_TOKEN": "T0K3N",
33 | "GITHUB_WORKSPACE": string(typedpath.NewRootRawPath("github", "workspace")),
34 | "GITHUB_EVENT_PATH": string(typedpath.NewRootRawPath("github", "workflows", "event.json")),
35 | "GITHUB_API_URL": "https://api.github.com",
36 | },
37 | Expected: &Options{
38 | Inputs: in,
39 | Env: inputs.ActionEnv{
40 | GitHubToken: "T0K3N",
41 | Workspace: typedpath.NewRootRawPath("github", "workspace"),
42 | EventPath: typedpath.NewRootRawPath("github", "workflows", "event.json"),
43 | APIURL: "https://api.github.com",
44 | },
45 | },
46 | },
47 | "-version": {
48 | Args: []string{"-version"},
49 | Expected: &Options{
50 | Version: true,
51 | },
52 | },
53 | }
54 |
55 | for desc, c := range cases {
56 | t.Run(desc, func(t *testing.T) {
57 | stdin := io.NopCloser(strings.NewReader(""))
58 | stdout := testutil.SpyWriteCloser(nil)
59 | stderr := testutil.SpyWriteCloser(nil)
60 | procInout := cli.ProcessInout{
61 | Stdin: stdin,
62 | Stdout: stdout,
63 | Stderr: stderr,
64 | }
65 |
66 | parse := NewParser()
67 | opts, err := parse(c.Args, procInout, cli.StubEnv(c.Env))
68 | if err != nil {
69 | t.Errorf("want nil, got %#v", err)
70 | return
71 | }
72 |
73 | if !reflect.DeepEqual(opts, c.Expected) {
74 | t.Error(cmp.Diff(c.Expected, opts))
75 | }
76 | })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tool/gh-action/runner/runner.go:
--------------------------------------------------------------------------------
1 | package runner
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/report"
6 | "github.com/DeNA/unity-meta-check/resultfilter"
7 | "github.com/DeNA/unity-meta-check/tool/unity-meta-autofix/autofix"
8 | prcomment "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/github"
9 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-junit/junit"
10 | "github.com/DeNA/unity-meta-check/unity/checker"
11 | "github.com/DeNA/unity-meta-check/util/logging"
12 | "io"
13 | )
14 |
15 | type Runner func(opts *Options) (bool, error)
16 |
17 | func NewRunner(
18 | check checker.Checker,
19 | filter resultfilter.Filter,
20 | writeJunitXML junit.WriteToFileFunc,
21 | send prcomment.SendFunc,
22 | doAutofix autofix.AutoFixer,
23 | w io.Writer,
24 | logger logging.Logger,
25 | ) Runner {
26 | return func(opts *Options) (bool, error) {
27 | logger.Debug(fmt.Sprintf("check: %#v", opts.CheckerOpts))
28 | resultNotFiltered, err := check(opts.RootDirAbs, opts.CheckerOpts)
29 | if err != nil {
30 | return false, err
31 | }
32 |
33 | logger.Debug(fmt.Sprintf("filter result: %#v", opts.FilterOpts))
34 | resultFiltered, err := filter(resultNotFiltered, opts.FilterOpts)
35 | if err != nil {
36 | return false, err
37 | }
38 |
39 | if opts.EnableAutofix {
40 | logger.Debug(fmt.Sprintf("autofix: %#v", opts.AutofixOpts))
41 | skipped, err := doAutofix(resultFiltered, opts.AutofixOpts)
42 | if err != nil {
43 | return false, err
44 | }
45 | resultFiltered = skipped
46 | } else {
47 | logger.Debug(`skip autofix because "enable_autofix" is false`)
48 | }
49 |
50 | logger.Debug("print check result")
51 | if err := report.WriteResult(w, resultFiltered); err != nil {
52 | return false, err
53 | }
54 |
55 | if opts.EnableJUnit {
56 | logger.Debug(fmt.Sprintf("write junit report: %q", opts.JUnitOutPath))
57 | if err := writeJunitXML(resultFiltered, opts.JUnitOutPath); err != nil {
58 | return false, err
59 | }
60 | } else {
61 | logger.Debug(`skip write junit report because "enable_junit" is false`)
62 | }
63 |
64 | if opts.EnablePRComment {
65 | logger.Debug(fmt.Sprintf("send pull request comment if necessary:\n%s", prcomment.MaskOptions(opts.PRCommentOpts)))
66 | if err := send(resultFiltered, opts.PRCommentOpts); err != nil {
67 | return false, err
68 | }
69 | } else {
70 | logger.Debug(`skip send a pull request comment because "enable_pr_comment" is false`)
71 | }
72 |
73 | return resultFiltered.Empty(), nil
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tool/gh-action/runner/testdata/InvalidProject/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tool/gh-action/runner/testdata/InvalidSubDir/dangling.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/gh-action/runner/testdata/InvalidSubDir/dangling.meta
--------------------------------------------------------------------------------
/tool/gh-action/runner/testdata/InvalidSubDir/missing:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/gh-action/runner/testdata/InvalidSubDir/missing
--------------------------------------------------------------------------------
/tool/gh-action/runner/testdata/ValidProject/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tool/gh-action/runner/testdata/ValidSubDir/ok:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/gh-action/runner/testdata/ValidSubDir/ok
--------------------------------------------------------------------------------
/tool/gh-action/runner/testdata/ValidSubDir/ok.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/gh-action/runner/testdata/ValidSubDir/ok.meta
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/autofix_stub.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "errors"
5 | "github.com/DeNA/unity-meta-check/unity/checker"
6 | )
7 |
8 | func StubAutoFixer(skipped *checker.CheckResult, err error) AutoFixer {
9 | return func(_ *checker.CheckResult, _ *Options) (*checker.CheckResult, error) {
10 | return skipped, err
11 | }
12 | }
13 |
14 | type AutoFixerCallArgs struct {
15 | Result *checker.CheckResult
16 | Options *Options
17 | }
18 |
19 | func SpyAutoFixer(inherited AutoFixer, callArgs *[]AutoFixerCallArgs) AutoFixer {
20 | if inherited == nil {
21 | inherited = StubAutoFixer(nil, errors.New("SPY_AUTO_FIXER"))
22 | }
23 | return func(result *checker.CheckResult, opts *Options) (*checker.CheckResult, error) {
24 | *callArgs = append(*callArgs, AutoFixerCallArgs{
25 | Result: result,
26 | Options: opts,
27 | })
28 | return inherited(result, opts)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/create.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/unity/meta"
7 | "github.com/DeNA/unity-meta-check/util/logging"
8 | "github.com/DeNA/unity-meta-check/util/typedpath"
9 | "os"
10 | )
11 |
12 | type MetaCreator func(metaType MetaType, originalPath typedpath.RawPath) error
13 |
14 | func NewMetaCreator(dryRun bool, guidGen meta.GUIDGen, logger logging.Logger) MetaCreator {
15 | return func(metaType MetaType, missingMeta typedpath.RawPath) error {
16 | guid, err := guidGen()
17 | if err != nil {
18 | return err
19 | }
20 |
21 | var metaGen meta.Gen
22 | switch metaType {
23 | case MetaTypeDefaultImporterFolder:
24 | metaGen = meta.DefaultImporterFolderGen{GUID: guid}
25 | case MetaTypeTextScriptImporter:
26 | metaGen = meta.TextScriptImporterGen{GUID: guid}
27 | case MetaTypeMonoImporter:
28 | metaGen = meta.MonoImporterGen{GUID: guid}
29 | case MetaTypeDefaultImporter:
30 | metaGen = meta.DefaultImporterGen{GUID: guid}
31 | default:
32 | return fmt.Errorf("unsupported meta type: %q", metaType)
33 | }
34 |
35 | _, err = os.Stat(string(missingMeta))
36 | if err == nil {
37 | return fmt.Errorf("file exists: %s", missingMeta)
38 | }
39 | if !os.IsNotExist(err) {
40 | return err
41 | }
42 |
43 | if dryRun {
44 | return createMetaDryRun(missingMeta, metaGen, logger)
45 | }
46 | return createMeta(missingMeta, metaGen)
47 | }
48 | }
49 |
50 | func createMetaDryRun(missingMeta typedpath.RawPath, metaGen meta.Gen, logger logging.Logger) error {
51 | buf := &bytes.Buffer{}
52 | if _, err := metaGen.WriteTo(buf); err != nil {
53 | return err
54 | }
55 | logger.Info(fmt.Sprintf("would write to %q:\n%s", missingMeta, buf.String()))
56 | return nil
57 | }
58 |
59 | func createMeta(missingMeta typedpath.RawPath, metaGen meta.Gen) error {
60 | file, err := os.OpenFile(string(missingMeta), os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
61 | if err != nil {
62 | return err
63 | }
64 | defer func(){ _ = file.Close() }()
65 |
66 | _, err = metaGen.WriteTo(file)
67 | if err != nil {
68 | return err
69 | }
70 | return nil
71 | }
72 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/create_stub.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | func StubMetaCreator(err error) MetaCreator {
8 | return func(MetaType, typedpath.RawPath) error {
9 | return err
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/create_test.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/unity/meta"
5 | "github.com/DeNA/unity-meta-check/util/logging"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | "os"
8 | "path/filepath"
9 | "testing"
10 | )
11 |
12 | func TestNewMetaCreator(t *testing.T) {
13 | cases := []MetaType{
14 | MetaTypeDefaultImporterFolder,
15 | MetaTypeTextScriptImporter,
16 | }
17 |
18 | for _, metaType := range cases {
19 | t.Run(string(metaType), func(t *testing.T) {
20 | spyLogger := logging.SpyLogger()
21 |
22 | workDir, err := os.MkdirTemp(os.TempDir(), "")
23 | if err != nil {
24 | panic(err.Error())
25 | }
26 | missingMeta := typedpath.RawPath(filepath.Join(workDir, "Missing.meta"))
27 |
28 | createMeta := NewMetaCreator(false, meta.StubGUIDGen(meta.AnyGUID(), nil), spyLogger)
29 | if err := createMeta(MetaTypeDefaultImporterFolder, missingMeta); err != nil {
30 | t.Log(spyLogger.Logs.String())
31 | t.Errorf("want nil, got %#v", err)
32 | return
33 | }
34 |
35 | if _, err := os.Stat(string(missingMeta)); err != nil {
36 | t.Log(spyLogger.Logs.String())
37 | t.Errorf("want nil, got %#v", err)
38 | return
39 | }
40 |
41 | _ = os.RemoveAll(workDir)
42 | })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/metatype.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/DeNA/unity-meta-check/unity"
8 | "github.com/DeNA/unity-meta-check/util/ostestable"
9 | "github.com/DeNA/unity-meta-check/util/typedpath"
10 | )
11 |
12 | type MetaType string
13 |
14 | const (
15 | MetaTypeDefaultImporterFolder MetaType = "MetaTypeDefaultImporterFolder"
16 | MetaTypeTextScriptImporter MetaType = "MetaTypeTextScriptImporter"
17 | MetaTypeMonoImporter MetaType = "MetaTypeMonoImporter"
18 | MetaTypeDefaultImporter MetaType = "MetaTypeDefaultImporter"
19 | )
20 |
21 | type MetaTypeDetector func(missingMeta typedpath.RawPath) (MetaType, error)
22 |
23 | func NewMetaTypeDetector(isDir ostestable.IsDir) MetaTypeDetector {
24 | return func(missingMeta typedpath.RawPath) (MetaType, error) {
25 | originalPath := unity.TrimMetaFromRaw(missingMeta)
26 | dir, err := isDir(originalPath)
27 | if err != nil {
28 | return "", err
29 | }
30 |
31 | ext := originalPath.Ext()
32 | if dir {
33 | if ext != "" {
34 | return "", fmt.Errorf("should not create .meta because the directory may require special .meta: %s", originalPath)
35 | }
36 | return MetaTypeDefaultImporterFolder, nil
37 | }
38 |
39 | switch strings.ToLower(ext) {
40 | case ".json", ".bytes", ".csv", ".pb", ".txt", ".xml", ".proto", ".md", ".asmdef":
41 | return MetaTypeTextScriptImporter, nil
42 | case ".cs":
43 | return MetaTypeMonoImporter, nil
44 | case ".yaml", ".yml":
45 | return MetaTypeDefaultImporter, nil
46 | default:
47 | switch originalPath.Base() {
48 | case "LICENSE":
49 | return MetaTypeTextScriptImporter, nil
50 | default:
51 | return "", fmt.Errorf("should not create .meta because the extension is not supported now: %s", originalPath)
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/metatype_stub.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | func StubMetaTypeDetector(result MetaType, err error) MetaTypeDetector {
8 | return func(typedpath.RawPath) (MetaType, error) {
9 | return result, err
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/metatype_test.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/util/ostestable"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | "testing"
8 | )
9 |
10 | func TestNewMetaTypeDetectorValid(t *testing.T) {
11 | cases := []struct {
12 | MissingMeta typedpath.RawPath
13 | IsDir bool
14 | Expected MetaType
15 | }{
16 | {
17 | MissingMeta: typedpath.NewRawPath("Assets", "Dir.meta"),
18 | IsDir: true,
19 | Expected: MetaTypeDefaultImporterFolder,
20 | },
21 | {
22 | MissingMeta: typedpath.NewRawPath("Assets", "file.json.meta"),
23 | IsDir: false,
24 | Expected: MetaTypeTextScriptImporter,
25 | },
26 | {
27 | MissingMeta: typedpath.NewRawPath("Assets", "FILE.JSON.meta"),
28 | IsDir: false,
29 | Expected: MetaTypeTextScriptImporter,
30 | },
31 | {
32 | MissingMeta: typedpath.NewRawPath("Assets", "README.md.meta"),
33 | IsDir: false,
34 | Expected: MetaTypeTextScriptImporter,
35 | },
36 | {
37 | MissingMeta: typedpath.NewRawPath("Assets", "LICENSE.meta"),
38 | IsDir: false,
39 | Expected: MetaTypeTextScriptImporter,
40 | },
41 | }
42 |
43 | for _, c := range cases {
44 | t.Run(fmt.Sprintf("%q (isDir=%t) -> %q", c.MissingMeta, c.IsDir, c.Expected), func(t *testing.T) {
45 | detectMetaType := NewMetaTypeDetector(ostestable.StubIsDir(c.IsDir, nil))
46 |
47 | actual, err := detectMetaType(c.MissingMeta)
48 | if err != nil {
49 | t.Errorf("want nil, got %#v", err)
50 | return
51 | }
52 |
53 | if actual != c.Expected {
54 | t.Errorf("want %q, got %q", c.Expected, actual)
55 | return
56 | }
57 | })
58 | }
59 | }
60 |
61 | func TestNewMetaTypeDetectorInvalid(t *testing.T) {
62 | cases := []struct {
63 | MissingMeta typedpath.RawPath
64 | IsDir bool
65 | }{
66 | {
67 | MissingMeta: typedpath.NewRawPath("Assets", "Some.pkg.meta"),
68 | IsDir: true,
69 | },
70 | {
71 | MissingMeta: typedpath.NewRawPath("Assets", "file.unknown.meta"),
72 | IsDir: false,
73 | },
74 | {
75 | MissingMeta: typedpath.NewRawPath("Assets", "no-extension.meta"),
76 | IsDir: false,
77 | },
78 | }
79 |
80 | for _, c := range cases {
81 | t.Run(fmt.Sprintf("%q (isDir=%t) -> error", c.MissingMeta, c.IsDir), func(t *testing.T) {
82 | detectMetaType := NewMetaTypeDetector(ostestable.StubIsDir(c.IsDir, nil))
83 |
84 | _, err := detectMetaType(c.MissingMeta)
85 | if err == nil {
86 | t.Errorf("want error, got nil")
87 | return
88 | }
89 | })
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/options.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/globs"
5 | "github.com/DeNA/unity-meta-check/util/ostestable"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | )
8 |
9 | type Options struct {
10 | RootDirAbs typedpath.RawPath
11 | RootDirRel typedpath.RawPath
12 | AllowedGlobs []globs.Glob
13 | }
14 |
15 | type OptionsBuilder func(rootDirAbs typedpath.RawPath, allowedGlobs []globs.Glob) (*Options, error)
16 |
17 | func NewOptionsBuilder(getwd ostestable.Getwd) OptionsBuilder {
18 | return func(rootDirAbs typedpath.RawPath, allowedGlobs []globs.Glob) (*Options, error) {
19 | cwdAbs, err := getwd()
20 | if err != nil {
21 | return nil, err
22 | }
23 |
24 | rootDirRel, err := cwdAbs.Rel(rootDirAbs)
25 | if err != nil {
26 | return nil, err
27 | }
28 |
29 | return &Options{
30 | RootDirAbs: rootDirAbs,
31 | RootDirRel: rootDirRel,
32 | AllowedGlobs: allowedGlobs,
33 | }, nil
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/options_stub.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/globs"
5 | "github.com/DeNA/unity-meta-check/util/typedpath"
6 | )
7 |
8 | func StubOptionsBuilderWithRootDirAbsAndRel(rootDirRel typedpath.RawPath) OptionsBuilder {
9 | return func(rootDirAbs typedpath.RawPath, allowedGlobs []globs.Glob) (*Options, error) {
10 | return &Options{
11 | RootDirAbs: rootDirAbs,
12 | RootDirRel: rootDirRel,
13 | AllowedGlobs: allowedGlobs,
14 | }, nil
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/remover.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/unity"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | "os"
8 | )
9 |
10 | type MetaRemover func(danglingMeta typedpath.RawPath) error
11 |
12 | func NewMetaRemover(dryRun bool) MetaRemover {
13 | return func(danglingMeta typedpath.RawPath) error {
14 | stat, err := os.Stat(string(danglingMeta))
15 | if err != nil {
16 | return err
17 | }
18 |
19 | if !unity.IsMeta(danglingMeta.ToSlash()) || stat.IsDir() {
20 | return fmt.Errorf("must be a meta file: %s", danglingMeta)
21 | }
22 |
23 | if dryRun {
24 | return nil
25 | }
26 | return os.Remove(string(danglingMeta))
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/remover_stub.go:
--------------------------------------------------------------------------------
1 | package autofix
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | func StubMetaRemover(err error) MetaRemover {
8 | return func(typedpath.RawPath) error {
9 | return err
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/testdata/InvalidProject/Assets/Dangling.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/unity-meta-autofix/autofix/testdata/InvalidProject/Assets/Dangling.meta
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/testdata/InvalidProject/Assets/Missing:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/unity-meta-autofix/autofix/testdata/InvalidProject/Assets/Missing
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/testdata/ValidProject/Assets/OK:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/unity-meta-autofix/autofix/testdata/ValidProject/Assets/OK
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/autofix/testdata/ValidProject/Assets/OK.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/unity-meta-autofix/autofix/testdata/ValidProject/Assets/OK.meta
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/cmd/cmd.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | options2 "github.com/DeNA/unity-meta-check/options"
7 | "github.com/DeNA/unity-meta-check/report"
8 | "github.com/DeNA/unity-meta-check/tool/unity-meta-autofix/autofix"
9 | "github.com/DeNA/unity-meta-check/tool/unity-meta-autofix/options"
10 | "github.com/DeNA/unity-meta-check/unity/meta"
11 | "github.com/DeNA/unity-meta-check/util/cli"
12 | "github.com/DeNA/unity-meta-check/util/logging"
13 | "github.com/DeNA/unity-meta-check/util/ostestable"
14 | "github.com/DeNA/unity-meta-check/version"
15 | )
16 |
17 | func NewMain() cli.Command {
18 | return func(args []string, procInout cli.ProcessInout, env cli.Env) cli.ExitStatus {
19 | parseOpts := options.NewParser(options2.NewRootDirValidator(ostestable.NewIsDir()))
20 | opts, err := parseOpts(args, procInout)
21 | if err != nil {
22 | if err != flag.ErrHelp {
23 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
24 | }
25 | return cli.ExitAbnormal
26 | }
27 |
28 | if opts.Version {
29 | _, _ = fmt.Fprintln(procInout.Stdout, version.Version)
30 | return cli.ExitNormal
31 | }
32 |
33 | parse := report.NewParser()
34 | result := parse(procInout.Stdin)
35 |
36 | logger := logging.NewLogger(opts.LogLevel, procInout.Stderr)
37 |
38 | buildOpts := autofix.NewOptionsBuilder(ostestable.NewGetwd())
39 | autofixOpts, err := buildOpts(opts.RootDirAbs, opts.AllowedGlobs)
40 | if err != nil {
41 | logger.Error(err.Error())
42 | return cli.ExitNormal
43 | }
44 |
45 | autofixFunc := autofix.NewAutoFixer(
46 | opts.DryRun,
47 | ostestable.NewGetwd(),
48 | autofix.NewMetaTypeDetector(ostestable.NewIsDir()),
49 | autofix.NewMetaCreator(opts.DryRun, meta.RandomGUIDGenerator(), logger),
50 | autofix.NewMetaRemover(opts.DryRun),
51 | logger,
52 | )
53 |
54 | skipped, err := autofixFunc(result, autofixOpts)
55 | if err != nil {
56 | logger.Error(err.Error())
57 | return cli.ExitAbnormal
58 | }
59 |
60 | if err := report.WriteResult(procInout.Stdout, skipped); err != nil {
61 | logger.Error(err.Error())
62 | return cli.ExitAbnormal
63 | }
64 |
65 | return cli.ExitNormal
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/cmd/cmd_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/cli"
5 | "github.com/DeNA/unity-meta-check/util/testutil"
6 | "path/filepath"
7 | "strings"
8 | "testing"
9 | )
10 |
11 | func TestValidDryRun(t *testing.T) {
12 | main := NewMain()
13 | stdout := testutil.SpyWriteCloser(nil)
14 | stderr := testutil.SpyWriteCloser(nil)
15 | procInout := cli.ProcessInout{
16 | Stdin: strings.NewReader(""),
17 | Stdout: stdout,
18 | Stderr: stderr,
19 | }
20 |
21 | rootDir := filepath.Join("testdata", "ValidProject")
22 | actual := main([]string{"-debug", "-dry-run", "-root-dir", rootDir, "Assets/*"}, procInout, cli.AnyEnv())
23 |
24 | expected := cli.ExitNormal
25 | if actual != expected {
26 | t.Logf("stdout:\n%s", stdout.Captured.String())
27 | t.Logf("stderr:\n%s", stderr.Captured.String())
28 | t.Errorf("want %#v, got %#v", expected, actual)
29 | return
30 | }
31 | }
32 |
33 | func TestInvalidDryRun(t *testing.T) {
34 | main := NewMain()
35 | stdout := testutil.SpyWriteCloser(nil)
36 | stderr := testutil.SpyWriteCloser(nil)
37 | procInout := cli.ProcessInout{
38 | Stdin: strings.NewReader(`missing Missing.meta
39 | dangling Dangling.meta`),
40 | Stdout: stdout,
41 | Stderr: stderr,
42 | }
43 |
44 | rootDir := filepath.Join("testdata", "InvalidProject")
45 | actual := main([]string{"-debug", "-dry-run", "-root-dir", rootDir, "Assets/*"}, procInout, cli.AnyEnv())
46 |
47 | expected := cli.ExitNormal
48 | if actual != expected {
49 | t.Logf("stdout:\n%s", stdout.Captured.String())
50 | t.Logf("stderr:\n%s", stderr.Captured.String())
51 | t.Errorf("want %#v, got %#v", expected, actual)
52 | return
53 | }
54 | }
55 |
56 | func TestVersion(t *testing.T) {
57 | main := NewMain()
58 | stdout := testutil.SpyWriteCloser(nil)
59 | stderr := testutil.SpyWriteCloser(nil)
60 | procInout := cli.ProcessInout{
61 | Stdin: strings.NewReader(""),
62 | Stdout: stdout,
63 | Stderr: stderr,
64 | }
65 |
66 | actual := main([]string{"-version"}, procInout, cli.AnyEnv())
67 |
68 | expected := cli.ExitNormal
69 | if actual != expected {
70 | t.Logf("stdout:\n%s", stdout.Captured.String())
71 | t.Logf("stderr:\n%s", stderr.Captured.String())
72 | t.Errorf("want %#v, got %#v", expected, actual)
73 | return
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/cmd/testdata/InvalidProject/Assets/Dangling.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/unity-meta-autofix/cmd/testdata/InvalidProject/Assets/Dangling.meta
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/cmd/testdata/InvalidProject/Assets/Missing:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/unity-meta-autofix/cmd/testdata/InvalidProject/Assets/Missing
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/cmd/testdata/ValidProject/Assets/OK:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/unity-meta-autofix/cmd/testdata/ValidProject/Assets/OK
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/cmd/testdata/ValidProject/Assets/OK.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/tool/unity-meta-autofix/cmd/testdata/ValidProject/Assets/OK.meta
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/tool/unity-meta-autofix/cmd"
5 | "github.com/DeNA/unity-meta-check/util/cli"
6 | "os"
7 | )
8 |
9 | func main() {
10 | main := cmd.NewMain()
11 | exitStatus := main(os.Args[1:], cli.GetProcessInout(), cli.NewEnv())
12 | os.Exit(int(exitStatus))
13 | }
14 |
--------------------------------------------------------------------------------
/tool/unity-meta-autofix/options/options.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "fmt"
7 | "github.com/DeNA/unity-meta-check/options"
8 | "github.com/DeNA/unity-meta-check/util/cli"
9 | "github.com/DeNA/unity-meta-check/util/globs"
10 | "github.com/DeNA/unity-meta-check/util/logging"
11 | "github.com/DeNA/unity-meta-check/util/typedpath"
12 | "path/filepath"
13 | "strings"
14 | )
15 |
16 | type Options struct {
17 | Version bool
18 | LogLevel logging.Severity
19 | DryRun bool
20 | AllowedGlobs []globs.Glob
21 | RootDirAbs typedpath.RawPath
22 | }
23 |
24 | type Parser func(args []string, procInout cli.ProcessInout) (*Options, error)
25 |
26 | func NewParser(validateRootDirAbs options.RootDirAbsValidator) Parser {
27 | return func(args []string, procInout cli.ProcessInout) (*Options, error) {
28 | opts := Options{}
29 |
30 | flags := flag.NewFlagSet("unity-meta-autofix", flag.ContinueOnError)
31 | flags.SetOutput(procInout.Stderr)
32 | flags.Usage = func() {
33 | _, _ = fmt.Fprint(procInout.Stderr, `usage: unity-meta-autofix [] [...]
34 |
35 | Fix missing or dangling .meta. Currently autofix is only limited support.
36 |
37 | ARGUMENTS
38 |
39 | glob pattern to path where autofix allowed on.
40 |
41 | OPTIONS
42 | `)
43 | flags.PrintDefaults()
44 |
45 | _, _ = fmt.Fprint(procInout.Stderr, `
46 | EXAMPLE USAGES
47 | $ unity-meta-check | unity-meta-autofix -dry-run path/to/autofix
48 | $ unity-meta-check | unity-meta-autofix |
49 | `)
50 | }
51 |
52 | var silent, debug bool
53 | var rootDir string
54 | flags.BoolVar(&opts.Version, "version", false, "print version")
55 | flags.BoolVar(&debug, "debug", false, "set log level to DEBUG (default INFO)")
56 | flags.BoolVar(&silent, "silent", false, "set log level to WARN (default INFO)")
57 | flags.BoolVar(&opts.DryRun, "dry-run", false, "dry run")
58 | flags.StringVar(&rootDir, "root-dir", ".", "directory path to where unity-meta-check checked at")
59 |
60 | if err := flags.Parse(args); err != nil {
61 | return nil, err
62 | }
63 |
64 | if opts.Version {
65 | return &opts, nil
66 | }
67 |
68 | targetPaths := flags.Args()
69 | if len(targetPaths) == 0 {
70 | return nil, errors.New("must specify at least one target path")
71 | }
72 | opts.AllowedGlobs = make([]globs.Glob, len(targetPaths))
73 | for i, targetPath := range targetPaths {
74 | opts.AllowedGlobs[i] = globs.Glob(strings.Trim(filepath.ToSlash(targetPath), "/"))
75 | }
76 |
77 | rootDirAbs, err := validateRootDirAbs(typedpath.NewRawPathUnsafe(rootDir))
78 | if err != nil {
79 | return nil, err
80 | }
81 | opts.RootDirAbs = rootDirAbs
82 |
83 | opts.LogLevel = cli.GetLogLevel(debug, silent)
84 |
85 | return &opts, nil
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/cmd/cmd.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/report"
7 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/github"
8 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/options"
9 | "github.com/DeNA/unity-meta-check/util/cli"
10 | "github.com/DeNA/unity-meta-check/util/logging"
11 | "github.com/DeNA/unity-meta-check/version"
12 | "io"
13 | )
14 |
15 | func NewMain() cli.Command {
16 | return func(args []string, procInout cli.ProcessInout, env cli.Env) cli.ExitStatus {
17 | opts, err := options.BuildOptions(args, procInout, env)
18 | if err != nil {
19 | if err != flag.ErrHelp {
20 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
21 | }
22 | return cli.ExitAbnormal
23 | }
24 |
25 | if opts.Version {
26 | _, _ = fmt.Fprintln(procInout.Stdout, version.Version)
27 | return cli.ExitNormal
28 | }
29 |
30 | logger := logging.NewLogger(opts.LogLevel, procInout.Stderr)
31 |
32 | send := github.NewSendFunc(
33 | github.NewPullRequestCommentSender(github.NewHttp(), logger),
34 | )
35 |
36 | parse := report.NewParser()
37 | result := parse(io.TeeReader(procInout.Stdin, procInout.Stdout))
38 |
39 | if err := send(result, &github.Options{
40 | Tmpl: opts.Tmpl,
41 | SendIfSuccess: opts.SendIfSuccess,
42 | Token: opts.Token,
43 | APIEndpoint: opts.APIEndpoint,
44 | Owner: opts.Owner,
45 | Repo: opts.Repo,
46 | PullNumber: opts.PullNumber,
47 | }); err != nil {
48 | logger.Error(err.Error())
49 | return cli.ExitAbnormal
50 | }
51 |
52 | if !result.Empty() {
53 | return cli.ExitAbnormal
54 | }
55 | return cli.ExitNormal
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/github/api.go:
--------------------------------------------------------------------------------
1 | package github
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/DeNA/unity-meta-check/util/logging"
8 | "io"
9 | "net/http"
10 | "net/url"
11 | "path"
12 | "strings"
13 | )
14 |
15 | type PullRequestCommentSender func(githubEndpoint APIEndpoint, token Token, owner Owner, repo Repo, pullNumber PullNumber, comment string) error
16 |
17 | type PullRequestComment struct {
18 | Body string `json:"body"`
19 | }
20 |
21 | func NewPullRequestCommentSender(send HttpFunc, logger logging.Logger) PullRequestCommentSender {
22 | return func(githubEndpoint APIEndpoint, token Token, owner Owner, repo Repo, pullNumber PullNumber, comment string) error {
23 | apiUrl := &url.URL{
24 | Scheme: githubEndpoint.Scheme,
25 | Host: githubEndpoint.Host,
26 | Path: path.Join(githubEndpoint.Path, "repos", string(owner), string(repo), "issues", fmt.Sprintf("%d", pullNumber), "comments"),
27 | }
28 | logger.Debug(fmt.Sprintf("url: %s", apiUrl.String()))
29 |
30 | body, err := json.Marshal(PullRequestComment{Body: comment})
31 | if err != nil {
32 | return err
33 | }
34 | logger.Debug(fmt.Sprintf("comment body: %s", body))
35 |
36 | req, err := http.NewRequest(http.MethodPost, apiUrl.String(), bytes.NewReader(body))
37 | if err != nil {
38 | return err
39 | }
40 |
41 | logger.Debug(fmt.Sprintf("token: %s", strings.Repeat("*", len(token))))
42 | req.Header.Add("Accept", "application/vnd.github.v3+json")
43 | req.Header.Add("Authorization", fmt.Sprintf("token %s", token))
44 | req.Header.Add("Content-Type", "application/json")
45 |
46 | res, err := send(req)
47 | if err != nil {
48 | return err
49 | }
50 |
51 | resBody, err := io.ReadAll(res.Body)
52 | if err != nil {
53 | return err
54 | }
55 |
56 | if res.StatusCode != http.StatusCreated {
57 | return fmt.Errorf("unexpected status code: %d\n%s", res.StatusCode, string(resBody))
58 | }
59 |
60 | logger.Debug(fmt.Sprintf("response: %s", resBody))
61 | return nil
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/github/api_test.go:
--------------------------------------------------------------------------------
1 | package github
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/logging"
5 | "github.com/DeNA/unity-meta-check/util/testutil"
6 | "net/url"
7 | "testing"
8 | )
9 |
10 | func TestNewPullRequestCommentSender(t *testing.T) {
11 | spyLogger := logging.SpyLogger()
12 |
13 | testEnv, err := testutil.GetTestEnv()
14 | if err != nil {
15 | t.Log(err.Error())
16 | t.Skip("no environment variables for tests")
17 | return
18 | }
19 | token := testEnv.Token
20 |
21 | endpoint, err := url.Parse("https://api.github.com")
22 | if err != nil {
23 | t.Errorf("want nil, got %#v", err)
24 | return
25 | }
26 |
27 | send := NewPullRequestCommentSender(NewHttp(), spyLogger)
28 | if err := send(endpoint, Token(token), "dena", "unity-meta-check-playground", 1, "TEST FROM unity-meta-check-github-pr-comment"); err != nil {
29 | t.Log(spyLogger.Logs.String())
30 | t.Errorf("want nil, got %#v", err)
31 | return
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/github/http.go:
--------------------------------------------------------------------------------
1 | package github
2 |
3 | import "net/http"
4 |
5 | type HttpFunc func(request *http.Request) (*http.Response, error)
6 |
7 | func NewHttp() HttpFunc {
8 | return func(request *http.Request) (*http.Response, error) {
9 | return http.DefaultClient.Do(request)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/github/send.go:
--------------------------------------------------------------------------------
1 | package github
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/l10n"
7 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/markdown"
8 | "github.com/DeNA/unity-meta-check/unity/checker"
9 | "strings"
10 | )
11 |
12 | type Options struct {
13 | Tmpl *l10n.Template
14 | SendIfSuccess bool
15 | Token Token
16 | APIEndpoint APIEndpoint
17 | Owner Owner
18 | Repo Repo
19 | PullNumber PullNumber
20 | }
21 |
22 | type SendFunc func(result *checker.CheckResult, opts *Options) error
23 |
24 | func NewSendFunc(postComment PullRequestCommentSender) SendFunc {
25 | return func(result *checker.CheckResult, opts *Options) error {
26 | buf := &bytes.Buffer{}
27 | if err := markdown.WriteMarkdown(result, opts.Tmpl, buf); err != nil {
28 | return err
29 | }
30 |
31 | if !result.Empty() || opts.SendIfSuccess {
32 | if err := postComment(opts.APIEndpoint, opts.Token, opts.Owner, opts.Repo, opts.PullNumber, buf.String()); err != nil {
33 | return err
34 | }
35 | }
36 |
37 | return nil
38 | }
39 | }
40 |
41 | func MaskOptions(opts *Options) string {
42 | return fmt.Sprintf(`
43 | Tmpl=%#v
44 | SendIfSuccess=%t
45 | Token=%q (len=%d)
46 | APIEndpoint=%#v
47 | Owner=%q
48 | Repo=%q
49 | PullNumber=%d`[1:],
50 | opts.Tmpl,
51 | opts.SendIfSuccess,
52 | strings.Repeat("*", len(opts.Token)),
53 | len(opts.Token),
54 | opts.APIEndpoint,
55 | opts.Owner,
56 | opts.Repo,
57 | opts.PullNumber,
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/github/send_stub.go:
--------------------------------------------------------------------------------
1 | package github
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/unity/checker"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | func StubSendFunc(err error) SendFunc {
9 | return func(_ *checker.CheckResult, _ *Options) error {
10 | return err
11 | }
12 | }
13 |
14 | type SendFuncCallArgs struct {
15 | Result *checker.CheckResult
16 | Options *Options
17 | }
18 |
19 | func SpySendFunc(inherited SendFunc, callArgs *[]SendFuncCallArgs) SendFunc {
20 | if inherited == nil {
21 | inherited = StubSendFunc(errors.New("SPY_SEND_FUNC"))
22 | }
23 | return func(result *checker.CheckResult, opts *Options) error {
24 | *callArgs = append(*callArgs, SendFuncCallArgs{
25 | Result: result,
26 | Options: opts,
27 | })
28 | return inherited(result, opts)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/github/types.go:
--------------------------------------------------------------------------------
1 | package github
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/url"
7 | )
8 |
9 | type APIEndpoint *url.URL
10 | type Token string
11 | type Owner string
12 | type Repo string
13 | type PullNumber int
14 |
15 | func ValidateOwner(unsafeOwner string) (Owner, error) {
16 | if unsafeOwner == "" {
17 | return "", errors.New("owner must be not empty")
18 | }
19 | return Owner(unsafeOwner), nil
20 | }
21 |
22 | func ValidateRepo(unsafeRepo string) (Repo, error) {
23 | if unsafeRepo == "" {
24 | return "", errors.New("repo must be not empty")
25 | }
26 | return Repo(unsafeRepo), nil
27 | }
28 |
29 | func ValidatePullNumber(unsafePullNumber int) (PullNumber, error) {
30 | if unsafePullNumber <= 0 {
31 | return 0, fmt.Errorf("pull number must be a positive integer: %d", unsafePullNumber)
32 | }
33 | return PullNumber(unsafePullNumber), nil
34 | }
35 |
36 | func ValidateToken(unsafeToken string) (Token, error) {
37 | if unsafeToken == "" {
38 | return "", fmt.Errorf("GitHub Personal Token must not be empty")
39 | }
40 | return Token(unsafeToken), nil
41 | }
42 |
43 | func ValidateAPIEndpoint(unsafeAPIEndpoint string) (APIEndpoint, error) {
44 | apiEndpoint, err := url.Parse(unsafeAPIEndpoint)
45 | if err != nil {
46 | return nil, err
47 | }
48 | return apiEndpoint, nil
49 | }
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/l10n/lang.go:
--------------------------------------------------------------------------------
1 | package l10n
2 |
3 | type Lang string
4 |
5 | const (
6 | LangEn Lang = "en"
7 | LangJa Lang = "ja"
8 | )
9 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/l10n/template.go:
--------------------------------------------------------------------------------
1 | package l10n
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "github.com/DeNA/unity-meta-check/util/typedpath"
8 | "io"
9 | "os"
10 | )
11 |
12 | type Template struct {
13 | SuccessMessage string `json:"success"`
14 | FailureMessage string `json:"failure"`
15 | StatusHeader string `json:"header_status"`
16 | FilePathHeader string `json:"header_file_path"`
17 | StatusMissing string `json:"status_missing"`
18 | StatusDangling string `json:"status_dangling"`
19 | }
20 |
21 | var En = Template{
22 | SuccessMessage: "No missing/dangling .meta found. Perfect!",
23 | FailureMessage: `Some missing or dangling .meta found. Fix commits are needed.`,
24 | StatusHeader: "Status",
25 | FilePathHeader: "File",
26 | StatusMissing: "Not committed",
27 | StatusDangling: "Not removed",
28 | }
29 |
30 | var Ja = Template{
31 | SuccessMessage: "commit忘れ・消し忘れの .meta はありませんでした。素晴らしい!",
32 | FailureMessage: "commit忘れ・消し忘れの .meta が見つかりました。修正コミットが必要です。",
33 | StatusHeader: "状態",
34 | FilePathHeader: "ファイル",
35 | StatusMissing: "commit されていない",
36 | StatusDangling: "消されていない",
37 | }
38 |
39 | func GetTemplate(lang Lang) (*Template, error) {
40 | switch lang {
41 | case LangEn:
42 | return &En, nil
43 | case LangJa:
44 | return &Ja, nil
45 | default:
46 | return nil, fmt.Errorf("unsupported lang: %s", lang)
47 | }
48 | }
49 |
50 | type TemplateFileReader func(path typedpath.RawPath) (*Template, error)
51 |
52 | func ReadTemplateFile(path typedpath.RawPath) (*Template, error) {
53 | file, err := os.Open(string(path))
54 | if err != nil {
55 | return nil, err
56 | }
57 | defer func(){ _ = file.Close() }()
58 |
59 | return ReadTemplate(file)
60 | }
61 |
62 | func ReadTemplate(reader io.Reader) (*Template, error) {
63 | bytes, err := io.ReadAll(reader)
64 | if err != nil {
65 | return nil, err
66 | }
67 |
68 | var tmpl Template
69 | if err := json.Unmarshal(bytes, &tmpl); err != nil {
70 | return nil, err
71 | }
72 |
73 | return &tmpl, nil
74 | }
75 |
76 | func WriteTemplateExample(writer io.Writer) {
77 | bytes, err := json.MarshalIndent(En, "", " ")
78 | if err != nil {
79 | panic(err.Error())
80 | }
81 | _, _ = writer.Write(bytes)
82 | }
83 |
84 | func ValidateTemplate(tmpl *Template) error {
85 | if tmpl.StatusHeader == "" {
86 | return errors.New(`empty "header_status"`)
87 | }
88 | if tmpl.FilePathHeader == "" {
89 | return errors.New(`empty "header_file_path"`)
90 | }
91 | if tmpl.StatusMissing == "" {
92 | return errors.New(`empty "status_missing"`)
93 | }
94 | if tmpl.StatusDangling == "" {
95 | return errors.New(`empty "status_dangling"`)
96 | }
97 | return nil
98 | }
99 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/l10n/template_stub.go:
--------------------------------------------------------------------------------
1 | package l10n
2 |
3 | import "github.com/DeNA/unity-meta-check/util/typedpath"
4 |
5 | func StubTemplateFileReader(tmpl *Template, err error) TemplateFileReader {
6 | return func(_ typedpath.RawPath) (*Template, error) {
7 | return tmpl, err
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/l10n/template_test.go:
--------------------------------------------------------------------------------
1 | package l10n
2 |
3 | import (
4 | "bytes"
5 | "github.com/google/go-cmp/cmp"
6 | "reflect"
7 | "strings"
8 | "testing"
9 | )
10 |
11 | func TestReadL10n(t *testing.T) {
12 | json := `{
13 | "header_status": "STATUS_HEADER",
14 | "header_file_path": "FILE_PATH_HEADER",
15 | "status_missing": "STATUS_MISSING",
16 | "status_dangling": "STATUS_DANGLING"
17 | }`
18 | actual, err := ReadTemplate(strings.NewReader(json))
19 |
20 | if err != nil {
21 | t.Errorf("want nil, got %#v", err)
22 | return
23 | }
24 |
25 | expected := &Template{
26 | StatusHeader: "STATUS_HEADER",
27 | FilePathHeader: "FILE_PATH_HEADER",
28 | StatusMissing: "STATUS_MISSING",
29 | StatusDangling: "STATUS_DANGLING",
30 | }
31 | if !reflect.DeepEqual(actual, expected) {
32 | t.Error(cmp.Diff(expected, actual))
33 | return
34 | }
35 | }
36 |
37 |
38 | func TestWriteTemplateExample(t *testing.T) {
39 | buf := &bytes.Buffer{}
40 |
41 | WriteTemplateExample(buf)
42 |
43 | if buf.Len() == 0 {
44 | t.Error("want greater than 0, but 0")
45 | return
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/cmd"
5 | "github.com/DeNA/unity-meta-check/util/cli"
6 | "os"
7 | )
8 |
9 | func main() {
10 | main := cmd.NewMain()
11 | exitStatus := main(os.Args[1:], cli.GetProcessInout(), cli.NewEnv())
12 | os.Exit(int(exitStatus))
13 | }
14 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/markdown/markdown.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/l10n"
7 | "github.com/DeNA/unity-meta-check/unity/checker"
8 | "io"
9 | "strings"
10 | )
11 |
12 | func WriteMarkdown(result *checker.CheckResult, tmpl *l10n.Template, writer io.Writer) error {
13 | if result.Empty() {
14 | _, _ = fmt.Fprintln(writer, tmpl.SuccessMessage)
15 | return nil
16 | }
17 |
18 | _, _ = fmt.Fprintln(writer, tmpl.FailureMessage)
19 | _, _ = io.WriteString(writer, "\n")
20 |
21 | if err := WriteTableRow(writer, tmpl.StatusHeader, tmpl.FilePathHeader); err != nil {
22 | return err
23 | }
24 |
25 | if err := WriteTableSep(writer, 2); err != nil {
26 | return err
27 | }
28 |
29 | for _, missingMeta := range result.MissingMeta {
30 | if err := WriteTableRow(writer, tmpl.StatusMissing, FormatAsInlineCode(string(missingMeta))); err != nil {
31 | return err
32 | }
33 | }
34 |
35 | for _, danglingMeta := range result.DanglingMeta {
36 | if err := WriteTableRow(writer, tmpl.StatusDangling, FormatAsInlineCode(string(danglingMeta))); err != nil {
37 | return err
38 | }
39 | }
40 |
41 | return nil
42 | }
43 |
44 | func WriteTableSep(writer io.Writer, num int) error {
45 | if num == 0 {
46 | return errors.New("must include at least one column")
47 | }
48 |
49 | _, _ = io.WriteString(writer, "|")
50 | _, _ = io.WriteString(writer, strings.Repeat(":--|", num))
51 | _, _ = io.WriteString(writer, "\n")
52 | return nil
53 | }
54 |
55 | func WriteTableRow(writer io.Writer, cols ...string) error {
56 | if len(cols) == 0 {
57 | return errors.New("must include at least one column")
58 | }
59 |
60 | _, _ = io.WriteString(writer, "| ")
61 | _, _ = io.WriteString(writer, cols[0])
62 | for _, col := range cols[1:] {
63 | _, _ = io.WriteString(writer, " | ")
64 | _, _ = io.WriteString(writer, col)
65 | }
66 | _, _ = io.WriteString(writer, " |")
67 | _, _ = io.WriteString(writer, "\n")
68 |
69 | return nil
70 | }
71 |
72 | func FormatAsInlineCode(s string) string {
73 | return fmt.Sprintf("`%s`", s)
74 | }
75 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/markdown/markdown_test.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-github-pr-comment/l10n"
7 | "github.com/DeNA/unity-meta-check/unity/checker"
8 | "github.com/DeNA/unity-meta-check/util/typedpath"
9 | "github.com/google/go-cmp/cmp"
10 | "strings"
11 | "testing"
12 | )
13 |
14 | func TestWriteMarkdown(t *testing.T) {
15 | cases := []struct{
16 | Result *checker.CheckResult
17 | Expected string
18 | }{
19 | {
20 | Result: &checker.CheckResult{
21 | MissingMeta: []typedpath.SlashPath{},
22 | DanglingMeta: []typedpath.SlashPath{},
23 | },
24 | Expected: "SUCCESS_MESSAGE\n",
25 | },
26 | {
27 | Result: &checker.CheckResult{
28 | MissingMeta: []typedpath.SlashPath{
29 | "path/to/missing.meta",
30 | },
31 | DanglingMeta: []typedpath.SlashPath{},
32 | },
33 | Expected: strings.Join([]string{
34 | "FAILURE_MESSAGE",
35 | "",
36 | "| HEADER_STATUS | HEADER_FILE_PATH |",
37 | "|:--|:--|",
38 | "| STATUS_MISSING | `path/to/missing.meta` |",
39 | "",
40 | }, "\n"),
41 | },
42 | {
43 | Result: &checker.CheckResult{
44 | MissingMeta: []typedpath.SlashPath{},
45 | DanglingMeta: []typedpath.SlashPath{
46 | "path/to/dangling.meta",
47 | },
48 | },
49 | Expected: strings.Join([]string{
50 | "FAILURE_MESSAGE",
51 | "",
52 | "| HEADER_STATUS | HEADER_FILE_PATH |",
53 | "|:--|:--|",
54 | "| STATUS_DANGLING | `path/to/dangling.meta` |",
55 | "",
56 | }, "\n"),
57 | },
58 | }
59 |
60 | for _, c := range cases {
61 | t.Run(fmt.Sprintf("%v", c), func(t *testing.T) {
62 | buf := &bytes.Buffer{}
63 |
64 | err := WriteMarkdown(c.Result, &l10n.Template{
65 | SuccessMessage: "SUCCESS_MESSAGE",
66 | FailureMessage: "FAILURE_MESSAGE",
67 | StatusHeader: "HEADER_STATUS",
68 | FilePathHeader: "HEADER_FILE_PATH",
69 | StatusMissing: "STATUS_MISSING",
70 | StatusDangling: "STATUS_DANGLING",
71 | }, buf)
72 | if err != nil {
73 | t.Errorf("want nil, got %#v", err)
74 | return
75 | }
76 |
77 | actual := buf.String()
78 | if actual != c.Expected {
79 | t.Error(cmp.Diff(c.Expected, actual))
80 | return
81 | }
82 | })
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-github-pr-comment/options/testdata/example-template.json:
--------------------------------------------------------------------------------
1 | {
2 | "success": "SUCCESS",
3 | "failure": "FAILURE",
4 | "header_status": "HEADER_STATUS",
5 | "header_file_path": "HEADER_FILE_PATH",
6 | "status_missing": "STATUS_MISSING",
7 | "status_dangling": "STATUS_DANGLING"
8 | }
--------------------------------------------------------------------------------
/tool/unity-meta-check-junit/cmd/cmd.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/report"
7 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-junit/junit"
8 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-junit/options"
9 | "github.com/DeNA/unity-meta-check/util/cli"
10 | "github.com/DeNA/unity-meta-check/version"
11 | "io"
12 | )
13 |
14 | func NewMain() cli.Command {
15 | return func(args []string, procInout cli.ProcessInout, env cli.Env) cli.ExitStatus {
16 | opts, err := options.BuildOptions(args, procInout)
17 | if err != nil {
18 | if err != flag.ErrHelp {
19 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
20 | }
21 | return cli.ExitAbnormal
22 | }
23 |
24 | if opts.Version {
25 | _, _ = fmt.Fprintln(procInout.Stdout, version.Version)
26 | return cli.ExitNormal
27 | }
28 |
29 | parse := report.NewParser()
30 | result := parse(io.TeeReader(procInout.Stdin, procInout.Stdout))
31 |
32 | if err := junit.WriteToFile(result, opts.OutPath); err != nil {
33 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
34 | return cli.ExitAbnormal
35 | }
36 |
37 | if !result.Empty() {
38 | return cli.ExitAbnormal
39 | }
40 | return cli.ExitNormal
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-junit/cmd/cmd_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/cli"
5 | "github.com/DeNA/unity-meta-check/util/testutil"
6 | "io/ioutil"
7 | "os"
8 | "path/filepath"
9 | "strings"
10 | "testing"
11 | )
12 |
13 | func TestValid(t *testing.T) {
14 | stdout := testutil.SpyWriteCloser(nil)
15 | stderr := testutil.SpyWriteCloser(nil)
16 |
17 | procInout := cli.ProcessInout{
18 | Stdin: strings.NewReader(""),
19 | Stdout: stdout,
20 | Stderr: stderr,
21 | }
22 |
23 | tmpDir, err := os.MkdirTemp(os.TempDir(), "")
24 | if err != nil {
25 | t.Errorf("want nil, got %#v", err)
26 | return
27 | }
28 |
29 | main := NewMain()
30 | actual := main([]string{filepath.Join(tmpDir, "valid.xml")}, procInout, cli.AnyEnv())
31 |
32 | expected := cli.ExitNormal
33 | if actual != expected {
34 | t.Logf("stdout:\n%s", stdout.Captured.String())
35 | t.Logf("stderr:\n%s", stderr.Captured.String())
36 | t.Errorf("want %#v, got %#v", expected, actual)
37 | return
38 | }
39 | }
40 |
41 | func TestInvalid(t *testing.T) {
42 | stdout := testutil.SpyWriteCloser(nil)
43 | stderr := testutil.SpyWriteCloser(nil)
44 |
45 | procInout := cli.ProcessInout{
46 | Stdin: strings.NewReader(`missing path/to/missing.meta
47 | dangling path/to/dangling.meta`),
48 | Stdout: stdout,
49 | Stderr: stderr,
50 | }
51 |
52 | tmpDir, err := ioutil.TempDir(os.TempDir(), "")
53 | if err != nil {
54 | t.Errorf("want nil, got %#v", err)
55 | return
56 | }
57 |
58 | main := NewMain()
59 | actual := main([]string{filepath.Join(tmpDir, "invalid.xml")}, procInout, cli.AnyEnv())
60 |
61 | expected := cli.ExitAbnormal
62 | if actual != expected {
63 | t.Logf("stdout:\n%s", stdout.Captured.String())
64 | t.Logf("stderr:\n%s", stderr.Captured.String())
65 | t.Errorf("want %#v, got %#v", expected, actual)
66 | return
67 | }
68 | }
69 |
70 | func TestVersion(t *testing.T) {
71 | main := NewMain()
72 | stdout := testutil.SpyWriteCloser(nil)
73 | stderr := testutil.SpyWriteCloser(nil)
74 | procInout := cli.ProcessInout{
75 | Stdin: strings.NewReader(""),
76 | Stdout: stdout,
77 | Stderr: stderr,
78 | }
79 |
80 | actual := main([]string{"-version"}, procInout, cli.AnyEnv())
81 |
82 | expected := cli.ExitNormal
83 | if actual != expected {
84 | t.Logf("stdout:\n%s", stdout.Captured.String())
85 | t.Logf("stderr:\n%s", stderr.Captured.String())
86 | t.Errorf("want %#v, got %#v", expected, actual)
87 | return
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-junit/junit/junit_stub.go:
--------------------------------------------------------------------------------
1 | package junit
2 |
3 | import (
4 | "errors"
5 | "github.com/DeNA/unity-meta-check/unity/checker"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | )
8 |
9 | func StubWriteToFileFunc(err error) WriteToFileFunc {
10 | return func(_ *checker.CheckResult, _ typedpath.RawPath) error {
11 | return err
12 | }
13 | }
14 |
15 | type WriteToFileCallArgs struct {
16 | CheckResult *checker.CheckResult
17 | OutPath typedpath.RawPath
18 | }
19 |
20 | func SpyWriteToFileFunc(inherited WriteToFileFunc, callArgs *[]WriteToFileCallArgs) WriteToFileFunc {
21 | if inherited == nil {
22 | inherited = StubWriteToFileFunc(errors.New("SPY_WRITE_TO_FILE_FUNC"))
23 | }
24 | return func(result *checker.CheckResult, outPath typedpath.RawPath) error {
25 | *callArgs = append(*callArgs, WriteToFileCallArgs{
26 | CheckResult: result,
27 | OutPath: outPath,
28 | })
29 | return inherited(result, outPath)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-junit/junit/junit_test.go:
--------------------------------------------------------------------------------
1 | package junit
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/unity/checker"
7 | "github.com/DeNA/unity-meta-check/util/typedpath"
8 | "github.com/google/go-cmp/cmp"
9 | "runtime"
10 | "testing"
11 | )
12 |
13 | func TestWrite(t *testing.T) {
14 | cases := map[string]struct {
15 | Result *checker.CheckResult
16 | Expected string
17 | }{
18 | "empty (boundary)": {
19 | Result: checker.NewCheckResult([]typedpath.SlashPath{}, []typedpath.SlashPath{}),
20 | Expected: fmt.Sprintf(`
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | `, runtime.Version()),
30 | },
31 | "both missing and dangling (easy to test)": {
32 | Result: checker.NewCheckResult(
33 | []typedpath.SlashPath{
34 | typedpath.NewSlashPathUnsafe("path/to/missing.meta"),
35 | },
36 | []typedpath.SlashPath{
37 | typedpath.NewSlashPathUnsafe("path/to/dangling.meta"),
38 | },
39 | ),
40 | Expected: fmt.Sprintf(`
41 |
42 |
43 |
44 |
45 |
46 |
47 | File or directory exists: path/to/missing
But .meta is missing: path/to/missing.meta
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | File or directory does not exist: path/to/dangling
But .meta is present: path/to/dangling.meta
56 |
57 |
58 |
59 | `, runtime.Version(), runtime.Version()),
60 | },
61 | }
62 |
63 | for name, c := range cases {
64 | t.Run(name, func(t *testing.T) {
65 |
66 | buf := &bytes.Buffer{}
67 | if err := Write(c.Result, buf); err != nil {
68 | t.Errorf("want nil, got %#v", err)
69 | return
70 | }
71 |
72 | actual := buf.String()
73 | if actual != c.Expected {
74 | t.Error(cmp.Diff(c.Expected, actual))
75 | return
76 | }
77 | })
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-junit/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/tool/unity-meta-check-junit/cmd"
5 | "github.com/DeNA/unity-meta-check/util/cli"
6 | "os"
7 | )
8 |
9 | func main() {
10 | main := cmd.NewMain()
11 | exitStatus := main(os.Args[1:], cli.GetProcessInout(), cli.NewEnv())
12 | os.Exit(int(exitStatus))
13 | }
14 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-junit/options/options.go:
--------------------------------------------------------------------------------
1 | package options
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "fmt"
7 | "github.com/DeNA/unity-meta-check/util/cli"
8 | "github.com/DeNA/unity-meta-check/util/typedpath"
9 | )
10 |
11 | type Options struct {
12 | Version bool
13 | OutPath typedpath.RawPath
14 | }
15 |
16 | func BuildOptions(args []string, procInout cli.ProcessInout) (*Options, error) {
17 | opts := &Options{}
18 |
19 | flags := flag.NewFlagSet("unity-meta-check-junit", flag.ContinueOnError)
20 | flags.SetOutput(procInout.Stderr)
21 | flags.Usage = func() {
22 | _, _ = fmt.Fprint(procInout.Stderr, `usage: unity-meta-check-junit [] []
23 |
24 | Save a JUnit report file for the result from unity-meta-check via STDIN.
25 |
26 |
27 | output path to write JUnit report
28 |
29 | OPTIONS
30 | `)
31 | flags.PrintDefaults()
32 |
33 | _, _ = fmt.Fprint(procInout.Stderr, `
34 | EXAMPLE USAGES
35 | $ unity-meta-check | unity-meta-check-junit path/to/junit-report.xml
36 | $ unity-meta-check | unity-meta-check-junit path/to/junit-report.xml |
37 | `)
38 | }
39 | flags.BoolVar(&opts.Version, "version", false, "print version")
40 |
41 | if err := flags.Parse(args); err != nil {
42 | return nil, err
43 | }
44 |
45 | if opts.Version {
46 | return opts, nil
47 | }
48 |
49 | outPaths := flags.Args()
50 | if len(outPaths) < 1 {
51 | return nil, errors.New("must specify a file path to output JUnit report")
52 | }
53 | if len(outPaths) > 1 {
54 | return nil, errors.New("too much arguments")
55 | }
56 |
57 | outPath := typedpath.NewRawPathUnsafe(args[0])
58 | opts.OutPath = outPath
59 | return opts, nil
60 | }
61 |
--------------------------------------------------------------------------------
/tool/unity-meta-check-meta-audit/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "fmt"
7 | "github.com/DeNA/unity-meta-check/unity"
8 | "github.com/DeNA/unity-meta-check/util/cli"
9 | "github.com/DeNA/unity-meta-check/util/typedpath"
10 | "github.com/scylladb/go-set/strset"
11 | "os"
12 | "path/filepath"
13 | "strings"
14 | "sync"
15 | )
16 |
17 | func main() {
18 | main := NewMain()
19 | exitStatus := main(os.Args[1:], cli.GetProcessInout(), cli.NewEnv())
20 | os.Exit(int(exitStatus))
21 | }
22 |
23 | type metaHistogram struct {
24 | extensions map[string]uint
25 | }
26 |
27 | func NewMain() cli.Command {
28 | return func(args []string, procInout cli.ProcessInout, env cli.Env) cli.ExitStatus {
29 | ignore := strset.New(args...)
30 |
31 | var wg sync.WaitGroup
32 | var mu sync.Mutex
33 | result := make(map[string]*metaHistogram, 1000000)
34 | metaPathCh := make(chan typedpath.RawPath, 4)
35 | var err error
36 |
37 | wg.Add(1)
38 | go func() {
39 | defer wg.Done()
40 | defer close(metaPathCh)
41 | if err2 := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
42 | if !strings.HasSuffix(path, unity.MetaSuffix) {
43 | return nil
44 | }
45 | ext := strings.ToLower(filepath.Ext(strings.TrimSuffix(path, unity.MetaSuffix)))
46 | if !ignore.Has(ext) {
47 | metaPathCh <- typedpath.RawPath(path)
48 | }
49 | return nil
50 | }); err2 != nil {
51 | err = err2
52 | }
53 | }()
54 |
55 | for i := 0; i < 4; i++ {
56 | wg.Add(1)
57 | go func() {
58 | defer wg.Done()
59 |
60 | buf := &bytes.Buffer{}
61 | for metaPath := range metaPathCh {
62 | file, err := os.Open(string(metaPath))
63 | if err != nil {
64 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
65 | return
66 | }
67 |
68 | buf.Reset()
69 | scanner := bufio.NewScanner(file)
70 | for scanner.Scan() {
71 | line := scanner.Text()
72 | if strings.HasPrefix(line, "guid: ") {
73 | buf.WriteString("guid: _\n")
74 | continue
75 | }
76 | if strings.HasPrefix(line, "timeCreated: ") {
77 | buf.WriteString("timeCreated: _\n")
78 | continue
79 | }
80 | buf.WriteString(line)
81 | buf.WriteByte('\n')
82 | }
83 | _ = file.Close()
84 | masked := buf.String()
85 | ext := filepath.Ext(string(unity.TrimMetaFromRaw(metaPath)))
86 |
87 | mu.Lock()
88 | hist, ok := result[masked]
89 | if ok {
90 | hist.extensions[ext] += 1
91 | } else {
92 | result[masked] = &metaHistogram{
93 | extensions: map[string]uint{ext: 1},
94 | }
95 | }
96 | mu.Unlock()
97 | }
98 | }()
99 | }
100 |
101 | wg.Wait()
102 | if err != nil {
103 | _, _ = fmt.Fprintln(procInout.Stderr, err.Error())
104 | return cli.ExitAbnormal
105 | }
106 |
107 | for masked, hist := range result {
108 | _, _ = fmt.Fprintf(procInout.Stdout, "%v ==================\n%s\n", hist.extensions, masked)
109 | }
110 |
111 | return cli.ExitNormal
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/unity/checker/checker.go:
--------------------------------------------------------------------------------
1 | package checker
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/filecollector"
5 | "github.com/DeNA/unity-meta-check/util/errutil"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "github.com/DeNA/unity-meta-check/util/typedpath"
8 | "sync"
9 | )
10 |
11 | type Checker func(rootDirAbs typedpath.RawPath, opts *Options) (*CheckResult, error)
12 |
13 | func NewChecker(selectStrategy StrategySelector, logger logging.Logger) Checker {
14 | return func(rootDirAbs typedpath.RawPath, opts *Options) (*CheckResult, error) {
15 | strategy, err := selectStrategy(rootDirAbs, opts)
16 | if err != nil {
17 | return nil, err
18 | }
19 |
20 | check := newCheckerByStrategy(strategy, logger)
21 | return check(rootDirAbs, opts)
22 | }
23 | }
24 |
25 | func newCheckerByStrategy(strategy Strategy, logger logging.Logger) Checker {
26 | return func(rootDirAbs typedpath.RawPath, opts *Options) (*CheckResult, error) {
27 | ch := make(chan filecollector.Entry)
28 |
29 | var wg sync.WaitGroup
30 | var errsMu sync.Mutex
31 | errs := make([]error, 0)
32 |
33 | wg.Add(1)
34 | go func() {
35 | defer wg.Done()
36 | defer close(ch)
37 | if err := strategy.CollectFiles(rootDirAbs, &filecollector.Options{IgnoreCase: opts.IgnoreCase}, ch); err != nil {
38 | errsMu.Lock()
39 | errs = append(errs, err)
40 | errsMu.Unlock()
41 | return
42 | }
43 | }()
44 |
45 | check := NewCheckingWorker(strategy.RequiresMeta, logger)
46 | result, err := check(rootDirAbs, opts.IgnoreCase, ch)
47 | if err != nil {
48 | errsMu.Lock()
49 | errs = append(errs, err)
50 | errsMu.Unlock()
51 | }
52 | if len(errs) > 0 {
53 | return nil, errutil.NewErrors(errs)
54 | }
55 | return result, nil
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/unity/checker/checker_stub.go:
--------------------------------------------------------------------------------
1 | package checker
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | func StubChecker(result *CheckResult, err error) Checker {
8 | return func(_ typedpath.RawPath, _ *Options) (*CheckResult, error) {
9 | return result, err
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/unity/checker/checker_test.go:
--------------------------------------------------------------------------------
1 | package checker
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/filecollector"
5 | "github.com/DeNA/unity-meta-check/unity"
6 | "github.com/DeNA/unity-meta-check/util/logging"
7 | "github.com/DeNA/unity-meta-check/util/typedpath"
8 | "github.com/google/go-cmp/cmp"
9 | "reflect"
10 | "testing"
11 | )
12 |
13 | func TestCheck(t *testing.T) {
14 | strategy := Strategy{
15 | CollectFiles: filecollector.StubSuccessfulFileAggregator([]filecollector.Entry{
16 | {Path: "Assets/MissingMeta", IsDir: false},
17 | {Path: "Assets/DanglingMeta.meta", IsDir: false},
18 | }),
19 | RequiresMeta: unity.ConstMetaNecessity(true),
20 | }
21 | spyLogger := logging.SpyLogger()
22 | opts := &Options{
23 | IgnoreCase: false,
24 | TargetType: TargetTypeIsUnityProjectRootDirectory,
25 | }
26 |
27 | checker := newCheckerByStrategy(strategy, spyLogger)
28 | actual, err := checker("/path/to/Project", opts)
29 |
30 | if err != nil {
31 | t.Log(spyLogger.Logs.String())
32 | t.Errorf("want nil, got %s", err.Error())
33 | return
34 | }
35 |
36 | expected := &CheckResult{
37 | MissingMeta: []typedpath.SlashPath{
38 | "Assets/MissingMeta.meta",
39 | },
40 | DanglingMeta: []typedpath.SlashPath{
41 | "Assets/DanglingMeta.meta",
42 | },
43 | }
44 | if !reflect.DeepEqual(actual, expected) {
45 | t.Log(spyLogger.Logs.String())
46 | t.Error(cmp.Diff(expected, actual))
47 | return
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/unity/checker/options.go:
--------------------------------------------------------------------------------
1 | package checker
2 |
3 | type TargetType string
4 |
5 | const (
6 | // TargetTypeIsUnityProjectRootDirectory means the target root directory point to a root directory of the Unity project.
7 | TargetTypeIsUnityProjectRootDirectory TargetType = "TargetTypeIsUnityProjectRootDirectory"
8 |
9 | // TargetTypeIsUnityProjectSubDirectory means the target root directory point to a sub directory that need meta files of the Unity project.
10 | // NOTE: this including UPM packages (because Packages/com.example.foo/ is a sub directory that need meta files of Unity projects).
11 | TargetTypeIsUnityProjectSubDirectory TargetType = "TargetTypeIsUnityProjectSubDirectory"
12 | )
13 |
14 | // Options for Checker.
15 | // NOTE: We should keep this options simple because the newWorker logic is already complicated.
16 | // Implement on ResultFilter if you want to filter the CheckResult.
17 | type Options struct {
18 | IgnoreCase bool
19 | IgnoreSubmodulesAndNested bool
20 | TargetType TargetType
21 | }
--------------------------------------------------------------------------------
/unity/checker/result.go:
--------------------------------------------------------------------------------
1 | package checker
2 |
3 | import "github.com/DeNA/unity-meta-check/util/typedpath"
4 |
5 | type CheckResult struct {
6 | MissingMeta []typedpath.SlashPath
7 | DanglingMeta []typedpath.SlashPath
8 | }
9 |
10 | func (c CheckResult) Empty() bool {
11 | return c.Len() == 0
12 | }
13 |
14 | func (c CheckResult) Len() int {
15 | return len(c.MissingMeta) + len(c.DanglingMeta)
16 | }
17 |
18 | func NewCheckResult(missingMeta []typedpath.SlashPath, danglingMeta []typedpath.SlashPath) *CheckResult {
19 | return &CheckResult{
20 | MissingMeta: missingMeta,
21 | DanglingMeta: danglingMeta,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/unity/checker/strategy.go:
--------------------------------------------------------------------------------
1 | package checker
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/filecollector"
6 | "github.com/DeNA/unity-meta-check/filecollector/repofinder"
7 | "github.com/DeNA/unity-meta-check/git"
8 | "github.com/DeNA/unity-meta-check/unity"
9 | "github.com/DeNA/unity-meta-check/util/logging"
10 | "github.com/DeNA/unity-meta-check/util/typedpath"
11 | )
12 |
13 | type Strategy struct {
14 | CollectFiles filecollector.FileAggregator
15 | RequiresMeta unity.MetaNecessity
16 | }
17 |
18 | type StrategySelector func(rootDirAbs typedpath.RawPath, opts *Options) (Strategy, error)
19 |
20 | // NewStrategySelector returns a checker strategy for either Unity projects or UPM packages.
21 | func NewStrategySelector(findPackages unity.FindPackages, lsFiles git.LsFiles, logger logging.Logger) StrategySelector {
22 | return func(rootDirAbs typedpath.RawPath, opts *Options) (Strategy, error) {
23 | switch opts.TargetType {
24 | case TargetTypeIsUnityProjectRootDirectory:
25 | foundPackages, err := findPackages(rootDirAbs)
26 | if err != nil {
27 | return Strategy{}, fmt.Errorf("cannot find local packages: %#v", err)
28 | }
29 |
30 | findRepo := NewRepoFinderForUnityProj(rootDirAbs, opts, foundPackages)
31 | return Strategy{
32 | CollectFiles: filecollector.NewFileAggregator(lsFiles, findRepo, logger),
33 | RequiresMeta: unity.NewMetaNecessityInUnityProject(unity.FoundPackagesToSlashRelPaths(foundPackages)),
34 | }, nil
35 |
36 | case TargetTypeIsUnityProjectSubDirectory:
37 | findRepo := NewRepoFinderFactoryForUPM(rootDirAbs, opts)
38 | return Strategy{
39 | CollectFiles: filecollector.NewFileAggregator(lsFiles, findRepo, logger),
40 | RequiresMeta: unity.NewMetaNecessityInUnityProjectSubDir(),
41 | }, nil
42 |
43 | default:
44 | return Strategy{}, fmt.Errorf("unsupported checking type: %q", opts.TargetType)
45 | }
46 | }
47 | }
48 |
49 | func NewRepoFinderForUnityProj(rootDirAbs typedpath.RawPath, opts *Options, foundPackages []*unity.FoundPackage) repofinder.RepoFinder {
50 | if opts.IgnoreSubmodulesAndNested {
51 | return repofinder.StubRepoFinder(nil, nil)
52 | }
53 |
54 | repoFinders := make([]repofinder.RepoFinder, len(foundPackages)+1)
55 | repoFinders[0] = repofinder.New(rootDirAbs, typedpath.RawPath(unity.AssetsDirBaseName))
56 | i := 1
57 | for _, foundPkg := range foundPackages {
58 | repoFinders[i] = repofinder.New(rootDirAbs, foundPkg.RelPath)
59 | i++
60 | }
61 | findRepo := repofinder.Compose(repoFinders)
62 | return findRepo
63 | }
64 |
65 | func NewRepoFinderFactoryForUPM(rootDirAbs typedpath.RawPath, opts *Options) repofinder.RepoFinder {
66 | if opts.IgnoreSubmodulesAndNested {
67 | return repofinder.StubRepoFinder(nil, nil)
68 | }
69 |
70 | return repofinder.New(rootDirAbs, ".")
71 | }
72 |
--------------------------------------------------------------------------------
/unity/checker/testdata/EmptyManifest/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {}
3 | }
4 |
--------------------------------------------------------------------------------
/unity/checker/testdata/LocalsManifest/LocalPackages/com.example.local/.gitempty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/unity/checker/testdata/LocalsManifest/LocalPackages/com.example.local/.gitempty
--------------------------------------------------------------------------------
/unity/checker/testdata/LocalsManifest/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.example.local": "file:../LocalPackages/com.example.local"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/unity/checker/testdata/NoLocalsManifest/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.example": "1.0.0"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/unity/manifest.go:
--------------------------------------------------------------------------------
1 | package unity
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/DeNA/unity-meta-check/util/typedpath"
6 | "os"
7 | )
8 |
9 | type ManifestJson struct {
10 | Dependencies map[string]string `json:"dependencies"`
11 | }
12 |
13 | var ManifestBasename typedpath.BaseName = "manifest.json"
14 |
15 | func ReadManifest(path typedpath.RawPath) (*ManifestJson, error) {
16 | bytes, err := os.ReadFile(string(path))
17 | if err != nil {
18 | return nil, err
19 | }
20 | return parseManifestJson(bytes)
21 | }
22 |
23 | func parseManifestJson(bytes []byte) (*ManifestJson, error) {
24 | var manifestJson ManifestJson
25 | if err := json.Unmarshal(bytes, &manifestJson); err != nil {
26 | return nil, err
27 | }
28 | return &manifestJson, nil
29 | }
30 |
--------------------------------------------------------------------------------
/unity/manifest_test.go:
--------------------------------------------------------------------------------
1 | package unity
2 |
3 | import (
4 | "github.com/google/go-cmp/cmp"
5 | "reflect"
6 | "testing"
7 | )
8 |
9 | func TestReadManifestJson(t *testing.T) {
10 | json := []byte(`{
11 | "scopedRegistries": [],
12 | "dependencies": {
13 | "foo": "1.2.3",
14 | "bar.baz": "file:../Bar/Buz"
15 | }
16 | }`)
17 |
18 | actual, err := parseManifestJson(json)
19 | if err != nil {
20 | t.Errorf("want nil, got %#v", err)
21 | return
22 | }
23 |
24 | expected := &ManifestJson{Dependencies: map[string]string{
25 | "foo": "1.2.3",
26 | "bar.baz": "file:../Bar/Buz",
27 | }}
28 |
29 | if !reflect.DeepEqual(actual, expected) {
30 | t.Error(cmp.Diff(expected, actual))
31 | return
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/unity/meta/defaultimportergen.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 | )
8 |
9 | type DefaultImporterGen struct {
10 | GUID *GUID
11 | }
12 |
13 | var _ Gen = MonoImporterGen{}
14 |
15 | func (t DefaultImporterGen) WriteTo(writer io.Writer) (int64, error) {
16 | content := strings.TrimLeft(fmt.Sprintf(`
17 | fileFormatVersion: 2
18 | guid: %s
19 | DefaultImporter:
20 | externalObjects: {}
21 | userData:
22 | assetBundleName:
23 | assetBundleVariant:
24 | `, t.GUID.String()), "\n")
25 | n, err := io.WriteString(writer, content)
26 | return int64(n), err
27 | }
28 |
--------------------------------------------------------------------------------
/unity/meta/defaultimportergen_test.go:
--------------------------------------------------------------------------------
1 | package meta_test
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/DeNA/unity-meta-check/unity/meta"
8 | "github.com/google/go-cmp/cmp"
9 | )
10 |
11 | func TestDefaultoImporterGen_WriteTo(t *testing.T) {
12 | gen := meta.DefaultImporterGen{GUID: meta.ZeroGUID()}
13 |
14 | buf := &bytes.Buffer{}
15 | _, err := gen.WriteTo(buf)
16 | if err != nil {
17 | t.Errorf("want nil, got %#v", err)
18 | return
19 | }
20 |
21 | actual := buf.String()
22 | expected := `fileFormatVersion: 2
23 | guid: 00000000000000000000000000000000
24 | DefaultImporter:
25 | externalObjects: {}
26 | userData:
27 | assetBundleName:
28 | assetBundleVariant:
29 | `
30 | if actual != expected {
31 | t.Error(cmp.Diff(expected, actual))
32 | return
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/unity/meta/foldergen.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 | )
8 |
9 | type DefaultImporterFolderGen struct {
10 | GUID *GUID
11 | }
12 |
13 | var _ Gen = DefaultImporterFolderGen{}
14 |
15 | func (f DefaultImporterFolderGen) WriteTo(writer io.Writer) (int64, error) {
16 | content := strings.TrimLeft(fmt.Sprintf(`
17 | fileFormatVersion: 2
18 | guid: %s
19 | folderAsset: yes
20 | DefaultImporter:
21 | externalObjects: {}
22 | userData:
23 | assetBundleName:
24 | assetBundleVariant:
25 | `, f.GUID.String()), "\n")
26 | n, err := io.WriteString(writer, content)
27 | return int64(n), err
28 | }
29 |
--------------------------------------------------------------------------------
/unity/meta/foldergen_test.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "bytes"
5 | "github.com/google/go-cmp/cmp"
6 | "testing"
7 | )
8 |
9 | func TestFolderGen_WriteTo(t *testing.T) {
10 | meta := DefaultImporterFolderGen{ZeroGUID()}
11 |
12 | buf := &bytes.Buffer{}
13 | _, err := meta.WriteTo(buf)
14 | if err != nil {
15 | t.Errorf("want nil, got %#v", err)
16 | return
17 | }
18 |
19 | actual := buf.String()
20 | expected := `fileFormatVersion: 2
21 | guid: 00000000000000000000000000000000
22 | folderAsset: yes
23 | DefaultImporter:
24 | externalObjects: {}
25 | userData:
26 | assetBundleName:
27 | assetBundleVariant:
28 | `
29 | if actual != expected {
30 | t.Error(cmp.Diff(expected, actual))
31 | return
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/unity/meta/gen.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "io"
5 | )
6 |
7 | type Gen interface {
8 | WriteTo(writer io.Writer) (int64, error)
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/unity/meta/guid.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "encoding/hex"
5 | "fmt"
6 | )
7 |
8 | type GUID struct {
9 | bytes []byte
10 | }
11 |
12 | const GUIDByteLength = 16
13 |
14 | func NewGUID(bytes []byte) (*GUID, error) {
15 | if len(bytes) != GUIDByteLength {
16 | return nil, fmt.Errorf("length of GUID must be %d bytes", GUIDByteLength)
17 | }
18 | return &GUID{bytes}, nil
19 | }
20 |
21 | func (g GUID) String() string {
22 | s := make([]byte, hex.EncodedLen(GUIDByteLength))
23 | hex.Encode(s, g.bytes)
24 | return string(s)
25 | }
26 |
--------------------------------------------------------------------------------
/unity/meta/guid_stub.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | func AnyGUID() *GUID {
4 | guid, err := NewGUID([]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf})
5 | if err != nil {
6 | panic(err.Error())
7 | }
8 | return guid
9 | }
10 |
11 | func ZeroGUID() *GUID {
12 | guid, err := NewGUID(make([]byte, GUIDByteLength))
13 | if err != nil {
14 | panic(err.Error())
15 | }
16 | return guid
17 | }
18 |
--------------------------------------------------------------------------------
/unity/meta/guid_test.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestNewGUID(t *testing.T) {
8 | guid, err := NewGUID([]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf})
9 | if err != nil {
10 | t.Errorf("want nil, got %#v", err)
11 | return
12 | }
13 |
14 | actual := guid.String()
15 | expected := "000102030405060708090a0b0c0d0e0f"
16 | if actual != expected {
17 | t.Errorf("want %q, got %q", expected, actual)
18 | return
19 | }
20 | }
21 |
22 | func TestRandomGUIDGenerator(t *testing.T) {
23 | guidGen := RandomGUIDGenerator()
24 | _, err := guidGen()
25 | if err != nil {
26 | t.Errorf("want nil, got %#v", err)
27 | return
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/unity/meta/guidgen.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import "crypto/rand"
4 |
5 | type GUIDGen func() (*GUID, error)
6 |
7 | func RandomGUIDGenerator() GUIDGen {
8 | return func() (*GUID, error) {
9 | bytes := make([]byte, GUIDByteLength)
10 | _, err := rand.Read(bytes)
11 | if err != nil {
12 | return nil, err
13 | }
14 | return NewGUID(bytes)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/unity/meta/guidgen_stub.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | func StubGUIDGen(guid *GUID, err error) GUIDGen {
4 | return func() (*GUID, error) {
5 | return guid, err
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/unity/meta/monoimportergen.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 | )
8 |
9 | type MonoImporterGen struct {
10 | GUID *GUID
11 | }
12 |
13 | var _ Gen = MonoImporterGen{}
14 |
15 | func (t MonoImporterGen) WriteTo(writer io.Writer) (int64, error) {
16 | content := strings.TrimLeft(fmt.Sprintf(`
17 | fileFormatVersion: 2
18 | guid: %s
19 | MonoImporter:
20 | externalObjects: {}
21 | userData:
22 | assetBundleName:
23 | assetBundleVariant:
24 | serializedVersion: 2
25 | defaultReferences: []
26 | executionOrder: 0
27 | icon: {instanceID: 0}
28 | `, t.GUID.String()), "\n")
29 | n, err := io.WriteString(writer, content)
30 | return int64(n), err
31 | }
32 |
--------------------------------------------------------------------------------
/unity/meta/monoimportergen_test.go:
--------------------------------------------------------------------------------
1 | package meta_test
2 |
3 | import (
4 | "bytes"
5 | "github.com/DeNA/unity-meta-check/unity/meta"
6 | "github.com/google/go-cmp/cmp"
7 | "testing"
8 | )
9 |
10 | func TestMonoImporterGen_WriteTo(t *testing.T) {
11 | gen := meta.MonoImporterGen{GUID: meta.ZeroGUID()}
12 |
13 | buf := &bytes.Buffer{}
14 | _, err := gen.WriteTo(buf)
15 | if err != nil {
16 | t.Errorf("want nil, got %#v", err)
17 | return
18 | }
19 |
20 | actual := buf.String()
21 | expected := `fileFormatVersion: 2
22 | guid: 00000000000000000000000000000000
23 | MonoImporter:
24 | externalObjects: {}
25 | userData:
26 | assetBundleName:
27 | assetBundleVariant:
28 | serializedVersion: 2
29 | defaultReferences: []
30 | executionOrder: 0
31 | icon: {instanceID: 0}
32 | `
33 | if actual != expected {
34 | t.Error(cmp.Diff(expected, actual))
35 | return
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/unity/meta/textscriptimportergen.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 | )
8 |
9 | type TextScriptImporterGen struct {
10 | GUID *GUID
11 | }
12 |
13 | var _ Gen = TextScriptImporterGen{}
14 |
15 | func (t TextScriptImporterGen) WriteTo(writer io.Writer) (int64, error) {
16 | content := strings.TrimLeft(fmt.Sprintf(`
17 | fileFormatVersion: 2
18 | guid: %s
19 | TextScriptImporter:
20 | externalObjects: {}
21 | userData:
22 | assetBundleName:
23 | assetBundleVariant:
24 | `, t.GUID.String()), "\n")
25 | n, err := io.WriteString(writer, content)
26 | return int64(n), err
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/unity/meta/textscriptimportergen_test.go:
--------------------------------------------------------------------------------
1 | package meta
2 |
3 | import (
4 | "bytes"
5 | "github.com/google/go-cmp/cmp"
6 | "testing"
7 | )
8 |
9 | func TestTextScriptImporterGen_WriteTo(t *testing.T) {
10 | meta := TextScriptImporterGen{ZeroGUID()}
11 |
12 | buf := &bytes.Buffer{}
13 | _, err := meta.WriteTo(buf)
14 | if err != nil {
15 | t.Errorf("want nil, got %#v", err)
16 | return
17 | }
18 |
19 | actual := buf.String()
20 | expected := `fileFormatVersion: 2
21 | guid: 00000000000000000000000000000000
22 | TextScriptImporter:
23 | externalObjects: {}
24 | userData:
25 | assetBundleName:
26 | assetBundleVariant:
27 | `
28 | if actual != expected {
29 | t.Error(cmp.Diff(expected, actual))
30 | return
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/unity/metanecessity_stub.go:
--------------------------------------------------------------------------------
1 | package unity
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | func ConstMetaNecessity(result bool) MetaNecessity {
8 | return func(typedpath.SlashPath) bool {
9 | return result
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/unity/package_test.go:
--------------------------------------------------------------------------------
1 | package unity
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/util/logging"
6 | "github.com/DeNA/unity-meta-check/util/typedpath"
7 | "github.com/google/go-cmp/cmp"
8 | "reflect"
9 | "testing"
10 | )
11 |
12 | func TestNewFindPackages(t *testing.T) {
13 | cwd, err := typedpath.Getwd()
14 | if err != nil {
15 | panic(err.Error())
16 | }
17 |
18 | cases := []struct{
19 | RootDirAbs typedpath.RawPath
20 | Expected []*FoundPackage
21 | } {
22 | {
23 | cwd.JoinRawPath(typedpath.NewRawPath("testdata", "EmptyManifest")),
24 | []*FoundPackage{},
25 | },
26 | {
27 | cwd.JoinRawPath(typedpath.NewRawPath("testdata", "NoLocalsManifest")),
28 | []*FoundPackage{
29 | {
30 | FilePrefix: false,
31 | AbsPath: cwd.JoinRawPath(typedpath.NewRawPath("testdata", "NoLocalsManifest", "Packages", "com.example.exists")),
32 | RelPath: typedpath.NewRawPath("Packages", "com.example.exists"),
33 | },
34 | },
35 | },
36 | {
37 | cwd.JoinRawPath(typedpath.NewRawPath("testdata", "LocalsManifest")),
38 | []*FoundPackage{
39 | {
40 | FilePrefix: true,
41 | AbsPath: cwd.JoinRawPath(typedpath.NewRawPath("testdata", "LocalsManifest", "LocalPackages", "com.example.local")),
42 | RelPath: typedpath.NewRawPath("LocalPackages", "com.example.local"),
43 | },
44 | },
45 | },
46 | }
47 |
48 | for _, c := range cases {
49 | t.Run(fmt.Sprintf("%s/Packages/manifest.json -> %v", c.RootDirAbs, c.Expected), func(t *testing.T) {
50 | spyLogger := logging.SpyLogger()
51 | findPackages := NewFindPackages(spyLogger)
52 |
53 | actual, err := findPackages(c.RootDirAbs)
54 | if err != nil {
55 | t.Log(spyLogger.Logs.String())
56 | t.Errorf("want nil, got %#v", err)
57 | return
58 | }
59 |
60 | if !reflect.DeepEqual(actual, c.Expected) {
61 | t.Log(spyLogger.Logs.String())
62 | t.Error(cmp.Diff(c.Expected, actual))
63 | return
64 | }
65 | })
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/unity/testdata/EmptyManifest/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {}
3 | }
4 |
--------------------------------------------------------------------------------
/unity/testdata/LocalsManifest/LocalPackages/com.example.local/.gitempty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/unity/testdata/LocalsManifest/LocalPackages/com.example.local/.gitempty
--------------------------------------------------------------------------------
/unity/testdata/LocalsManifest/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.example.local": "file:../LocalPackages/com.example.local"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/unity/testdata/NoLocalsManifest/Packages/com.example.exists/.gitempty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeNA/unity-meta-check/d682fd262e2c7b142bc4f1882aa930acf37a7d7e/unity/testdata/NoLocalsManifest/Packages/com.example.exists/.gitempty
--------------------------------------------------------------------------------
/unity/testdata/NoLocalsManifest/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.example.exists": "1.0.0",
4 | "com.example.does.not.exist": "1.0.0"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/util/chanutil/fromslice.go:
--------------------------------------------------------------------------------
1 | package chanutil
2 |
3 | func FromSlice[T interface{}](ss []T) <-chan T {
4 | ch := make(chan T)
5 | go func() {
6 | defer close(ch)
7 | for _, str := range ss {
8 | ch <- str
9 | }
10 | }()
11 | return ch
12 | }
13 |
--------------------------------------------------------------------------------
/util/chanutil/toslice.go:
--------------------------------------------------------------------------------
1 | package chanutil
2 |
3 | func ToSlice[T interface{}](ch <-chan T) []T {
4 | result := make([]T, 0)
5 | for x := range ch {
6 | result = append(result, x)
7 | }
8 | return result
9 | }
10 |
--------------------------------------------------------------------------------
/util/cli/cmd.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | type Command func(args []string, procInout ProcessInout, env Env) ExitStatus
4 |
5 | type ExitStatus int
6 |
7 | const (
8 | // ExitNormal means exit successfully.
9 | // SEE: http://tldp.org/LDP/abs/html/exitcodes.html
10 | ExitNormal ExitStatus = 0
11 |
12 | // ExitAbnormal means exit not successfully.
13 | // SEE: http://tldp.org/LDP/abs/html/exitcodes.html
14 | ExitAbnormal ExitStatus = 1
15 | )
16 |
--------------------------------------------------------------------------------
/util/cli/env.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import "os"
4 |
5 | type Env func(key string) string
6 |
7 | func NewEnv() Env {
8 | return func(key string) string {
9 | return os.Getenv(key)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/util/cli/env_stub.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | func AnyEnv() Env {
4 | return ConstEnv("ANY_ENV")
5 | }
6 |
7 | func ConstEnv(result string) Env {
8 | return func(string) string {
9 | return result
10 | }
11 | }
12 |
13 | func StubEnv(m map[string]string) Env {
14 | return func(n string) string {
15 | return m[n]
16 | }
17 | }
--------------------------------------------------------------------------------
/util/cli/loglevel.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import "github.com/DeNA/unity-meta-check/util/logging"
4 |
5 | func GetLogLevel(debug, silent bool) logging.Severity {
6 | if debug {
7 | return logging.SeverityDebug
8 | }
9 | if silent {
10 | return logging.SeverityWarn
11 | }
12 | return logging.SeverityInfo
13 | }
14 |
--------------------------------------------------------------------------------
/util/cli/opt/flag.go:
--------------------------------------------------------------------------------
1 | package opt
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | )
7 |
8 | type (
9 | Type string
10 | Name string
11 | Desc string
12 |
13 | Option interface {
14 | Required() bool
15 | Name() Name
16 | Desc() Desc
17 | Type() Type
18 | DefaultValueText() string
19 | }
20 | )
21 |
22 | const (
23 | TypeString Type = "string"
24 | TypeBool Type = "bool"
25 | )
26 |
27 | func NewOptionalStringOption(name Name, desc Desc, defaultValue string) StringOption {
28 | return StringOption{
29 | required: false,
30 | name: name,
31 | desc: desc,
32 | DefaultValue: defaultValue,
33 | }
34 | }
35 |
36 | func NewRequiredStringOption(name Name, desc Desc) StringOption {
37 | return StringOption{
38 | required: true,
39 | name: name,
40 | desc: desc,
41 | }
42 | }
43 |
44 | type StringOption struct {
45 | required bool
46 | name Name
47 | desc Desc
48 | DefaultValue string
49 | }
50 |
51 | func (s StringOption) Name() Name {
52 | return s.name
53 | }
54 |
55 | func (s StringOption) Desc() Desc {
56 | return s.desc
57 | }
58 |
59 | func (s StringOption) Type() Type {
60 | return TypeString
61 | }
62 |
63 | func (s StringOption) Required() bool {
64 | return s.required
65 | }
66 |
67 | func (s StringOption) DefaultValueText() string {
68 | return fmt.Sprintf("%q", s.DefaultValue)
69 | }
70 |
71 | func NewOptionalBoolOption(name Name, desc Desc, defaultValue bool) BoolOption {
72 | return BoolOption{
73 | required: false,
74 | name: name,
75 | desc: desc,
76 | DefaultValue: defaultValue,
77 | }
78 | }
79 |
80 | func NewRequiredBoolOption(name Name, desc Desc) BoolOption {
81 | return BoolOption{
82 | required: true,
83 | name: name,
84 | desc: desc,
85 | }
86 | }
87 |
88 | type BoolOption struct {
89 | required bool
90 | name Name
91 | desc Desc
92 | DefaultValue bool
93 | }
94 |
95 | func (b BoolOption) Name() Name {
96 | return b.name
97 | }
98 |
99 | func (b BoolOption) Desc() Desc {
100 | return b.desc
101 | }
102 |
103 | func (b BoolOption) Type() Type {
104 | return TypeBool
105 | }
106 |
107 | func (b BoolOption) Required() bool {
108 | return b.required
109 | }
110 |
111 | func (b BoolOption) DefaultValueText() string {
112 | return fmt.Sprintf("%t", b.DefaultValue)
113 | }
114 |
115 | func Sort(flags []Option) {
116 | sort.Slice(flags, func(i, j int) bool {
117 | if flags[i].Required() != flags[i].Required() {
118 | return flags[i].Required()
119 | }
120 | return flags[i].Name() > flags[j].Name()
121 | })
122 | }
123 |
--------------------------------------------------------------------------------
/util/cli/opt/flag/flag.go:
--------------------------------------------------------------------------------
1 | package flag
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/DeNA/unity-meta-check/util/cli/opt"
7 | )
8 |
9 | func descSuffix(required bool, desc opt.Desc) string {
10 | if required {
11 | return fmt.Sprintf("%s (required)", desc)
12 | }
13 | return fmt.Sprintf("%s (optional)", desc)
14 | }
15 |
16 | func DefineString(f *flag.FlagSet, o opt.StringOption) *string {
17 | return f.String(string(o.Name()), o.DefaultValue, descSuffix(o.Required(), o.Desc()))
18 | }
19 |
20 | func DefineBool(f *flag.FlagSet, o opt.BoolOption) *bool {
21 | return f.Bool(string(o.Name()), o.DefaultValue, descSuffix(o.Required(), o.Desc()))
22 | }
23 |
--------------------------------------------------------------------------------
/util/cli/opt/flag/flag_test.go:
--------------------------------------------------------------------------------
1 | package flag
2 |
3 | import (
4 | "bytes"
5 | "flag"
6 | "github.com/DeNA/unity-meta-check/util/cli/opt"
7 | "github.com/google/go-cmp/cmp"
8 | "testing"
9 | )
10 |
11 | func TestDefineHelp(t *testing.T) {
12 | t.Run("required string option (easies case)", func(t *testing.T) {
13 | buf := &bytes.Buffer{}
14 | flags := flag.NewFlagSet("test", flag.ContinueOnError)
15 | flags.SetOutput(buf)
16 |
17 | o := opt.NewRequiredStringOption("req-str", "required string option")
18 | DefineString(flags, o)
19 |
20 | flags.PrintDefaults()
21 |
22 | expected := `
23 | -req-str string
24 | required string option (required)
25 | `[1:]
26 |
27 | if expected != buf.String() {
28 | t.Error(cmp.Diff(expected, buf.String()))
29 | }
30 | })
31 |
32 | t.Run("optional string option (easies case)", func(t *testing.T) {
33 | buf := &bytes.Buffer{}
34 | flags := flag.NewFlagSet("test", flag.ContinueOnError)
35 | flags.SetOutput(buf)
36 |
37 | o := opt.NewOptionalStringOption("opt-str", "optional string option", "DEFAULT")
38 | DefineString(flags, o)
39 |
40 | flags.PrintDefaults()
41 |
42 | expected := `
43 | -opt-str string
44 | optional string option (optional) (default "DEFAULT")
45 | `[1:]
46 |
47 | if expected != buf.String() {
48 | t.Error(cmp.Diff(expected, buf.String()))
49 | }
50 | })
51 |
52 | t.Run("required bool option (easies case)", func(t *testing.T) {
53 | buf := &bytes.Buffer{}
54 | flags := flag.NewFlagSet("test", flag.ContinueOnError)
55 | flags.SetOutput(buf)
56 |
57 | o := opt.NewRequiredBoolOption("req-bool", "required bool option")
58 | DefineBool(flags, o)
59 |
60 | flags.PrintDefaults()
61 |
62 | expected := `
63 | -req-bool
64 | required bool option (required)
65 | `[1:]
66 |
67 | if expected != buf.String() {
68 | t.Error(cmp.Diff(expected, buf.String()))
69 | }
70 | })
71 |
72 | t.Run("optional bool option (easies case)", func(t *testing.T) {
73 | buf := &bytes.Buffer{}
74 | flags := flag.NewFlagSet("test", flag.ContinueOnError)
75 | flags.SetOutput(buf)
76 |
77 | o := opt.NewOptionalBoolOption("opt-bool", "optional bool option", true)
78 | DefineBool(flags, o)
79 |
80 | flags.PrintDefaults()
81 |
82 | expected := `
83 | -opt-bool
84 | optional bool option (optional) (default true)
85 | `[1:]
86 |
87 | if expected != buf.String() {
88 | t.Error(cmp.Diff(expected, buf.String()))
89 | }
90 | })
91 | }
92 |
--------------------------------------------------------------------------------
/util/cli/opt/flag_test.go:
--------------------------------------------------------------------------------
1 | package opt
2 |
3 | var _ Option = StringOption{}
4 | var _ Option = BoolOption{}
5 |
--------------------------------------------------------------------------------
/util/cli/opt/ghactions/ghactions.go:
--------------------------------------------------------------------------------
1 | package ghactions
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/util/cli/opt"
6 | "io"
7 | )
8 |
9 | func WriteTo(w io.Writer, flags ...opt.Option) (i int64, err error) {
10 | opt.Sort(flags)
11 |
12 | for _, f := range flags {
13 | var j int
14 | if f.Required() {
15 | j, err = fmt.Fprintf(w, `
16 | %q:
17 | description: %q
18 | required: true
19 | `[1:], f.Name(), f.Desc())
20 | } else {
21 | var defVal string
22 | defVal, err = defaultValue(f)
23 | if err != nil {
24 | return
25 | }
26 |
27 | j, err = fmt.Fprintf(w, `
28 | %q:
29 | description: %q
30 | required: false
31 | default: %s
32 | `[1:], f.Name(), f.Desc(), defVal)
33 | }
34 |
35 | i += int64(j)
36 | if err != nil {
37 | return
38 | }
39 | }
40 |
41 | return
42 | }
43 |
44 | func defaultValue(f opt.Option) (string, error) {
45 | switch f.(type) {
46 | case opt.StringOption:
47 | return fmt.Sprintf("%q", f.(opt.StringOption).DefaultValue), nil
48 | case opt.BoolOption:
49 | return fmt.Sprintf("%v", f.(opt.BoolOption).DefaultValue), nil
50 | default:
51 | return "", fmt.Errorf("unknown opt.Option type: %#v", f)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/util/cli/opt/ghactions/ghactions_test.go:
--------------------------------------------------------------------------------
1 | package ghactions
2 |
3 | import (
4 | "bytes"
5 | "github.com/DeNA/unity-meta-check/util/cli/opt"
6 | "github.com/google/go-cmp/cmp"
7 | "testing"
8 | )
9 |
10 | func TestWriteTo(t *testing.T) {
11 | cases := map[string]struct {
12 | Options []opt.Option
13 | Expected string
14 | }{
15 | "empty (boundary)": {
16 | Options: []opt.Option{},
17 | Expected: ``,
18 | },
19 | "required string option (easiest case)": {
20 | Options: []opt.Option{
21 | opt.NewRequiredStringOption("req-str", "required string option"),
22 | },
23 | Expected: `
24 | "req-str":
25 | description: "required string option"
26 | required: true
27 | `[1:],
28 | },
29 | "optional string option (easiest case)": {
30 | Options: []opt.Option{
31 | opt.NewOptionalStringOption("opt-str", "optional string option", "DEFAULT"),
32 | },
33 | Expected: `
34 | "opt-str":
35 | description: "optional string option"
36 | required: false
37 | default: "DEFAULT"
38 | `[1:],
39 | },
40 | "required bool option (easiest case)": {
41 | Options: []opt.Option{
42 | opt.NewRequiredBoolOption("req-bool", "required bool option"),
43 | },
44 | Expected: `
45 | "req-bool":
46 | description: "required bool option"
47 | required: true
48 | `[1:],
49 | },
50 | "optional bool option (easiest case)": {
51 | Options: []opt.Option{
52 | opt.NewOptionalBoolOption("opt-bool", "optional bool option", true),
53 | },
54 | Expected: `
55 | "opt-bool":
56 | description: "optional bool option"
57 | required: false
58 | default: true
59 | `[1:],
60 | },
61 | }
62 |
63 | for desc, c := range cases {
64 | t.Run(desc, func(t *testing.T) {
65 | buf := &bytes.Buffer{}
66 |
67 | _, err := WriteTo(buf, c.Options...)
68 | if err != nil {
69 | t.Errorf("want nil, got %#v", err)
70 | return
71 | }
72 |
73 | if buf.String() != c.Expected {
74 | t.Error(cmp.Diff(c.Expected, buf.String()))
75 | }
76 | })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/util/cli/opt/markdown/markdown.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/util/cli/opt"
6 | "io"
7 | )
8 |
9 | func WriteTo(w io.Writer, opts ...opt.Option) (i int64, err error) {
10 | opt.Sort(opts)
11 |
12 | j, err := fmt.Fprintln(w, `
13 | | Option | Description | Required or Default Value |
14 | |:-------|:------------|:--------------------------|`[1:])
15 | i += int64(j)
16 | if err != nil {
17 | return
18 | }
19 |
20 | for _, o := range opts {
21 | var required string
22 | if o.Required() {
23 | required = "required"
24 | } else {
25 | required = fmt.Sprintf("optional (default: `%s`)", o.DefaultValueText())
26 | }
27 |
28 | var oText string
29 | oText, err = optionText(o)
30 | if err != nil {
31 | return
32 | }
33 |
34 | var j int
35 | j, err = fmt.Fprintf(w, "| `%s` | %s | %s |\n", oText, o.Desc(), required)
36 | i += int64(j)
37 | if err != nil {
38 | return
39 | }
40 | }
41 |
42 | return
43 | }
44 |
45 | func optionText(o opt.Option) (string, error) {
46 | switch o.(type) {
47 | case opt.StringOption:
48 | return fmt.Sprintf("--%s ", o.Name()), nil
49 | case opt.BoolOption:
50 | return fmt.Sprintf("--%s", o.Name()), nil
51 | default:
52 | return "", fmt.Errorf("unknown option type: %#v", o)
53 | }
54 | }
--------------------------------------------------------------------------------
/util/cli/opt/markdown/markdown_test.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "bytes"
5 | "github.com/DeNA/unity-meta-check/util/cli/opt"
6 | "github.com/google/go-cmp/cmp"
7 | "strings"
8 | "testing"
9 | )
10 |
11 | func TestWriteTo(t *testing.T) {
12 | cases := map[string]struct {
13 | Options []opt.Option
14 | Expected string
15 | }{
16 | "empty (boundary case)": {
17 | Options: []opt.Option{},
18 | Expected: strings.Join([]string{
19 | "| Option | Description | Required or Default Value |\n",
20 | "|:-------|:------------|:--------------------------|\n",
21 | }, ""),
22 | },
23 | "optional string option (easiest case)": {
24 | Options: []opt.Option{
25 | opt.NewOptionalStringOption("opt-str", "optional string option", "DEFAULT"),
26 | },
27 | Expected: strings.Join([]string{
28 | "| Option | Description | Required or Default Value |\n",
29 | "|:-------|:------------|:--------------------------|\n",
30 | "| `--opt-str ` | optional string option | optional (default: `\"DEFAULT\"`) |\n",
31 | }, ""),
32 | },
33 | "required string option (easiest case)": {
34 | Options: []opt.Option{
35 | opt.NewRequiredStringOption("req-str", "required string option"),
36 | },
37 | Expected: strings.Join([]string{
38 | "| Option | Description | Required or Default Value |\n",
39 | "|:-------|:------------|:--------------------------|\n",
40 | "| `--req-str ` | required string option | required |\n",
41 | }, ""),
42 | },
43 | "optional bool option (easiest case)": {
44 | Options: []opt.Option{
45 | opt.NewOptionalBoolOption("opt-bool", "optional bool option", true),
46 | },
47 | Expected: strings.Join([]string{
48 | "| Option | Description | Required or Default Value |\n",
49 | "|:-------|:------------|:--------------------------|\n",
50 | "| `--opt-bool` | optional bool option | optional (default: `true`) |\n",
51 | }, ""),
52 | },
53 | "required bool option (easiest case)": {
54 | Options: []opt.Option{
55 | opt.NewRequiredBoolOption("req-bool", "required bool option"),
56 | },
57 | Expected: strings.Join([]string{
58 | "| Option | Description | Required or Default Value |\n",
59 | "|:-------|:------------|:--------------------------|\n",
60 | "| `--req-bool` | required bool option | required |\n",
61 | }, ""),
62 | },
63 | }
64 |
65 | for desc, c := range cases {
66 | t.Run(desc, func(t *testing.T) {
67 | buf := &bytes.Buffer{}
68 |
69 | _, err := WriteTo(buf, c.Options...)
70 | if err != nil {
71 | t.Errorf("want nil, got %#v", err)
72 | return
73 | }
74 |
75 | if buf.String() != c.Expected {
76 | t.Error(cmp.Diff(c.Expected, buf.String()))
77 | }
78 | })
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/util/cli/processinout.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "io"
5 | "os"
6 | )
7 |
8 | type ProcessInout struct {
9 | Stdin io.Reader
10 | Stdout io.WriteCloser
11 | Stderr io.WriteCloser
12 | }
13 |
14 | func GetProcessInout() ProcessInout {
15 | return ProcessInout{
16 | Stdin: os.Stdin,
17 | Stdout: os.Stdout,
18 | Stderr: os.Stderr,
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/util/cli/processinout_stub.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/testutil"
5 | )
6 |
7 | func AnyProcInout() ProcessInout {
8 | return ProcessInout{
9 | Stdin: &testutil.ErrorReadCloserStub{},
10 | Stdout: &testutil.NullWriteCloser{},
11 | Stderr: &testutil.NullWriteCloser{},
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/util/cli/prof.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "path/filepath"
8 | "runtime/pprof"
9 | )
10 |
11 | func NewCommandWithCPUProfile(cmd Command) Command {
12 | profFile, err := newProfFile()
13 | if err != nil {
14 | panic(err.Error())
15 | }
16 |
17 | return func(args []string, procInout ProcessInout, env Env) ExitStatus {
18 | if err := pprof.StartCPUProfile(profFile); err != nil {
19 | panic(err.Error())
20 | }
21 |
22 | exitStatus := cmd(args, procInout, env)
23 |
24 | pprof.StopCPUProfile()
25 | _ = profFile.Close()
26 | return exitStatus
27 | }
28 | }
29 |
30 | func NewCommandWithHeapProfile(cmd Command) Command {
31 | profFile, err := newProfFile()
32 | if err != nil {
33 | panic(err.Error())
34 | }
35 |
36 | return func(args []string, procInout ProcessInout, env Env) ExitStatus {
37 | exitStatus := cmd(args, procInout, env)
38 |
39 | if err := pprof.Lookup("heap").WriteTo(profFile, 0); err != nil {
40 | panic(err.Error())
41 | }
42 | _ = profFile.Close()
43 | return exitStatus
44 | }
45 | }
46 |
47 | func newProfFile() (io.WriteCloser, error) {
48 | tmpDir, err := os.MkdirTemp(os.TempDir(), "")
49 | if err != nil {
50 | return nil, err
51 | }
52 |
53 | profPath := filepath.Join(tmpDir, "unity-meta-check.prof")
54 | fmt.Printf("profile path: %s\n", profPath)
55 |
56 | profFile, err := os.OpenFile(profPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
57 | if err != nil {
58 | return nil, err
59 | }
60 | return profFile, nil
61 | }
62 |
--------------------------------------------------------------------------------
/util/cstrset/cistrset.go:
--------------------------------------------------------------------------------
1 | package cstrset
2 |
3 | import (
4 | "github.com/scylladb/go-set/strset"
5 | "strings"
6 | )
7 |
8 | type CaseInsensitiveSet map[string]string
9 |
10 | var _ Set = &CaseInsensitiveSet{}
11 |
12 | func NewCaseInsensitive(items... string) *CaseInsensitiveSet {
13 | s := CaseInsensitiveSet(make(map[string]string, len(items)))
14 | for _, item := range items {
15 | s.Add(item)
16 | }
17 | return &s
18 | }
19 |
20 | func NewCaseInsensitiveWithSize(size int) *CaseInsensitiveSet {
21 | s := CaseInsensitiveSet(make(map[string]string, size))
22 | return &s
23 | }
24 |
25 | func (s CaseInsensitiveSet) Has(e string) bool {
26 | _, ok := s[strings.ToLower(e)]
27 | return ok
28 | }
29 |
30 | func (s *CaseInsensitiveSet) Add(e string) {
31 | (*s)[strings.ToLower(e)] = e
32 | }
33 |
34 | func (s *CaseInsensitiveSet) Difference(other Set) *strset.Set {
35 | return difference(s, other.(*CaseInsensitiveSet))
36 | }
37 |
38 | func (s CaseInsensitiveSet) Len() int {
39 | return len(s)
40 | }
41 |
42 | func difference(a, b *CaseInsensitiveSet) *strset.Set {
43 | values := strset.NewWithSize(len(*a))
44 | strset.Difference(a.keys(), b.keys()).Each(func(item string) bool {
45 | values.Add((*a)[item])
46 | return true
47 | })
48 | return values
49 | }
50 |
51 | func (s *CaseInsensitiveSet) keys() *strset.Set {
52 | result := strset.NewWithSize(len(*s))
53 | for key := range *s {
54 | result.Add(key)
55 | }
56 | return result
57 | }
58 |
--------------------------------------------------------------------------------
/util/cstrset/csstrset.go:
--------------------------------------------------------------------------------
1 | package cstrset
2 |
3 | import "github.com/scylladb/go-set/strset"
4 |
5 | type CaseSensitiveSet struct {
6 | s *strset.Set
7 | }
8 |
9 | var _ Set = &CaseSensitiveSet{}
10 |
11 | func NewCaseSensitive(items... string) *CaseSensitiveSet {
12 | return &CaseSensitiveSet{strset.New(items...)}
13 | }
14 |
15 | func NewCaseSensitiveWithSize(size int) *CaseSensitiveSet {
16 | return &CaseSensitiveSet{strset.NewWithSize(size)}
17 | }
18 |
19 | func (s *CaseSensitiveSet) Has(e string) bool {
20 | return s.s.Has(e)
21 | }
22 |
23 | func (s *CaseSensitiveSet) Add(e string) {
24 | s.s.Add(e)
25 | }
26 |
27 | func (s *CaseSensitiveSet) Difference(other Set) *strset.Set {
28 | return strset.Difference(s.s, other.(*CaseSensitiveSet).s)
29 | }
30 |
31 | func (s CaseSensitiveSet) Len() int {
32 | return s.s.Size()
33 | }
34 |
--------------------------------------------------------------------------------
/util/cstrset/cstrset.go:
--------------------------------------------------------------------------------
1 | package cstrset
2 |
3 | import "github.com/scylladb/go-set/strset"
4 |
5 | type Set interface {
6 | Add(string)
7 | Has(string) bool
8 | Difference(Set) *strset.Set
9 | Len() int
10 | }
11 |
--------------------------------------------------------------------------------
/util/errutil/errors.go:
--------------------------------------------------------------------------------
1 | package errutil
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | )
7 |
8 | type Errors []error
9 |
10 | func NewErrors(errs []error) error {
11 | if len(errs) == 0 {
12 | panic("empty errors must be not an error")
13 | }
14 | return Errors(errs)
15 | }
16 |
17 | var _ error = Errors{}
18 |
19 | func (s Errors) Error() string {
20 | buf := &bytes.Buffer{}
21 | for _, e := range s {
22 | _, _ = fmt.Fprintln(buf, e.Error())
23 | }
24 | return buf.String()
25 | }
26 |
--------------------------------------------------------------------------------
/util/globs/glob.go:
--------------------------------------------------------------------------------
1 | package globs
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/pathutil"
5 | "github.com/DeNA/unity-meta-check/util/typedpath"
6 | "path"
7 | )
8 |
9 | type Glob string
10 |
11 | func (g Glob) ToSlash() typedpath.SlashPath {
12 | return typedpath.NewSlashPathUnsafe(string(g))
13 | }
14 |
15 | func MatchAny(p typedpath.SlashPath, globs []Glob, cwd typedpath.SlashPath) (bool, Glob, error) {
16 | pAbs := joinCwdIfRel(cwd, p)
17 | ancestors := pathutil.AllAncestorsAndSelf(pAbs)
18 | for _, ancestor := range ancestors {
19 | for _, relGlob := range globs {
20 | glob := joinCwdIfRel(cwd, relGlob.ToSlash())
21 | ok, err := path.Match(string(glob), string(ancestor))
22 | if err != nil {
23 | return false, "", err
24 | }
25 | if ok {
26 | return true, relGlob, nil
27 | }
28 | }
29 | }
30 | return false, "", nil
31 | }
32 |
33 | func joinCwdIfRel(cwd typedpath.SlashPath, path typedpath.SlashPath) typedpath.SlashPath {
34 | if path.IsAbs() {
35 | return path
36 | }
37 | return cwd.JoinSlashPath(path)
38 | }
--------------------------------------------------------------------------------
/util/globs/glob_test.go:
--------------------------------------------------------------------------------
1 | package globs
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "testing"
6 | )
7 |
8 | func TestMatchAny(t *testing.T) {
9 | cases := map[string]struct {
10 | Path typedpath.SlashPath
11 | Globs []Glob
12 | Cwd typedpath.SlashPath
13 | Expected bool
14 | }{
15 | "empty globs (boundary case)": {
16 | Path: "path/to/file",
17 | Cwd: "/cwd",
18 | Globs: []Glob{},
19 | Expected: false,
20 | },
21 | "easiest case": {
22 | Path: "path/to/file",
23 | Cwd: "/cwd",
24 | Globs: []Glob{
25 | "path",
26 | },
27 | Expected: true,
28 | },
29 | "asterisk pattern": {
30 | Path: "path/to/file",
31 | Cwd: "/cwd",
32 | Globs: []Glob{
33 | "path/*",
34 | },
35 | Expected: true,
36 | },
37 | "only dot (edge case)": {
38 | Path: "path/to/file",
39 | Cwd: "/cwd",
40 | Globs: []Glob{
41 | ".",
42 | },
43 | Expected: true,
44 | },
45 | "only asterisk (edge case)": {
46 | Path: "path/to/file",
47 | Cwd: "/cwd",
48 | Globs: []Glob{
49 | "*",
50 | },
51 | Expected: true,
52 | },
53 | "dot asterisk (edge case)": {
54 | Path: "path/to/file",
55 | Cwd: "/cwd",
56 | Globs: []Glob{
57 | "./*",
58 | },
59 | Expected: true,
60 | },
61 | "empty glob (edge case)": {
62 | Path: "path/to/file",
63 | Cwd: "/cwd",
64 | Globs: []Glob{
65 | "",
66 | },
67 | Expected: true,
68 | },
69 | "only asterisk not match absolute path because the glob based on relative": {
70 | Path: "/path/to/file",
71 | Cwd: "/cwd",
72 | Globs: []Glob{
73 | "*",
74 | },
75 | Expected: false,
76 | },
77 | "empty glob not match absolute path because the glob based on relative": {
78 | Path: "/path/to/file",
79 | Cwd: "/cwd",
80 | Globs: []Glob{
81 | "",
82 | },
83 | Expected: false,
84 | },
85 | "only dot not match absolute path": {
86 | Path: "/path/to/file",
87 | Cwd: "/cwd",
88 | Globs: []Glob{
89 | ".",
90 | },
91 | Expected: false,
92 | },
93 | "slash asterisk match absolute path": {
94 | Path: "/path/to/file",
95 | Cwd: "/cwd",
96 | Globs: []Glob{
97 | "/*",
98 | },
99 | Expected: true,
100 | },
101 | }
102 |
103 | for desc, c := range cases {
104 | t.Run(desc, func(t *testing.T) {
105 | actual, _, err := MatchAny(c.Path, c.Globs, c.Cwd)
106 | if err != nil {
107 | t.Errorf("want nil, got %#v", err)
108 | return
109 | }
110 |
111 | if actual != c.Expected {
112 | t.Errorf("want %t, got %t", c.Expected, actual)
113 | return
114 | }
115 | })
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/util/logging/logger.go:
--------------------------------------------------------------------------------
1 | package logging
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 | )
8 |
9 | type Severity int
10 |
11 | const (
12 | SeverityDebug Severity = iota
13 | SeverityInfo Severity = iota
14 | SeverityWarn Severity = iota
15 | SeverityError Severity = iota
16 | )
17 |
18 | func (s Severity) String() string {
19 | switch s {
20 | case SeverityDebug:
21 | return "DEBUG"
22 | case SeverityInfo:
23 | return "INFO"
24 | case SeverityWarn:
25 | return "WARN"
26 | case SeverityError:
27 | return "ERROR"
28 | default:
29 | panic("unreachable")
30 | }
31 | }
32 |
33 | func ParseSeverity(s string) (Severity, error) {
34 | switch s {
35 | case "DEBUG":
36 | return SeverityDebug, nil
37 | case "INFO":
38 | return SeverityInfo, nil
39 | case "WARN":
40 | return SeverityWarn, nil
41 | case "ERROR":
42 | return SeverityError, nil
43 | default:
44 | return 0, fmt.Errorf("unknown severity: %q", s)
45 | }
46 | }
47 |
48 | // MustParseSeverity return the severity if given "DEBUG"/"INFO"/"WARN"/"ERROR". otherwise return "DEBUG" to fallback.
49 | func MustParseSeverity(s string) Severity {
50 | v, err := ParseSeverity(s)
51 | if err != nil {
52 | // NOTE: Fallback
53 | return SeverityDebug
54 | }
55 | return v
56 | }
57 |
58 | type Logger interface {
59 | Debug(string)
60 | Info(string)
61 | Warn(string)
62 | Error(string)
63 | Log(Severity, string)
64 | }
65 |
66 | func NewLogger(severity Severity, writer io.Writer) Logger {
67 | return &severityLogger{
68 | severity: severity,
69 | writer: writer,
70 | }
71 | }
72 |
73 | type severityLogger struct {
74 | severity Severity
75 | writer io.Writer
76 | }
77 |
78 | func (logger *severityLogger) Debug(message string) {
79 | logger.Log(SeverityDebug, message)
80 | }
81 |
82 | func (logger *severityLogger) Info(message string) {
83 | logger.Log(SeverityInfo, message)
84 | }
85 |
86 | func (logger *severityLogger) Warn(message string) {
87 | logger.Log(SeverityWarn, message)
88 | }
89 |
90 | func (logger *severityLogger) Error(message string) {
91 | logger.Log(SeverityError, message)
92 | }
93 |
94 | func (logger *severityLogger) Log(severity Severity, message string) {
95 | if logger.severity <= severity {
96 | _, _ = fmt.Fprintf(logger.writer, "%s: %s\n", strings.ToLower(severity.String()), message)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/util/logging/logger_stub.go:
--------------------------------------------------------------------------------
1 | package logging
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | )
7 |
8 | func SpyLogger() *LoggerSpy {
9 | return &LoggerSpy{
10 | Logs: &bytes.Buffer{},
11 | }
12 | }
13 |
14 | type LoggerSpy struct {
15 | Logs *bytes.Buffer
16 | }
17 |
18 | var _ Logger = &LoggerSpy{}
19 |
20 | func (s *LoggerSpy) Debug(message string) {
21 | s.Log(SeverityDebug, message)
22 | }
23 |
24 | func (s *LoggerSpy) Info(message string) {
25 | s.Log(SeverityInfo, message)
26 | }
27 |
28 | func (s *LoggerSpy) Warn(message string) {
29 | s.Log(SeverityWarn, message)
30 | }
31 |
32 | func (s *LoggerSpy) Error(message string) {
33 | s.Log(SeverityError, message)
34 | }
35 |
36 | func (s *LoggerSpy) Log(severity Severity, message string) {
37 | s.Logs.WriteString(fmt.Sprintf("%s: %s\n", severity.String(), message))
38 | }
39 |
--------------------------------------------------------------------------------
/util/ostestable/getwd.go:
--------------------------------------------------------------------------------
1 | package ostestable
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | type Getwd func() (typedpath.RawPath, error)
8 |
9 | func NewGetwd() Getwd {
10 | return typedpath.Getwd
11 | }
12 |
--------------------------------------------------------------------------------
/util/ostestable/getwd_stub.go:
--------------------------------------------------------------------------------
1 | package ostestable
2 |
3 | import "github.com/DeNA/unity-meta-check/util/typedpath"
4 |
5 | //goland:noinspection GoUnusedExportedFunction
6 | func StubGetwd(cwd typedpath.RawPath, err error) Getwd {
7 | return func() (typedpath.RawPath, error) {
8 | return cwd, err
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/util/ostestable/isdir.go:
--------------------------------------------------------------------------------
1 | package ostestable
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "os"
6 | )
7 |
8 | type IsDir func(path typedpath.RawPath) (bool, error)
9 |
10 | func NewIsDir() IsDir {
11 | return func(path typedpath.RawPath) (bool, error) {
12 | stat, err := os.Stat(string(path))
13 | if err != nil {
14 | return false, err
15 | }
16 | return stat.IsDir(), nil
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/util/ostestable/isdir_stub.go:
--------------------------------------------------------------------------------
1 | package ostestable
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | )
6 |
7 | func StubIsDir(isDir bool, err error) IsDir {
8 | return func(typedpath.RawPath) (bool, error) {
9 | return isDir, err
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/util/pathutil/ancestors.go:
--------------------------------------------------------------------------------
1 | package pathutil
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "path"
6 | "strings"
7 | )
8 |
9 | func AllAncestorsAndSelf(targetPath typedpath.SlashPath) []typedpath.SlashPath {
10 | result := make([]typedpath.SlashPath, 0)
11 |
12 | current := path.Clean(strings.TrimRight(string(targetPath), "/"))
13 | for current != "" && current != "." {
14 | result = append(result, typedpath.SlashPath(current))
15 | if current == "/" {
16 | break
17 | }
18 | current = path.Dir(current)
19 | }
20 |
21 | return result
22 | }
23 |
--------------------------------------------------------------------------------
/util/pathutil/ancestors_test.go:
--------------------------------------------------------------------------------
1 | package pathutil
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "github.com/google/go-cmp/cmp"
6 | "reflect"
7 | "sort"
8 | "testing"
9 | )
10 |
11 | func TestAllAncestorsAndSelf(t *testing.T) {
12 | cases := map[string]struct{
13 | Path typedpath.SlashPath
14 | Expected []typedpath.SlashPath
15 | } {
16 | "empty (boundary case)": {
17 | Path: "",
18 | Expected: []typedpath.SlashPath{},
19 | },
20 | "only dot (boundary case)": {
21 | Path: ".",
22 | Expected: []typedpath.SlashPath{
23 | },
24 | },
25 | "single path (easiest case)": {
26 | Path: "path",
27 | Expected: []typedpath.SlashPath{
28 | "path",
29 | },
30 | },
31 | "trailing slash (edge case)": {
32 | Path: "path/",
33 | Expected: []typedpath.SlashPath{
34 | "path",
35 | },
36 | },
37 | "relative path (normal case)": {
38 | Path: "path/to/file",
39 | Expected: []typedpath.SlashPath{
40 | "path",
41 | "path/to",
42 | "path/to/file",
43 | },
44 | },
45 | "dot-prefix path (edge case)": {
46 | Path: "./path/to/file",
47 | Expected: []typedpath.SlashPath{
48 | "path",
49 | "path/to",
50 | "path/to/file",
51 | },
52 | },
53 | "dot-dot-prefix path (edge case)": {
54 | Path: "../path/to/file",
55 | Expected: []typedpath.SlashPath{
56 | "..",
57 | "../path",
58 | "../path/to",
59 | "../path/to/file",
60 | },
61 | },
62 | "absolute path (edge case)": {
63 | Path: "/path/to/file",
64 | Expected: []typedpath.SlashPath{
65 | "/",
66 | "/path",
67 | "/path/to",
68 | "/path/to/file",
69 | },
70 | },
71 | }
72 |
73 | for desc, c := range cases {
74 | t.Run(desc, func(t *testing.T) {
75 | actual := AllAncestorsAndSelf(c.Path)
76 |
77 | sort.Slice(actual, func(i, j int) bool {
78 | return actual[i] < actual[j]
79 | })
80 |
81 | if !reflect.DeepEqual(actual, c.Expected) {
82 | t.Error(cmp.Diff(c.Expected, actual))
83 | }
84 | })
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/util/pathutil/set.go:
--------------------------------------------------------------------------------
1 | package pathutil
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/cstrset"
5 | "github.com/DeNA/unity-meta-check/util/typedpath"
6 | )
7 |
8 | type PathSet struct {
9 | set cstrset.Set
10 | }
11 |
12 | func NewPathSet(ignoreCase bool, ss ...typedpath.SlashPath) *PathSet {
13 | items := make([]string, len(ss))
14 | for i, s := range ss {
15 | items[i] = string(s)
16 | }
17 | if ignoreCase {
18 | return &PathSet{cstrset.NewCaseInsensitive(items...)}
19 | }
20 | return &PathSet{cstrset.NewCaseSensitive(items...)}
21 | }
22 |
23 | func NewPathSetWithSize(ignoreCase bool, size int) *PathSet {
24 | if ignoreCase {
25 | return &PathSet{cstrset.NewCaseInsensitiveWithSize(size)}
26 | }
27 | return &PathSet{cstrset.NewCaseSensitiveWithSize(size)}
28 | }
29 |
30 | func (s *PathSet) Add(path typedpath.SlashPath) {
31 | s.set.Add(string(path))
32 | }
33 |
34 | func (s *PathSet) Has(path typedpath.SlashPath) bool {
35 | return s.set.Has(string(path))
36 | }
37 |
38 | func (s *PathSet) Difference(other *PathSet) []typedpath.SlashPath {
39 | diff := s.set.Difference(other.set)
40 | result := make([]typedpath.SlashPath, diff.Size())
41 | i := 0
42 | diff.Each(func(item string) bool {
43 | result[i] = typedpath.SlashPath(item)
44 | i++
45 | return true
46 | })
47 | return result
48 | }
49 |
50 | func (s *PathSet) Len() int {
51 | return s.set.Len()
52 | }
53 |
--------------------------------------------------------------------------------
/util/pathutil/split.go:
--------------------------------------------------------------------------------
1 | package pathutil
2 |
3 | import (
4 | "github.com/DeNA/unity-meta-check/util/typedpath"
5 | "path"
6 | "strings"
7 | )
8 |
9 | func SplitPathElements(targetPath typedpath.SlashPath) []typedpath.BaseName {
10 | result := make([]typedpath.BaseName, 0)
11 | dir := targetPath
12 | for dir != "" {
13 | newDir, file := path.Split(strings.TrimRight(string(dir), "/"))
14 | result = append(result, typedpath.BaseName(file))
15 | dir = typedpath.SlashPath(newDir)
16 | }
17 | reverse(result)
18 | return result
19 | }
20 |
21 | func reverse(baseNames []typedpath.BaseName) {
22 | for i := 0; i < len(baseNames)/2; i++ {
23 | j := len(baseNames) - i - 1
24 | baseNames[i], baseNames[j] = baseNames[j], baseNames[i]
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/util/pathutil/split_test.go:
--------------------------------------------------------------------------------
1 | package pathutil
2 |
3 | import (
4 | "fmt"
5 | "github.com/DeNA/unity-meta-check/util/typedpath"
6 | "github.com/google/go-cmp/cmp"
7 | "reflect"
8 | "testing"
9 | )
10 |
11 | func TestSplitPathComponents(t *testing.T) {
12 | cases := []struct{
13 | path typedpath.SlashPath
14 | expected []typedpath.BaseName
15 | }{
16 | { "", []typedpath.BaseName{} },
17 | { "Foo", []typedpath.BaseName{"Foo"} },
18 | { "Foo/", []typedpath.BaseName{"Foo"} },
19 | { "Foo/Bar", []typedpath.BaseName{"Foo", "Bar"} },
20 | { "Foo/Bar/Baz", []typedpath.BaseName{"Foo", "Bar", "Baz"} },
21 | }
22 |
23 | for _, c := range cases {
24 | t.Run(fmt.Sprintf("%q", c.path), func(t *testing.T) {
25 | actual := SplitPathElements(c.path)
26 |
27 | if !reflect.DeepEqual(actual, c.expected) {
28 | t.Errorf(cmp.Diff(c.expected, actual))
29 | return
30 | }
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/util/prefix/writer.go:
--------------------------------------------------------------------------------
1 | package prefix
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | )
7 |
8 | type Writer struct {
9 | prefix string
10 | base io.Writer
11 | buf *bytes.Buffer
12 | firstDone bool
13 | }
14 |
15 | func NewWriter(prefix string, base io.Writer) io.Writer {
16 | return &Writer{prefix, base, &bytes.Buffer{}, false}
17 | }
18 |
19 | func (w *Writer) Write(bs []byte) (int, error) {
20 | w.buf.Reset()
21 |
22 | if !w.firstDone {
23 | w.buf.WriteString(w.prefix)
24 | w.firstDone = true
25 | }
26 |
27 | for _, b := range bs {
28 | if b == '\n' {
29 | _ = w.buf.WriteByte(b)
30 | w.buf.WriteString(w.prefix)
31 | } else {
32 | w.buf.WriteByte(b)
33 | }
34 | }
35 |
36 | n, err := w.buf.WriteTo(w.base)
37 | return int(n), err
38 | }
39 |
--------------------------------------------------------------------------------
/util/prefix/writer_test.go:
--------------------------------------------------------------------------------
1 | package prefix
2 |
3 | import (
4 | "bytes"
5 | "github.com/google/go-cmp/cmp"
6 | "io"
7 | "testing"
8 | )
9 |
10 | func TestWriterEmpty(t *testing.T) {
11 | buf := &bytes.Buffer{}
12 |
13 | writer := NewWriter("PREFIX:", buf)
14 |
15 | if _, err := io.WriteString(writer, ""); err != nil {
16 | t.Errorf("want nil, got %#v", err)
17 | return
18 | }
19 |
20 | actual := buf.String()
21 | expected := "PREFIX:"
22 | if actual != expected {
23 | t.Error(cmp.Diff(expected, actual))
24 | return
25 | }
26 | }
27 |
28 | func TestWriterFirstLine(t *testing.T) {
29 | buf := &bytes.Buffer{}
30 |
31 | writer := NewWriter("PREFIX:", buf)
32 |
33 | if _, err := io.WriteString(writer, "LINE1"); err != nil {
34 | t.Errorf("want nil, got %#v", err)
35 | return
36 | }
37 |
38 | actual := buf.String()
39 | expected := "PREFIX:LINE1"
40 | if actual != expected {
41 | t.Error(cmp.Diff(expected, actual))
42 | return
43 | }
44 | }
45 |
46 | func TestWriterFirstLineEnd(t *testing.T) {
47 | buf := &bytes.Buffer{}
48 |
49 | writer := NewWriter("PREFIX:", buf)
50 |
51 | if _, err := io.WriteString(writer, "LINE1\n"); err != nil {
52 | t.Errorf("want nil, got %#v", err)
53 | return
54 | }
55 |
56 | actual := buf.String()
57 | expected := "PREFIX:LINE1\nPREFIX:"
58 | if actual != expected {
59 | t.Error(cmp.Diff(expected, actual))
60 | return
61 | }
62 | }
63 |
64 | func TestWriterFirstLine2(t *testing.T) {
65 | buf := &bytes.Buffer{}
66 |
67 | writer := NewWriter("PREFIX:", buf)
68 |
69 | if _, err := io.WriteString(writer, "LINE1\nLINE2"); err != nil {
70 | t.Errorf("want nil, got %#v", err)
71 | return
72 | }
73 |
74 | actual := buf.String()
75 | expected := "PREFIX:LINE1\nPREFIX:LINE2"
76 | if actual != expected {
77 | t.Error(cmp.Diff(expected, actual))
78 | return
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/util/testutil/env.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "strings"
7 | )
8 |
9 | type TestEnv struct {
10 | ApiEndpoint string
11 | Owner string
12 | Repo string
13 | Pull string
14 | Token string
15 | }
16 |
17 | type TestEnvError []string
18 |
19 | func (t TestEnvError) Error() string {
20 | return strings.Join(t, "\n")
21 | }
22 |
23 | func GetTestEnv() (*TestEnv, error) {
24 | err := TestEnvError{}
25 |
26 | apiEndpoint := os.Getenv("UNITY_META_CHECK_GITHUB_API_ENDPOINT")
27 | if apiEndpoint == "" {
28 | err = append(err, "missing UNITY_META_CHECK_GITHUB_API_ENDPOINT")
29 | }
30 |
31 | owner := os.Getenv("UNITY_META_CHECK_GITHUB_OWNER")
32 | if owner == "" {
33 | err = append(err, "missing UNITY_META_CHECK_GITHUB_OWNER")
34 | }
35 |
36 | repo := os.Getenv("UNITY_META_CHECK_GITHUB_REPO")
37 | if repo == "" {
38 | err = append(err, "missing UNITY_META_CHECK_GITHUB_REPO")
39 | }
40 |
41 | pull := os.Getenv("UNITY_META_CHECK_GITHUB_PULL_NUMBER")
42 | if pull == "" {
43 | err = append(err, "missing UNITY_META_CHECK_GITHUB_PULL_NUMBER")
44 | }
45 |
46 | token := os.Getenv("UNITY_META_CHECK_GITHUB_TOKEN")
47 | if token == "" {
48 | return nil, errors.New("missing UNITY_META_CHECK_GITHUB_TOKEN")
49 | }
50 |
51 | return &TestEnv{
52 | apiEndpoint,
53 | owner,
54 | repo,
55 | pull,
56 | token,
57 | }, nil
58 | }
59 |
--------------------------------------------------------------------------------
/util/testutil/readcloser_stub.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | type ErrorReadCloserStub struct{}
8 |
9 | func (*ErrorReadCloserStub) Close() error {
10 | return errors.New("close: EXPECTED_FAILURE")
11 | }
12 |
13 | func (*ErrorReadCloserStub) Read(_ []byte) (n int, err error) {
14 | return 0, errors.New("read: EXPECTED_FAILURE")
15 | }
16 |
--------------------------------------------------------------------------------
/util/testutil/writecloser_spy.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | )
7 |
8 | func SpyWriteCloser(base io.WriteCloser) *WriteCloserSpy {
9 | var inherited io.WriteCloser
10 | if base != nil {
11 | inherited = base
12 | } else {
13 | inherited = &NullWriteCloser{}
14 | }
15 |
16 | captured := &bytes.Buffer{}
17 |
18 | return &WriteCloserSpy{
19 | Inherited: inherited,
20 | Captured: captured,
21 | IsClosed: false,
22 | }
23 | }
24 |
25 | type WriteCloserSpy struct {
26 | Inherited io.WriteCloser
27 | Captured *bytes.Buffer
28 | IsClosed bool
29 | }
30 |
31 | func (s *WriteCloserSpy) Write(p []byte) (n int, err error) {
32 | s.Captured.Write(p)
33 | return s.Inherited.Write(p)
34 | }
35 |
36 | func (s *WriteCloserSpy) Close() error {
37 | s.IsClosed = true
38 | return s.Inherited.Close()
39 | }
40 |
--------------------------------------------------------------------------------
/util/testutil/writecloser_stub.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "io"
5 | )
6 |
7 | type WriteCloserStub struct {
8 | WriteErr error
9 | CloseErr error
10 | }
11 |
12 | func (w WriteCloserStub) Write(p []byte) (n int, err error) {
13 | if w.WriteErr == nil {
14 | return len(p), nil
15 | }
16 | return 0, w.WriteErr
17 | }
18 |
19 | func (w WriteCloserStub) Close() error {
20 | return w.CloseErr
21 | }
22 |
23 | //goland:noinspection GoUnusedExportedFunction
24 | func StubWriteCloser(writeErr, closeErr error) io.WriteCloser {
25 | return &WriteCloserStub{
26 | WriteErr: writeErr,
27 | CloseErr: closeErr,
28 | }
29 | }
30 |
31 | type NullWriteCloser struct{}
32 |
33 | func (*NullWriteCloser) Write(p []byte) (n int, err error) {
34 | return len(p), nil
35 | }
36 |
37 | func (*NullWriteCloser) Close() error {
38 | return nil
39 | }
40 |
--------------------------------------------------------------------------------
/util/typedpath/basename.go:
--------------------------------------------------------------------------------
1 | package typedpath
2 |
3 | type BaseName string
4 |
--------------------------------------------------------------------------------
/util/typedpath/raw.go:
--------------------------------------------------------------------------------
1 | package typedpath
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strings"
7 | )
8 |
9 | // RawPath is OS depended path. This style is for directly handling file systems.
10 | type RawPath string
11 |
12 | func NewRawPath(basenames ...BaseName) RawPath {
13 | elements := make([]string, len(basenames))
14 | for i, basename := range basenames {
15 | elements[i] = string(basename)
16 | }
17 | return RawPath(filepath.Join(elements...))
18 | }
19 |
20 | func NewRootRawPath(basenames ...BaseName) RawPath {
21 | return NewRawPath(append([]BaseName{BaseName([]byte{os.PathSeparator})}, basenames...)...)
22 | }
23 |
24 | func NewRawPathUnsafe(path string) RawPath {
25 | return RawPath(path)
26 | }
27 |
28 | func Getwd() (RawPath, error) {
29 | result, err := os.Getwd()
30 | if err != nil {
31 | return "", err
32 | }
33 | return RawPath(result), err
34 | }
35 |
36 | func (r RawPath) ToSlash() SlashPath {
37 | return SlashPath(filepath.ToSlash(string(r)))
38 | }
39 |
40 | func (r RawPath) JoinRawPath(other RawPath) RawPath {
41 | return RawPath(filepath.Join(string(r), string(other)))
42 | }
43 |
44 | func (r RawPath) JoinBaseName(others ...BaseName) RawPath {
45 | return r.JoinRawPath(NewRawPath(others...))
46 | }
47 |
48 | func (r RawPath) IsAbs() bool {
49 | return filepath.IsAbs(string(r))
50 | }
51 |
52 | func (r RawPath) Rel(path RawPath) (RawPath, error) {
53 | result, err := filepath.Rel(string(r), string(path))
54 | if err != nil {
55 | return "", err
56 | }
57 | return RawPath(result), nil
58 | }
59 |
60 | func (r RawPath) Dir() RawPath {
61 | return RawPath(filepath.Dir(string(r)))
62 | }
63 |
64 | func (r RawPath) Ext() string {
65 | return filepath.Ext(string(r))
66 | }
67 |
68 | func (r RawPath) Base() string {
69 | return filepath.Base(string(r))
70 | }
71 |
72 | func (r RawPath) TrimLastSep() RawPath {
73 | return RawPath(strings.TrimRight(string(r), string(filepath.Separator)))
74 | }
75 |
--------------------------------------------------------------------------------
/util/typedpath/raw_test.go:
--------------------------------------------------------------------------------
1 | package typedpath
2 |
3 | import "testing"
4 |
5 | func TestNewRawPath(t *testing.T) {
6 | cases := map[string]struct {
7 | BaseNames []BaseName
8 | Expected RawPath
9 | }{
10 | "empty": {
11 | BaseNames: nil,
12 | Expected: NewSlashPathUnsafe("/").ToRaw(),
13 | },
14 | "several paths": {
15 | BaseNames: []BaseName{"path", "to", "file"},
16 | Expected: NewSlashPathUnsafe("/path/to/file").ToRaw(),
17 | },
18 | }
19 |
20 | for desc, c := range cases {
21 | t.Run(desc, func(t *testing.T) {
22 | actual := NewRootRawPath(c.BaseNames...)
23 |
24 | if actual != c.Expected {
25 | t.Errorf("want %q, got %q", c.Expected, actual)
26 | }
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/util/typedpath/slash.go:
--------------------------------------------------------------------------------
1 | package typedpath
2 |
3 | import (
4 | "path"
5 | "path/filepath"
6 | "strings"
7 | )
8 |
9 | // SlashPath is slash separated path. Typically, Git uses the Slash Path.
10 | type SlashPath string
11 |
12 | func NewSlashPathUnsafe(path string) SlashPath {
13 | return SlashPath(path)
14 | }
15 |
16 | func SlashPathFromBaseName(baseName BaseName) SlashPath {
17 | return SlashPath(baseName)
18 | }
19 |
20 | func (s SlashPath) ToRaw() RawPath {
21 | return RawPath(filepath.FromSlash(string(s)))
22 | }
23 |
24 | func (s SlashPath) JoinSlashPath(other SlashPath) SlashPath {
25 | return SlashPath(path.Join(string(s), string(other)))
26 | }
27 |
28 | func (s SlashPath) JoinBaseName(other BaseName) SlashPath {
29 | return SlashPath(path.Join(string(s), string(other)))
30 | }
31 |
32 | func (s SlashPath) IsAbs() bool {
33 | return strings.HasPrefix(string(s), "/")
34 | }
35 |
36 | func (s SlashPath) Dir() SlashPath {
37 | return SlashPath(path.Dir(string(s)))
38 | }
39 |
40 | func (s SlashPath) Ext() string {
41 | return path.Ext(string(s))
42 | }
43 |
44 | func (s SlashPath) Split() (SlashPath, BaseName) {
45 | dirname, basename := path.Split(string(s))
46 | return SlashPath(dirname), BaseName(basename)
47 | }
48 |
--------------------------------------------------------------------------------
/version/version.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | // NOTE: Also edit the image tag in action.yml
4 | const Version = "4.0.0-alpha3"
5 |
--------------------------------------------------------------------------------