diff --git a/.envrc.sample b/.envrc.sample new file mode 100644 index 0000000..107822f --- /dev/null +++ b/.envrc.sample @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +set -a + +use nix +mkdir -p "${TMPDIR}" 2>/dev/null || true diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..1557067 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + - package-ecosystem: cargo + directory: / + schedule: + interval: daily diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index e557792..aa5e965 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -13,11 +13,16 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.0.2 + - uses: actions/checkout@v5 with: set-safe-directory: true - uses: actions/setup-python@v3 - - uses: pre-commit/action@v3.0.0 + - uses: actions/setup-go@v5 + - name: Install Deps + run: | + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0 + pip install pre-commit + pre-commit run --all-files test: name: test runs-on: ubuntu-latest @@ -26,7 +31,7 @@ jobs: - name: Install git run: apk add --update --no-cache git - name: Checkout code - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v5 with: fetch-depth: 0 set-safe-directory: true @@ -53,7 +58,7 @@ jobs: - name: Install git run: apk add --update --no-cache git - name: Checkout - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v5 with: fetch-depth: 0 set-safe-directory: true @@ -79,26 +84,15 @@ jobs: tag_name: ${{ steps.get_tag.outputs.git_tag }} steps: - name: Checkout - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v5 with: fetch-depth: 0 set-safe-directory: true - - name: Generate a changelog - uses: orhun/git-cliff-action@v4 - id: git-cliff - with: - config: cliff.toml - args: --verbose --latest - env: - OUTPUT: changelog.md - - name: Get the tag - id: get_tag - run: echo ::set-output name=git_tag::${GITHUB_REF/refs\/tags\//} - name: Create Release id: create_release - uses: ncipollo/release-action@v1.14.0 + uses: ncipollo/release-action@v1.18.0 with: - bodyFile: ./changelog.md + generateReleaseNotes: true release-artifacts: name: Release Artifacts needs: @@ -118,7 +112,7 @@ jobs: suffix: .exe if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: infinity-dialog-${{ matrix.os }}-${{ matrix.arch }} - name: Upload Release Asset - ${{ matrix.os }}-${{ matrix.arch }} diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..11d3ee4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,266 @@ +formatters: + enable: + - gofumpt + - goimports + + settings: + gofmt: + # simplify code: gofmt with `-s` option, true by default + simplify: true + + gofumpt: + # Choose whether to use the extra rules + extra-rules: true + + goimports: + # put imports beginning with prefix after 3rd-party packages; + # it's a comma-separated list of prefixes + local-prefixes: + - go.opentelemetry.io/collector + +issues: + # Maximum issues count per one linter. + max-issues-per-linter: 0 + # Maximum count of issues with the same text. + max-same-issues: 0 + +linters: + enable: + - asasalint + - contextcheck + - copyloopvar + - decorder + - depguard + - errcheck + - errorlint + - fatcontext + - gocritic + - gosec + - govet + - misspell + - nolintlint + - perfsprint + - revive + - staticcheck + - testifylint + - thelper + - unconvert + - unparam + - unused + - usestdlibvars + - usetesting + - whitespace + + exclusions: + presets: + - std-error-handling + + # Excluding configuration per-path, per-linter, per-text and per-source + rules: [] + + # Log a warning if an exclusion rule is unused. + warn-unused: true + + # all available settings of specific linters + settings: + depguard: + rules: + denied-deps: + deny: + - pkg: "go.uber.org/atomic" + desc: "Use 'sync/atomic' instead of go.uber.org/atomic" + - pkg: "github.com/pkg/errors" + desc: "Use 'errors' or 'fmt' instead of github.com/pkg/errors" + - pkg: "github.com/hashicorp/go-multierror" + desc: "Use go.uber.org/multierr instead of github.com/hashicorp/go-multierror" + - pkg: "math/rand$" + desc: "Use the newer 'math/rand/v2' instead of math/rand" + - pkg: "sigs.k8s.io/yaml" + desc: "Use 'go.yaml.in/yaml' instead of sigs.k8s.io/yaml" + # Add a different guard rule so that we can ignore tests. + ignore-in-test: + # Allow in tests for testing pdata or other receivers/exporters that expect OTLP. + files: + - '!**/*_test.go' + deny: + - pkg: go.opentelemetry.io/proto + desc: Use go.opentelemetry.io/collector/pdata instead + + gocritic: + disabled-checks: + - commentedOutCode + - deferInLoop + - filepathJoin + - hugeParam + - importShadow + - rangeValCopy + - unnamedResult + - whyNoLint + enable-all: true + + gosec: + excludes: + - G104 # FIXME + - G402 + - G404 + + govet: + disable: + # We want to order fields according to readability and grouping them by use cases. + # This linter does not offer a discernible performance improvement as the structs + # defined in this repository are not in the execution hot path. + # See https://github.com/open-telemetry/opentelemetry-collector/issues/2789 + - fieldalignment + enable-all: true + # settings per analyzer + settings: + printf: # analyzer name, run `go tool vet help` to see all analyzers + funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer + - Infof + - Warnf + - Errorf + - Fatalf + + misspell: + # Correct spellings using locale preferences for US or UK. + # Default is to use a neutral variety of English. + # Setting locale to US will correct the British spelling of 'colour' to 'color'. + locale: US + ignore-rules: + - cancelled + + nolintlint: + require-specific: true + + perfsprint: + # Optimizes even if it requires an int or uint type cast. + int-conversion: true + # Optimizes into `err.Error()` even if it is only equivalent for non-nil errors. + err-error: true + # Optimizes `fmt.Errorf`. + errorf: true + # Optimizes `fmt.Sprintf` with only one argument. + sprintf1: true + # Optimizes into strings concatenation. + strconcat: true + + revive: + # minimal confidence for issues, default is 0.8 + confidence: 0.8 + rules: + # Blank import should be only in a main or test package, or have a comment justifying it. + - name: blank-imports + # context.Context() should be the first parameter of a function when provided as argument. + - name: context-as-argument + # Basic types should not be used as a key in `context.WithValue` + - name: context-keys-type + # Importing with `.` makes the programs much harder to understand + - name: dot-imports + - name: early-return + arguments: + - preserveScope + # Empty blocks make code less readable and could be a symptom of a bug or unfinished refactoring. + - name: empty-block + # for better readability, variables of type `error` must be named with the prefix `err`. + - name: error-naming + # for better readability, the errors should be last in the list of returned values by a function. + - name: error-return + # for better readability, error messages should not be capitalized or end with punctuation or a newline. + - name: error-strings + # report when replacing `errors.New(fmt.Sprintf())` with `fmt.Errorf()` is possible + - name: errorf + # incrementing an integer variable by 1 is recommended to be done using the `++` operator + - name: increment-decrement + # highlights redundant else-blocks that can be eliminated from the code + - name: indent-error-flow + # This rule suggests a shorter way of writing ranges that do not use the second value. + - name: range + # receiver names in a method should reflect the struct name (p for Person, for example) + - name: receiver-naming + # redefining built in names (true, false, append, make) can lead to bugs very difficult to detect. + - name: redefines-builtin-id + # redundant else-blocks that can be eliminated from the code. + - name: superfluous-else + arguments: + - preserveScope + # prevent confusing name for variables when using `time` package + - name: time-naming + # warns when an exported function or method returns a value of an un-exported type. + - name: unexported-return + - name: unnecessary-stmt + # spots and proposes to remove unreachable code. also helps to spot errors + - name: unreachable-code + # Functions or methods with unused parameters can be a symptom of an unfinished refactoring or a bug. + - name: unused-parameter + # Since Go 1.18, interface{} has an alias: any. This rule proposes to replace instances of interface{} with any. + - name: use-any + # report when a variable declaration can be simplified + - name: var-declaration + # warns when initialism, variable or package naming conventions are not followed. + - name: var-naming + + staticcheck: + checks: + - all + - -ST1000 + - -ST1021 + - -ST1022 + + testifylint: + enable-all: true + + thelper: + benchmark: + begin: false + fuzz: + begin: false + tb: + begin: false + test: + begin: false + +# output configuration options +output: + # The formats used to render issues. + formats: + # Prints issues in a text format with colors, line number, and linter name. + text: + # Output path can be either `stdout`, `stderr` or path to the file to write to. + path: stdout + # print linter name in the end of issue text, default is true + print-linter-name: true + # print lines of code with issue, default is true + print-issued-lines: true + + # Show statistics per linter. + show-stats: false + +# options for analysis running +run: + # Allow multiple parallel golangci-lint instances running. + # If false (default) - golangci-lint acquires file lock on start. + allow-parallel-runners: true + + # default concurrency is a available CPU number + concurrency: 4 + + # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + modules-download-mode: readonly + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 10m + +version: "2" diff --git a/.mise.toml b/.mise.toml index ed714fd..1afa84c 100644 --- a/.mise.toml +++ b/.mise.toml @@ -1,2 +1,3 @@ [tools] -go = "1.23" +go = "1.25" +golangci-lint = "latest" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b7f17a5..b9f8857 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,14 +32,6 @@ repos: language: system types: [go] pass_filenames: false - - id: go-fmt - stages: [pre-commit] - name: go fmt - description: Format files with cargo fmt. - entry: go fmt ./... - language: system - types: [go] - pass_filenames: false - id: go-mod-tidy stages: [pre-commit] name: go mod tidy @@ -48,6 +40,22 @@ repos: language: system types: [go] pass_filenames: false + - id: fmt + stages: [pre-commit] + name: format + description: Run go critic + entry: golangci-lint fmt ./... + language: system + types: [go] + pass_filenames: false + - id: golangci-lint + stages: [pre-commit] + name: golangci-lint + description: Run go critic + entry: golangci-lint run ./... + language: system + types: [go] + pass_filenames: false - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: diff --git a/.vscode/launch.json b/.vscode/launch.json index 5fb50f6..a96ddfd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,8 @@ "mode": "remote", "port": 2345, "host": "127.0.0.1", - "showLog": true + "showLog": true, + "preLaunchTask": "dlv debug --headless --api-version=2 --listen=0.0.0.0:2345 || reset" } ] } diff --git a/cliff.toml b/cliff.toml deleted file mode 100644 index 07b94fd..0000000 --- a/cliff.toml +++ /dev/null @@ -1,87 +0,0 @@ -# git-cliff ~ default configuration file -# https://git-cliff.org/docs/configuration -[remote.github] -owner = "The-Mod-Elephant" -repo = "infinity_dialog" -[changelog] -# changelog header -header = "" -# template for the changelog body -# https://tera.netlify.app/docs -body = """\ -### Changelog \ -{% for group, commits in commits | group_by(attribute="group") %} - \n#### {{ group | striptags | trim | upper_first }}: - {% for commit in commits - | filter(attribute="group") - | unique(attribute="message") - | filter(attribute="merge_commit", value=false) %} - - {{commit.scope}}: \ - {{ commit.message }}\ - {% if not commit.github.pr_number %} [{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}){%- endif %}\ - {% if commit.github.username %} by @{{ commit.github.username }}\ - {% else %} by [{{ commit.author.name }}](https://github.com/{{ commit.author.name }}){%- endif %}\ - {%- endfor -%} -{% endfor %} -{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} -### New Contributors -{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} - * @{{ contributor.username }} made their first contribution in #{{ contributor.pr_number }} -{%- endfor -%} -{%- endif %} -Full Changelog: [{{ previous.version }}...{{ version }}]({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}) -{%- macro remote_url() -%} - https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} -{%- endmacro -%} -""" -# remove the leading and trailing whitespace from the template -trim = true -# changelog footer -footer = "" -# postprocessors -postprocessors = [ - # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL -] -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = false -# process each line of a commit as an individual commit -split_commits = false -# regex for preprocessing the commit messages -commit_preprocessors = [ - { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, -] -# regex for parsing and grouping commits -commit_parsers = [ - { message = "^(refactor|ref)", group = "Internal/Other", scope="ref" }, - { message = "^build", group = "Internal/Other", scope="build" }, - { message = "^chore", group = "Internal/Other", scope="chore" }, - { message = "^ci", group = "Internal/Other", scope="ci" }, - { message = "^dep", group = "Internal/Other", scope="dep" }, - { message = "^doc", group = "Internal/Other", scope="doc" }, - { message = "^feat", group = "Features", scope="feat" }, - { message = "^fix", group = "Fixes", scope="fix" }, - { message = "^perf", group = "Internal/Other", scope="perf" }, - { message = "^style", group = "Internal/Other", scope="style" }, - { message = "^test", group = "Internal/Other", scope="test" }, - { body = ".*security", group = "Internal/Other", scope="sec" }, - { message = "^revert", group = "Internal/Other", scope="revert" }, -] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = false -# filter out the commits that are not matched by commit parsers -filter_commits = false -# glob pattern for matching git tags -tag_pattern = "^[0-9]+.[0-9]+.[0-9]+$" -# regex for skipping tags -skip_tags = "^nightly-.*" -# regex for ignoring tags -ignore_tags = "^nightly-.*" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" -# limit the number of commits included in the changelog. -# limit_commits = 42 diff --git a/cmd/check-varriables.go b/cmd/check-varriables.go index dead34f..ae1df82 100644 --- a/cmd/check-varriables.go +++ b/cmd/check-varriables.go @@ -1,26 +1,30 @@ package cmd import ( + "cmp" "fmt" "io/fs" + "os" "path/filepath" + "slices" + "strconv" "strings" + "github.com/The-Mod-Elephant/infinity_dialog/pkg/readers" "github.com/The-Mod-Elephant/infinity_dialog/pkg/translation" - "github.com/The-Mod-Elephant/infinity_dialog/pkg/util" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) -type checkVariables struct { +type CheckVariables struct { table table.Model loadFiles map[string]map[string]string root string langDir string } -func NewCheck() checkVariables { +func NewCheck() CheckVariables { columns := []table.Column{ {Title: "Lang", Width: int(0.1 * float64(width))}, {Title: "Filename", Width: int(0.2 * float64(width))}, @@ -46,42 +50,42 @@ func NewCheck() checkVariables { Bold(false) t.SetStyles(s) - return checkVariables{table: t} + return CheckVariables{table: t} } -func (c *checkVariables) findPath() string { +func (c *CheckVariables) findPath() string { lang := c.table.SelectedRow()[0] - file_name := c.table.SelectedRow()[1] - path := c.loadFiles[lang][file_name] - if len(path) == 0 { - path = filepath.Join(c.langDir, lang, file_name) + fileName := c.table.SelectedRow()[1] + path, ok := c.loadFiles[lang][fileName] + if ok { + path = filepath.Join(c.langDir, lang, fileName) } return path } -func (c *checkVariables) genRows() *[]table.Row { +func (c *CheckVariables) genRows() *[]table.Row { rows := map[string]map[string][]string{} _ = filepath.WalkDir(c.root, func(path string, file fs.DirEntry, err error) error { if err != nil { return err } ext := filepath.Ext(file.Name()) - if !file.IsDir() && strings.ToLower(ext) == ".tra" { - if len(c.langDir) == 0 { + if !file.IsDir() && strings.EqualFold(ext, ".tra") { + if c.langDir == "" { c.langDir = filepath.Dir(filepath.Dir(path)) } lang := filepath.Base(filepath.Dir(path)) - if len(c.loadFiles[lang]) == 0 { + if _, ok := c.loadFiles[lang]; !ok { c.loadFiles[lang] = map[string]string{} } c.loadFiles[lang][file.Name()] = path - fileContent, err := util.ReadFileToSlice(path) + fileContent, err := readers.ReadFileToSlice(path) if err != nil { return err } variables, err := translation.FromFileContents(fileContent) if err == nil { - if len(rows[lang]) == 0 { + if _, ok := rows[lang]; !ok { rows[lang] = map[string][]string{} } for _, v := range *variables { @@ -100,12 +104,11 @@ func (c *checkVariables) genRows() *[]table.Row { } } out := []table.Row{} - for lang, _ := range rows { + for lang := range rows { for filename, stringVariables := range largest { - size_for_lang := rows[lang][filename] - sliceDiff := util.SortedDifference(&stringVariables, &size_for_lang) - diff := strings.Join(*sliceDiff, ",") - if len(diff) > 0 { + sizeForLang := rows[lang][filename] + sliceDiff := SortedDifference(&stringVariables, &sizeForLang) + if diff := strings.Join(*sliceDiff, ","); diff != "" { out = append(out, table.Row{lang, filename, diff}) } } @@ -113,9 +116,9 @@ func (c *checkVariables) genRows() *[]table.Row { return &out } -func (c checkVariables) Init() tea.Cmd { return nil } +func (c CheckVariables) Init() tea.Cmd { return nil } -func (c checkVariables) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (c CheckVariables) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case SelectedFilePath: c.loadFiles = map[string]map[string]string{} @@ -147,13 +150,12 @@ func (c checkVariables) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return c, tea.Quit case "f": if len(c.table.Rows()) > 0 { - strings := strings.Split(c.table.SelectedRow()[2], ",") content := []string{"\n"} for _, missing := range strings { content = append(content, fmt.Sprintf("@%s = ~~\n", missing)) } - err := util.WriteToFile(c.findPath(), &content) + err := WriteToFile(c.findPath(), &content) if err != nil { panic(err) } @@ -162,8 +164,8 @@ func (c checkVariables) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "e", "enter": if len(c.table.Rows()) > 0 { lang := c.table.SelectedRow()[0] - file_name := c.table.SelectedRow()[1] - return state.SetAndGetNextCommand(c), SendPathCmd(c.loadFiles[lang][file_name]) + fileName := c.table.SelectedRow()[1] + return state.SetAndGetNextCommand(c), SendPathCmd(c.loadFiles[lang][fileName]) } } } @@ -172,7 +174,43 @@ func (c checkVariables) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return c, cmd } -func (c checkVariables) View() string { +func (c CheckVariables) View() string { body := []string{c.table.View(), "\n\n", c.table.HelpView(), " e enter view, f fix"} return baseStyle.Render(body...) } + +func SortedDifference(slice1, slice2 *[]string) *[]string { + diff := []string{} + m := map[string]int{} + for _, s := range *slice1 { + m[s] = 1 + } + for _, s := range *slice2 { + m[s]++ + } + for k, v := range m { + if v > 1 { + diff = append(diff, k) + } + } + slices.SortFunc(diff, func(a, b string) int { + v1, _ := strconv.Atoi(a) + v2, _ := strconv.Atoi(b) + return cmp.Compare(v1, v2) + }) + return &diff +} + +func WriteToFile(path string, content *[]string) error { + f, err := os.OpenFile(filepath.Clean(path), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600) + if err != nil { + return err + } + defer f.Close() + for _, line := range *content { + if _, err = f.WriteString(line); err != nil { + return err + } + } + return nil +} diff --git a/cmd/cmds.go b/cmd/cmds.go index c075cf3..3332f13 100644 --- a/cmd/cmds.go +++ b/cmd/cmds.go @@ -2,10 +2,12 @@ package cmd import tea "github.com/charmbracelet/bubbletea" -type SelectedFilePath string -type ContentMsg string -type PathMsg string -type TitleMsg string +type ( + SelectedFilePath string + ContentMsg string + PathMsg string + TitleMsg string +) func SendSelectedFile(areapath string) tea.Cmd { return func() tea.Msg { diff --git a/cmd/directory-picker.go b/cmd/directory-picker.go index a4b7354..91c893d 100644 --- a/cmd/directory-picker.go +++ b/cmd/directory-picker.go @@ -11,11 +11,9 @@ import ( "github.com/charmbracelet/lipgloss" ) -var ( - helpStyle = lipgloss.NewStyle().Padding(1, 0, 0, 2).Foreground(lipgloss.Color("241")) -) +var helpStyle = lipgloss.NewStyle().Padding(1, 0, 0, 2).Foreground(lipgloss.Color("241")) -type directoryPicker struct { +type DirectoryPicker struct { filepicker filepicker.Model message string selectedFile string @@ -24,8 +22,7 @@ type directoryPicker struct { err error } -// TODO: Hide unselectable -func NewDirectoryPicker(dir bool, message string) directoryPicker { +func NewDirectoryPicker(dir bool, message string) DirectoryPicker { fp := filepicker.New() fp.DirAllowed = dir fp.FileAllowed = !dir @@ -33,7 +30,7 @@ func NewDirectoryPicker(dir bool, message string) directoryPicker { fp.Height = height - h - 5 fp.AutoHeight = true fp.ShowHidden = false - return directoryPicker{ + return DirectoryPicker{ filepicker: fp, message: message, } @@ -47,11 +44,11 @@ func clearErrorAfter(t time.Duration) tea.Cmd { }) } -func (d directoryPicker) Init() tea.Cmd { +func (d DirectoryPicker) Init() tea.Cmd { return d.filepicker.Init() } -func (d directoryPicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (d DirectoryPicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case SelectedFilePath: d.filepicker.CurrentDirectory = string(msg) @@ -59,7 +56,7 @@ func (d directoryPicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return d, d.Init() case tea.WindowSizeMsg: h, _ := docStyle.GetFrameSize() - d.filepicker.Height = d.filepicker.Height - h + d.filepicker.Height -= h case tea.KeyMsg: switch msg.String() { case "e": @@ -98,7 +95,7 @@ func (d directoryPicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return d, cmd } -func (d directoryPicker) View() string { +func (d DirectoryPicker) View() string { var s strings.Builder s.WriteString("\n ") diff --git a/cmd/file-view.go b/cmd/file-view.go index b019114..cab0c41 100644 --- a/cmd/file-view.go +++ b/cmd/file-view.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - "github.com/The-Mod-Elephant/infinity_dialog/pkg/util" + "github.com/The-Mod-Elephant/infinity_dialog/pkg/readers" "github.com/The-Mod-Elephant/infinity_file_formats/bg" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" @@ -28,14 +28,14 @@ var ( }() ) -type fileview struct { +type Fileview struct { title string content string viewport viewport.Model } -func NewFileView() fileview { - f := fileview{} +func NewFileView() Fileview { + f := Fileview{} headerHeight := lipgloss.Height(f.headerView()) footerHeight := lipgloss.Height(f.footerView()) verticalMarginHeight := headerHeight + footerHeight @@ -44,53 +44,65 @@ func NewFileView() fileview { return f } -func GetFileContents(path string) (string, string) { - dir := filepath.Base(path) - content := "" - f, err := os.Open(path) +func GetFileContents(path string) (string, error) { + f, err := os.Open(filepath.Clean(path)) if err != nil { + return "", err } defer f.Close() - switch strings.ToLower(filepath.Ext(path)) { + info, err := f.Stat() + if err != nil { + return "", err + } + switch strings.ToLower(filepath.Ext(info.Name())) { case ".are": area, err := bg.OpenArea(f) if err != nil { + return "", err } buf := new(bytes.Buffer) err = area.WriteJson(buf) if err != nil { + return "", err } - content = buf.String() + return buf.String(), nil case ".bam": bam, err := bg.OpenBAM(f, nil) if err != nil { + return "", err } buf := new(bytes.Buffer) err = bam.WriteJson(buf) if err != nil { + return "", err } - content = buf.String() + return buf.String(), nil case ".cre": cre, err := bg.OpenCre(f) if err != nil { + return "", err } buf := new(bytes.Buffer) err = cre.WriteJson(buf) if err != nil { + return "", err } - content = buf.String() + return buf.String(), nil case ".dlg": dlg, err := bg.OpenDlg(f) if err != nil { + return "", err } buf := new(bytes.Buffer) err = dlg.WriteJson(buf) if err != nil { + return "", err } - content = buf.String() + return buf.String(), nil case ".eff": effv1, effv2, err := bg.OpenEff(f) if err != nil { + return "", err } buf := new(bytes.Buffer) if effv1 != nil { @@ -99,49 +111,55 @@ func GetFileContents(path string) (string, string) { err = effv2.WriteJson(buf) } if err != nil { + return "", err } - content = buf.String() + return buf.String(), nil case ".itm": item, err := bg.OpenITM(f) if err != nil { + return "", err } buf := new(bytes.Buffer) err = item.WriteJson(buf) if err != nil { + return "", err } - content = buf.String() + return buf.String(), nil case ".sto": dlg, err := bg.OpenSTO(f) if err != nil { + return "", err } buf := new(bytes.Buffer) err = dlg.WriteJson(buf) if err != nil { + return "", err } - content = buf.String() + return buf.String(), nil case ".spl": dlg, err := bg.OpenSPL(f) if err != nil { + return "", err } buf := new(bytes.Buffer) err = dlg.WriteJson(buf) if err != nil { + return "", err } - content = buf.String() + return buf.String(), nil default: - content, err = util.ReadFileToString(path) - if err != nil { - return content, dir + if contents, err := readers.ReadFileToString(path); err != nil { + return contents, nil } } - return content, dir + return "", nil } -func (f fileview) Init() tea.Cmd { +func (f Fileview) Init() tea.Cmd { return nil } -func (f fileview) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (f Fileview) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case TitleMsg: f.title = string(msg) @@ -151,16 +169,22 @@ func (f fileview) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.viewport.SetContent(f.content) return f, f.Init() case SelectedFilePath: - content, title := GetFileContents(string(msg)) + content, err := GetFileContents(string(msg)) + if err != nil { + return f, tea.Quit + } f.content = content f.viewport.SetContent(f.content) - f.title = title + f.title = filepath.Base(string(msg)) return f, f.Init() case PathMsg: - content, title := GetFileContents(string(msg)) + content, err := GetFileContents(string(msg)) + if err != nil { + return f, tea.Quit + } f.content = content f.viewport.SetContent(f.content) - f.title = title + f.title = filepath.Base(string(msg)) return f, f.Init() case tea.KeyMsg: switch msg.String() { @@ -177,7 +201,7 @@ func (f fileview) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, cmd } -func setViewport(f fileview, msg tea.WindowSizeMsg) { +func setViewport(f Fileview, msg tea.WindowSizeMsg) { headerHeight := lipgloss.Height(f.headerView()) footerHeight := lipgloss.Height(f.footerView()) verticalMarginHeight := headerHeight + footerHeight @@ -188,17 +212,17 @@ func setViewport(f fileview, msg tea.WindowSizeMsg) { f.viewport.Height = msg.Height - verticalMarginHeight } -func (f fileview) View() string { +func (f Fileview) View() string { return fmt.Sprintf("%s\n%s\n%s", f.headerView(), f.viewport.View(), f.footerView()) } -func (f fileview) headerView() string { +func (f Fileview) headerView() string { title := titleStyle.Render(f.title) line := strings.Repeat("─", max(0, f.viewport.Width-lipgloss.Width(title))) return lipgloss.JoinHorizontal(lipgloss.Center, title, line) } -func (f fileview) footerView() string { +func (f Fileview) footerView() string { info := infoStyle.Render(fmt.Sprintf("%3.f%%", f.viewport.ScrollPercent()*100)) line := strings.Repeat("─", max(0, f.viewport.Width-lipgloss.Width(info))) return lipgloss.JoinHorizontal(lipgloss.Center, line, info) diff --git a/cmd/initial.go b/cmd/initial.go index 65fdf6e..81c3a54 100644 --- a/cmd/initial.go +++ b/cmd/initial.go @@ -9,6 +9,10 @@ import ( "github.com/charmbracelet/lipgloss" ) +const ( + Title = "Infinity Dialog" +) + var ( state = nav.NewState() docStyle = lipgloss.NewStyle().Margin(1, 2) @@ -16,42 +20,42 @@ var ( height = 0 ) -type item struct { +type Item struct { title, desc string } -func (i item) Title() string { return i.title } -func (i item) Description() string { return i.desc } -func (i item) FilterValue() string { return i.title } +func (i Item) Title() string { return i.title } +func (i Item) Description() string { return i.desc } +func (i Item) FilterValue() string { return i.title } -type initial struct { +type Initial struct { list list.Model } -func InitialModel() initial { +func InitialModel() Initial { items := []list.Item{ - item{title: "Check", desc: "Check all strings in a mod/directory"}, - item{title: "Discover", desc: "Find all strings in a mod/directory"}, - item{title: "Traverse", desc: "Show tree of locations through a mod"}, - item{title: "View", desc: "View any Infinity Engine file or text file"}, + Item{title: "Missing", desc: "Find missing strings for langs in a mod/directory"}, + Item{title: "Discover", desc: "Find all strings in a mod/directory"}, + Item{title: "Traverse", desc: "Show tree of locations through a mod"}, + Item{title: "View", desc: "View any Infinity Engine file or text file"}, // TODO: Implement these // item{title: "Add", desc: "Add strings to tra"}, // item{title: "Range", desc: "What range of numbers are free"}, // item{title: "Convert", desc: "Convert files to be traified"}, - // item{title: "Decompiler", desc: "Dialogue decompiler"}, + // item{title: "Decompiler", desc: "Dialog decompiler"}, } - i := initial{ + i := Initial{ list: list.New(items, list.NewDefaultDelegate(), 0, 0), } - i.list.Title = "Infinity Dialogue" + i.list.Title = Title return i } -func (i initial) Init() tea.Cmd { +func (i Initial) Init() tea.Cmd { return nil } -func (i initial) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (i Initial) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: h, v := docStyle.GetFrameSize() @@ -63,35 +67,35 @@ func (i initial) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return i, tea.Quit case "enter", " ": state = nav.NewState() - current_path, err := os.Getwd() + currentPath, err := os.Getwd() if err != nil { return i, tea.Quit } switch i.list.SelectedItem().FilterValue() { - case "Check": + case "Missing": d := NewDirectoryPicker(true, "Select a Mod Directory") c := NewCheck() f := NewFileView() state.SetNextCommand(d).SetNextCommand(c).SetNextCommand(f) - return state.SetAndGetNextCommand(i), SendSelectedFile(current_path) + return state.SetAndGetNextCommand(i), SendSelectedFile(currentPath) case "Discover": d := NewDirectoryPicker(true, "Select a Mod Directory") l := NewList() f := NewFileView() state.SetNextCommand(d).SetNextCommand(l).SetNextCommand(f) - return state.SetAndGetNextCommand(i), SendSelectedFile(current_path) + return state.SetAndGetNextCommand(i), SendSelectedFile(currentPath) case "Traverse": d := NewDirectoryPicker(true, "Select a Mod Directory") f := NewDirectoryPicker(false, "Select an area to start") t := NewTree() v := NewFileView() state.SetNextCommand(d).SetNextCommand(f).SetNextCommand(t).SetNextCommand(v) - return state.SetAndGetNextCommand(i), SendSelectedFile(current_path) + return state.SetAndGetNextCommand(i), SendSelectedFile(currentPath) case "View": d := NewDirectoryPicker(false, "Select a file to start") v := NewFileView() state.SetNextCommand(d).SetNextCommand(v) - return state.SetAndGetNextCommand(i), SendSelectedFile(current_path) + return state.SetAndGetNextCommand(i), SendSelectedFile(currentPath) } } } @@ -100,6 +104,6 @@ func (i initial) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return i, cmd } -func (i initial) View() string { +func (i Initial) View() string { return docStyle.Render(i.list.View()) } diff --git a/cmd/list-varriables.go b/cmd/list-varriables.go index 544a153..1c8c8ea 100644 --- a/cmd/list-varriables.go +++ b/cmd/list-varriables.go @@ -5,8 +5,8 @@ import ( "path/filepath" "strings" + "github.com/The-Mod-Elephant/infinity_dialog/pkg/readers" "github.com/The-Mod-Elephant/infinity_dialog/pkg/translation" - "github.com/The-Mod-Elephant/infinity_dialog/pkg/util" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -16,13 +16,13 @@ var baseStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")) -type listVariables struct { +type ListVariables struct { table table.Model } func generateRows(path string, file fs.FileInfo) *[]table.Row { rows := []table.Row{} - fileContent, err := util.ReadFileToSlice(path) + fileContent, err := readers.ReadFileToSlice(path) if err != nil { return &rows } @@ -36,7 +36,7 @@ func generateRows(path string, file fs.FileInfo) *[]table.Row { return &rows } -func NewList() listVariables { +func NewList() ListVariables { columns := []table.Column{ {Title: "FileName", Width: int(0.2 * float64(width))}, {Title: "Lang", Width: int(0.1 * float64(width))}, @@ -63,33 +63,39 @@ func NewList() listVariables { Bold(false) t.SetStyles(s) - return listVariables{table: t} + return ListVariables{table: t} } -func (l listVariables) readPath(path string) *[]table.Row { +func (l ListVariables) readPath(path string) (*[]table.Row, error) { rows := []table.Row{} - filepath.WalkDir(path, func(path string, file fs.DirEntry, err error) error { + err := filepath.WalkDir(path, func(path string, file fs.DirEntry, err error) error { if err != nil { return err } ext := filepath.Ext(file.Name()) - if !file.IsDir() && strings.ToLower(ext) == ".tra" { + if !file.IsDir() && strings.EqualFold(ext, ".tra") { info, _ := file.Info() - file_rows := *generateRows(path, info) - rows = append(rows, file_rows...) + fileRows := *generateRows(path, info) + rows = append(rows, fileRows...) } return nil }) - return &rows + if err != nil { + return nil, err + } + return &rows, nil } -func (l listVariables) Init() tea.Cmd { return nil } +func (l ListVariables) Init() tea.Cmd { return nil } -func (l listVariables) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (l ListVariables) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case SelectedFilePath: - rows := l.readPath(string(msg)) + rows, err := l.readPath(string(msg)) + if err != nil { + return l, tea.Quit + } l.table.SetRows(*rows) return l, nil case tea.WindowSizeMsg: @@ -138,7 +144,7 @@ func (l listVariables) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return l, cmd } -func (l listVariables) View() string { +func (l ListVariables) View() string { body := []string{l.table.View(), "\n\n", l.table.HelpView(), " enter"} return baseStyle.Render(body...) } diff --git a/cmd/tree.go b/cmd/tree.go index ce4a2c7..1b32ccc 100644 --- a/cmd/tree.go +++ b/cmd/tree.go @@ -6,7 +6,7 @@ import ( "path/filepath" "strings" - "github.com/The-Mod-Elephant/infinity_dialog/pkg/util" + "github.com/The-Mod-Elephant/infinity_dialog/pkg/readers" "github.com/The-Mod-Elephant/infinity_file_formats/bg" "github.com/charmbracelet/bubbles/paginator" tea "github.com/charmbracelet/bubbletea" @@ -14,17 +14,17 @@ import ( tree "github.com/savannahostrowski/tree-bubble" ) -type nested struct { +type Nested struct { tree tree.Model - file_map *map[string]string + fileMap map[string]string paginator paginator.Model } -func (m nested) Init() tea.Cmd { +func (n Nested) Init() tea.Cmd { return nil } -func NewTree() nested { +func NewTree() Nested { h, w := docStyle.GetFrameSize() _, right, _, left := docStyle.GetPadding() w = w - left - right @@ -37,27 +37,30 @@ func NewTree() nested { p.ActiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}).Render("•") p.InactiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "238"}).Render("•") - return nested{tree: tree.New([]tree.Node{}, w, h), paginator: p} + return Nested{tree: tree.New([]tree.Node{}, w, h), paginator: p} } -func (n nested) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (n Nested) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case PathMsg: - file_map := map[string]string{} - filepath.Walk(string(msg), func(path string, info os.FileInfo, err error) error { + fileMap := map[string]string{} + err := filepath.Walk(string(msg), func(path string, info os.FileInfo, err error) error { if err != nil { return err } // Only search baf + are files if !info.IsDir() && (filepath.Ext(info.Name()) == ".are" || filepath.Ext(info.Name()) == ".baf") { - file_map[strings.ToLower(info.Name())] = path + fileMap[strings.ToLower(info.Name())] = path } return nil }) - n.file_map = &file_map + if err != nil { + return n, tea.Quit + } + n.fileMap = fileMap case SelectedFilePath: nodes := []tree.Node{} - parseArea(&nodes, string(msg), n.file_map) + parseArea(&nodes, string(msg), n.fileMap) n.paginator.SetTotalPages(size(&nodes)) n.tree.SetNodes(nodes) return n, n.Init() @@ -93,19 +96,19 @@ func (n nested) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } var ( - pag_cmd tea.Cmd - tree_cmd tea.Cmd + pagCmd tea.Cmd + treeCmd tea.Cmd ) - n.tree, tree_cmd = n.tree.Update(msg) - n.paginator, pag_cmd = n.paginator.Update(msg) - return n, tea.Batch(pag_cmd, tree_cmd) + n.tree, treeCmd = n.tree.Update(msg) + n.paginator, pagCmd = n.paginator.Update(msg) + return n, tea.Batch(pagCmd, treeCmd) } -func (n nested) View() string { +func (n Nested) View() string { // TODO: Collapse and expand tree items := strings.Split(n.tree.View(), "\n") var b strings.Builder - b.WriteString("\n Dialogue Tree\n\n") + b.WriteString("\n Dialog Tree\n\n") start, end := n.paginator.GetSliceBounds(len(items)) for _, item := range items[start:end] { b.WriteString(item + "\n") @@ -115,25 +118,25 @@ func (n nested) View() string { return n.tree.Styles.Shapes.Render(b.String()) } -func getSelected(n *nested, nodes *[]tree.Node, counter int) (*tree.Node, int) { +func getSelected(n *Nested, nodes *[]tree.Node, counter int) (*tree.Node, int) { for _, node := range *nodes { - counter += 1 + counter++ if counter-1 == n.tree.Cursor() { return &node, counter } if len(node.Children) > 0 { - if child, cnt := getSelected(n, &node.Children, counter); child != nil { + child, cnt := getSelected(n, &node.Children, counter) + if child != nil { return child, cnt - } else { - counter = cnt } + counter = cnt } } return nil, counter } -func parseArea(nodes *[]tree.Node, areapath string, file_map *map[string]string) { - f, err := os.Open(areapath) +func parseArea(nodes *[]tree.Node, path string, fileMap map[string]string) { + f, err := os.Open(filepath.Clean(path)) if err != nil { return } @@ -143,44 +146,44 @@ func parseArea(nodes *[]tree.Node, areapath string, file_map *map[string]string) return } - child_name := fmt.Sprintf("%s.%s", strings.Split(strings.ToLower(string(area.Offsets.Script.Name[:])), "\x00")[0], "baf") - file_path := (*file_map)[child_name] + childName := fmt.Sprintf("%s.%s", strings.Split(strings.ToLower(string(area.Offsets.Script.Name[:])), "\x00")[0], "baf") + filePath := fileMap[childName] parent := tree.Node{ - Value: filepath.Base(areapath), - Desc: areapath, + Value: filepath.Base(path), + Desc: path, Children: []tree.Node{{ - Value: child_name, - Desc: file_path, + Value: childName, + Desc: filePath, Children: []tree.Node{}, }}, } *nodes = append((*nodes), parent) - if err := findChildren(file_path, file_map, nodes, &parent.Children[len(parent.Children)-1], 0); err != nil { + if err := findChildren(filePath, fileMap, nodes, &parent.Children[len(parent.Children)-1], 0); err != nil { return } for _, entrance := range area.Entrances { - area_name := fmt.Sprintf("%s.%s", strings.ToLower(string(entrance.Name.Value[:])), "are") - area_path := (*file_map)[child_name] - if !presentInTopOfTree(*nodes, area_name) { - parseArea(nodes, area_path, file_map) + areaName := fmt.Sprintf("%s.%s", strings.ToLower(string(entrance.Name.Value[:])), "are") + areaPath := fileMap[childName] + if !presentInTopOfTree(*nodes, areaName) { + parseArea(nodes, areaPath, fileMap) } } } -func findChildren(path string, file_map *map[string]string, nodes *[]tree.Node, child *tree.Node, depth int) error { +func findChildren(path string, fileMap map[string]string, nodes *[]tree.Node, child *tree.Node, depth int) error { if depth > 3 { return nil } - contents, err := util.ReadFileToString(path) + contents, err := readers.ReadFileToString(path) contents = strings.ToLower(contents) if err != nil { return err } filename := filepath.Base(path) - for k, v := range *file_map { + for k, v := range fileMap { if k != filename && strings.Contains(contents, "\""+k[:len(k)-4]+"\")") { child.Children = append(child.Children, tree.Node{ Value: k, @@ -189,11 +192,11 @@ func findChildren(path string, file_map *map[string]string, nodes *[]tree.Node, }) if k[len(k)-3:] == "are" { if !presentInTopOfTree(*nodes, k) { - parseArea(nodes, v, file_map) + parseArea(nodes, v, fileMap) } } else { if !presentInTreeExcludingTop(nodes, k) { - err := findChildren(v, file_map, nodes, &child.Children[len(child.Children)-1], depth+1) + err := findChildren(v, fileMap, nodes, &child.Children[len(child.Children)-1], depth+1) if err != nil { return err } @@ -241,7 +244,7 @@ func presentInTree(nodes *[]tree.Node, name string) bool { func size(nodes *[]tree.Node) int { start := 0 for _, child := range *nodes { - start += 1 + start++ if len(child.Children) > 0 { start += size(&child.Children) } diff --git a/main.go b/main.go index 9739426..e4ec6cd 100644 --- a/main.go +++ b/main.go @@ -9,13 +9,11 @@ import ( ) func main() { - if len(os.Getenv("DEBUG")) > 0 { - f, err := tea.LogToFile("debug.log", "debug") - if err != nil { + if os.Getenv("DEBUG") != "" { + if _, err := tea.LogToFile("debug.log", "debug"); err != nil { fmt.Println("fatal:", err) os.Exit(1) } - defer f.Close() } p := tea.NewProgram(cmd.InitialModel(), tea.WithAltScreen()) if _, err := p.Run(); err != nil { diff --git a/pkg/nav/state.go b/pkg/nav/state.go index 7a88df0..1a8f504 100644 --- a/pkg/nav/state.go +++ b/pkg/nav/state.go @@ -4,19 +4,19 @@ import tea "github.com/charmbracelet/bubbletea" var position = 0 -type state struct { +type State struct { model tea.Model - next *state - previous *state + next *State + previous *State } -func NewState() *state { - return &state{} +func NewState() *State { + return &State{} } -func (s *state) setCurrentCommand(m tea.Model) { +func (s *State) setCurrentCommand(m tea.Model) { if s.model == nil { - *s = state{ + *s = State{ model: m, next: s.next, previous: s.previous, @@ -30,8 +30,8 @@ func (s *state) setCurrentCommand(m tea.Model) { s.model = m } -func (s *state) NextCommand() tea.Model { - position += 1 +func (s *State) NextCommand() tea.Model { + position++ for range position { if s.next != nil { s = s.next @@ -41,26 +41,25 @@ func (s *state) NextCommand() tea.Model { return m } -func (s *state) SetNextCommand(m tea.Model) *state { +func (s *State) SetNextCommand(m tea.Model) *State { if s.next == nil { - s.next = &state{ + s.next = &State{ model: m, next: nil, previous: s, } return s.next - } else { - return s.next.SetNextCommand(m) } + return s.next.SetNextCommand(m) } -func (s *state) SetAndGetNextCommand(m tea.Model) tea.Model { +func (s *State) SetAndGetNextCommand(m tea.Model) tea.Model { s.setCurrentCommand(m) return s.NextCommand() } -func (s *state) PreviousCommand() tea.Model { - position -= 1 +func (s *State) PreviousCommand() tea.Model { + position-- for range position { if s.next != nil { s = s.next @@ -70,20 +69,19 @@ func (s *state) PreviousCommand() tea.Model { return m } -func (s *state) SetPreviousCommand(m tea.Model) *state { +func (s *State) SetPreviousCommand(m tea.Model) *State { if s.previous == nil { - s.previous = &state{ + s.previous = &State{ model: m, next: s, previous: nil, } return s.previous - } else { - return s.previous.SetPreviousCommand(m) } + return s.previous.SetPreviousCommand(m) } -func (s *state) SetAndGetPreviousCommand(m tea.Model) tea.Model { +func (s *State) SetAndGetPreviousCommand(m tea.Model) tea.Model { s.setCurrentCommand(m) return s.PreviousCommand() } diff --git a/pkg/util/read.go b/pkg/readers/read.go similarity index 80% rename from pkg/util/read.go rename to pkg/readers/read.go index 7734fe9..7e0c688 100644 --- a/pkg/util/read.go +++ b/pkg/readers/read.go @@ -1,4 +1,4 @@ -package util +package readers import ( "bufio" @@ -7,9 +7,9 @@ import ( "path/filepath" ) -func GetFiles(path string, ext string) []fs.FileInfo { +func GetFiles(path, ext string) []fs.FileInfo { out := []fs.FileInfo{} - f, err := os.Open(path) + f, err := os.Open(filepath.Clean(path)) if err != nil { return out } @@ -27,7 +27,7 @@ func GetFiles(path string, ext string) []fs.FileInfo { } func ReadFile(path string) (*[]byte, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(filepath.Clean(path)) if err != nil { return nil, err } @@ -43,7 +43,7 @@ func ReadFileToString(path string) (string, error) { } func ReadFileToSlice(path string) (*[]string, error) { - file, err := os.Open(path) + file, err := os.Open(filepath.Clean(path)) if err != nil { return nil, err } diff --git a/pkg/translation/varriables.go b/pkg/translation/varriables.go index f3157ce..35f8686 100644 --- a/pkg/translation/varriables.go +++ b/pkg/translation/varriables.go @@ -34,15 +34,7 @@ func (v *Variable) isRecording() bool { return v.recording } -func (v *Variable) start() int { - return v.identifierStart -} - -func (v *Variable) end() int { - return v.valueEnd -} - -func ToAscii(str string) string { +func ToASCII(str string) string { result, _, err := transform.String(transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn))), str) if err != nil { return "" @@ -74,7 +66,7 @@ func FromString(s string) (*Variable, error) { // "~" is 126 if v.isRecording() && s[i] == 126 { v.valueEnd = i - v.Value = ToAscii(s[v.valueStart:v.valueEnd]) + v.Value = ToASCII(s[v.valueStart:v.valueEnd]) v.toggleRecording() break } @@ -96,20 +88,22 @@ func FromFileContents(fileContents *[]string) (*[]Variable, error) { buffer := "" for _, line := range *fileContents { // Deal with single line comment - if len(line) > 2 && line[0:1] != "//" { - if !multi && strings.Count(line, "~") != 2 { - multi = true - buffer += line - } else if strings.Count(line, "~") == 1 { - multi = false - buffer += line - if v, err := FromString(buffer); err == nil { - out = append(out, *v) - } - } else { - if v, err := FromString(line); err == nil { - out = append(out, *v) - } + if len(line) < 2 && line[0:1] == "//" { + continue + } + switch { + case !multi && strings.Count(line, "~") != 2: + multi = true + buffer += line + case strings.Count(line, "~") == 1: + multi = false + buffer += line + if v, err := FromString(buffer); err == nil { + out = append(out, *v) + } + default: + if v, err := FromString(line); err == nil { + out = append(out, *v) } } } diff --git a/pkg/translation/varriables_test.go b/pkg/translation/varriables_test.go index f38b345..32823f1 100644 --- a/pkg/translation/varriables_test.go +++ b/pkg/translation/varriables_test.go @@ -32,9 +32,9 @@ func TestString(t *testing.T) { { expected: &Variable{ Identifier: "0", - Value: "Aboleth le�� ve sv� n�dr�i, mimo dosah tv�ch zbran�. Mus� naj�t jin� zp�sob, jak zni�it mechanismus, kter� ho dr�� na�ivu.", + Value: "Aboleth le�� ve sv� n�dr�i, mimo dosah tv�ch zbran�. Mus� naj�t jin� zp�sob, jak zni�it mechanisms, kter� ho dr�� na�ivu.", }, - testString: "@0 = ~Aboleth le�� ve sv� n�dr�i, mimo dosah tv�ch zbran�. Mus� naj�t jin� zp�sob, jak zni�it mechanismus, kter� ho dr�� na�ivu.~", + testString: "@0 = ~Aboleth le�� ve sv� n�dr�i, mimo dosah tv�ch zbran�. Mus� naj�t jin� zp�sob, jak zni�it mechanisms, kter� ho dr�� na�ivu.~", error: nil, }, } diff --git a/pkg/util/diff.go b/pkg/util/diff.go deleted file mode 100644 index df05581..0000000 --- a/pkg/util/diff.go +++ /dev/null @@ -1,30 +0,0 @@ -package util - -import ( - "cmp" - "slices" - "strconv" -) - -func SortedDifference(slice1 *[]string, slice2 *[]string) *[]string { - diff := []string{} - m := map[string]int{} - - for _, s := range *slice1 { - m[s] = 1 - } - for _, s := range *slice2 { - m[s] += 1 - } - for k, v := range m { - if v == 1 { - diff = append(diff, k) - } - } - slices.SortFunc(diff, func(a, b string) int { - v1, _ := strconv.Atoi(a) - v2, _ := strconv.Atoi(b) - return cmp.Compare(v1, v2) - }) - return &diff -} diff --git a/pkg/util/write.go b/pkg/util/write.go deleted file mode 100644 index 1161898..0000000 --- a/pkg/util/write.go +++ /dev/null @@ -1,19 +0,0 @@ -package util - -import ( - "os" -) - -func WriteToFile(path string, content *[]string) error { - f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - return err - } - defer f.Close() - for _, line := range *content { - if _, err = f.WriteString(line); err != nil { - return err - } - } - return nil -} diff --git a/shell.nix b/shell.nix index 8928c82..1e991a4 100644 --- a/shell.nix +++ b/shell.nix @@ -3,6 +3,7 @@ pkgs.mkShell { buildInputs = with pkgs; [ git go + golangci-lint pre-commit ]; }