diff --git a/.gh-dash.yml b/.gh-dash.yml deleted file mode 100644 index 89bf4f9..0000000 --- a/.gh-dash.yml +++ /dev/null @@ -1,96 +0,0 @@ -prSections: - - title: Responsible PRs - filters: repo:tlinden/tablizer is:open NOT dependabot - layout: - repoName: - hidden: true - - - title: Responsible Dependabot PRs - filters: repo:tlinden/tablizer is:open dependabot - layout: - repoName: - hidden: true - -issuesSections: - - title: Responsible Issues - filters: is:open repo:tlinden/tablizer -author:@me - layout: - repoName: - hidden: true - - - title: Note-to-Self Issues - filters: is:open repo:tlinden/tablizer author:@me - layout: - creator: - hidden: true - repoName: - hidden: true - -defaults: - preview: - open: false - width: 100 - -keybindings: - universal: - - key: "shift+down" - builtin: pageDown - - key: "shift+up" - builtin: pageUp - prs: - - key: g - name: gitu - command: > - cd {{.RepoPath}} && /home/scip/bin/gitu - - key: M - name: squash-merge - command: gh pr merge --rebase --squash --admin --repo {{.RepoName}} {{.PrNumber}} - - key: i - name: show ci checks - command: gh pr checks --repo {{.RepoName}} {{.PrNumber}} | glow -p - - key: e - name: edit pr - command: ~/.config/gh-dash/edit-gh-pr {{.RepoName}} {{.PrNumber}} - - key: E - name: open repo in emacs - command: emacsclient {{.RepoPath}} & - issues: - - key: v - name: view - command: gh issue view --repo {{.RepoName}} {{.IssueNumber}} | glow -p - - key: l - name: add label - command: gh issue --repo {{.RepoName}} edit {{.IssueNumber}} --add-label $(gum choose bug enhancement question dependencies wontfix) - - key: L - name: remove label - command: gh issue --repo {{.RepoName}} edit {{.IssueNumber}} --remove-label $(gum choose bug enhancement question dependencies wontfix) - - key: E - name: open repo in emacs - command: emacsclient {{.RepoPath}} & - -theme: - ui: - sectionsShowCount: true - table: - compact: false - showSeparator: true - colors: - text: - primary: "#E2E1ED" - secondary: "#6770cb" - inverted: "#242347" - faint: "#b0793b" - warning: "#E0AF68" - success: "#3DF294" - background: - selected: "#1B1B33" - border: - primary: "#383B5B" - secondary: "#39386B" - faint: "#8d3e0b" - -repoPaths: - :owner/:repo: ~/dev/:repo - -pager: - diff: delta diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 0e35092..0000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# vim: set ts=2 sw=2 tw=0 fo=cnqoj - -version: 2 - -before: - hooks: - - go mod tidy - -gitea_urls: - api: https://codeberg.org/api/v1 - download: https://codeberg.org - -builds: - - env: - - CGO_ENABLED=0 - goos: - - linux - - windows - - darwin - - freebsd - -archives: - - formats: [tar.gz] - # this name template makes the OS and Arch compatible with the results of `uname`. - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }}_{{ .Tag }} - # use zip for windows archives - format_overrides: - - goos: windows - formats: [zip] - - goos: linux - formats: [tar.gz,binary] - files: - - src: "*.md" - strip_parent: true - - src: "docs/*" - strip_parent: true - - src: Makefile.dist - dst: Makefile - wrap_in_directory: true - -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" - groups: - - title: Improved - regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$' - order: 0 - - title: Fixed - regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$' - order: 1 - - title: Changed - order: 999 - -release: - header: "# Release Notes" - footer: >- - - --- - - Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/epuppy/compare/{{ .PreviousTag }}...{{ .Tag }}) diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml deleted file mode 100644 index e20cc6f..0000000 --- a/.woodpecker/build.yaml +++ /dev/null @@ -1,36 +0,0 @@ -matrix: - platform: - - linux/amd64 - goversion: - - 1.24 - -labels: - platform: ${platform} - -steps: - build: - when: - event: [push] - image: golang:${goversion} - commands: - - go get - - go build - - linter: - when: - event: [push] - image: golang:${goversion} - commands: - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 - - golangci-lint --version - - golangci-lint run ./... - depends_on: [build] - - test: - when: - event: [push] - image: golang:${goversion} - commands: - - go get - - go test -v -cover - depends_on: [build,linter] diff --git a/.woodpecker/image.yaml b/.woodpecker/image.yaml deleted file mode 100644 index 687796c..0000000 --- a/.woodpecker/image.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# https://woodpecker-ci.org/plugins/docker-buildx -# enable Package unit and go to /scip/-/packages after building to link to proj - -variables: - - &repo codeberg.org/${CI_REPO_OWNER}/tablizer - -steps: - dryrun: - image: docker.io/woodpeckerci/plugin-docker-buildx:latest - settings: - dockerfile: Dockerfile - platforms: linux/amd64 - dry_run: true - repo: *repo - tags: latest - when: - event: [pull_request] - - publish: - image: docker.io/woodpeckerci/plugin-docker-buildx:latest - settings: - dockerfile: Dockerfile - platforms: linux/amd64 - repo: *repo - registry: codeberg.org - tags: latest,${CI_COMMIT_SHA:0:8},${CI_COMMIT_TAG} - username: ${CI_REPO_OWNER} - password: - from_secret: REGISTRY_TOKEN - when: - event: [tag] - branch: main diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml deleted file mode 100644 index 916c008..0000000 --- a/.woodpecker/release.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# build release - -labels: - platform: linux/amd64 - -steps: - goreleaser: - image: goreleaser/goreleaser - when: - event: [tag] - environment: - GITEA_TOKEN: - from_secret: DEPLOY_TOKEN - commands: - - goreleaser release --clean --verbose diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 749a318..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,278 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org). - -## [v1.0.14](https://codeberg.org/scip/tablizer/tree/v1.0.14) - 2023-01-23 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.13...v1.0.14) - -### Fixed - -- The -D parameter could not be used together with -a. - -- Fixed invalid argv handling: when the user wanted to read from stdin - but gave an argument which was meant as a pattern, but also existed - as a filename, then tablizer opened the file, ignored stdin. - -- Makefile indentation - - -### Added - -- added licens notes about dependencies - -- using hard coded uniseq version, see actions#3396457307 - -- updated dependencies (go module versions) - - -## [v1.0.13](https://codeberg.org/scip/tablizer/tree/v1.0.13) - 2022-11-03 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.12...v1.0.13) - -### Added - -- Added command line flag to generate shell completion code - -- Added an animated demo gif to the README to demonstrate the tool - -### Fixed - -- The `-A` flag wasn't implemented (default output mode). - -- Fixed building from source on systems w/o perls pod tools, - which is not requrired anyway since I always commit the latest - manpage. - - -## [v1.0.12](https://codeberg.org/scip/tablizer/tree/v1.0.12) - 2022-10-25 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.11...v1.0.12) - -### Added - -- Added support to parse CSV input - -- Added CSV output support - -- Added support for environment variables - -### Changed - -- We do not use the generated help message anymore, instead we use the - usage from the manpage, which we have to maintain anyway. It looks - better and has flag groups, which cobra is still lacking as of this - writing. - -- More refactoring and re-organization, runtime configuration now - lives in the cfg module. - - -### Fixed - -- Fixed [Bug #5](https://codeberg.org/scip/tablizer/issues/5), where - matches have not been highlighted correctly in some rare cases. - - - -## [v1.0.11](https://codeberg.org/scip/tablizer/tree/v1.0.11) - 2022-10-19 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.10...v1.0.11) - -### Added - -- Added CI job golinter to regularly check for common mistakes. - -- Added YAML output mode. - -- Added more unit tests, we're over 95% in the lib module. - -### Changed - -- do not use any global variables anymore, makes the code easier to - maintain, understand and test - -- using io.Writer in print* functions, which is easier to test, also - re-implemented the print tests. - -- replaced go-str2duration with my own implementation `duration2int()`. - - - -## [v1.0.10](https://codeberg.org/scip/tablizer/tree/v1.0.10) - 2022-10-15 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.9...v1.0.10) - -### Added - -- Added various sort modes: sort by time, by duration, numerical (-a -t -i) - -- Added possibility to modify sort order to descending (-D) - -- Added support to specify a regexp in column selector -c, which can - also be mixed with numerical column spec - -- More unit tests - -### Fixed - -- Column specification allowed to specify duplicate columns like `-c - 1,2,1,2` unchecked. Now this list will be deduplicated before use. - - - -## [v1.0.9](https://codeberg.org/scip/tablizer/tree/v1.0.9) - 2022-10-14 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.8...v1.0.9) - -### Added - -- Added Changelog, Contribution guidelines and no COC. - -### Changed - -- some minor changes to satisfy linter. - - - -## [v1.0.8](https://codeberg.org/scip/tablizer/tree/v1.0.8) - 2022-10-13 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.7...v1.0.8) - -### Added - -- Added sort support with the new parameter -k (like sort(1)). - - - -## [v1.0.7](https://codeberg.org/scip/tablizer/tree/v1.0.7) - 2022-10-11 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.6...v1.0.7) - -### Added - -- Added pattern highlighting support. - -- Added more unit tests. - -### Fixed - -- Fixed extended more output in combination with -c. - -- Fixed issue #4, the version string was missing. - - - -## [v1.0.6](https://codeberg.org/scip/tablizer/tree/v1.0.6) - 2022-10-05 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.5...v1.0.6) - -### Added - -- Added documentation about regexp syntax in the manpage. - -- Added more unit tests. - -### Changed - -- Rewrote the input parser. - -- Some more refactoring work has been done. - - - -## [v1.0.5](https://codeberg.org/scip/tablizer/tree/v1.0.5) - 2022-10-05 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.4...v1.0.5) - -### Added - -- A new option has been added: --invert-match -v which behaves like - the same option in grep(1): it inverts the pattern match. - -- A few more unit tests have been added. - -### Fixed - -- Pattern matching did not work, because the (new) help subcommand - lead to cobra taking care of the first arg to the program - (argv[1]). So now there's a new parameter -m which displays the - manpage and no more subcommands. - - - -## [v1.0.4](https://codeberg.org/scip/tablizer/tree/v1.0.4) - 2022-10-04 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.3...v1.0.4) - -### Added - -- Development version of the compiled binary now uses git vars - in addition to program version. - -- Added an option to display the manual page (compiled in) as text: - --help, for cases where a user just installed the binary. - -### Changed - -- Fixed go module namespace. - - - -## [v1.0.3](https://codeberg.org/scip/tablizer/tree/v1.0.3) - 2022-10-03 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.2...v1.0.3) - -### Added - -- Added a new output mode: shell mode, which allows the user - to use the output in a shell eval loop to further process - the data. - -### Changed - -- More refactoring work has been done. - - - -## [v1.0.2](https://codeberg.org/scip/tablizer/tree/v1.0.2) - 2022-10-02 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.1...v1.0.2) - -### Added - -- Added some basic unit tests. - -### Changed - -- Code has been refactored to be more efficient. - -- Replaced table generation code with Tablewriter. - - - - - -## [v1.0.1](https://codeberg.org/scip/tablizer/tree/v1.0.1) - 2022-09-30 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/v1.0.0...v1.0.1) - -### Added - -- Added a unix manual page. - -- Added release builder to Makefile - -### Changed - -- Various minor fixes. - - - -## [v1.0.0](https://codeberg.org/scip/tablizer/tree/v1.0.0) - 2022-09-28 - -[Full Changelog](https://codeberg.org/scip/tablizer/compare/02a64a5c3fe4220df2c791ff1421d16ebd428c19...v1.0.0) - -Initial release. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index c7b8dfe..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,113 +0,0 @@ -# No Code of Conduct - -*TL;DR:* This project does **NOT** have a so called Code of Conduct, -nor will it ever have one. - -## The Rant - -The reasons are somewhat complicated and I'll try my best to document -them here. - -Ethical codes or rules come along like laws. But how is ethical or -moral behavior defined? And who defines which behavior is ethical and -which is not? Certainly not me. - -Unless you live in a dictatorship (and more than half of the -population of planet earth do as of this writing), laws come into -existence by democratic procedures. Laws cover almost every aspect of -live in a society. Laws allow and forbid behavior and laws sanction -infringements. - -A software project like this one on the other hand is not a society. -There are not enough people involved to form democratic -structures. And there will always be a minority of users who have the -right to commit or reject code. How could any maintainer of a software -project dare to decree rules upon others? Actually, am I, the current -maintainer of this very project authorized to do so? - -I think the anser to this question clearly is NO. - -The issue is being complicated by the fact, that open source -development these days happens on planetary scale. And this planet -houses hundreds if not thousands of different cultures, philosophies, -ideologies and worldviews. The answer to many ethical questions will -in most cases vague and nebulous. - -Ones joke will always be another ones insult. - -Then there is the problem of language. I myself am not an english -native, but I publish everyting using the english language. I am able -to communicate with most people in the open source community because -of that. But I am certainly not able to understand everything and -everyone. There might be nuances to a sentence I don't sense, there -might be sarcastic connotations I don't understand or references to -historical figures, events or traditions I don't know and never have -heard of. - -Judging over other peoples online behavior looks like a titanic task -to me. It is just not my job to judge others, I am not legitimized or -authorized to do so and I am not interested in this kind of business. - -Another huge problem with ethical rules is that you need to outline -and enforce sanctions on those who violate the rules. But since I am -not an elected authority how would I be able to do this? I don't -know. And what happens if someone complains about myself? Shall I -remove myself from my own project? Come on! - -Last but not least there's the law. So, let's say someone in india -says something insulting to some other developer in an issue. Of -course german law does not apply to indian people. More, the insult -might actually not be an insult in india. In the end, nothing would -happen. Under normal circumstances, maintainers would delete the -posting, ban the user or remove push privileges etc. - -But then, is there a way for the offending user to defend himself? Of -course not, since neither indian or german law alone applies. I cannot -go to a german court and sue the guy and he cannot do the same in -india. Or - we possibly could but the judges on both countries would -just laugh and close the case. - -That being said, I don't have the power nor the tools, nor the -authority to enforce serious sanctions of any meaningful kind against -others. Therefore I cannot outline any rules whatsoever. - -And let's not even start talking about there undemocratic "comitees" -many projects are forming to circumvent this problem. Some projects -even include external entities like a lawer or some bureaucrat -somewhere just to have the ability to complain against a comitee -member. What a mess! - - - -## So, which are the ethical rules within this project then? - -Well, there are none. - -This project is about code, not society. It doesn't matter where you -come from, how you look, how you think, what you believe, who your -friends are, whay you said or did sometime in the past. I don't even -care if you are a human being. You are an alien so bored that you need -to submit code on github? Fine with me. You're a convicted criminal? I -don't give a shit! - -**The only thing I am interested here is Code and only Code.** - -So if anyhing happens here I don't like or I am obliged by law to act -on, I will decide on a case to case basis what to do. And -unfortunately, since this is the nature of a github project, you -cannot complain, object or protest. I am very sorry! - -If you will, let's at least outline these: - -- Please - just please - behave towards others as you'd expect others - to behave towards yourself. - -- Don't judge others for any reason. - -- Only judge the code. - -But these are not rules, only a friendly appeal to you as a developer -and user. - - -Thanks a lot! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 7d215b5..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,94 +0,0 @@ -## Project Goals - -The goal of this project is to build a small tool which helps in day -to day work with tabular output of various commandline programs. It -should be small, fast and easy to understand. The idea is to replace -multiline shell pipes using awk, sed and grep with just one -binary. - -There will be no GUI, no web interface, no public API of some sort, no -builtin interpreter. - -The programming language used for this project will always be -[GOLANG](https://go.dev/) with the exception of the documentation -([Perl POD](https://perldoc.perl.org/perlpod)) and the Makefile. - -# Contributing - -You can contribute to this project in various ways: - -## Open an issue - -If you encounter a problem or don't understand how the program works -or if you think the documentation is unclear, please don't hesitate to -open an issue. - -Please add as much information about the case as possible, such as: - -- Your environment (operating system etc) -- tablizer version (`tablizer --version`) -- Input data. Please replace sensitive information with mock data! -- Actual program output. -- Expected program output. -- Error message - if any. - -Be aware that I am working on this (and some other) project in my -spare time which is scarce. Therefore please don't expect me to -respond to your query within hours or even days. Be patient, but I -WILL respond. - -## Pull Requests - -Code and documentation help is always much appreciated! Please follow -thes guidelines to successfully contribute: - -- Every pull request shall be based on latest `development` - branch. `main` is only used for releases. - -- Execute the unit tests before committing: `make test`. There shall - be no errors. - -- Strive to be backwards compatible so that users who are already - using the program don't have to change their habits - unless it is - really neccessary. - -- Try to add a unit test for your addition. - -- Don't ever change existing unit tests! - -- Add a meaningful and comprehensive rationale about your contribution: - - Why do you think it might be useful for others? - - What did you actually change or add? - - Is there an open issue which this PR fixes and if so, please link - to that issue. - -- [Re-]format your code with `gofmt -s`. - -- Avoid unneccesary dependencies, especially for very small functions. - -- **If** a new dependency is being added, it must be compatible with - our [license agreement](LICENSE). - -- You need to accept that the code or documentation you contribute - will be redistributed under the terms of said license agreement. If - your contribution is considerably large or if you contribute - regularly, then feel free to add your name and if you want your - email address to the *AUTHORS* section of the - [manpage](tablizer.pod). - -- Adhere to the above mentioned project goals. - -- If you are unsure if your addition or change will be accepted, - better ask before starting coding. Open an issue about your proposal - and let's discuss it! That way we avoid doing unnessesary work on - both sides. - -Each pull request will be carefully reviewed and if it is a useful -addition it will be accepted. However, please be prepared that -sometimes a PR will be rejected. The reasons may vary and will be -documented. Perhaps the above guidelines are not matched, or the -addition seems to be not so useful from my perspective, maybe there -are too much changes or there might be changes I don't even -understand. - -But whatever happens: your contribution is always welcome! diff --git a/Makefile b/Makefile deleted file mode 100644 index beb723d..0000000 --- a/Makefile +++ /dev/null @@ -1,99 +0,0 @@ - -# Copyright © 2022 Thomas von Dein - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# -# no need to modify anything below -tool = tablizer -version = $(shell egrep "= .v" cfg/config.go | cut -d'=' -f2 | cut -d'"' -f 2) -archs = android darwin freebsd linux netbsd openbsd windows -PREFIX = /usr/local -UID = root -GID = 0 -BRANCH = $(shell git branch --show-current) -COMMIT = $(shell git rev-parse --short=8 HEAD) -BUILD = $(shell date +%Y.%m.%d.%H%M%S) -VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version)) -HAVE_POD := $(shell pod2text -h 2>/dev/null) - -all: $(tool).1 cmd/$(tool).go buildlocal - -%.1: %.pod -ifdef HAVE_POD - pod2man -c "User Commands" -r 1 -s 1 $*.pod > $*.1 -endif - -cmd/%.go: %.pod -ifdef HAVE_POD - echo "package cmd" > cmd/$*.go - echo >> cmd/$*.go - echo "var manpage = \`" >> cmd/$*.go - pod2text $*.pod >> cmd/$*.go - echo "\`" >> cmd/$*.go - - echo "var usage = \`" >> cmd/$*.go - awk '/SYNOPS/{f=1;next} /DESCR/{f=0} f' $*.pod | sed 's/^ //' >> cmd/$*.go - echo "\`" >> cmd/$*.go -endif - -buildlocal: - go build -ldflags "-X 'codeberg.org/scip/tablizer/cfg.VERSION=$(VERSION)'" - -release: - gh release create $(version) --generate-notes - -install: buildlocal - install -d -o $(UID) -g $(GID) $(PREFIX)/bin - install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1 - install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/ - install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/ - -clean: - rm -rf $(tool) releases coverage.out - -test: clean - go test -count=1 -cover ./... $(OPTS) - -singletest: - @echo "Call like this: 'make singletest TEST=TestPrepareColumns MOD=lib'" - go test -run $(TEST) codeberg.org/scip/tablizer/$(MOD) $(OPTS) - -cover-report: - go test ./... -cover -coverprofile=coverage.out - go tool cover -html=coverage.out - -show-versions: buildlocal - @echo "### tablizer version:" - @./tablizer --version - - @echo - @echo "### go module versions:" - @go list -m all - - @echo - @echo "### go version used for building:" - @grep -m 1 go go.mod - -goupdate: - go get -t -u=patch ./... - -lint: - golangci-lint run - -# keep til ireturn -lint-full: - golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,gci,godox,goimports,ireturn,stylecheck,testpackage,mirror,nestif,revive,goerr113,gomnd - gocritic check -enableAll *.go diff --git a/Makefile.dist b/Makefile.dist deleted file mode 100644 index cb76bca..0000000 --- a/Makefile.dist +++ /dev/null @@ -1,20 +0,0 @@ -# -*-make-*- - -.PHONY: install all - -tool = rpn -PREFIX = /usr/local -UID = root -GID = 0 - -all: - @echo "Type 'sudo make install' to install the tool." - @echo "To change prefix, type 'sudo make install PREFIX=/opt'" - -install: - install -d -o $(UID) -g $(GID) $(PREFIX)/bin - install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1 - install -d -o $(UID) -g $(GID) $(PREFIX)/share/doc - install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/ - install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/ - install -o $(UID) -g $(GID) -m 444 *.md $(PREFIX)/share/doc/ diff --git a/README.md b/README.md index ec47053..5c021d6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ ## tablizer - Manipulate tabular output of other programs +> [!IMPORTANT] +> This software is now being maintained on [Codeberg](https://codeberg.org/scip/tablizer/). + Tablizer can be used to re-format tabular output of other programs. While you could do this using standard unix tools, in some cases it's a hard job. With tablizer you can filter by column[s], diff --git a/TODO.md b/TODO.md deleted file mode 100644 index b9fdacc..0000000 --- a/TODO.md +++ /dev/null @@ -1,8 +0,0 @@ -## Fixes to be implemented - -## Features to be implemented - -- add comment support (csf.NewReader().Comment = '#') - -- add --no-headers option - diff --git a/cfg/config.go b/cfg/config.go deleted file mode 100644 index b61d988..0000000 --- a/cfg/config.go +++ /dev/null @@ -1,497 +0,0 @@ -/* -Copyright © 2022-2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -package cfg - -import ( - "errors" - "fmt" - "os" - "regexp" - "strings" - - "github.com/gookit/color" - "github.com/hashicorp/hcl/v2/hclsimple" -) - -const ( - Version = "v1.5.11" - MAXPARTS = 2 -) - -var ( - DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config" - VERSION string // maintained by -x - - SeparatorTemplates = map[string]string{ - ":tab:": `\s*\t\s*`, // tab but eats spaces around - ":spaces:": `\s{2,}`, // 2 or more spaces - ":pipe:": `\s*\|\s*`, // one pipe eating spaces around - ":default:": `(\s\s+|\t)`, // 2 or more spaces or tab - ":nonword:": `\W`, // word boundary - ":nondigit:": `\D`, // same for numbers - ":special:": `[\*\+\-_\(\)\[\]\{\}?\\/<>=&$§"':,\^]+`, // match any special char - ":nonprint:": `[[:^print:]]+`, // non printables - } -) - -// public config, set via config file or using defaults -type Settings struct { - FG string `hcl:"FG"` - BG string `hcl:"BG"` - HighlightFG string `hcl:"HighlightFG"` - HighlightBG string `hcl:"HighlightBG"` - NoHighlightFG string `hcl:"NoHighlightFG"` - NoHighlightBG string `hcl:"NoHighlightBG"` - HighlightHdrFG string `hcl:"HighlightHdrFG"` - HighlightHdrBG string `hcl:"HighlightHdrBG"` -} - -type Transposer struct { - Search regexp.Regexp - Replace string -} - -type Pattern struct { - Pattern string - PatternRe *regexp.Regexp - Negate bool -} - -type Filter struct { - Regex *regexp.Regexp - Negate bool -} - -// internal config -type Config struct { - Debug bool - Numbering bool - NoHeaders bool - Columns string - UseColumns []int - YankColumns string - UseYankColumns []int - Separator string - OutputMode int - InvertMatch bool - Patterns []*Pattern - UseFuzzySearch bool - UseHighlight bool - Interactive bool - InputJSON bool - AutoHeaders bool - CustomHeaders []string - - SortMode string - SortDescending bool - SortByColumn string // 1,2 - UseSortByColumn []int // []int{1,2} - - TransposeColumns string // 1,2 - UseTransposeColumns []int // []int{1,2} - Transposers []string // []string{"/ /-/", "/foo/bar/"} - UseTransposers []Transposer // {Search: re, Replace: string} - - /* - FIXME: make configurable somehow, config file or ENV - see https://github.com/gookit/color. - */ - ColorStyle color.Style - HighlightStyle color.Style - NoHighlightStyle color.Style - HighlightHdrStyle color.Style - - NoColor bool - - // config file, optional - Configfile string - - Settings Settings - - // used for field filtering - Rawfilters []string - Filters map[string]Filter //map[string]*regexp.Regexp - - // -r - InputFile string - - OFS string -} - -// maps outputmode short flags to output mode, ie. -O => -o orgtbl -type Modeflag struct { - X bool - O bool - M bool - S bool - Y bool - A bool - C bool - J bool -} - -// used for switching printers -const ( - Extended = iota + 1 - Orgtbl - Markdown - Shell - Yaml - CSV - ASCII - Json -) - -// various sort types -type Sortmode struct { - Numeric bool - Time bool - Age bool -} - -// default color schemes -func (conf *Config) Colors() map[color.Level]map[string]color.Color { - colors := map[color.Level]map[string]color.Color{ - color.Level16: { - "bg": color.BgGreen, "fg": color.FgWhite, - "hlbg": color.BgGray, "hlfg": color.FgWhite, - }, - color.Level256: { - "bg": color.BgLightGreen, "fg": color.FgWhite, - "hlbg": color.BgLightBlue, "hlfg": color.FgWhite, - }, - color.LevelRgb: { - "bg": color.BgLightGreen, "fg": color.FgWhite, - "hlbg": color.BgHiGreen, "hlfg": color.FgWhite, - "nohlbg": color.BgWhite, "nohlfg": color.FgLightGreen, - "hdrbg": color.BgBlue, "hdrfg": color.FgWhite, - }, - } - - if len(conf.Settings.BG) > 0 { - colors[color.Level16]["bg"] = ColorStringToBGColor(conf.Settings.BG) - colors[color.Level256]["bg"] = ColorStringToBGColor(conf.Settings.BG) - colors[color.LevelRgb]["bg"] = ColorStringToBGColor(conf.Settings.BG) - } - - if len(conf.Settings.FG) > 0 { - colors[color.Level16]["fg"] = ColorStringToColor(conf.Settings.FG) - colors[color.Level256]["fg"] = ColorStringToColor(conf.Settings.FG) - colors[color.LevelRgb]["fg"] = ColorStringToColor(conf.Settings.FG) - } - - if len(conf.Settings.HighlightBG) > 0 { - colors[color.Level16]["hlbg"] = ColorStringToBGColor(conf.Settings.HighlightBG) - colors[color.Level256]["hlbg"] = ColorStringToBGColor(conf.Settings.HighlightBG) - colors[color.LevelRgb]["hlbg"] = ColorStringToBGColor(conf.Settings.HighlightBG) - } - - if len(conf.Settings.HighlightFG) > 0 { - colors[color.Level16]["hlfg"] = ColorStringToColor(conf.Settings.HighlightFG) - colors[color.Level256]["hlfg"] = ColorStringToColor(conf.Settings.HighlightFG) - colors[color.LevelRgb]["hlfg"] = ColorStringToColor(conf.Settings.HighlightFG) - } - - if len(conf.Settings.NoHighlightBG) > 0 { - colors[color.Level16]["nohlbg"] = ColorStringToBGColor(conf.Settings.NoHighlightBG) - colors[color.Level256]["nohlbg"] = ColorStringToBGColor(conf.Settings.NoHighlightBG) - colors[color.LevelRgb]["nohlbg"] = ColorStringToBGColor(conf.Settings.NoHighlightBG) - } - - if len(conf.Settings.NoHighlightFG) > 0 { - colors[color.Level16]["nohlfg"] = ColorStringToColor(conf.Settings.NoHighlightFG) - colors[color.Level256]["nohlfg"] = ColorStringToColor(conf.Settings.NoHighlightFG) - colors[color.LevelRgb]["nohlfg"] = ColorStringToColor(conf.Settings.NoHighlightFG) - } - - if len(conf.Settings.HighlightHdrBG) > 0 { - colors[color.Level16]["hdrbg"] = ColorStringToBGColor(conf.Settings.HighlightHdrBG) - colors[color.Level256]["hdrbg"] = ColorStringToBGColor(conf.Settings.HighlightHdrBG) - colors[color.LevelRgb]["hdrbg"] = ColorStringToBGColor(conf.Settings.HighlightHdrBG) - } - - if len(conf.Settings.HighlightHdrFG) > 0 { - colors[color.Level16]["hdrfg"] = ColorStringToColor(conf.Settings.HighlightHdrFG) - colors[color.Level256]["hdrfg"] = ColorStringToColor(conf.Settings.HighlightHdrFG) - colors[color.LevelRgb]["hdrfg"] = ColorStringToColor(conf.Settings.HighlightHdrFG) - } - - return colors -} - -// find supported color mode, modifies config based on constants -func (conf *Config) DetermineColormode() { - if !isTerminal(os.Stdout) { - color.Disable() - } else { - level := color.TermColorLevel() - colors := conf.Colors() - - conf.ColorStyle = color.New(colors[level]["bg"], colors[level]["fg"]) - conf.HighlightStyle = color.New(colors[level]["hlbg"], colors[level]["hlfg"]) - conf.NoHighlightStyle = color.New(colors[level]["nohlbg"], colors[level]["nohlfg"]) - conf.HighlightHdrStyle = color.New(colors[level]["hdrbg"], colors[level]["hdrfg"]) - } -} - -// Return true if current terminal is interactive -func isTerminal(f *os.File) bool { - o, _ := f.Stat() - - return (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice -} - -// main program version -// generated version string, used by -v contains lib.Version on -// -// main branch, and lib.Version-$branch-$lastcommit-$date on -// -// development branch -func Getversion() string { - return fmt.Sprintf("This is tablizer version %s", VERSION) -} - -func (conf *Config) PrepareSortFlags(flag Sortmode) { - switch { - case flag.Numeric: - conf.SortMode = "numeric" - case flag.Age: - conf.SortMode = "duration" - case flag.Time: - conf.SortMode = "time" - default: - conf.SortMode = "string" - } -} - -func (conf *Config) PrepareModeFlags(flag Modeflag) { - switch { - case flag.X: - conf.OutputMode = Extended - case flag.O: - conf.OutputMode = Orgtbl - case flag.M: - conf.OutputMode = Markdown - case flag.S: - conf.OutputMode = Shell - case flag.Y: - conf.OutputMode = Yaml - case flag.C: - conf.OutputMode = CSV - case flag.J: - conf.OutputMode = Json - default: - conf.OutputMode = ASCII - } -} - -func (conf *Config) PrepareFilters() error { - conf.Filters = make(map[string]Filter, len(conf.Rawfilters)) - - for _, rawfilter := range conf.Rawfilters { - filter := Filter{} - - parts := strings.Split(rawfilter, "!=") - if len(parts) != MAXPARTS { - parts = strings.Split(rawfilter, "=") - - if len(parts) != MAXPARTS { - return errors.New("filter field and value must be separated by '=' or '!='") - } - } else { - filter.Negate = true - } - - reg, err := regexp.Compile(parts[1]) - if err != nil { - return fmt.Errorf("failed to compile filter regex for field %s: %w", - parts[0], err) - } - - filter.Regex = reg - conf.Filters[strings.ToLower(parts[0])] = filter - } - - return nil -} - -// check if transposers match transposer columns and prepare transposer structs -func (conf *Config) PrepareTransposers() error { - if len(conf.Transposers) != len(conf.UseTransposeColumns) { - return fmt.Errorf("the number of transposers needs to correspond to the number of transpose columns: %d != %d", - len(conf.Transposers), len(conf.UseTransposeColumns)) - } - - for _, transposer := range conf.Transposers { - parts := strings.Split(transposer, string(transposer[0])) - if len(parts) != 4 { - return fmt.Errorf("transposer function must have the format /regexp/replace-string/") - } - - conf.UseTransposers = append(conf.UseTransposers, - Transposer{ - Search: *regexp.MustCompile(parts[1]), - Replace: parts[2]}, - ) - } - - return nil -} - -func (conf *Config) CheckEnv() { - // check for environment vars, command line flags have precedence, - // NO_COLOR is being checked by the color module itself. - if !conf.Numbering { - _, set := os.LookupEnv("T_HEADER_NUMBERING") - if set { - conf.Numbering = true - } - } - - if len(conf.Columns) == 0 { - cols := os.Getenv("T_COLUMNS") - if len(cols) > 1 { - conf.Columns = cols - } - } -} - -func (conf *Config) ApplyDefaults() { - // mode specific defaults - if conf.OutputMode == Yaml || conf.OutputMode == CSV { - conf.Numbering = false - } - - if conf.Separator[0] == ':' && conf.Separator[len(conf.Separator)-1] == ':' { - separator, ok := SeparatorTemplates[conf.Separator] - if ok { - conf.Separator = separator - } - } -} - -func (conf *Config) PreparePattern(patterns []*Pattern) error { - // regex checks if a pattern looks like /$pattern/[i!] - flagre := regexp.MustCompile(`^/(.*)/([i!]*)$`) - - for _, pattern := range patterns { - matches := flagre.FindAllStringSubmatch(pattern.Pattern, -1) - - // we have a regex with flags - for _, match := range matches { - pattern.Pattern = match[1] // the inner part is our actual pattern - flags := match[2] // the flags - - for _, flag := range flags { - switch flag { - case 'i': - pattern.Pattern = `(?i)` + pattern.Pattern - case '!': - pattern.Negate = true - } - } - } - - PatternRe, err := regexp.Compile(pattern.Pattern) - if err != nil { - return fmt.Errorf("regexp pattern %s is invalid: %w", pattern.Pattern, err) - } - - pattern.PatternRe = PatternRe - } - - conf.Patterns = patterns - - return nil -} - -func (conf *Config) PrepareCustomHeaders(custom string) { - if len(custom) > 0 { - conf.CustomHeaders = strings.Split(custom, ",") - } -} - -// Parse config file. Ignore if the file doesn't exist but return an -// error if it exists but fails to read or parse -func (conf *Config) ParseConfigfile() error { - path, err := os.Stat(conf.Configfile) - - if err != nil { - if os.IsNotExist(err) { - // ignore non-existent files - return nil - } - - return fmt.Errorf("failed to stat config file: %w", err) - } - - if path.IsDir() { - // ignore non-existent or dirs - return nil - } - - configstring, err := os.ReadFile(path.Name()) - if err != nil { - return fmt.Errorf("failed to read config file %s: %w", path.Name(), err) - } - - err = hclsimple.Decode( - path.Name(), - configstring, - nil, - &conf.Settings) - if err != nil { - return fmt.Errorf("failed to load configuration file %s: %w", - path.Name(), err) - } - - return nil -} - -// translate color string to internal color value -func ColorStringToColor(colorname string) color.Color { - for name, color := range color.FgColors { - if name == colorname { - return color - } - } - - for name, color := range color.ExFgColors { - if name == colorname { - return color - } - } - - return color.Normal -} - -// same, for background colors -func ColorStringToBGColor(colorname string) color.Color { - for name, color := range color.BgColors { - if name == colorname { - return color - } - } - - for name, color := range color.ExBgColors { - if name == colorname { - return color - } - } - - return color.Normal -} diff --git a/cfg/config_test.go b/cfg/config_test.go deleted file mode 100644 index b4b1058..0000000 --- a/cfg/config_test.go +++ /dev/null @@ -1,138 +0,0 @@ -/* -Copyright © 2022 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cfg - -import ( - "fmt" - // "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPrepareModeFlags(t *testing.T) { - var tests = []struct { - flag Modeflag - expect int // output (constant enum) - }{ - // short commandline flags like -M - {Modeflag{X: true}, Extended}, - {Modeflag{S: true}, Shell}, - {Modeflag{O: true}, Orgtbl}, - {Modeflag{Y: true}, Yaml}, - {Modeflag{M: true}, Markdown}, - {Modeflag{}, ASCII}, - } - - // FIXME: use a map for easier printing - for _, testdata := range tests { - testname := fmt.Sprintf("PrepareModeFlags-expect-%d", testdata.expect) - t.Run(testname, func(t *testing.T) { - conf := Config{} - - conf.PrepareModeFlags(testdata.flag) - - assert.EqualValues(t, testdata.expect, conf.OutputMode) - }) - } -} - -func TestPrepareSortFlags(t *testing.T) { - var tests = []struct { - flag Sortmode - expect string // output - }{ - // short commandline flags like -M - {Sortmode{Numeric: true}, "numeric"}, - {Sortmode{Age: true}, "duration"}, - {Sortmode{Time: true}, "time"}, - {Sortmode{}, "string"}, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("PrepareSortFlags-expect-%s", testdata.expect) - t.Run(testname, func(t *testing.T) { - conf := Config{} - - conf.PrepareSortFlags(testdata.flag) - - assert.EqualValues(t, testdata.expect, conf.SortMode) - }) - } -} - -func TestPreparePattern(t *testing.T) { - var tests = []struct { - patterns []*Pattern - name string - wanterror bool - wanticase bool - wantneg bool - }{ - { - []*Pattern{{Pattern: "[A-Z]+"}}, - "simple", - false, - false, - false, - }, - { - []*Pattern{{Pattern: "[a-z"}}, - "regfail", - true, - false, - false, - }, - { - []*Pattern{{Pattern: "/[A-Z]+/i"}}, - "icase", - false, - true, - false, - }, - { - []*Pattern{{Pattern: "/[A-Z]+/!"}}, - "negate", - false, - false, - true, - }, - { - []*Pattern{{Pattern: "/[A-Z]+/!i"}}, - "negicase", - false, - true, - true, - }, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("PreparePattern-pattern-%s-wanterr-%t", testdata.name, testdata.wanterror) - t.Run(testname, func(t *testing.T) { - conf := Config{} - - err := conf.PreparePattern(testdata.patterns) - - if testdata.wanterror { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 6086305..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,208 +0,0 @@ -/* -Copyright © 2022-2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -package cmd - -import ( - "errors" - "fmt" - "os" - "slices" - "strings" - - "github.com/spf13/cobra" - "codeberg.org/scip/tablizer/cfg" - "codeberg.org/scip/tablizer/lib" -) - -func completion(cmd *cobra.Command, mode string) error { - switch mode { - case "bash": - return cmd.Root().GenBashCompletion(os.Stdout) - case "zsh": - return cmd.Root().GenZshCompletion(os.Stdout) - case "fish": - return cmd.Root().GenFishCompletion(os.Stdout, true) - case "powershell": - return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) - default: - return errors.New("invalid shell parameter! Valid ones: bash|zsh|fish|powershell") - } -} - -// we die with exit 1 if there's an error -func wrapE(err error) { - if err != nil { - fmt.Println(err.Error()) - os.Exit(1) - } -} - -func Execute() { - var ( - conf cfg.Config - ShowManual bool - ShowVersion bool - ShowCompletion string - modeflag cfg.Modeflag - sortmode cfg.Sortmode - headers string - ) - - var rootCmd = &cobra.Command{ - Use: "tablizer [regex] [file, ...]", - Short: "[Re-]tabularize tabular data", - Long: `Manipulate tabular output of other programs`, - - Run: func(cmd *cobra.Command, args []string) { - if ShowVersion { - fmt.Println(cfg.Getversion()) - - return - } - - if ShowManual { - lib.Pager("tablizer manual page", manpage) - - return - } - - if len(ShowCompletion) > 0 { - wrapE(completion(cmd, ShowCompletion)) - - return - } - - // Setup - wrapE(conf.ParseConfigfile()) - - conf.CheckEnv() - conf.PrepareModeFlags(modeflag) - conf.PrepareSortFlags(sortmode) - conf.PrepareCustomHeaders(headers) - - wrapE(conf.PrepareFilters()) - - conf.DetermineColormode() - conf.ApplyDefaults() - - // actual execution starts here - wrapE(lib.ProcessFiles(&conf, args)) - }, - } - - // options - rootCmd.PersistentFlags().BoolVarP(&conf.Debug, "debug", "d", false, - "Enable debugging") - rootCmd.PersistentFlags().BoolVarP(&conf.Numbering, "numbering", "n", false, - "Disable header numbering") - rootCmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "H", false, - "Disable header display") - rootCmd.PersistentFlags().BoolVarP(&conf.NoColor, "no-color", "N", false, - "Disable pattern highlighting") - rootCmd.PersistentFlags().BoolVarP(&ShowVersion, "version", "V", false, - "Print program version") - rootCmd.PersistentFlags().BoolVarP(&conf.InvertMatch, "invert-match", "v", false, - "select non-matching rows") - rootCmd.PersistentFlags().BoolVarP(&ShowManual, "man", "m", false, - "Display manual page") - rootCmd.PersistentFlags().BoolVarP(&conf.UseFuzzySearch, "fuzzy", "z", false, - "Use fuzzy searching") - rootCmd.PersistentFlags().BoolVarP(&conf.UseHighlight, "highlight-lines", "L", false, - "Use alternating background colors") - rootCmd.PersistentFlags().StringVarP(&ShowCompletion, "completion", "", "", - "Display completion code") - rootCmd.PersistentFlags().StringVarP(&conf.Separator, "separator", "s", cfg.SeparatorTemplates[":default:"], - "Custom field separator") - rootCmd.PersistentFlags().StringVarP(&conf.Columns, "columns", "c", "", - "Only show the speficied columns (separated by ,)") - rootCmd.PersistentFlags().StringVarP(&conf.YankColumns, "yank-columns", "y", "", - "Yank the speficied columns (separated by ,) to the clipboard") - rootCmd.PersistentFlags().StringVarP(&conf.TransposeColumns, "transpose-columns", "T", "", - "Transpose the speficied columns (separated by ,)") - rootCmd.PersistentFlags().BoolVarP(&conf.Interactive, "interactive", "I", false, - "interactive mode") - rootCmd.PersistentFlags().StringVarP(&conf.OFS, "ofs", "o", "", - "Output field separator (' ' for ascii table, ',' for CSV)") - rootCmd.PersistentFlags().BoolVarP(&conf.InputJSON, "json", "j", false, - "JSON input mode") - rootCmd.PersistentFlags().BoolVarP(&conf.AutoHeaders, "auto-headers", "g", false, - "Generate headers automatically") - rootCmd.PersistentFlags().StringVarP(&headers, "custom-headers", "x", "", - "Custom headers") - - // sort options - rootCmd.PersistentFlags().StringVarP(&conf.SortByColumn, "sort-by", "k", "", - "Sort by column (default: 1)") - - // sort mode, only 1 allowed - rootCmd.PersistentFlags().BoolVarP(&conf.SortDescending, "sort-desc", "D", false, - "Sort in descending order (default: ascending)") - rootCmd.PersistentFlags().BoolVarP(&sortmode.Numeric, "sort-numeric", "i", false, - "sort according to string numerical value") - rootCmd.PersistentFlags().BoolVarP(&sortmode.Time, "sort-time", "t", false, - "sort according to time string") - rootCmd.PersistentFlags().BoolVarP(&sortmode.Age, "sort-age", "a", false, - "sort according to age (duration) string") - rootCmd.MarkFlagsMutuallyExclusive("sort-numeric", "sort-time", - "sort-age") - - // output flags, only 1 allowed - rootCmd.PersistentFlags().BoolVarP(&modeflag.X, "extended", "X", false, - "Enable extended output") - rootCmd.PersistentFlags().BoolVarP(&modeflag.M, "markdown", "M", false, - "Enable markdown table output") - rootCmd.PersistentFlags().BoolVarP(&modeflag.O, "orgtbl", "O", false, - "Enable org-mode table output") - rootCmd.PersistentFlags().BoolVarP(&modeflag.S, "shell", "S", false, - "Enable shell mode output") - rootCmd.PersistentFlags().BoolVarP(&modeflag.Y, "yaml", "Y", false, - "Enable yaml output") - rootCmd.PersistentFlags().BoolVarP(&modeflag.J, "jsonout", "J", false, - "Enable json output") - rootCmd.PersistentFlags().BoolVarP(&modeflag.C, "csv", "C", false, - "Enable CSV output") - rootCmd.PersistentFlags().BoolVarP(&modeflag.A, "ascii", "A", false, - "Enable ASCII output (default)") - rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl", - "shell", "yaml", "csv") - - // config file - rootCmd.PersistentFlags().StringVarP(&conf.Configfile, "config", "f", cfg.DefaultConfigfile, - "config file (default: ~/.config/tablizer/config)") - - // filters - rootCmd.PersistentFlags().StringArrayVarP(&conf.Rawfilters, - "filter", "F", nil, "Filter by field (field=regexp || field!=regexp)") - rootCmd.PersistentFlags().StringArrayVarP(&conf.Transposers, - "regex-transposer", "R", nil, "apply /search/replace/ regexp to fields given in -T") - - // input - rootCmd.PersistentFlags().StringVarP(&conf.InputFile, "read-file", "r", "", - "Read input data from file") - - rootCmd.SetUsageTemplate(strings.TrimSpace(usage) + "\n") - - if slices.Contains(os.Args, "-h") { - fmt.Println(shortusage) - os.Exit(0) - } - - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} diff --git a/cmd/shortusage.go b/cmd/shortusage.go deleted file mode 100644 index 34cc3ee..0000000 --- a/cmd/shortusage.go +++ /dev/null @@ -1,18 +0,0 @@ -package cmd - -const shortusage = `tablizer [regex,...] [-r file] [flags] --c col,... show specified columns -L highlight matching lines --k col,... sort by specified columns -j read JSON input --F col=reg filter field with regexp -v invert match --T col,... transpose specified columns -n numberize columns --R /from/to/ apply replacement to columns in -T -N do not use colors --y col,... yank columns to clipboard -H do not show headers ---ofs char output field separator -s specify field separator --r file read input from file -z use fuzzy search --f file read config from file -I interactive filter mode --x col,... use custom headers -d debug --o char use char as output separator -g auto generate headers - --O org -C CSV -M md -X ext -S shell -Y yaml -J json -D sort descending order --m show manual --help show detailed help -v show version --a sort by age -i sort numerically -t sort by time` diff --git a/cmd/tablizer.go b/cmd/tablizer.go deleted file mode 100644 index 9a4e851..0000000 --- a/cmd/tablizer.go +++ /dev/null @@ -1,556 +0,0 @@ -package cmd - -var manpage = ` -NAME - tablizer - Manipulate tabular output of other programs - -SYNOPSIS - Usage: - tablizer [regex,...] [-r file] [flags] - - Operational Flags: - -c, --columns string Only show the speficied columns (separated by ,) - -v, --invert-match select non-matching rows - -n, --numbering Enable header numbering - -N, --no-color Disable pattern highlighting - -H, --no-headers Disable headers display - -s, --separator Custom field separator (maybe char, string or :class:) - -k, --sort-by Sort by column (default: 1) - -z, --fuzzy Use fuzzy search [experimental] - -F, --filter Filter given field with regex, can be used multiple times - -T, --transpose-columns string Transpose the speficied columns (separated by ,) - -R, --regex-transposer Apply /search/replace/ regexp to fields given in -T - -j, --json Read JSON input (must be array of hashes) - -I, --interactive Interactively filter and select rows - -g, --auto-headers Generate headers if there are none present in input - -x, --custom-headers a,b,... Use custom headers, separated by comma - - Output Flags (mutually exclusive): - -X, --extended Enable extended output - -M, --markdown Enable markdown table output - -O, --orgtbl Enable org-mode table output - -S, --shell Enable shell evaluable output - -Y, --yaml Enable yaml output - -J, --jsonout Enable JSON output - -C, --csv Enable CSV output - -A, --ascii Default output mode, ascii tabular - -L, --hightlight-lines Use alternating background colors for tables - -o, --ofs Output field separator, used by -A and -C. - -y, --yank-columns Yank specified columns (separated by ,) to clipboard, - space separated - - Sort Mode Flags (mutually exclusive): - -a, --sort-age sort according to age (duration) string - -D, --sort-desc Sort in descending order (default: ascending) - -i, --sort-numeric sort according to string numerical value - -t, --sort-time sort according to time string - - Other Flags: - -r --read-file Use as input instead of STDIN - --completion Generate the autocompletion script for - -f, --config Configuration file (default: ~/.config/tablizer/config) - -d, --debug Enable debugging - -h, --help help for tablizer - -m, --man Display manual page - -V, --version Print program version - -DESCRIPTION - Many programs generate tabular output. But sometimes you need to - post-process these tables, you may need to remove one or more columns or - you may want to filter for some pattern (See PATTERNS) or you may need - the output in another program and need to parse it somehow. Standard - unix tools such as awk(1), grep(1) or column(1) may help, but sometimes - it's a tedious business. - - Let's take the output of the tool kubectl. It contains cells with - withespace and they do not separate columns by TAB characters. This is - not easy to process. - - You can use tablizer to do these and more things. - - tablizer analyses the header fields of a table, registers the column - positions of each header field and separates columns by those positions. - - Without any options it reads its input from "STDIN", but you can also - specify a file as a parameter. If you want to reduce the output by some - regular expression, just specify it as its first parameter. You may also - use the -v option to exclude all rows which match the pattern. Hence: - - # read from STDIN - kubectl get pods | tablizer - - # read a file - tablizer -r filename - - # search for pattern in a file (works like grep) - tablizer regex -r filename - - # search for pattern in STDIN - kubectl get pods | tablizer regex - - The output looks like the original one. You can add the option -n, then - every header field will have a numer associated with it, e.g.: - - NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) - - These numbers denote the column and you can use them to specify which - columns you want to have in your output (see COLUMNS: - - kubectl get pods | tablizer -c1,3 - - You can specify the numbers in any order but output will always follow - the original order. - - However, you may also just use the header names instead of numbers, eg: - - kubectl get pods | tablizer -cname,status - - You can also use regular expressions with -c, eg: - - kubectl get pods | tablizer -c '[ae]' - - By default tablizer shows a header containing the names of each column. - This can be disabled using the -H option. Be aware that this only - affects tabular output modes. Shell, Extended, Yaml and CSV output modes - always use the column names. - - By default, if a pattern has been speficied, matches will be - highlighted. You can disable this behavior with the -N option. - - Use the -k option to specify by which column to sort the tabular data - (as in GNU sort(1)). The default sort column is the first one. You can - specify column numbers or names. Column numbers start with 1, names are - case insensitive. You can specify multiple columns separated by comma to - sort, but the type must be the same. For example if you want to sort - numerically, all columns must be numbers. If you use column numbers, - then be aware, that these are the numbers before column extraction. For - example if you have a table with 4 columns and specify "-c4", then only - 1 column (the fourth) will be printed, however if you want to sort by - this column, you'll have to specify "-k4". - - The default sort order is ascending. You can change this to descending - order using the option -D. The default sort order is by alphanumeric - string, but there are other sort modes: - - -a --sort-age - Sorts duration strings like "1d4h32m51s". - - -i --sort-numeric - Sorts numeric fields. - - -t --sort-time - Sorts timestamps. - - Finally the -d option enables debugging output which is mostly useful - for the developer. - - SEPARATOR - The option -s can be a single character, in which case the CSV parser - will be invoked. You can also specify a string as separator. The string - will be interpreted as literal string unless it is a valid go regular - expression. For example: - - -s '\t{2,}\' - - is being used as a regexp and will match two or more consecutive tabs. - - -s 'foo' - - on the other hand is no regular expression and will be used literally. - - To make live easier, there are a couple of predefined regular - expressions, which you can specify as classes: - - * :tab: - - Matches a tab and eats spaces around it. - - * :spaces: - - Matches 2 or more spaces. - - * :pipe: - - Matches a pipe character and eats spaces around it. - - * :default: - - Matches 2 or more spaces or tab. This is the default separator if - none is specified. - - * :nonword: - - Matches a non-word character. - - * :nondigit: - - Matches a non-digit character. - - * :special: - - Matches one or more special chars like brackets, dollar sign, - slashes etc. - - * :nonprint: - - Matches one or more non-printable characters. - - PATTERNS AND FILTERING - You can reduce the rows being displayed by using one or more regular - expression patterns. The regexp language being used is the one of - GOLANG, refer to the syntax cheat sheet here: - . - - If you want to read a more comprehensive documentation about the topic - and have perl installed you can read it with: - - perldoc perlre - - Or read it online: . But please note - that the GO regexp engine does NOT support all perl regex terms, - especially look-ahead and look-behind. - - If you want to supply flags to a regex, then surround it with slashes - and append the flag. The following flags are supported: - - i => case insensitive - ! => negative match - - Example for a case insensitive search: - - kubectl get pods -A | tablizer "/account/i" - - If you use the "!" flag, then the regex match will be negated, that is, - if a line in the input matches the given regex, but "!" is supplied, - tablizer will NOT include it in the output. - - For example, here we want to get all lines matching "foo" but not "bar": - - cat table | tablizer foo '/bar/!' - - This would match a line "foo zorro" but not "foo bar". - - The flags can also be combined. - - You can also use the experimental fuzzy search feature by providing the - option -z, in which case the pattern is regarded as a fuzzy search term, - not a regexp. - - Sometimes you want to filter by one or more columns. You can do that - using the -F option. The option can be specified multiple times and has - the following format: - - fieldname=regexp - - Fieldnames (== columns headers) are case insensitive. - - If you specify more than one filter, both filters have to match (AND - operation). - - These field filters can also be negated: - - fieldname!=regexp - - If the option -v is specified, the filtering is inverted. - - INTERACTIVE FILTERING - You can also use the interactive mode, enabled with "-I" to filter and - select rows. This mode is complementary, that is, other filter options - are still being respected. - - To enter e filter, hit "/", enter a filter string and finish with - "ENTER". Use "SPACE" to select/deselect rows, use "a" to select all - (visible) rows. - - Commit your selection with "q". The selected rows are being fed to the - requested output mode as usual. Abort with "CTRL-c", in which case the - results of the interactive mode are being ignored and all rows are being - fed to output. - - COLUMNS - The parameter -c can be used to specify, which columns to display. By - default tablizer numerizes the header names and these numbers can be - used to specify which header to display, see example above. - - However, beside numbers, you can also use regular expressions with -c, - also separated by comma. And you can mix column numbers with regexps. - - Lets take this table: - - PID TTY TIME CMD - 14001 pts/0 00:00:00 bash - 42871 pts/0 00:00:00 ps - 42872 pts/0 00:00:00 sed - - We want to see only the CMD column and use a regex for this: - - ps | tablizer -s '\s+' -c C - CMD(4) - bash - ps - tablizer - sed - - where "C" is our regexp which matches CMD. - - If a column specifier doesn't look like a regular expression, matching - against header fields will be case insensitive. So, if you have a field - with the name "ID" then these will all match: "-c id", "-c Id". The same - rule applies to the options "-T" and "-F". - - TRANSPOSE FIELDS USING REGEXPS - You can manipulate field contents using regular expressions. You have to - tell tablizer which field[s] to operate on using the option "-T" and the - search/replace pattern using "-R". The number of columns and patterns - must match. - - A search/replace pattern consists of the following elements: - - /search-regexp/replace-string/ - - The separator can be any valid character. Especially if you want to use - a regexp containing the "/" character, eg: - - |search-regexp|replace-string| - - Example: - - cat t/testtable2 - NAME DURATION - x 10 - a 100 - z 0 - u 4 - k 6 - - cat t/testtable2 | tablizer -T2 -R '/^\d/4/' -n - NAME DURATION - x 40 - a 400 - z 4 - u 4 - k 4 - - OUTPUT MODES - There might be cases when the tabular output of a program is way too - large for your current terminal but you still need to see every column. - In such cases the -o extended or -X option can be useful which enables - *extended mode*. In this mode, each row will be printed vertically, - header left, value right, aligned by the field widths. Here's an - example: - - kubectl get pods | ./tablizer -o extended - NAME: repldepl-7bcd8d5b64-7zq4l - READY: 1/1 - STATUS: Running - RESTARTS: 1 (71m ago) - AGE: 5h28m - - You can of course still use a regex to reduce the number of rows - displayed. - - The option -o shell can be used if the output has to be processed by the - shell, it prints variable assignments for each cell, one line per row: - - kubectl get pods | ./tablizer -o extended ./tablizer -o shell - NAME="repldepl-7bcd8d5b64-7zq4l" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" - NAME="repldepl-7bcd8d5b64-m48n8" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" - NAME="repldepl-7bcd8d5b64-q2bf4" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" - - You can use this in an eval loop. - - Beside normal ascii mode (the default) and extended mode there are more - output modes available: orgtbl which prints an Emacs org-mode table and - markdown which prints a Markdown table, yaml, which prints yaml encoding - and CSV mode, which prints a comma separated value file. - - PUT FIELDS TO CLIPBOARD - You can let tablizer put fields to the clipboard using the option "-y". - This best fits the use-case when the result of your filtering yields - just one row. For example: - - cloudctl cluster ls | tablizer -yid matchbox - - If "matchbox" matches one cluster, you can immediately use the id of - that cluster somewhere else and paste it. Of course, if there are - multiple matches, then all id's will be put into the clipboard separated - by one space. - - ENVIRONMENT VARIABLES - tablizer supports certain environment variables which use can use to - influence program behavior. Commandline flags have always precedence - over environment variables. - - - enable numbering of header fields, like -n. - - comma separated list of columns to output, like -c - - disable colorization of matches, like -N - - COMPLETION - Shell completion for command line options can be enabled by using the - --completion flag. The required parameter is the name of your shell. - Currently supported are: bash, zsh, fish and powershell. - - Detailed instructions: - - Bash: - source <(tablizer --completion bash) - - To load completions for each session, execute once: - - # Linux: - $ tablizer --completion bash > /etc/bash_completion.d/tablizer - - # macOS: - $ tablizer --completion bash > $(brew --prefix)/etc/bash_completion.d/tablizer - - Zsh: - If shell completion is not already enabled in your environment, you - will need to enable it. You can execute the following once: - - echo "autoload -U compinit; compinit" >> ~/.zshrc - - To load completions for each session, execute once: - - $ tablizer --completion zsh > "${fpath[1]}/_tablizer" - - You will need to start a new shell for this setup to take effect. - - fish: - tablizer --completion fish | source - - To load completions for each session, execute once: - - tablizer --completion fish > ~/.config/fish/completions/tablizer.fish - - PowerShell: - tablizer --completion powershell | Out-String | Invoke-Expression - - To load completions for every new session, run: - - tablizer --completion powershell > tablizer.ps1 - - and source this file from your PowerShell profile. - -CONFIGURATION AND COLORS - YOu can put certain configuration values into a configuration file in - HCL format. By default tablizer looks for - "$HOME/.config/tablizer/config", but you can provide one using the - parameter "-f". - - In the configuration the following variables can be defined: - - BG = "lightGreen" - FG = "white" - HighlightBG = "lightGreen" - HighlightFG = "white" - NoHighlightBG = "white" - NoHighlightFG = "lightGreen" - HighlightHdrBG = "red" - HighlightHdrFG = "white" - - The following color definitions are available: - - black, blue, cyan, darkGray, default, green, lightBlue, lightCyan, - lightGreen, lightMagenta, lightRed, lightWhite, lightYellow, magenta, - red, white, yellow - - The Variables FG and BG are being used to highlight matches. The other - *FG and *BG variables are for colored table output (enabled with the - "-L" parameter). - - Colorization can be turned off completely either by setting the - parameter "-N" or the environment variable NO_COLOR to a true value. - -BUGS - In order to report a bug, unexpected behavior, feature requests or to - submit a patch, please open an issue on github: - . - -LICENSE - This software is licensed under the GNU GENERAL PUBLIC LICENSE version - 3. - - Copyright (c) 2022-2024 by Thomas von Dein - - This software uses the following GO modules: - - repr (https://github.com/alecthomas/repr) - Released under the MIT License, Copyright (c) 2016 Alec Thomas - - cobra (https://github.com/spf13/cobra) - Released under the Apache 2.0 license, Copyright 2013-2022 The Cobra - Authors - - dateparse (github.com/araddon/dateparse) - Released under the MIT License, Copyright (c) 2015-2017 Aaron Raddon - - color (github.com/gookit/color) - Released under the MIT License, Copyright (c) 2016 inhere - - tablewriter (github.com/olekukonko/tablewriter) - Released under the MIT License, Copyright (c) 201 by Oleku Konko - - yaml (gopkg.in/yaml.v3) - Released under the MIT License, Copyright (c) 2006-2011 Kirill - Simonov - - bubble-table (https://github.com/Evertras/bubble-table) - Released under the MIT License, Copyright (c) 2022 Brandon Fulljames - -AUTHORS - Thomas von Dein tom AT vondein DOT org - -` -var usage = ` - -Usage: - tablizer [regex,...] [-r file] [flags] - -Operational Flags: - -c, --columns string Only show the speficied columns (separated by ,) - -v, --invert-match select non-matching rows - -n, --numbering Enable header numbering - -N, --no-color Disable pattern highlighting - -H, --no-headers Disable headers display - -s, --separator Custom field separator (maybe char, string or :class:) - -k, --sort-by Sort by column (default: 1) - -z, --fuzzy Use fuzzy search [experimental] - -F, --filter Filter given field with regex, can be used multiple times - -T, --transpose-columns string Transpose the speficied columns (separated by ,) - -R, --regex-transposer Apply /search/replace/ regexp to fields given in -T - -j, --json Read JSON input (must be array of hashes) - -I, --interactive Interactively filter and select rows - -g, --auto-headers Generate headers if there are none present in input - -x, --custom-headers a,b,... Use custom headers, separated by comma - -Output Flags (mutually exclusive): - -X, --extended Enable extended output - -M, --markdown Enable markdown table output - -O, --orgtbl Enable org-mode table output - -S, --shell Enable shell evaluable output - -Y, --yaml Enable yaml output - -J, --jsonout Enable JSON output - -C, --csv Enable CSV output - -A, --ascii Default output mode, ascii tabular - -L, --hightlight-lines Use alternating background colors for tables - -o, --ofs Output field separator, used by -A and -C. - -y, --yank-columns Yank specified columns (separated by ,) to clipboard, - space separated - -Sort Mode Flags (mutually exclusive): - -a, --sort-age sort according to age (duration) string - -D, --sort-desc Sort in descending order (default: ascending) - -i, --sort-numeric sort according to string numerical value - -t, --sort-time sort according to time string - -Other Flags: - -r --read-file Use as input instead of STDIN - --completion Generate the autocompletion script for - -f, --config Configuration file (default: ~/.config/tablizer/config) - -d, --debug Enable debugging - -h, --help help for tablizer - -m, --man Display manual page - -V, --version Print program version - - -` diff --git a/config.hcl b/config.hcl deleted file mode 100644 index acd41e7..0000000 --- a/config.hcl +++ /dev/null @@ -1,12 +0,0 @@ -# supported colors: -# black, blue, cyan, darkGray, default, green, lightBlue, lightCyan, -# lightGreen, lightMagenta, lightRed, lightWhite, lightYellow, -# magenta, red, white, yellow -BG = "lightGreen" -FG = "white" -HighlightBG = "lightGreen" -HighlightFG = "white" -NoHighlightBG = "white" -NoHighlightFG = "lightGreen" -HighlightHdrBG = "red" -HighlightHdrFG = "white" diff --git a/go.mod b/go.mod deleted file mode 100644 index fcc1c38..0000000 --- a/go.mod +++ /dev/null @@ -1,61 +0,0 @@ -module codeberg.org/scip/tablizer - -go 1.24.0 - -require ( - github.com/alecthomas/repr v0.5.2 - github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de - github.com/charmbracelet/bubbles v0.21.0 - github.com/charmbracelet/bubbletea v1.3.10 - github.com/charmbracelet/lipgloss v1.1.0 - github.com/evertras/bubble-table v0.19.2 - github.com/gookit/color v1.6.0 - github.com/hashicorp/hcl/v2 v2.24.0 - github.com/lithammer/fuzzysearch v1.1.8 - github.com/mattn/go-isatty v0.0.20 - github.com/olekukonko/tablewriter v1.1.0 - github.com/rogpeppe/go-internal v1.14.1 - github.com/spf13/cobra v1.10.1 - github.com/stretchr/testify v1.11.1 - github.com/tiagomelo/go-clipboard v0.1.2 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - github.com/agext/levenshtein v1.2.3 // indirect - github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/atotto/clipboard v0.1.4 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/colorprofile v0.3.1 // indirect - github.com/charmbracelet/x/ansi v0.10.1 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13 // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/fatih/color v1.18.0 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect - github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/olekukonko/errors v1.1.0 // indirect - github.com/olekukonko/ll v0.0.9 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/pflag v1.0.9 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/zclconf/go-cty v1.16.3 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/tools v0.26.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 185fca0..0000000 --- a/go.sum +++ /dev/null @@ -1,156 +0,0 @@ -github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= -github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= -github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= -github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= -github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= -github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= -github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= -github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= -github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= -github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= -github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= -github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/evertras/bubble-table v0.19.2 h1:u77oiM6JlRR+CvS5FZc3Hz+J6iEsvEDcR5kO8OFb1Yw= -github.com/evertras/bubble-table v0.19.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= -github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= -github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= -github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= -github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= -github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= -github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= -github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= -github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= -github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= -github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= -github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tiagomelo/go-clipboard v0.1.2 h1:Ph2icR0vZRIj3v5ExvsGweBwsbbDUTlS6HoF40MkQD8= -github.com/tiagomelo/go-clipboard v0.1.2/go.mod h1:kXtjJBIMimZaGbxmcKZ8+JqK+acSNf5tAJiChlZBOr8= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk= -github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= -github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= -github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/common.go b/lib/common.go deleted file mode 100644 index 6a6c929..0000000 --- a/lib/common.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright © 2022-2024 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -// contains a whole parsed table -type Tabdata struct { - maxwidthHeader int // longest header - columns int // count - headers []string // [ "ID", "NAME", ...] - entries [][]string -} - -func (data *Tabdata) CloneEmpty() Tabdata { - newdata := Tabdata{ - maxwidthHeader: data.maxwidthHeader, - columns: data.columns, - headers: data.headers, - } - - return newdata -} diff --git a/lib/filter.go b/lib/filter.go deleted file mode 100644 index f603c8b..0000000 --- a/lib/filter.go +++ /dev/null @@ -1,192 +0,0 @@ -/* -Copyright © 2022-2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "bufio" - "io" - "strings" - - "github.com/lithammer/fuzzysearch/fuzzy" - "codeberg.org/scip/tablizer/cfg" -) - -/* -* [!]Match a line, use fuzzy search for normal pattern strings and -* regexp otherwise. - - 'foo bar' foo, /bar/! => false => line contains foo and not (not bar) - 'foo nix' foo, /bar/! => ture => line contains foo and (not bar) - 'foo bar' foo, /bar/ => true => line contains both foo and bar - 'foo nix' foo, /bar/ => false => line does not contain bar - 'foo bar' foo, /nix/ => false => line does not contain nix -*/ -func matchPattern(conf cfg.Config, line string) bool { - if len(conf.Patterns) == 0 { - // any line always matches "" - return true - } - - if conf.UseFuzzySearch { - // fuzzy search only considers the 1st pattern - return fuzzy.MatchFold(conf.Patterns[0].Pattern, line) - } - - var match int - - //fmt.Printf("<%s>\n", line) - for _, re := range conf.Patterns { - patmatch := re.PatternRe.MatchString(line) - if re.Negate { - // toggle the meaning of match - patmatch = !patmatch - } - - if patmatch { - match++ - } - - //fmt.Printf("patmatch: %t, match: %d, pattern: %s, negate: %t\n", patmatch, match, re.Pattern, re.Negate) - } - - // fmt.Printf("result: %t\n", match == len(conf.Patterns)) - //fmt.Println() - return match == len(conf.Patterns) -} - -/* - * Filter parsed data by fields. The filter is positive, so if one or - * more filters match on a row, it will be kept, otherwise it will be - * excluded. - */ -func FilterByFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) { - if len(conf.Filters) == 0 { - // no filters, no checking - return nil, false, nil - } - - newdata := data.CloneEmpty() - - for _, row := range data.entries { - keep := true - - for idx, header := range data.headers { - lcheader := strings.ToLower(header) - if !Exists(conf.Filters, lcheader) { - // do not filter by unspecified field - continue - } - - match := conf.Filters[lcheader].Regex.MatchString(row[idx]) - if conf.Filters[lcheader].Negate { - match = !match - } - - if !match { - keep = false - break - } - } - - if keep == !conf.InvertMatch { - // also apply -v - newdata.entries = append(newdata.entries, row) - } - } - - return &newdata, true, nil -} - -/* - * Transpose fields using search/replace regexp. - */ -func TransposeFields(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) { - if len(conf.UseTransposers) == 0 { - // nothing to be done - return nil, false, nil - } - - newdata := data.CloneEmpty() - transposed := false - - for _, row := range data.entries { - transposedrow := false - - for idx := range data.headers { - transposeidx, hasone := findindex(conf.UseTransposeColumns, idx+1) - if hasone { - row[idx] = - conf.UseTransposers[transposeidx].Search.ReplaceAllString( - row[idx], - conf.UseTransposers[transposeidx].Replace, - ) - transposedrow = true - } - } - - if transposedrow { - // also apply -v - newdata.entries = append(newdata.entries, row) - transposed = true - } - } - - return &newdata, transposed, nil -} - -/* generic map.Exists(key) */ -func Exists[K comparable, V any](m map[K]V, v K) bool { - if _, ok := m[v]; ok { - return true - } - - return false -} - -/* - * Filters the whole input lines, returns filtered lines - */ -func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) { - if len(conf.Patterns) == 0 { - return input, nil - } - - scanner := bufio.NewScanner(input) - lines := []string{} - hadFirst := false - - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if hadFirst { - // don't match 1st line, it's the header - if matchPattern(conf, line) == conf.InvertMatch { - // by default -v is false, so if a line does NOT - // match the pattern, we will ignore it. However, - // if the user specified -v, the matching is inverted, - // so we ignore all lines, which DO match. - continue - } - } - - lines = append(lines, line) - - hadFirst = true - } - - return strings.NewReader(strings.Join(lines, "\n")), nil -} diff --git a/lib/filter_test.go b/lib/filter_test.go deleted file mode 100644 index 1cf4032..0000000 --- a/lib/filter_test.go +++ /dev/null @@ -1,172 +0,0 @@ -/* -Copyright © 2024-2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "codeberg.org/scip/tablizer/cfg" -) - -func TestMatchPattern(t *testing.T) { - var input = []struct { - name string - fuzzy bool - patterns []*cfg.Pattern - line string - }{ - { - name: "normal", - patterns: []*cfg.Pattern{{Pattern: "haus"}}, - line: "hausparty", - }, - { - name: "fuzzy", - patterns: []*cfg.Pattern{{Pattern: "hpt"}}, - line: "haus-party-termin", - fuzzy: true, - }, - } - - for _, inputdata := range input { - testname := fmt.Sprintf("match-pattern-%s", inputdata.name) - - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{} - - if inputdata.fuzzy { - conf.UseFuzzySearch = true - } - - err := conf.PreparePattern(inputdata.patterns) - - assert.NoError(t, err) - - res := matchPattern(conf, inputdata.line) - assert.EqualValues(t, true, res) - }) - } -} - -func TestFilterByFields(t *testing.T) { - data := Tabdata{ - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - {"asd", "igig", "cxxxncnc"}, - {"19191", "EDD 1", "x"}, - {"8d8", "AN 1", "y"}, - }, - } - - var input = []struct { - name string - filter []string - expect Tabdata - invert bool - }{ - { - name: "one-field", - filter: []string{"one=19"}, - expect: Tabdata{ - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - {"19191", "EDD 1", "x"}, - }, - }, - }, - - { - name: "one-field-negative", - filter: []string{"one!=asd"}, - expect: Tabdata{ - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - {"19191", "EDD 1", "x"}, - {"8d8", "AN 1", "y"}, - }, - }, - }, - - { - name: "one-field-inverted", - filter: []string{"one=19"}, - invert: true, - expect: Tabdata{ - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - {"asd", "igig", "cxxxncnc"}, - {"8d8", "AN 1", "y"}, - }, - }, - }, - - { - name: "many-fields", - filter: []string{"one=19", "two=DD"}, - expect: Tabdata{ - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - {"19191", "EDD 1", "x"}, - }, - }, - }, - - { - name: "many-fields-inverted", - filter: []string{"one=19", "two=DD"}, - invert: true, - expect: Tabdata{ - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - {"asd", "igig", "cxxxncnc"}, - {"8d8", "AN 1", "y"}, - }, - }, - }, - } - - for _, inputdata := range input { - testname := fmt.Sprintf("filter-by-fields-%s", inputdata.name) - - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{Rawfilters: inputdata.filter, InvertMatch: inputdata.invert} - - err := conf.PrepareFilters() - - assert.NoError(t, err) - - data, _, _ := FilterByFields(conf, &data) - - assert.EqualValues(t, inputdata.expect, *data) - }) - } -} diff --git a/lib/helpers.go b/lib/helpers.go deleted file mode 100644 index cd63cd8..0000000 --- a/lib/helpers.go +++ /dev/null @@ -1,302 +0,0 @@ -/* -Copyright © 2022 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "errors" - "fmt" - "os" - "regexp" - "slices" - "strconv" - "strings" - - "github.com/gookit/color" - "codeberg.org/scip/tablizer/cfg" -) - -func findindex(s []int, e int) (int, bool) { - for i, a := range s { - if a == e { - return i, true - } - } - - return 0, false -} - -// validate the consitency of parsed data -func ValidateConsistency(data *Tabdata) error { - expectedfields := len(data.headers) - - for idx, row := range data.entries { - if len(row) != expectedfields { - return fmt.Errorf("row %d does not contain expected %d elements, but %d", - idx, expectedfields, len(row)) - } - } - - return nil -} - -// parse columns list given with -c, modifies config.UseColumns based -// on eventually given regex. -// This is an output filter, because -cN,N,... is being applied AFTER -// processing of the input data. -func PrepareColumns(conf *cfg.Config, data *Tabdata) error { - // -c columns - usecolumns, err := PrepareColumnVars(conf.Columns, data) - if err != nil { - return err - } - - conf.UseColumns = usecolumns - - // -y columns - useyankcolumns, err := PrepareColumnVars(conf.YankColumns, data) - if err != nil { - return err - } - - conf.UseYankColumns = useyankcolumns - - return nil -} - -// Same thing as above but for -T option, which is an input option, -// because transposers are being applied before output. -func PrepareTransposerColumns(conf *cfg.Config, data *Tabdata) error { - // -T columns - usetransposecolumns, err := PrepareColumnVars(conf.TransposeColumns, data) - if err != nil { - return err - } - - conf.UseTransposeColumns = usetransposecolumns - - // verify that columns and transposers match and prepare transposer structs - if err := conf.PrepareTransposers(); err != nil { - return err - } - - return nil -} - -// output option, prepare -k1,2 sort fields -func PrepareSortColumns(conf *cfg.Config, data *Tabdata) error { - // -c columns - usecolumns, err := PrepareColumnVars(conf.SortByColumn, data) - if err != nil { - return err - } - - conf.UseSortByColumn = usecolumns - - return nil -} - -func PrepareColumnVars(columns string, data *Tabdata) ([]int, error) { - if columns == "" { - return nil, nil - } - - usecolumns := []int{} - - isregex := regexp.MustCompile(`\W`) - - for _, columnpattern := range strings.Split(columns, ",") { - if len(columnpattern) == 0 { - return nil, fmt.Errorf("could not parse columns list %s: empty column", columns) - } - - usenum, err := strconv.Atoi(columnpattern) - if err != nil { - // not a number - - if !isregex.MatchString(columnpattern) { - // is not a regexp (contains no non-word chars) - // lc() it so that word searches are case insensitive - columnpattern = strings.ToLower(columnpattern) - - for i, head := range data.headers { - if columnpattern == strings.ToLower(head) { - usecolumns = append(usecolumns, i+1) - } - } - } else { - colPattern, err := regexp.Compile("(?i)" + columnpattern) - if err != nil { - msg := fmt.Sprintf("Could not parse columns list %s: %v", columns, err) - - return nil, errors.New(msg) - } - - // find matching header fields, ignoring case - for i, head := range data.headers { - if colPattern.MatchString(strings.ToLower(head)) { - usecolumns = append(usecolumns, i+1) - } - } - } - } else { - // we digress from go best practises here, because if - // a colum spec is not a number, we process them above - // inside the err handler for atoi(). so only add the - // number, if it's really just a number. - usecolumns = append(usecolumns, usenum) - } - } - - // deduplicate columns, preserve order - deduped := []int{} - for _, i := range usecolumns { - if !slices.Contains(deduped, i) { - deduped = append(deduped, i) - } - } - - return deduped, nil -} - -// prepare headers: add numbers to headers -func numberizeAndReduceHeaders(conf cfg.Config, data *Tabdata) { - numberedHeaders := make([]string, len(data.headers)) - - maxwidth := 0 // start from scratch, so we only look at displayed column widths - - // add numbers to headers if needed, get widest cell width - for idx, head := range data.headers { - var headlen int - - if conf.Numbering { - newhead := fmt.Sprintf("%s(%d)", head, idx+1) - numberedHeaders[idx] = newhead - headlen = len(newhead) - } else { - headlen = len(head) - } - - if headlen > maxwidth { - maxwidth = headlen - } - } - - if conf.Numbering { - data.headers = numberedHeaders - } - - if len(conf.UseColumns) > 0 { - // re-align headers based on user requested column list - headers := make([]string, len(conf.UseColumns)) - - for i, col := range conf.UseColumns { - for idx := range data.headers { - if col-1 == idx { - headers[i] = data.headers[col-1] - } - } - } - - data.headers = headers - } - - if data.maxwidthHeader != maxwidth && maxwidth > 0 { - data.maxwidthHeader = maxwidth - } -} - -// exclude columns, if any -func reduceColumns(conf cfg.Config, data *Tabdata) { - if len(conf.Columns) > 0 { - reducedEntries := [][]string{} - - for _, entry := range data.entries { - var reducedEntry []string - - for _, col := range conf.UseColumns { - col-- - - for idx, value := range entry { - if idx == col { - reducedEntry = append(reducedEntry, value) - } - } - } - - reducedEntries = append(reducedEntries, reducedEntry) - } - - data.entries = reducedEntries - } -} - -// FIXME: refactor this beast! -func colorizeData(conf cfg.Config, output string) string { - switch { - case conf.UseHighlight && color.IsConsole(os.Stdout): - highlight := true - colorized := "" - first := true - - for _, line := range strings.Split(output, "\n") { - if highlight { - if first { - // we need to add two spaces to the header line - // because tablewriter omits them for some reason - // in pprint mode. This doesn't matter as long as - // we don't use colorization. But with colors the - // missing spaces can be seen. - if conf.OutputMode == cfg.ASCII { - line += " " - } - - line = conf.HighlightHdrStyle.Sprint(line) - first = false - } else { - line = conf.HighlightStyle.Sprint(line) - } - } else { - line = conf.NoHighlightStyle.Sprint(line) - } - - highlight = !highlight - - colorized += line + "\n" - } - - return colorized - - case len(conf.Patterns) > 0 && !conf.NoColor && color.IsConsole(os.Stdout): - out := output - - for _, re := range conf.Patterns { - if !re.Negate { - r := regexp.MustCompile("(" + re.Pattern + ")") - - out = r.ReplaceAllStringFunc(out, func(in string) string { - return conf.ColorStyle.Sprint(in) - }) - } - } - - return out - - default: - return output - } -} diff --git a/lib/helpers_test.go b/lib/helpers_test.go deleted file mode 100644 index 8df6379..0000000 --- a/lib/helpers_test.go +++ /dev/null @@ -1,232 +0,0 @@ -/* -Copyright © 2022 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "fmt" - "slices" - "testing" - - "github.com/stretchr/testify/assert" - "codeberg.org/scip/tablizer/cfg" -) - -func TestContains(t *testing.T) { - var tests = []struct { - list []int - search int - want bool - }{ - {[]int{1, 2, 3}, 2, true}, - {[]int{2, 3, 4}, 5, false}, - } - - for _, tt := range tests { - testname := fmt.Sprintf("contains-%d,%d,%t", tt.list, tt.search, tt.want) - t.Run(testname, func(t *testing.T) { - answer := slices.Contains(tt.list, tt.search) - - assert.EqualValues(t, tt.want, answer) - }) - } -} - -func TestPrepareColumns(t *testing.T) { - data := Tabdata{ - maxwidthHeader: 5, - columns: 3, - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - { - "2", "3", "4", - }, - }, - } - - var tests = []struct { - input string - exp []int - wanterror bool // expect error - }{ - {"1,2,3", []int{1, 2, 3}, false}, - {"1,2,", []int{}, true}, - {"T.", []int{2, 3}, false}, - {"T.,2,3", []int{2, 3}, false}, - {"[a-z,4,5", []int{4, 5}, true}, // invalid regexp - } - - for _, testdata := range tests { - testname := fmt.Sprintf("PrepareColumns-%s-%t", - testdata.input, testdata.wanterror) - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{Columns: testdata.input} - err := PrepareColumns(&conf, &data) - - if testdata.wanterror { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.EqualValues(t, testdata.exp, conf.UseColumns) - } - }) - } -} - -func TestPrepareTransposerColumns(t *testing.T) { - data := Tabdata{ - maxwidthHeader: 5, - columns: 3, - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - { - "2", "3", "4", - }, - }, - } - - var tests = []struct { - input string - transp []string - exp int - wanterror bool // expect error - }{ - { - "1", - []string{`/\d/x/`}, - 1, - false, - }, - { - "T.", // will match [T]WO and [T]HREE - []string{`/\d/x/`, `/.//`}, - 2, - false, - }, - { - "TH.,2", - []string{`/\d/x/`, `/.//`}, - 2, - false, - }, - { - "1", - []string{}, - 1, - true, - }, - { - "", - []string{`|.|N|`}, - 0, - true, - }, - { - "1", - []string{`|.|N|`}, - 1, - false, - }, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("PrepareTransposerColumns-%s-%t", testdata.input, testdata.wanterror) - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{TransposeColumns: testdata.input, Transposers: testdata.transp} - err := PrepareTransposerColumns(&conf, &data) - - if testdata.wanterror { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.EqualValues(t, testdata.exp, len(conf.UseTransposeColumns)) - assert.EqualValues(t, len(conf.UseTransposeColumns), len(conf.Transposers)) - } - }) - } -} - -func TestReduceColumns(t *testing.T) { - var tests = []struct { - expect [][]string - columns []int - }{ - { - expect: [][]string{{"a", "b"}}, - columns: []int{1, 2}, - }, - { - expect: [][]string{{"a", "c"}}, - columns: []int{1, 3}, - }, - { - expect: [][]string{{"a"}}, - columns: []int{1}, - }, - { - expect: [][]string{nil}, - columns: []int{4}, - }, - } - - input := [][]string{{"a", "b", "c"}} - - for _, testdata := range tests { - testname := fmt.Sprintf("reduce-columns-by-%+v", testdata.columns) - - t.Run(testname, func(t *testing.T) { - c := cfg.Config{Columns: "x", UseColumns: testdata.columns} - data := Tabdata{entries: input} - reduceColumns(c, &data) - - assert.EqualValues(t, testdata.expect, data.entries) - }) - } -} - -func TestNumberizeHeaders(t *testing.T) { - data := Tabdata{ - headers: []string{"ONE", "TWO", "THREE"}, - } - - var tests = []struct { - expect []string - columns []int - numberize bool - }{ - {[]string{"ONE(1)", "TWO(2)", "THREE(3)"}, []int{1, 2, 3}, true}, - {[]string{"ONE(1)", "TWO(2)"}, []int{1, 2}, true}, - {[]string{"ONE", "TWO"}, []int{1, 2}, false}, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("numberize-headers-columns-%+v-nonum-%t", - testdata.columns, testdata.numberize) - - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{Columns: "x", UseColumns: testdata.columns, Numbering: testdata.numberize} - usedata := data - numberizeAndReduceHeaders(conf, &usedata) - - assert.EqualValues(t, testdata.expect, usedata.headers) - }) - } -} diff --git a/lib/io.go b/lib/io.go deleted file mode 100644 index 6d3b98f..0000000 --- a/lib/io.go +++ /dev/null @@ -1,116 +0,0 @@ -/* -Copyright © 2022 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "errors" - "fmt" - "io" - "os" - - "codeberg.org/scip/tablizer/cfg" -) - -const RWRR = 0755 - -func ProcessFiles(conf *cfg.Config, args []string) error { - fd, patterns, err := determineIO(conf, args) - - if err != nil { - return err - } - - if err := conf.PreparePattern(patterns); err != nil { - return err - } - - data, err := Parse(*conf, fd) - if err != nil { - return err - } - - if err = ValidateConsistency(&data); err != nil { - return err - } - - err = PrepareSortColumns(conf, &data) - if err != nil { - return err - } - - err = PrepareColumns(conf, &data) - if err != nil { - return err - } - - if conf.Interactive { - newdata, err := tableEditor(conf, &data) - if err != nil { - return err - } - - data = *newdata - } - - printData(os.Stdout, *conf, &data) - - return nil -} - -func determineIO(conf *cfg.Config, args []string) (io.Reader, []*cfg.Pattern, error) { - var filehandle io.Reader - var patterns []*cfg.Pattern - var haveio bool - - switch { - case conf.InputFile == "-": - filehandle = os.Stdin - haveio = true - case conf.InputFile != "": - fd, err := os.OpenFile(conf.InputFile, os.O_RDONLY, RWRR) - - if err != nil { - return nil, nil, fmt.Errorf("failed to read input file %s: %w", conf.InputFile, err) - } - - filehandle = fd - haveio = true - } - - if !haveio { - stat, _ := os.Stdin.Stat() - if (stat.Mode() & os.ModeCharDevice) == 0 { - // we're reading from STDIN, which takes precedence over file args - filehandle = os.Stdin - haveio = true - } - } - - if len(args) > 0 { - patterns = make([]*cfg.Pattern, len(args)) - for i, arg := range args { - patterns[i] = &cfg.Pattern{Pattern: arg} - } - } - - if !haveio { - return nil, nil, errors.New("no file specified and nothing to read on stdin") - } - - return filehandle, patterns, nil -} diff --git a/lib/pager.go b/lib/pager.go deleted file mode 100644 index b7e2910..0000000 --- a/lib/pager.go +++ /dev/null @@ -1,120 +0,0 @@ -package lib - -// pager setup using bubbletea -// file shamlelessly copied from: -// https://github.com/charmbracelet/bubbletea/tree/main/examples/pager - -import ( - "fmt" - "os" - "strings" - - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -var ( - titleStyle = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Right = "├" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) - }() - - infoStyle = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Left = "┤" - return titleStyle.BorderStyle(b) - }() -) - -type Doc struct { - content string - title string - ready bool - viewport viewport.Model -} - -func (m Doc) Init() tea.Cmd { - return nil -} - -func (m Doc) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var ( - cmd tea.Cmd - cmds []tea.Cmd - ) - - switch msg := msg.(type) { - case tea.KeyMsg: - if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" { - return m, tea.Quit - } - - case tea.WindowSizeMsg: - headerHeight := lipgloss.Height(m.headerView()) - footerHeight := lipgloss.Height(m.footerView()) - verticalMarginHeight := headerHeight + footerHeight - - if !m.ready { - // Since this program is using the full size of the viewport we - // need to wait until we've received the window dimensions before - // we can initialize the viewport. The initial dimensions come in - // quickly, though asynchronously, which is why we wait for them - // here. - m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) - m.viewport.YPosition = headerHeight - m.viewport.SetContent(m.content) - m.ready = true - } else { - m.viewport.Width = msg.Width - m.viewport.Height = msg.Height - verticalMarginHeight - } - } - - // Handle keyboard and mouse events in the viewport - m.viewport, cmd = m.viewport.Update(msg) - cmds = append(cmds, cmd) - - return m, tea.Batch(cmds...) -} - -func (m Doc) View() string { - if !m.ready { - return "\n Initializing..." - } - return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView()) -} - -func (m Doc) headerView() string { - // title := titleStyle.Render("RPN Help Overview") - title := titleStyle.Render(m.title) - line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title))) - return lipgloss.JoinHorizontal(lipgloss.Center, title, line) -} - -func (m Doc) footerView() string { - info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) - line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info))) - return lipgloss.JoinHorizontal(lipgloss.Center, line, info) -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -func Pager(title, message string) { - p := tea.NewProgram( - Doc{content: message, title: title}, - tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer" - tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel - ) - - if _, err := p.Run(); err != nil { - fmt.Println("could not run pager:", err) - os.Exit(1) - } -} diff --git a/lib/parser.go b/lib/parser.go deleted file mode 100644 index 872e9a4..0000000 --- a/lib/parser.go +++ /dev/null @@ -1,402 +0,0 @@ -/* -Copyright © 2022-2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "bufio" - "encoding/csv" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "math" - "regexp" - "strings" - - "github.com/alecthomas/repr" - "codeberg.org/scip/tablizer/cfg" -) - -/* -Parser switch -*/ -func Parse(conf cfg.Config, input io.Reader) (Tabdata, error) { - var data Tabdata - var err error - - // first step, parse the data - if len(conf.Separator) == 1 { - data, err = parseCSV(conf, input) - } else if conf.InputJSON { - data, err = parseJSON(conf, input) - } else { - data, err = parseTabular(conf, input) - } - - if err != nil { - return data, err - } - - // 2nd step, apply filters, code or transposers, if any - postdata, changed, err := PostProcess(conf, &data) - if err != nil { - return data, err - } - - if changed { - return *postdata, nil - } - - return data, err -} - -/* - * Setup headers, given headers might be usable headers or just the - * first row, which we use to determine how many headers to generate, - * if enabled. - */ -func SetHeaders(conf cfg.Config, headers []string) []string { - if !conf.AutoHeaders && len(conf.CustomHeaders) == 0 { - return headers - } - - if conf.AutoHeaders { - heads := make([]string, len(headers)) - for idx := range headers { - heads[idx] = fmt.Sprintf("%d", idx+1) - } - - return heads - } - - if len(conf.CustomHeaders) == len(headers) { - return conf.CustomHeaders - } - - // use as much custom ones we have, generate the remainder - heads := make([]string, len(headers)) - - for idx := range headers { - if idx < len(conf.CustomHeaders) { - heads[idx] = conf.CustomHeaders[idx] - } else { - heads[idx] = fmt.Sprintf("%d", idx+1) - } - } - - return heads -} - -/* -Parse CSV input. -*/ -func parseCSV(conf cfg.Config, input io.Reader) (Tabdata, error) { - data := Tabdata{} - - // apply pattern, if any - content, err := FilterByPattern(conf, input) - if err != nil { - return data, err - } - - csvreader := csv.NewReader(content) - csvreader.Comma = rune(conf.Separator[0]) - - records, err := csvreader.ReadAll() - if err != nil { - return data, fmt.Errorf("could not parse CSV input: %w", err) - } - - if len(records) >= 1 { - data.headers = SetHeaders(conf, records[0]) - data.columns = len(records) - - for _, head := range data.headers { - // register widest header field - headerlen := len(head) - if headerlen > data.maxwidthHeader { - data.maxwidthHeader = headerlen - } - } - - if len(records) >= 1 { - if conf.AutoHeaders || len(conf.CustomHeaders) > 0 { - data.entries = records - } else { - data.entries = records[1:] - } - } - - } - - return data, nil -} - -/* -Parse tabular input. -*/ -func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) { - data := Tabdata{} - - var scanner *bufio.Scanner - - hadFirst := false - separate := regexp.MustCompile(conf.Separator) - - scanner = bufio.NewScanner(input) - - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - parts := separate.Split(line, -1) - - if !hadFirst { - // header processing - data.columns = len(parts) - - // process all header fields - firstrow := make([]string, len(parts)) - - for idx, part := range parts { - // register widest header field - headerlen := len(part) - if headerlen > data.maxwidthHeader { - data.maxwidthHeader = headerlen - } - - // register fields data - firstrow[idx] = strings.TrimSpace(part) - - // done - hadFirst = true - } - - data.headers = SetHeaders(conf, firstrow) - - if conf.AutoHeaders || len(conf.CustomHeaders) > 0 { - // we do not use generated headers, consider as row - if matchPattern(conf, line) == conf.InvertMatch { - continue - } - - data.entries = append(data.entries, firstrow) - } - } else { - // data processing - if matchPattern(conf, line) == conf.InvertMatch { - // by default -v is false, so if a line does NOT - // match the pattern, we will ignore it. However, - // if the user specified -v, the matching is inverted, - // so we ignore all lines, which DO match. - continue - } - - idx := 0 // we cannot use the header index, because we could exclude columns - values := []string{} - for _, part := range parts { - // if Debug { - // fmt.Printf("<%s> ", value) - // } - values = append(values, strings.TrimSpace(part)) - - idx++ - } - - // fill up missing fields, if any - for i := len(values); i < len(data.headers); i++ { - values = append(values, "") - } - - data.entries = append(data.entries, values) - } - } - - if scanner.Err() != nil { - return data, fmt.Errorf("failed to read from io.Reader: %w", scanner.Err()) - } - - return data, nil -} - -/* -Parse JSON input. We only support an array of maps. -*/ -func parseRawJSON(conf cfg.Config, input io.Reader) (Tabdata, error) { - dec := json.NewDecoder(input) - headers := []string{} - idxmap := map[string]int{} - data := [][]string{} - row := []string{} - iskey := true - haveheaders := false - var currentfield string - var idx int - var isjson bool - - for { - t, err := dec.Token() - if err == io.EOF { - break - } - if err != nil { - log.Fatal(err) - } - - switch val := t.(type) { - case string: - if iskey { - if !haveheaders { - // consider only the keys of the first item as headers - headers = append(headers, val) - } - currentfield = val - } else { - if !haveheaders { - // the first row uses the order as it comes in - row = append(row, val) - } else { - // use the pre-determined order, that way items - // can be in any order as long as they contain all - // neccessary fields. They may also contain less - // fields than the first item, these will contain - // the empty string - row[idxmap[currentfield]] = val - } - } - - case float64: - var value string - - // we set precision to 0 if the float is a whole number - if val == math.Trunc(val) { - value = fmt.Sprintf("%.f", val) - } else { - value = fmt.Sprintf("%f", val) - } - - if !haveheaders { - row = append(row, value) - } else { - row[idxmap[currentfield]] = value - } - - case nil: - // we ignore here if a value shall be an int or a string, - // because tablizer only works with strings anyway - if !haveheaders { - row = append(row, "") - } else { - row[idxmap[currentfield]] = "" - } - - case json.Delim: - if val.String() == "}" { - data = append(data, row) - row = make([]string, len(headers)) - idx++ - - if !haveheaders { - // remember the array position of header fields, - // which we use to assign elements to the correct - // row index - for i, header := range headers { - idxmap[header] = i - } - } - - haveheaders = true - } - isjson = true - default: - fmt.Printf("unknown token: %v type: %T\n", t, t) - } - - iskey = !iskey - } - - if isjson && (len(headers) == 0 || len(data) == 0) { - return Tabdata{}, errors.New("failed to parse JSON, input did not contain array of hashes") - } - - return Tabdata{headers: headers, entries: data, columns: len(headers)}, nil -} - -func parseJSON(conf cfg.Config, input io.Reader) (Tabdata, error) { - // parse raw json - data, err := parseRawJSON(conf, input) - if err != nil { - return data, err - } - - // apply filter, if any - filtered := [][]string{} - var line string - - for _, row := range data.entries { - line = strings.Join(row, " ") - - if matchPattern(conf, line) == conf.InvertMatch { - continue - } - - filtered = append(filtered, row) - } - - if len(filtered) != len(data.entries) { - data.entries = filtered - } - - return data, nil -} - -func PostProcess(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) { - var modified bool - - // filter by field filters, if any - filtereddata, changed, err := FilterByFields(conf, data) - if err != nil { - return data, false, fmt.Errorf("failed to filter fields: %w", err) - } - - if changed { - data = filtereddata - modified = true - } - - // check if transposers are valid and turn into Transposer structs - if err := PrepareTransposerColumns(&conf, data); err != nil { - return data, false, err - } - - // transpose if demanded - modifieddata, changed, err := TransposeFields(conf, data) - if err != nil { - return data, false, fmt.Errorf("failed to transpose fields: %w", err) - } - - if changed { - data = modifieddata - modified = true - } - - if conf.Debug { - repr.Print(data) - } - - return data, modified, nil -} diff --git a/lib/parser_test.go b/lib/parser_test.go deleted file mode 100644 index 209ec8b..0000000 --- a/lib/parser_test.go +++ /dev/null @@ -1,429 +0,0 @@ -/* -Copyright © 2022-2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "fmt" - "io" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "codeberg.org/scip/tablizer/cfg" -) - -var input = []struct { - name string - text string - separator string -}{ - { - name: "tabular-data", - separator: cfg.SeparatorTemplates[":default:"], - text: ` -ONE TWO THREE -asd igig cxxxncnc -19191 EDD 1 X`, - }, - { - name: "csv-data", - separator: ",", - text: ` -ONE,TWO,THREE -asd,igig,cxxxncnc -19191,"EDD 1",X`, - }, -} - -func TestParser(t *testing.T) { - data := Tabdata{ - maxwidthHeader: 5, - columns: 3, - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - {"asd", "igig", "cxxxncnc"}, - {"19191", "EDD 1", "X"}, - }, - } - - for _, testdata := range input { - testname := fmt.Sprintf("parse-%s", testdata.name) - t.Run(testname, func(t *testing.T) { - readFd := strings.NewReader(strings.TrimSpace(testdata.text)) - conf := cfg.Config{Separator: testdata.separator} - gotdata, err := wrapValidateParser(conf, readFd) - - assert.NoError(t, err) - assert.EqualValues(t, data, gotdata) - }) - } -} - -func TestParserPatternmatching(t *testing.T) { - var tests = []struct { - name string - entries [][]string - patterns []*cfg.Pattern - invert bool - wanterror bool - }{ - { - name: "match", - entries: [][]string{ - {"asd", "igig", "cxxxncnc"}, - }, - patterns: []*cfg.Pattern{{Pattern: "ig"}}, - invert: false, - }, - { - name: "invert", - entries: [][]string{ - {"19191", "EDD 1", "X"}, - }, - patterns: []*cfg.Pattern{{Pattern: "ig"}}, - invert: true, - }, - } - - for _, inputdata := range input { - for _, testdata := range tests { - testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t", - inputdata.name, testdata.name, testdata.invert) - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{ - InvertMatch: testdata.invert, - Patterns: testdata.patterns, - Separator: inputdata.separator, - } - - _ = conf.PreparePattern(testdata.patterns) - - readFd := strings.NewReader(strings.TrimSpace(inputdata.text)) - data, err := wrapValidateParser(conf, readFd) - - if testdata.wanterror { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.EqualValues(t, testdata.entries, data.entries) - } - }) - } - } -} - -func TestParserIncompleteRows(t *testing.T) { - data := Tabdata{ - maxwidthHeader: 5, - columns: 3, - headers: []string{ - "ONE", "TWO", "THREE", - }, - entries: [][]string{ - {"asd", "igig", ""}, - {"19191", "EDD 1", "X"}, - }, - } - - table := ` -ONE TWO THREE -asd igig -19191 EDD 1 X` - - readFd := strings.NewReader(strings.TrimSpace(table)) - conf := cfg.Config{Separator: cfg.SeparatorTemplates[":default:"]} - gotdata, err := wrapValidateParser(conf, readFd) - - assert.NoError(t, err) - assert.EqualValues(t, data, gotdata) -} - -func TestParserJSONInput(t *testing.T) { - var tests = []struct { - name string - input string - expect Tabdata - wanterror bool // true: expect fail, false: expect success - }{ - { - // too deep nesting - name: "invalidjson", - wanterror: true, - input: `[ - { - "item": { - "NAME": "postgres-operator-7f4c7c8485-ntlns", - "READY": "1/1", - "STATUS": "Running", - "RESTARTS": "0", - "AGE": "24h" - } - } -`, - expect: Tabdata{}, - }, - - { - // contains nil, int and float values - name: "niljson", - wanterror: false, - input: `[ - { - "NAME": "postgres-operator-7f4c7c8485-ntlns", - "READY": "1/1", - "STATUS": "Running", - "RESTARTS": 0, - "AGE": null, - "X": 12, - "Y": 34.222 - } -]`, - expect: Tabdata{ - columns: 7, - headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE", "X", "Y"}, - entries: [][]string{ - []string{ - "postgres-operator-7f4c7c8485-ntlns", - "1/1", - "Running", - "0", - "", - "12", - "34.222000", - }, - }, - }, - }, - - { - // one field missing + different order - // but shall not fail - name: "kgpfail", - wanterror: false, - input: `[ - { - "NAME": "postgres-operator-7f4c7c8485-ntlns", - "READY": "1/1", - "STATUS": "Running", - "RESTARTS": "0", - "AGE": "24h" - }, - { - "NAME": "wal-g-exporter-778dcd95f5-wcjzn", - "RESTARTS": "0", - "READY": "1/1", - "AGE": "24h" - } -]`, - expect: Tabdata{ - columns: 5, - headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}, - entries: [][]string{ - []string{ - "postgres-operator-7f4c7c8485-ntlns", - "1/1", - "Running", - "0", - "24h", - }, - []string{ - "wal-g-exporter-778dcd95f5-wcjzn", - "1/1", - "", - "0", - "24h", - }, - }, - }, - }, - - { - name: "kgp", - wanterror: false, - input: `[ - { - "NAME": "postgres-operator-7f4c7c8485-ntlns", - "READY": "1/1", - "STATUS": "Running", - "RESTARTS": "0", - "AGE": "24h" - }, - { - "NAME": "wal-g-exporter-778dcd95f5-wcjzn", - "STATUS": "Running", - "READY": "1/1", - "RESTARTS": "0", - "AGE": "24h" - } -]`, - expect: Tabdata{ - columns: 5, - headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}, - entries: [][]string{ - []string{ - "postgres-operator-7f4c7c8485-ntlns", - "1/1", - "Running", - "0", - "24h", - }, - []string{ - "wal-g-exporter-778dcd95f5-wcjzn", - "1/1", - "Running", - "0", - "24h", - }, - }, - }, - }, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("parse-json-%s", testdata.name) - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{InputJSON: true} - - readFd := strings.NewReader(strings.TrimSpace(testdata.input)) - data, err := wrapValidateParser(conf, readFd) - - if testdata.wanterror { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.EqualValues(t, testdata.expect, data) - } - }) - } -} - -func TestParserSeparators(t *testing.T) { - list := []string{"alpha", "beta", "delta"} - - tests := []struct { - input string - sep string - }{ - { - input: `🎲`, - sep: ":nonprint:", - }, - { - input: `|`, - sep: ":pipe:", - }, - { - input: ` `, - sep: ":spaces:", - }, - { - input: " \t ", - sep: ":tab:", - }, - { - input: `-`, - sep: ":nonword:", - }, - { - input: `//$`, - sep: ":special:", - }, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("parse-%s", testdata.sep) - t.Run(testname, func(t *testing.T) { - header := strings.Join(list, testdata.input) - row := header - content := header + "\n" + row - - readFd := strings.NewReader(strings.TrimSpace(content)) - conf := cfg.Config{Separator: testdata.sep} - conf.ApplyDefaults() - - gotdata, err := wrapValidateParser(conf, readFd) - - assert.NoError(t, err) - assert.EqualValues(t, [][]string{list}, gotdata.entries) - }) - } -} - -func TestParserSetHeaders(t *testing.T) { - row := []string{"c", "b", "c", "d", "e"} - - tests := []struct { - name string - custom []string - expect []string - auto bool - }{ - { - name: "default", - expect: row, - }, - { - name: "auto", - expect: strings.Split("1 2 3 4 5", " "), - auto: true, - }, - { - name: "custom-complete", - custom: strings.Split("A B C D E", " "), - expect: strings.Split("A B C D E", " "), - }, - { - name: "custom-too-short", - custom: strings.Split("A B", " "), - expect: strings.Split("A B 3 4 5", " "), - }, - { - name: "custom-too-long", - custom: strings.Split("A B C D E F G", " "), - expect: strings.Split("A B C D E", " "), - }, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("parse-%s", testdata.name) - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{ - AutoHeaders: testdata.auto, - CustomHeaders: testdata.custom, - } - headers := SetHeaders(conf, row) - - assert.NotNil(t, headers) - assert.EqualValues(t, testdata.expect, headers) - }) - } -} - -func wrapValidateParser(conf cfg.Config, input io.Reader) (Tabdata, error) { - data, err := Parse(conf, input) - - if err != nil { - return data, err - } - - err = ValidateConsistency(&data) - - return data, err -} diff --git a/lib/printer.go b/lib/printer.go deleted file mode 100644 index 2aa6463..0000000 --- a/lib/printer.go +++ /dev/null @@ -1,390 +0,0 @@ -/* -Copyright © 2022-2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "encoding/csv" - "encoding/json" - "fmt" - "io" - "log" - "strconv" - "strings" - - "codeberg.org/scip/tablizer/cfg" - "github.com/gookit/color" - "github.com/olekukonko/tablewriter" - "github.com/olekukonko/tablewriter/renderer" - "github.com/olekukonko/tablewriter/tw" - "gopkg.in/yaml.v3" -) - -func printData(writer io.Writer, conf cfg.Config, data *Tabdata) { - // Sort the data first, before headers+entries are being - // reduced. That way the user can specify any valid column to sort - // by, independently if it's being used for display or not. - sortTable(conf, data) - - // put one or more columns into clipboard - yankColumns(conf, data) - - // add numbers to headers and remove those we're not interested in - numberizeAndReduceHeaders(conf, data) - - // remove unwanted columns, if any - reduceColumns(conf, data) - - switch conf.OutputMode { - case cfg.Extended: - printExtendedData(writer, conf, data) - case cfg.ASCII: - printASCIIData(writer, conf, data) - case cfg.Orgtbl: - printOrgmodeData(writer, conf, data) - case cfg.Markdown: - printMarkdownData(writer, conf, data) - case cfg.Shell: - printShellData(writer, data) - case cfg.Yaml: - printYamlData(writer, data) - case cfg.Json: - printJsonData(writer, data) - case cfg.CSV: - printCSVData(writer, conf, data) - default: - printASCIIData(writer, conf, data) - } -} - -func output(writer io.Writer, str string) { - _, err := fmt.Fprint(writer, str) - if err != nil { - log.Fatalf("failed to print output: %s", err) - } -} - -/* -Emacs org-mode compatible table (also orgtbl-mode) -*/ -func printOrgmodeData(writer io.Writer, conf cfg.Config, data *Tabdata) { - tableString := &strings.Builder{} - - table := tablewriter.NewTable(tableString, - tablewriter.WithRenderer( - renderer.NewBlueprint( - tw.Rendition{ - Borders: tw.Border{ - Left: tw.On, - Right: tw.On, - Top: tw.On, - Bottom: tw.On, - }, - Settings: tw.Settings{ - Separators: tw.Separators{ - ShowHeader: tw.On, - ShowFooter: tw.Off, - BetweenRows: tw.Off, - BetweenColumns: 0, - }, - }, - Symbols: tw.NewSymbols(tw.StyleASCII), - })), - - tablewriter.WithConfig( - tablewriter.Config{ - Header: tw.CellConfig{ - Formatting: tw.CellFormatting{ - Alignment: tw.AlignLeft, - AutoFormat: tw.Off, - }, - }, - Row: tw.CellConfig{ - Formatting: tw.CellFormatting{ - Alignment: tw.AlignLeft, - }, - }, - }, - ), - ) - - if !conf.NoHeaders { - table.Header(data.headers) - } - - if err := table.Bulk(data.entries); err != nil { - log.Fatalf("Failed to add data to table renderer: %s", err) - } - - if err := table.Render(); err != nil { - log.Fatalf("Failed to render table: %s", err) - } - - output(writer, color.Sprint(colorizeData(conf, tableString.String()))) -} - -/* -Markdown table -*/ -func printMarkdownData(writer io.Writer, conf cfg.Config, data *Tabdata) { - tableString := &strings.Builder{} - - table := tablewriter.NewTable(tableString, - tablewriter.WithRenderer( - renderer.NewBlueprint( - tw.Rendition{ - Borders: tw.Border{ - Left: tw.On, - Right: tw.On, - Top: tw.Off, - Bottom: tw.Off, - }, - Settings: tw.Settings{ - Separators: tw.Separators{ - ShowHeader: tw.On, - ShowFooter: tw.Off, - BetweenRows: tw.Off, - BetweenColumns: 0, - }, - }, - Symbols: tw.NewSymbols(tw.StyleMarkdown), - })), - - tablewriter.WithConfig( - tablewriter.Config{ - Header: tw.CellConfig{ - Formatting: tw.CellFormatting{ - Alignment: tw.AlignLeft, - AutoFormat: tw.Off, - }, - }, - Row: tw.CellConfig{ - Formatting: tw.CellFormatting{ - Alignment: tw.AlignLeft, - }, - }, - }, - ), - ) - - if !conf.NoHeaders { - table.Header(data.headers) - } - - if err := table.Bulk(data.entries); err != nil { - log.Fatalf("Failed to add data to table renderer: %s", err) - } - - if err := table.Render(); err != nil { - log.Fatalf("Failed to render table: %s", err) - } - - output(writer, color.Sprint(colorizeData(conf, tableString.String()))) -} - -/* -Simple ASCII table without any borders etc, just like the input we expect -*/ -func printASCIIData(writer io.Writer, conf cfg.Config, data *Tabdata) { - OFS := " " - if conf.OFS != "" { - OFS = conf.OFS - } - - tableString := &strings.Builder{} - - styleTSV := tw.NewSymbolCustom("space").WithColumn("\t") - - table := tablewriter.NewTable(tableString, - tablewriter.WithRenderer( - renderer.NewBlueprint(tw.Rendition{ - Borders: tw.BorderNone, - Symbols: styleTSV, - Settings: tw.Settings{ - Separators: tw.SeparatorsNone, - Lines: tw.LinesNone, - }, - })), - tablewriter.WithConfig(tablewriter.Config{ - Header: tw.CellConfig{ - Formatting: tw.CellFormatting{ - AutoFormat: tw.Off, - }, - Padding: tw.CellPadding{Global: tw.Padding{Left: "", Right: OFS}}, - }, - Row: tw.CellConfig{ - Formatting: tw.CellFormatting{ - AutoWrap: tw.WrapNone, - Alignment: tw.AlignLeft, - }, - Padding: tw.CellPadding{Global: tw.Padding{Right: OFS}}, - }, - - Debug: true, - }), - ) - - if !conf.NoHeaders { - table.Header(data.headers) - } - - if err := table.Bulk(data.entries); err != nil { - log.Fatalf("Failed to add data to table renderer: %s", err) - } - - if err := table.Render(); err != nil { - log.Fatalf("Failed to render table: %s", err) - } - - output(writer, color.Sprint(colorizeData(conf, tableString.String()))) -} - -/* -We simulate the \x command of psql (the PostgreSQL client) -*/ -func printExtendedData(writer io.Writer, conf cfg.Config, data *Tabdata) { - // needed for data output - format := fmt.Sprintf("%%%ds: %%s\n", data.maxwidthHeader) - out := "" - - if len(data.entries) > 0 { - for _, entry := range data.entries { - for i, value := range entry { - out += color.Sprintf(format, data.headers[i], value) - } - - out += "\n" - } - } - - output(writer, colorizeData(conf, out)) -} - -/* -Shell output, ready to be eval'd. Just like FreeBSD stat(1) -*/ -func printShellData(writer io.Writer, data *Tabdata) { - out := "" - - if len(data.entries) > 0 { - for _, entry := range data.entries { - shentries := []string{} - - for idx, value := range entry { - shentries = append(shentries, fmt.Sprintf("%s=\"%s\"", - data.headers[idx], value)) - } - - out += strings.Join(shentries, " ") + "\n" - } - } - - // no colorization here - output(writer, out) -} - -func printJsonData(writer io.Writer, data *Tabdata) { - objlist := make([]map[string]any, len(data.entries)) - - if len(data.entries) > 0 { - for i, entry := range data.entries { - obj := make(map[string]any, len(entry)) - - for idx, value := range entry { - num, err := strconv.Atoi(value) - if err == nil { - obj[data.headers[idx]] = num - } else { - obj[data.headers[idx]] = value - } - } - - objlist[i] = obj - } - } - - jsonstr, err := json.MarshalIndent(&objlist, "", " ") - - if err != nil { - log.Fatal(err) - } - - output(writer, string(jsonstr)) -} - -func printYamlData(writer io.Writer, data *Tabdata) { - type Data struct { - Entries []map[string]interface{} `yaml:"entries"` - } - - yamlout := Data{} - - for _, entry := range data.entries { - yamldata := map[string]interface{}{} - - for idx, entry := range entry { - style := yaml.TaggedStyle - - _, err := strconv.Atoi(entry) - if err != nil { - style = yaml.DoubleQuotedStyle - } - - yamldata[strings.ToLower(data.headers[idx])] = - &yaml.Node{ - Kind: yaml.ScalarNode, - Style: style, - Value: entry} - } - - yamlout.Entries = append(yamlout.Entries, yamldata) - } - - yamlstr, err := yaml.Marshal(&yamlout) - - if err != nil { - log.Fatal(err) - } - - output(writer, string(yamlstr)) -} - -func printCSVData(writer io.Writer, conf cfg.Config, data *Tabdata) { - OFS := "," - if conf.OFS != "" { - OFS = conf.OFS - } - - csvout := csv.NewWriter(writer) - csvout.Comma = []rune(OFS)[0] - - if err := csvout.Write(data.headers); err != nil { - log.Fatalln("error writing record to csv:", err) - } - - for _, entry := range data.entries { - if err := csvout.Write(entry); err != nil { - log.Fatalln("error writing record to csv:", err) - } - } - - csvout.Flush() - - if err := csvout.Error(); err != nil { - log.Fatal(err) - } -} diff --git a/lib/printer_test.go b/lib/printer_test.go deleted file mode 100644 index 0d82c88..0000000 --- a/lib/printer_test.go +++ /dev/null @@ -1,340 +0,0 @@ -/* -Copyright © 2022 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "bytes" - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "codeberg.org/scip/tablizer/cfg" -) - -func newData() Tabdata { - return Tabdata{ - maxwidthHeader: 8, - columns: 4, - headers: []string{ - "NAME", - "DURATION", - "COUNT", - "WHEN", - }, - entries: [][]string{ - { - "beta", - "1d10h5m1s", - "33", - "3/1/2014", - }, - { - "alpha", - "4h35m", - "170", - "2013-Feb-03", - }, - { - "ceta", - "33d12h", - "9", - "06/Jan/2008 15:04:05 -0700", - }, - }, - } -} - -var tests = []struct { - name string // so we can identify which one fails, can be the same - // for multiple tests, because flags will be appended to the name - sortby string // empty == default - column int // sort by this column (numbers start by 1) - desc bool // sort in descending order, default == ascending - numberize bool // add header numbering - mode int // shell, orgtbl, etc. empty == default: ascii - usecol []int // columns to display, empty == display all - usecolstr string // for testname, must match usecol - expect string // rendered output we expect -}{ - // --------------------- Default settings mode tests `` - { - mode: cfg.ASCII, - numberize: true, - name: "default", - expect: ` -NAME(1) DURATION(2) COUNT(3) WHEN(4) -beta 1d10h5m1s 33 3/1/2014 -alpha 4h35m 170 2013-Feb-03 -ceta 33d12h 9 06/Jan/2008 15:04:05 -0700`, - }, - { - mode: cfg.CSV, - numberize: false, - name: "csv", - expect: ` -NAME,DURATION,COUNT,WHEN -beta,1d10h5m1s,33,3/1/2014 -alpha,4h35m,170,2013-Feb-03 -ceta,33d12h,9,06/Jan/2008 15:04:05 -0700`, - }, - { - name: "orgtbl", - numberize: true, - mode: cfg.Orgtbl, - expect: ` -+---------+-------------+----------+----------------------------+ -| NAME(1) | DURATION(2) | COUNT(3) | WHEN(4) | -+---------+-------------+----------+----------------------------+ -| beta | 1d10h5m1s | 33 | 3/1/2014 | -| alpha | 4h35m | 170 | 2013-Feb-03 | -| ceta | 33d12h | 9 | 06/Jan/2008 15:04:05 -0700 | -+---------+-------------+----------+----------------------------+`, - }, - { - name: "markdown", - mode: cfg.Markdown, - numberize: true, - expect: ` -| NAME(1) | DURATION(2) | COUNT(3) | WHEN(4) | -|---------|-------------|----------|----------------------------| -| beta | 1d10h5m1s | 33 | 3/1/2014 | -| alpha | 4h35m | 170 | 2013-Feb-03 | -| ceta | 33d12h | 9 | 06/Jan/2008 15:04:05 -0700 |`, - }, - { - name: "shell", - mode: cfg.Shell, - numberize: false, - expect: ` -NAME="beta" DURATION="1d10h5m1s" COUNT="33" WHEN="3/1/2014" -NAME="alpha" DURATION="4h35m" COUNT="170" WHEN="2013-Feb-03" -NAME="ceta" DURATION="33d12h" COUNT="9" WHEN="06/Jan/2008 15:04:05 -0700"`, - }, - { - name: "json", - mode: cfg.Json, - numberize: false, - expect: `[ - { - "COUNT": 33, - "DURATION": "1d10h5m1s", - "NAME": "beta", - "WHEN": "3/1/2014" - }, - { - "COUNT": 170, - "DURATION": "4h35m", - "NAME": "alpha", - "WHEN": "2013-Feb-03" - }, - { - "COUNT": 9, - "DURATION": "33d12h", - "NAME": "ceta", - "WHEN": "06/Jan/2008 15:04:05 -0700" - } -]`, - }, - { - name: "yaml", - mode: cfg.Yaml, - numberize: false, - expect: ` -entries: - - count: 33 - duration: "1d10h5m1s" - name: "beta" - when: "3/1/2014" - - count: 170 - duration: "4h35m" - name: "alpha" - when: "2013-Feb-03" - - count: 9 - duration: "33d12h" - name: "ceta" - when: "06/Jan/2008 15:04:05 -0700"`, - }, - { - name: "extended", - mode: cfg.Extended, - numberize: true, - expect: ` - NAME(1): beta -DURATION(2): 1d10h5m1s - COUNT(3): 33 - WHEN(4): 3/1/2014 - - NAME(1): alpha -DURATION(2): 4h35m - COUNT(3): 170 - WHEN(4): 2013-Feb-03 - - NAME(1): ceta -DURATION(2): 33d12h - COUNT(3): 9 - WHEN(4): 06/Jan/2008 15:04:05 -0700`, - }, - - //------------------------ SORT TESTS - { - name: "sortbycolumn3", - column: 3, - sortby: "numeric", - numberize: true, - desc: false, - expect: ` -NAME(1) DURATION(2) COUNT(3) WHEN(4) -ceta 33d12h 9 06/Jan/2008 15:04:05 -0700 -beta 1d10h5m1s 33 3/1/2014 -alpha 4h35m 170 2013-Feb-03`, - }, - { - name: "sortbycolumn4", - column: 4, - sortby: "time", - desc: false, - numberize: true, - expect: ` -NAME(1) DURATION(2) COUNT(3) WHEN(4) -ceta 33d12h 9 06/Jan/2008 15:04:05 -0700 -alpha 4h35m 170 2013-Feb-03 -beta 1d10h5m1s 33 3/1/2014`, - }, - { - name: "sortbycolumn2", - column: 2, - sortby: "duration", - numberize: true, - desc: false, - expect: ` -NAME(1) DURATION(2) COUNT(3) WHEN(4) -alpha 4h35m 170 2013-Feb-03 -beta 1d10h5m1s 33 3/1/2014 -ceta 33d12h 9 06/Jan/2008 15:04:05 -0700`, - }, - - // ----------------------- UseColumns Tests - { - name: "usecolumns", - usecol: []int{1, 4}, - numberize: true, - usecolstr: "1,4", - expect: ` -NAME(1) WHEN(4) -beta 3/1/2014 -alpha 2013-Feb-03 -ceta 06/Jan/2008 15:04:05 -0700`, - }, - { - name: "usecolumns2", - usecol: []int{2}, - numberize: true, - usecolstr: "2", - expect: ` -DURATION(2) -1d10h5m1s -4h35m -33d12h`, - }, - { - name: "usecolumns3", - usecol: []int{3}, - numberize: true, - usecolstr: "3", - expect: ` -COUNT(3) -33 -170 -9`, - }, - { - name: "usecolumns4", - column: 0, - usecol: []int{1, 3}, - numberize: true, - usecolstr: "1,3", - expect: ` -NAME(1) COUNT(3) -beta 33 -alpha 170 -ceta 9`, - }, - { - name: "usecolumns", - usecol: []int{2, 4}, - numberize: true, - usecolstr: "2,4", - expect: ` -DURATION(2) WHEN(4) -1d10h5m1s 3/1/2014 -4h35m 2013-Feb-03 -33d12h 06/Jan/2008 15:04:05 -0700`, - }, -} - -func TestPrinter(t *testing.T) { - for _, testdata := range tests { - testname := fmt.Sprintf("print-%s-%d-desc-%t-sortby-%s-mode-%d-usecolumns-%s-numberize-%t", - testdata.name, testdata.column, testdata.desc, testdata.sortby, - testdata.mode, testdata.usecolstr, testdata.numberize) - - t.Run(testname, func(t *testing.T) { - // replaces os.Stdout, but we ignore it - var writer bytes.Buffer - - // cmd flags - conf := cfg.Config{ - SortDescending: testdata.desc, - SortMode: testdata.sortby, - OutputMode: testdata.mode, - Numbering: testdata.numberize, - UseColumns: testdata.usecol, - NoColor: true, - OFS: " ", - } - - if conf.OutputMode == cfg.CSV { - conf.OFS = "," - } - - if testdata.column > 0 { - conf.UseSortByColumn = []int{testdata.column} - } - - conf.Separator = cfg.SeparatorTemplates[":default:"] - conf.ApplyDefaults() - - // the test checks the len! - if len(testdata.usecol) > 0 { - conf.Columns = "yes" - } else { - conf.Columns = "" - } - - data := newData() - exp := strings.TrimSpace(testdata.expect) - - printData(&writer, conf, &data) - - got := strings.TrimSpace(writer.String()) - - assert.EqualValues(t, exp, got) - }) - } -} diff --git a/lib/sort.go b/lib/sort.go deleted file mode 100644 index bbf5431..0000000 --- a/lib/sort.go +++ /dev/null @@ -1,140 +0,0 @@ -/* -Copyright © 2022 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "cmp" - "regexp" - "sort" - "strconv" - - "github.com/araddon/dateparse" - "codeberg.org/scip/tablizer/cfg" -) - -func sortTable(conf cfg.Config, data *Tabdata) { - if len(conf.UseSortByColumn) == 0 { - // no sorting wanted - return - } - - // sanity checks - if len(data.entries) == 0 { - return - } - - // actual sorting - sort.SliceStable(data.entries, func(i, j int) bool { - // holds the result of a sort of one column - comparators := []int{} - - // iterate over all columns to be sorted, conf.SortMode must be identical! - for _, column := range conf.UseSortByColumn { - comparators = append(comparators, compare(&conf, data.entries[i][column-1], data.entries[j][column-1])) - } - - // return the combined result - res := cmp.Or(comparators...) - - switch res { - case 0: - return true - default: - return false - } - - }) -} - -// config is not modified here, but it would be inefficient to copy it every loop -func compare(conf *cfg.Config, left string, right string) int { - var comp bool - - switch conf.SortMode { - case "numeric": - left, err := strconv.Atoi(left) - if err != nil { - left = 0 - } - - right, err := strconv.Atoi(right) - if err != nil { - right = 0 - } - - comp = left < right - case "duration": - left := duration2int(left) - right := duration2int(right) - - comp = left < right - case "time": - left, _ := dateparse.ParseAny(left) - right, _ := dateparse.ParseAny(right) - - comp = left.Unix() < right.Unix() - default: - comp = left < right - } - - if conf.SortDescending { - comp = !comp - } - - switch comp { - case true: - return 0 - default: - return 1 - } -} - -/* -We could use time.ParseDuration(), but this doesn't support days. - -We could also use github.com/xhit/go-str2duration/v2, which does -the job, but it's just another dependency, just for this little -gem. And we don't need a time.Time value. And int is good enough -for duration comparison. - -Convert a duration into an integer. Valid time units are "s", -"m", "h" and "d". -*/ -func duration2int(duration string) int { - re := regexp.MustCompile(`(\d+)([dhms])`) - seconds := 0 - - for _, match := range re.FindAllStringSubmatch(duration, -1) { - if len(match) == 3 { - durationvalue, _ := strconv.Atoi(match[1]) - - switch match[2][0] { - case 'd': - seconds += durationvalue * 86400 - case 'h': - seconds += durationvalue * 3600 - case 'm': - seconds += durationvalue * 60 - case 's': - seconds += durationvalue - } - } - } - - return seconds -} diff --git a/lib/sort_test.go b/lib/sort_test.go deleted file mode 100644 index f547f74..0000000 --- a/lib/sort_test.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright © 2022 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "codeberg.org/scip/tablizer/cfg" -) - -func TestDuration2Seconds(t *testing.T) { - var tests = []struct { - dur string - expect int - }{ - {"1d", 60 * 60 * 24}, - {"1h", 60 * 60}, - {"10m", 60 * 10}, - {"2h4m10s", (60 * 120) + (4 * 60) + 10}, - {"88u", 0}, - {"19t77X what?4s", 4}, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("duration-%s", testdata.dur) - t.Run(testname, func(t *testing.T) { - seconds := duration2int(testdata.dur) - assert.EqualValues(t, testdata.expect, seconds) - }) - } -} - -func TestCompare(t *testing.T) { - var tests = []struct { - mode string - a string - b string - want int - desc bool - }{ - // ascending - {"numeric", "10", "20", 0, false}, - {"duration", "2d4h5m", "45m", 1, false}, - {"time", "12/24/2022", "1/1/1970", 1, false}, - - // descending - {"numeric", "10", "20", 1, true}, - {"duration", "2d4h5m", "45m", 0, true}, - {"time", "12/24/2022", "1/1/1970", 0, true}, - } - - for _, testdata := range tests { - testname := fmt.Sprintf("compare-mode-%s-a-%s-b-%s-desc-%t", - testdata.mode, testdata.a, testdata.b, testdata.desc) - - t.Run(testname, func(t *testing.T) { - c := cfg.Config{SortMode: testdata.mode, SortDescending: testdata.desc} - got := compare(&c, testdata.a, testdata.b) - assert.EqualValues(t, testdata.want, got) - }) - } -} diff --git a/lib/tableeditor.go b/lib/tableeditor.go deleted file mode 100644 index a9b9b5f..0000000 --- a/lib/tableeditor.go +++ /dev/null @@ -1,524 +0,0 @@ -/* -Copyright © 2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "fmt" - "os" - "strings" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/evertras/bubble-table/table" - "github.com/mattn/go-isatty" - "codeberg.org/scip/tablizer/cfg" -) - -// The context exists outside of the bubble loop, and is being used as -// pointer reciever. That way we can use it as our primary storage -// container. -type Context struct { - selectedColumn int - showHelp bool - descending bool - data *Tabdata - - // Window dimensions - totalWidth int - totalHeight int - - // Table dimensions - horizontalMargin int - verticalMargin int -} - -// Execute tablizer sort function, feed it with fresh config, we do -// NOT use the existing runtime config, because sorting is -// configurable in the UI separately. -func (ctx *Context) Sort(mode string) { - conf := cfg.Config{ - SortMode: mode, - SortDescending: ctx.descending, - UseSortByColumn: []int{ctx.selectedColumn + 1}, - } - - ctx.descending = !ctx.descending - - sortTable(conf, ctx.data) -} - -// The actual table model, holds the context pointer, a copy of the -// pre-processed data and some flags -type FilterTable struct { - Table table.Model - - Rows int - - quitting bool - unchanged bool - - maxColumns int - headerIdx map[string]int - - ctx *Context - - columns []table.Column -} - -type HelpLine []string -type HelpColumn []HelpLine - -const ( - // header+footer - ExtraRows = 5 - - HelpFooter = "?:help | " -) - -var ( - // we use our own custom border style - customBorder = table.Border{ - Top: "─", - Left: "│", - Right: "│", - Bottom: "─", - - TopRight: "╮", - TopLeft: "╭", - BottomRight: "╯", - BottomLeft: "╰", - - TopJunction: "┬", - LeftJunction: "├", - RightJunction: "┤", - BottomJunction: "┴", - InnerJunction: "┼", - - InnerDivider: "│", - } - - // Cells in selected columns will be highlighted - StyleSelected = lipgloss.NewStyle(). - Background(lipgloss.Color("#696969")). - Foreground(lipgloss.Color("#ffffff")). - Align(lipgloss.Left) - - StyleHeader = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#ff4500")). - Align(lipgloss.Left).Bold(true) - - // help buffer styles - StyleKey = lipgloss.NewStyle().Bold(true) - StyleHelp = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff4500")) - - // the default style - NoStyle = lipgloss.NewStyle().Align(lipgloss.Left) - - HelpData = []HelpColumn{ - { - HelpLine{"up", "navigate up"}, - HelpLine{"down", "navigate down"}, - HelpLine{"tab", "navigate columns"}, - }, - { - HelpLine{"s", "sort alpha-numerically"}, - HelpLine{"n", "sort numerically"}, - HelpLine{"t", "sort by time"}, - HelpLine{"d", "sort by duration"}, - }, - { - HelpLine{"spc", "[de]select a row"}, - HelpLine{"a", "[de]select all visible rows"}, - HelpLine{"f", "enter fuzzy filter"}, - HelpLine{"esc", "finish filter input"}, - }, - { - HelpLine{"?", "show help buffer"}, - HelpLine{"q", "commit and quit"}, - HelpLine{"c-c", "discard and quit"}, - }, - } - - // rendered from Help above - Help = "" - - // number of lines taken by help below, adjust accordingly! - HelpRows = 0 -) - -// generate a lipgloss styled help buffer consisting of various -// columns -func generateHelp() { - help := strings.Builder{} - helpcols := []string{} - maxrows := 0 - - for _, col := range HelpData { - help.Reset() - - // determine max key width to avoid excess spaces between keys and help - keylen := 0 - for _, line := range col { - if len(line[0]) > keylen { - keylen = len(line[0]) - } - } - - keylenstr := fmt.Sprintf("%d", keylen) - - for _, line := range col { - // 0: key, 1: help text - help.WriteString(StyleKey.Render(fmt.Sprintf("%-"+keylenstr+"s", line[0]))) - help.WriteString(" " + StyleHelp.Render(line[1]) + " \n") - } - - helpcols = append(helpcols, help.String()) - - if len(col) > maxrows { - maxrows = len(col) - } - } - - HelpRows = maxrows + 1 - Help = "\n" + lipgloss.JoinHorizontal(lipgloss.Top, helpcols...) -} - -// initializes the table model -func NewModel(data *Tabdata, ctx *Context) FilterTable { - columns := make([]table.Column, len(data.headers)) - lengths := make([]int, len(data.headers)) - hidx := make(map[string]int, len(data.headers)) - - // give columns at least the header width - for idx, header := range data.headers { - lengths[idx] = len(header) - hidx[strings.ToLower(header)] = idx - } - - // determine max width per column - for _, entry := range data.entries { - for i, cell := range entry { - if len(cell) > lengths[i] { - lengths[i] = len(cell) - } - } - } - - // determine flexFactor with base 10, used by flexColumns - for i, len := range lengths { - if len <= 10 { - lengths[i] = 1 - } else { - lengths[i] = len / 10 - } - } - - // setup column data with flexColumns - for idx, header := range data.headers { - columns[idx] = table.NewFlexColumn( - strings.ToLower(header), - StyleHeader.Render(header), - lengths[idx]).WithFiltered(true).WithStyle(NoStyle) - } - - // separate variable so we can share the row filling code - filtertbl := FilterTable{ - maxColumns: len(data.headers), - Rows: len(data.entries), - headerIdx: hidx, - ctx: ctx, - columns: columns, - } - - filtertbl.Table = table.New(columns) - filtertbl.fillRows() - - // finally construct help buffer - generateHelp() - - return filtertbl -} - -// Applied to every cell on every change (TAB,up,down key, resize -// event etc) -func CellController(input table.StyledCellFuncInput, m FilterTable) lipgloss.Style { - if m.headerIdx[input.Column.Key()] == m.ctx.selectedColumn { - return StyleSelected - } - - return NoStyle -} - -// Selects or deselects ALL rows -func (m *FilterTable) ToggleAllSelected() { - rows := m.Table.GetVisibleRows() - selected := m.Table.SelectedRows() - - if len(selected) > 0 { - for i, row := range selected { - rows[i] = row.Selected(false) - } - } else { - for i, row := range rows { - rows[i] = row.Selected(true) - } - } - - m.Table.WithRows(rows) -} - -// ? pressed, display help message -func (m FilterTable) ToggleHelp() { - m.ctx.showHelp = !m.ctx.showHelp -} - -func (m FilterTable) Init() tea.Cmd { - return nil -} - -// Forward call to context sort -func (m *FilterTable) Sort(mode string) { - m.ctx.Sort(mode) - m.fillRows() -} - -// Fills the table rows with our data. Called once on startup and -// repeatedly if the user changes the sort order in some way -func (m *FilterTable) fillRows() { - // required to be able to feed the model to the controller - controllerWrapper := func(input table.StyledCellFuncInput) lipgloss.Style { - return CellController(input, *m) - } - - // fill the rows with style - rows := make([]table.Row, len(m.ctx.data.entries)) - for idx, entry := range m.ctx.data.entries { - rowdata := make(table.RowData, len(entry)) - - for i, cell := range entry { - rowdata[strings.ToLower(m.ctx.data.headers[i])] = - table.NewStyledCellWithStyleFunc(cell+" ", controllerWrapper) - } - - rows[idx] = table.NewRow(rowdata) - } - - m.Table = m.Table. - WithRows(rows). - Filtered(true). - WithFuzzyFilter(). - Focused(true). - SelectableRows(true). - WithSelectedText(" ", "✓"). - WithFooterVisibility(true). - WithHeaderVisibility(true). - HighlightStyle(StyleSelected). - Border(customBorder) -} - -// Part of the bubbletea event loop, called every tick -func (m FilterTable) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var ( - cmd tea.Cmd - cmds []tea.Cmd - ) - - m.Table, cmd = m.Table.Update(msg) - cmds = append(cmds, cmd) - - // If the user is about to enter filter text, do NOT respond to - // key bindings, as they might be part of the filter! - if !m.Table.GetIsFilterInputFocused() { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "q": - m.quitting = true - m.unchanged = false - cmds = append(cmds, tea.Quit) - - case "ctrl+c": - m.quitting = true - m.unchanged = true - cmds = append(cmds, tea.Quit) - - case "a": - m.ToggleAllSelected() - - case "tab": - m.SelectNextColumn() - - case "?": - m.ToggleHelp() - m.recalculateTable() - - case "s": - m.Sort("alphanumeric") - - case "n": - m.Sort("numeric") - - case "d": - m.Sort("duration") - - case "t": - m.Sort("time") - } - } - } - - // Happens when the terminal window has been resized - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.ctx.totalWidth = msg.Width - m.ctx.totalHeight = msg.Height - - m.recalculateTable() - } - - m.updateFooter() - - return m, tea.Batch(cmds...) -} - -// Add some info to the footer -func (m *FilterTable) updateFooter() { - selected := m.Table.SelectedRows() - footer := fmt.Sprintf("selected: %d ", len(selected)) - - if m.Table.GetIsFilterInputFocused() { - footer = fmt.Sprintf("/%s %s", m.Table.GetCurrentFilter(), footer) - } else if m.Table.GetIsFilterActive() { - footer = fmt.Sprintf("Filter: %s %s", m.Table.GetCurrentFilter(), footer) - } - - m.Table = m.Table.WithStaticFooter(HelpFooter + footer) -} - -// Called on resize event (or if help has been toggled) -func (m *FilterTable) recalculateTable() { - m.Table = m.Table. - WithTargetWidth(m.calculateWidth()). - WithMinimumHeight(m.calculateHeight()). - WithPageSize(m.calculateHeight() - ExtraRows) -} - -func (m *FilterTable) calculateWidth() int { - return m.ctx.totalWidth - m.ctx.horizontalMargin -} - -// Take help height into account, if enabled -func (m *FilterTable) calculateHeight() int { - height := m.Rows + ExtraRows - - if height >= m.ctx.totalHeight { - height = m.ctx.totalHeight - m.ctx.verticalMargin - } else { - height = m.ctx.totalHeight - } - - if m.ctx.showHelp { - height = height - HelpRows - } - - return height -} - -// Part of the bubbletable event view, called every tick -func (m FilterTable) View() string { - body := strings.Builder{} - - if !m.quitting { - body.WriteString(m.Table.View()) - - if m.ctx.showHelp { - body.WriteString(Help) - } - } - - return body.String() -} - -// User hit the TAB key -func (m *FilterTable) SelectNextColumn() { - if m.ctx.selectedColumn == m.maxColumns-1 { - m.ctx.selectedColumn = 0 - } else { - m.ctx.selectedColumn++ - } -} - -// entry point from outside tablizer into table editor -func tableEditor(conf *cfg.Config, data *Tabdata) (*Tabdata, error) { - // we render to STDERR to avoid dead lock when the user redirects STDOUT - // see https://github.com/charmbracelet/bubbletea/issues/860 - // - // TODO: doesn't work with libgloss v2 anymore! - - out := os.Stderr - - if isatty.IsTerminal(os.Stdout.Fd()) { - out = os.Stdout - } - - lipgloss.SetDefaultRenderer(lipgloss.NewRenderer(out)) - - ctx := &Context{data: data} - - // Output to STDERR because there's a known bubbletea/lipgloss - // issue: if a program with a tui is expected to write something - // to STDOUT when the tui is finished, then the styles do not - // work. So we write to STDERR (which works) and tablizer can - // still be used inside pipes. - program := tea.NewProgram( - NewModel(data, ctx), - tea.WithOutput(out), - tea.WithAltScreen()) - - m, err := program.Run() - - if err != nil { - return nil, err - } - - if m.(FilterTable).unchanged { - return data, err - } - - // Data has been modified. Extract it, put it back into our own - // structure and give control back to cmdline tablizer. - filteredtable := m.(FilterTable) - - data.entries = make([][]string, len(filteredtable.Table.SelectedRows())) - for pos, row := range m.(FilterTable).Table.SelectedRows() { - entry := make([]string, len(data.headers)) - for idx, field := range data.headers { - cell := row.Data[strings.ToLower(field)] - switch value := cell.(type) { - case string: - entry[idx] = value - case table.StyledCell: - entry[idx] = value.Data.(string) - } - } - - data.entries[pos] = entry - } - - return data, err -} diff --git a/lib/yank.go b/lib/yank.go deleted file mode 100644 index 18f7c13..0000000 --- a/lib/yank.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright © 2022-2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "log" - "strings" - - "github.com/tiagomelo/go-clipboard/clipboard" - "codeberg.org/scip/tablizer/cfg" -) - -func yankColumns(conf cfg.Config, data *Tabdata) { - var yank []string - - if len(data.entries) == 0 || len(conf.UseYankColumns) == 0 { - return - } - - for _, row := range data.entries { - for i, field := range row { - for _, idx := range conf.UseYankColumns { - if i == idx-1 { - yank = append(yank, field) - } - } - } - } - - if len(yank) > 0 { - cb := clipboard.New(clipboard.ClipboardOptions{Primary: true}) - if err := cb.CopyText(strings.Join(yank, " ")); err != nil { - log.Fatalln("error writing string to clipboard:", err) - } - } -} diff --git a/lib/yank_test.go b/lib/yank_test.go deleted file mode 100644 index 32aec62..0000000 --- a/lib/yank_test.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright © 2025 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "bytes" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/tiagomelo/go-clipboard/clipboard" - "codeberg.org/scip/tablizer/cfg" -) - -var yanktests = []struct { - name string - yank []int // -y$colum,$column... after processing - filter string - expect string -}{ - { - name: "one", - yank: []int{1}, - filter: "beta", - }, -} - -func DISABLED_TestYankColumns(t *testing.T) { - cb := clipboard.New() - - for _, testdata := range yanktests { - testname := fmt.Sprintf("yank-%s-filter-%s", - testdata.name, testdata.filter) - t.Run(testname, func(t *testing.T) { - conf := cfg.Config{ - OutputMode: cfg.ASCII, - UseYankColumns: testdata.yank, - NoColor: true, - } - - conf.ApplyDefaults() - data := newData() // defined in printer_test.go, reused here - - var writer bytes.Buffer - printData(&writer, conf, &data) - - got, err := cb.PasteText() - - assert.NoError(t, err) - assert.EqualValues(t, testdata.expect, got) - }) - } -} diff --git a/main.go b/main.go deleted file mode 100644 index ea695a3..0000000 --- a/main.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright © 2022 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package main - -import ( - "os" - - "codeberg.org/scip/tablizer/cmd" -) - -func main() { - os.Exit(Main()) -} - -func Main() int { - cmd.Execute() - - return 0 // cmd takes care of exit 1 itself -} diff --git a/main_test.go b/main_test.go deleted file mode 100644 index ddad86e..0000000 --- a/main_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "testing" - - "github.com/rogpeppe/go-internal/testscript" -) - -func TestMain(m *testing.M) { - testscript.Main(m, map[string]func(){ - "tablizer": main, - }) -} - -func TestTablizer(t *testing.T) { - testscript.Run(t, testscript.Params{ - Dir: "t", - }) -} diff --git a/mkrel.sh b/mkrel.sh deleted file mode 100755 index c48ac96..0000000 --- a/mkrel.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -# Copyright © 2022 Thomas von Dein - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# get list with: go tool dist list -DIST="darwin/amd64 -freebsd/amd64 -linux/amd64 -netbsd/amd64 -openbsd/amd64 -windows/amd64" - -tool="$1" -version="$2" - -if test -z "$version"; then - echo "Usage: $0 " - exit 1 -fi - -rm -rf releases -mkdir -p releases - - -for D in $DIST; do - os=${D/\/*/} - arch=${D/*\//} - binfile="releases/${tool}-${os}-${arch}-${version}" - tardir="${tool}-${os}-${arch}-${version}" - tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz" - pie="" - - if test "$D" = "linux/amd64"; then - pie="-buildmode=pie" - fi - - set -x - GOOS=${os} GOARCH=${arch} go build -tags osusergo,netgo -ldflags "-extldflags=-static -w -X 'codeberg.org/scip/tablizer/cfg.VERSION=${version}'" --trimpath $pie -o ${binfile} - strip --strip-all ${binfile} - mkdir -p ${tardir} - cp ${binfile} README.md LICENSE ${tardir}/ - echo 'tool = tablizer -PREFIX = /usr/local -UID = root -GID = 0 - -install: - install -d -o $(UID) -g $(GID) $(PREFIX)/bin - install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1 - install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/ - install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile - tar cpzf ${tarfile} ${tardir} - sha256sum ${binfile} | cut -d' ' -f1 > ${binfile}.sha256 - sha256sum ${tarfile} | cut -d' ' -f1 > ${tarfile}.sha256 - rm -rf ${tardir} - set +x -done - diff --git a/t/plugintest.zy b/t/plugintest.zy deleted file mode 100644 index 5299306..0000000 --- a/t/plugintest.zy +++ /dev/null @@ -1,10 +0,0 @@ -/* -Simple filter hook function. Splits the argument by whitespace, -fetches the 2nd element, converts it to an int and returns true -if it s larger than 5, false otherwise. -*/ -(defn uselarge [line] - (cond (> (atoi (second (resplit line `\s+`))) 5) true false)) - -/* Register the filter hook */ -(addhook %filter %uselarge) diff --git a/t/test-basics.txtar b/t/test-basics.txtar deleted file mode 100644 index 4355ce9..0000000 --- a/t/test-basics.txtar +++ /dev/null @@ -1,42 +0,0 @@ -# usage -exec tablizer --help -stdout Usage - -exec tablizer -h -stdout show - -# version -exec tablizer -V -stdout version - -# completion -exec tablizer --completion bash -stdout __tablizer_init_completion - -# use config (configures colors, but these are not being used, since -# this env doesn't support it, but at least it should succeed. -exec tablizer -f config.hcl -r testtable.txt Runn -stdout Runn - - - -# will be automatically created in work dir --- testtable.txt -- -NAME READY STATUS RESTARTS AGE -alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d -grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m -kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m -kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s - - --- config.hcl -- -BG = "lightGreen" -FG = "white" -HighlightBG = "lightGreen" -HighlightFG = "white" -NoHighlightBG = "white" -NoHighlightFG = "lightGreen" -HighlightHdrBG = "red" -HighlightHdrFG = "white" - diff --git a/t/test-csv.txtar b/t/test-csv.txtar deleted file mode 100644 index 6725e67..0000000 --- a/t/test-csv.txtar +++ /dev/null @@ -1,26 +0,0 @@ -# reading from file and matching with lowercase words -exec tablizer -c name,status -r testtable.csv -s, -stdout grafana.*Runn - -# matching mixed case -exec tablizer -c NAME,staTUS -r testtable.csv -s, -stdout grafana.*Runn - -# matching using numbers -exec tablizer -c 1,3 -r testtable.csv -s, -stdout grafana.*Runn - -# matching using regex -exec tablizer -c 'na.*,stat.' -r testtable.csv -s, -stdout grafana.*Runn - - -# will be automatically created in work dir --- testtable.csv -- -NAME,READY,STATUS,RESTARTS,AGE -alertmanager-kube-prometheus-alertmanager-0,2/2,Running,35 (45m ago),11d -grafana-fcc54cbc9-bk7s8,1/1,Running,17 (45m ago),1d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7,1/1,Running,17 (45m ago),1h44m -kube-prometheus-kube-state-metrics-b4cd9487-75p7f,1/1,Running,20 (45m ago),45m -kube-prometheus-node-exporter-bfzpl,1/1,Running,17 (45m ago),54s - diff --git a/t/test-filtering.txtar b/t/test-filtering.txtar deleted file mode 100644 index f7f0477..0000000 --- a/t/test-filtering.txtar +++ /dev/null @@ -1,21 +0,0 @@ -# filtering -exec tablizer -r testtable.txt -F name=grafana -stdout grafana.*Runn - -# filtering two columns -exec tablizer -r testtable.txt -F name=prometh -F age=1h -stdout blackbox.*Runn - -# filtering two same columns -exec tablizer -r testtable.txt -F name=prometh -F name=alert -stdout prometheus-alertmanager.*Runn - - -# will be automatically created in work dir --- testtable.txt -- -NAME READY STATUS RESTARTS AGE -alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d -grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m -kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m -kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s diff --git a/t/test-headermatching.txtar b/t/test-headermatching.txtar deleted file mode 100644 index 98c41ff..0000000 --- a/t/test-headermatching.txtar +++ /dev/null @@ -1,25 +0,0 @@ -# reading from file and matching with lowercase words -exec tablizer -c name,status -r testtable.txt -stdout grafana.*Runn - -# matching mixed case -exec tablizer -c NAME,staTUS -r testtable.txt -stdout grafana.*Runn - -# matching using numbers -exec tablizer -c 1,3 -r testtable.txt -stdout grafana.*Runn - -# matching using regex -exec tablizer -c 'na.*,stat.' -r testtable.txt -stdout grafana.*Runn - - -# will be automatically created in work dir --- testtable.txt -- -NAME READY STATUS RESTARTS AGE -alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d -grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m -kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m -kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s diff --git a/t/test-multipatterns.txtar b/t/test-multipatterns.txtar deleted file mode 100644 index 90852a1..0000000 --- a/t/test-multipatterns.txtar +++ /dev/null @@ -1,46 +0,0 @@ -# filtering - -# a AND b -exec tablizer -r testtable.txt -H -cspecies invasive imperium -stdout 'namak' -! stdout human - -# a AND !b -exec tablizer -r testtable.txt -H -cspecies invasive '/imperium/!' -stdout 'human' -! stdout namak - -# a AND !b AND c -exec tablizer -r testtable.txt -H -cspecies peaceful '/imperium/!' planetary -stdout 'kenaha' -! stdout 'namak|heduu|riedl' - -# case insensitive -exec tablizer -r testtable.txt -H -cspecies '/REGIONAL/i' -stdout namak -! stdout 'human|riedl|heduu|kenaa' - -# case insensitive negated -exec tablizer -r testtable.txt -H -cspecies '/REGIONAL/!i' -stdout 'human|riedl|heduu|kenaa' -! stdout namak - -# !a AND !b -exec tablizer -r testtable.txt -H -cspecies '/galactic/!' '/planetary/!' -stdout namak -! stdout 'human|riedl|heduu|kenaa' - -# same case insensitive -exec tablizer -r testtable.txt -H -cspecies '/GALACTIC/i!' '/PLANETARY/!i' -stdout namak -! stdout 'human|riedl|heduu|kenaa' - -# will be automatically created in work dir --- testtable.txt -- -SPECIES TYPE HOME STAGE SPREAD -human invasive earth brink planetary -riedl peaceful keauna civilized pangalactic -namak invasive namak imperium regional -heduu peaceful iu imperium galactic -kenaha peaceful kohi hunter-gatherer planetary - diff --git a/t/test-sort.txtar b/t/test-sort.txtar deleted file mode 100644 index 1d9e3fd..0000000 --- a/t/test-sort.txtar +++ /dev/null @@ -1,49 +0,0 @@ -# sort by name -exec tablizer -r testtable.txt -k 1 -stdout '^alert.*\n^grafana.*\n^kube' - -# sort by name reversed -exec tablizer -r testtable.txt -k 1 -D -stdout 'kube.*\n^grafana.*\n^alert' - -# sort by starts numerically -exec tablizer -r testtable.txt -k 4 -i -c4 -stdout '17\s*\n^20\s*\n^35' - -# sort by starts numerically reversed -exec tablizer -r testtable.txt -k 4 -i -c4 -D -stdout '35\s*\n^20\s*\n^17' - -# sort by age -exec tablizer -r testtable.txt -k 5 -a -stdout '45m\s*\n.*1h44m' - -# sort by age reverse -exec tablizer -r testtable.txt -k 5 -a -D -stdout '1h44m\s*\n.*45m' - -# sort by time -exec tablizer -r timetable.txt -k 2 -t -stdout '^sel.*\n^foo.*\nbar' - -# sort by time reverse -exec tablizer -r timetable.txt -k 2 -t -D -stdout '^bar.*\n^foo.*\nsel' - - -# will be automatically created in work dir --- testtable.txt -- -NAME READY STATUS STARTS AGE -alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 11d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 1h44m -grafana-fcc54cbc9-bk7s8 1/1 Running 17 1d -kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 45m -kube-prometheus-node-exporter-bfzpl 1/1 Running 17 54s - - - --- timetable.txt -- -NAME TIME -foo 2024-11-18T12:00:00+01:00 -bar 2024-11-18T12:45:00+01:00 -sel 2024-07-18T12:00:00+01:00 diff --git a/t/test-stdin.txtar b/t/test-stdin.txtar deleted file mode 100644 index 30a25f3..0000000 --- a/t/test-stdin.txtar +++ /dev/null @@ -1,18 +0,0 @@ -# reading from stdin and matching with lowercase words -stdin testtable.txt -exec tablizer -c name,status -stdout grafana.*Runn - -# reading from -r stdin and matching with lowercase words -stdin testtable.txt -exec tablizer -c name,status -r - -stdout grafana.*Runn - -# will be automatically created in work dir --- testtable.txt -- -NAME READY STATUS RESTARTS AGE -alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d -grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m -kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m -kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s diff --git a/t/test-transpose.txtar b/t/test-transpose.txtar deleted file mode 100644 index 0ade9ef..0000000 --- a/t/test-transpose.txtar +++ /dev/null @@ -1,21 +0,0 @@ -# transpose one field -exec tablizer -r testtable.txt -T status -R '/Running/OK/' -stdout grafana.*OK - -# transpose two fields -exec tablizer -r testtable.txt -T name,status -R '/alertmanager-//' -R '/Running/OK/' -stdout prometheus-0.*OK - -# transpose one field and show one column -exec tablizer -r testtable.txt -T status -R '/Running/OK/' -c name -! stdout grafana.*OK - - -# will be automatically created in work dir --- testtable.txt -- -NAME READY STATUS RESTARTS AGE -alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d -grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m -kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m -kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s diff --git a/t/testtable b/t/testtable deleted file mode 100644 index 6f64f3e..0000000 --- a/t/testtable +++ /dev/null @@ -1,6 +0,0 @@ -NAME READY STATUS RESTARTS AGE -alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 (45m ago) 11d -grafana-fcc54cbc9-bk7s8 1/1 Running 17 (45m ago) 1d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 (45m ago) 1h44m -kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 (45m ago) 45m -kube-prometheus-node-exporter-bfzpl 1/1 Running 17 (45m ago) 54s diff --git a/t/testtable.csv b/t/testtable.csv deleted file mode 100644 index 3581eca..0000000 --- a/t/testtable.csv +++ /dev/null @@ -1,6 +0,0 @@ -NAME,DURATION -x,10 -a,100 -z,0 -u,4 -k,6 diff --git a/t/testtable2 b/t/testtable2 deleted file mode 100644 index 6397b92..0000000 --- a/t/testtable2 +++ /dev/null @@ -1,6 +0,0 @@ -NAME DURATION -x 10 -a 100 -z 0 -u 4 -k 6 diff --git a/t/testtable3 b/t/testtable3 deleted file mode 100644 index 4d8dac7..0000000 --- a/t/testtable3 +++ /dev/null @@ -1,6 +0,0 @@ -NAME READY STATUS STARTS AGE -alertmanager-kube-prometheus-alertmanager-0 2/2 Running 35 11d -kube-prometheus-blackbox-exporter-5d85b5d8f4-tskh7 1/1 Running 17 1h44m -grafana-fcc54cbc9-bk7s8 1/1 Running 17 1d -kube-prometheus-kube-state-metrics-b4cd9487-75p7f 1/1 Running 20 45m -kube-prometheus-node-exporter-bfzpl 1/1 Running 17 54s diff --git a/t/testtable4 b/t/testtable4 deleted file mode 100644 index cc06395..0000000 --- a/t/testtable4 +++ /dev/null @@ -1,4 +0,0 @@ -ONE TWO -1 4 -3 1 -5 2 diff --git a/t/testtable5 b/t/testtable5 deleted file mode 100644 index f8f9eb3..0000000 --- a/t/testtable5 +++ /dev/null @@ -1,6 +0,0 @@ -SPECIES TYPE HOME STAGE -human invasive earth brink -riedl peaceful keauna civilized -namak invasive namak imperium -heduu peaceful iu imperium -kenaha peaceful kohi hunter-gatherer diff --git a/tablizer.1 b/tablizer.1 deleted file mode 100644 index a6c1799..0000000 --- a/tablizer.1 +++ /dev/null @@ -1,711 +0,0 @@ -.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42) -.\" -.\" Standard preamble: -.\" ======================================================================== -.de Sp \" Vertical space (when we can't use .PP) -.if t .sp .5v -.if n .sp -.. -.de Vb \" Begin verbatim text -.ft CW -.nf -.ne \\$1 -.. -.de Ve \" End verbatim text -.ft R -.fi -.. -.\" Set up some character translations and predefined strings. \*(-- will -.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left -.\" double quote, and \*(R" will give a right double quote. \*(C+ will -.\" give a nicer C++. Capital omega is used to do unbreakable dashes and -.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, -.\" nothing in troff, for use with C<>. -.tr \(*W- -.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' -.ie n \{\ -. ds -- \(*W- -. ds PI pi -. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch -. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch -. ds L" "" -. ds R" "" -. ds C` "" -. ds C' "" -'br\} -.el\{\ -. ds -- \|\(em\| -. ds PI \(*p -. ds L" `` -. ds R" '' -. ds C` -. ds C' -'br\} -.\" -.\" Escape single quotes in literal strings from groff's Unicode transform. -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.\" -.\" If the F register is >0, we'll generate index entries on stderr for -.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index -.\" entries marked with X<> in POD. Of course, you'll have to process the -.\" output yourself in some meaningful fashion. -.\" -.\" Avoid warning from groff about undefined register 'F'. -.de IX -.. -.nr rF 0 -.if \n(.g .if rF .nr rF 1 -.if (\n(rF:(\n(.g==0)) \{\ -. if \nF \{\ -. de IX -. tm Index:\\$1\t\\n%\t"\\$2" -.. -. if !\nF==2 \{\ -. nr % 0 -. nr F 2 -. \} -. \} -.\} -.rr rF -.\" -.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). -.\" Fear. Run. Save yourself. No user-serviceable parts. -. \" fudge factors for nroff and troff -.if n \{\ -. ds #H 0 -. ds #V .8m -. ds #F .3m -. ds #[ \f1 -. ds #] \fP -.\} -.if t \{\ -. ds #H ((1u-(\\\\n(.fu%2u))*.13m) -. ds #V .6m -. ds #F 0 -. ds #[ \& -. ds #] \& -.\} -. \" simple accents for nroff and troff -.if n \{\ -. ds ' \& -. ds ` \& -. ds ^ \& -. ds , \& -. ds ~ ~ -. ds / -.\} -.if t \{\ -. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" -. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' -. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' -. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' -. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' -. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' -.\} -. \" troff and (daisy-wheel) nroff accents -.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' -.ds 8 \h'\*(#H'\(*b\h'-\*(#H' -.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] -.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' -.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' -.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] -.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] -.ds ae a\h'-(\w'a'u*4/10)'e -.ds Ae A\h'-(\w'A'u*4/10)'E -. \" corrections for vroff -.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' -.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' -. \" for low resolution devices (crt and lpr) -.if \n(.H>23 .if \n(.V>19 \ -\{\ -. ds : e -. ds 8 ss -. ds o a -. ds d- d\h'-1'\(ga -. ds D- D\h'-1'\(hy -. ds th \o'bp' -. ds Th \o'LP' -. ds ae ae -. ds Ae AE -.\} -.rm #[ #] #H #V #F C -.\" ======================================================================== -.\" -.IX Title "TABLIZER 1" -.TH TABLIZER 1 "2025-10-13" "1" "User Commands" -.\" For nroff, turn off justification. Always turn off hyphenation; it makes -.\" way too many mistakes in technical documents. -.if n .ad l -.nh -.SH "NAME" -tablizer \- Manipulate tabular output of other programs -.SH "SYNOPSIS" -.IX Header "SYNOPSIS" -.Vb 2 -\& Usage: -\& tablizer [regex,...] [\-r file] [flags] -\& -\& Operational Flags: -\& \-c, \-\-columns string Only show the speficied columns (separated by ,) -\& \-v, \-\-invert\-match select non\-matching rows -\& \-n, \-\-numbering Enable header numbering -\& \-N, \-\-no\-color Disable pattern highlighting -\& \-H, \-\-no\-headers Disable headers display -\& \-s, \-\-separator Custom field separator (maybe char, string or :class:) -\& \-k, \-\-sort\-by Sort by column (default: 1) -\& \-z, \-\-fuzzy Use fuzzy search [experimental] -\& \-F, \-\-filter Filter given field with regex, can be used multiple times -\& \-T, \-\-transpose\-columns string Transpose the speficied columns (separated by ,) -\& \-R, \-\-regex\-transposer Apply /search/replace/ regexp to fields given in \-T -\& \-j, \-\-json Read JSON input (must be array of hashes) -\& \-I, \-\-interactive Interactively filter and select rows -\& \-g, \-\-auto\-headers Generate headers if there are none present in input -\& \-x, \-\-custom\-headers a,b,... Use custom headers, separated by comma -\& -\& Output Flags (mutually exclusive): -\& \-X, \-\-extended Enable extended output -\& \-M, \-\-markdown Enable markdown table output -\& \-O, \-\-orgtbl Enable org\-mode table output -\& \-S, \-\-shell Enable shell evaluable output -\& \-Y, \-\-yaml Enable yaml output -\& \-J, \-\-jsonout Enable JSON output -\& \-C, \-\-csv Enable CSV output -\& \-A, \-\-ascii Default output mode, ascii tabular -\& \-L, \-\-hightlight\-lines Use alternating background colors for tables -\& \-o, \-\-ofs Output field separator, used by \-A and \-C. -\& \-y, \-\-yank\-columns Yank specified columns (separated by ,) to clipboard, -\& space separated -\& -\& Sort Mode Flags (mutually exclusive): -\& \-a, \-\-sort\-age sort according to age (duration) string -\& \-D, \-\-sort\-desc Sort in descending order (default: ascending) -\& \-i, \-\-sort\-numeric sort according to string numerical value -\& \-t, \-\-sort\-time sort according to time string -\& -\& Other Flags: -\& \-r \-\-read\-file Use as input instead of STDIN -\& \-\-completion Generate the autocompletion script for -\& \-f, \-\-config Configuration file (default: ~/.config/tablizer/config) -\& \-d, \-\-debug Enable debugging -\& \-h, \-\-help help for tablizer -\& \-m, \-\-man Display manual page -\& \-V, \-\-version Print program version -.Ve -.SH "DESCRIPTION" -.IX Header "DESCRIPTION" -Many programs generate tabular output. But sometimes you need to -post-process these tables, you may need to remove one or more columns -or you may want to filter for some pattern (See \s-1PATTERNS\s0) or you -may need the output in another program and need to parse it somehow. -Standard unix tools such as \fBawk\fR\|(1), \fBgrep\fR\|(1) or \fBcolumn\fR\|(1) may help, but -sometimes it's a tedious business. -.PP -Let's take the output of the tool kubectl. It contains cells with -withespace and they do not separate columns by \s-1TAB\s0 characters. This is -not easy to process. -.PP -You can use \fBtablizer\fR to do these and more things. -.PP -\&\fBtablizer\fR analyses the header fields of a table, registers the -column positions of each header field and separates columns by those -positions. -.PP -Without any options it reads its input from \f(CW\*(C`STDIN\*(C'\fR, but you can also -specify a file as a parameter. If you want to reduce the output by -some regular expression, just specify it as its first parameter. You -may also use the \fB\-v\fR option to exclude all rows which match the -pattern. Hence: -.PP -.Vb 2 -\& # read from STDIN -\& kubectl get pods | tablizer -\& -\& # read a file -\& tablizer \-r filename -\& -\& # search for pattern in a file (works like grep) -\& tablizer regex \-r filename -\& -\& # search for pattern in STDIN -\& kubectl get pods | tablizer regex -.Ve -.PP -The output looks like the original one. You can add the option \fB\-n\fR, -then every header field will have a numer associated with it, e.g.: -.PP -.Vb 1 -\& NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) -.Ve -.PP -These numbers denote the column and you can use them to specify which -columns you want to have in your output (see \s-1COLUMNS\s0: -.PP -.Vb 1 -\& kubectl get pods | tablizer \-c1,3 -.Ve -.PP -You can specify the numbers in any order but output will always follow -the original order. -.PP -However, you may also just use the header names instead of numbers, -eg: -.PP -.Vb 1 -\& kubectl get pods | tablizer \-cname,status -.Ve -.PP -You can also use regular expressions with \fB\-c\fR, eg: -.PP -.Vb 1 -\& kubectl get pods | tablizer \-c \*(Aq[ae]\*(Aq -.Ve -.PP -By default tablizer shows a header containing the names of each -column. This can be disabled using the \fB\-H\fR option. Be aware that -this only affects tabular output modes. Shell, Extended, Yaml and \s-1CSV\s0 -output modes always use the column names. -.PP -By default, if a \fBpattern\fR has been speficied, matches will be -highlighted. You can disable this behavior with the \fB\-N\fR option. -.PP -Use the \fB\-k\fR option to specify by which column to sort the tabular -data (as in \s-1GNU\s0 \fBsort\fR\|(1)). The default sort column is the first -one. You can specify column numbers or names. Column numbers start -with 1, names are case insensitive. You can specify multiple columns -separated by comma to sort, but the type must be the same. For example -if you want to sort numerically, all columns must be numbers. If you -use column numbers, then be aware, that these are the numbers before -column extraction. For example if you have a table with 4 columns and -specify \f(CW\*(C`\-c4\*(C'\fR, then only 1 column (the fourth) will be printed, -however if you want to sort by this column, you'll have to specify -\&\f(CW\*(C`\-k4\*(C'\fR. -.PP -The default sort order is ascending. You can change this to -descending order using the option \fB\-D\fR. The default sort order is by -alphanumeric string, but there are other sort modes: -.IP "\fB\-a \-\-sort\-age\fR" 4 -.IX Item "-a --sort-age" -Sorts duration strings like \*(L"1d4h32m51s\*(R". -.IP "\fB\-i \-\-sort\-numeric\fR" 4 -.IX Item "-i --sort-numeric" -Sorts numeric fields. -.IP "\fB\-t \-\-sort\-time\fR" 4 -.IX Item "-t --sort-time" -Sorts timestamps. -.PP -Finally the \fB\-d\fR option enables debugging output which is mostly -useful for the developer. -.SS "\s-1SEPARATOR\s0" -.IX Subsection "SEPARATOR" -The option \fB\-s\fR can be a single character, in which case the \s-1CSV\s0 -parser will be invoked. You can also specify a string as -separator. The string will be interpreted as literal string unless it -is a valid go regular expression. For example: -.PP -.Vb 1 -\& \-s \*(Aq\et{2,}\e\*(Aq -.Ve -.PP -is being used as a regexp and will match two or more consecutive tabs. -.PP -.Vb 1 -\& \-s \*(Aqfoo\*(Aq -.Ve -.PP -on the other hand is no regular expression and will be used literally. -.PP -To make live easier, there are a couple of predefined regular -expressions, which you can specify as classes: -.Sp -.RS 4 -* :tab: -.Sp -Matches a tab and eats spaces around it. -.Sp -* :spaces: -.Sp -Matches 2 or more spaces. -.Sp -* :pipe: -.Sp -Matches a pipe character and eats spaces around it. -.Sp -* :default: -.Sp -Matches 2 or more spaces or tab. This is the default separator if none -is specified. -.Sp -* :nonword: -.Sp -Matches a non-word character. -.Sp -* :nondigit: -.Sp -Matches a non-digit character. -.Sp -* :special: -.Sp -Matches one or more special chars like brackets, dollar sign, slashes etc. -.Sp -* :nonprint: -.Sp -Matches one or more non-printable characters. -.RE -.SS "\s-1PATTERNS AND FILTERING\s0" -.IX Subsection "PATTERNS AND FILTERING" -You can reduce the rows being displayed by using one or more regular -expression patterns. The regexp language being used is the one of -\&\s-1GOLANG,\s0 refer to the syntax cheat sheet here: -. -.PP -If you want to read a more comprehensive documentation about the -topic and have perl installed you can read it with: -.PP -.Vb 1 -\& perldoc perlre -.Ve -.PP -Or read it online: . But please note -that the \s-1GO\s0 regexp engine does \s-1NOT\s0 support all perl regex terms, -especially look-ahead and look-behind. -.PP -If you want to supply flags to a regex, then surround it with slashes -and append the flag. The following flags are supported: -.PP -.Vb 2 -\& i => case insensitive -\& ! => negative match -.Ve -.PP -Example for a case insensitive search: -.PP -.Vb 1 -\& kubectl get pods \-A | tablizer "/account/i" -.Ve -.PP -If you use the \f(CW\*(C`!\*(C'\fR flag, then the regex match will be negated, that -is, if a line in the input matches the given regex, but \f(CW\*(C`!\*(C'\fR is -supplied, tablizer will \s-1NOT\s0 include it in the output. -.PP -For example, here we want to get all lines matching \*(L"foo\*(R" but not -\&\*(L"bar\*(R": -.PP -.Vb 1 -\& cat table | tablizer foo \*(Aq/bar/!\*(Aq -.Ve -.PP -This would match a line \*(L"foo zorro\*(R" but not \*(L"foo bar\*(R". -.PP -The flags can also be combined. -.PP -You can also use the experimental fuzzy search feature by providing the -option \fB\-z\fR, in which case the pattern is regarded as a fuzzy search -term, not a regexp. -.PP -Sometimes you want to filter by one or more columns. You can do that -using the \fB\-F\fR option. The option can be specified multiple times and -has the following format: -.PP -.Vb 1 -\& fieldname=regexp -.Ve -.PP -Fieldnames (== columns headers) are case insensitive. -.PP -If you specify more than one filter, both filters have to match (\s-1AND\s0 -operation). -.PP -These field filters can also be negated: -.PP -.Vb 1 -\& fieldname!=regexp -.Ve -.PP -If the option \fB\-v\fR is specified, the filtering is inverted. -.SS "\s-1INTERACTIVE FILTERING\s0" -.IX Subsection "INTERACTIVE FILTERING" -You can also use the interactive mode, enabled with \f(CW\*(C`\-I\*(C'\fR to filter -and select rows. This mode is complementary, that is, other filter -options are still being respected. -.PP -To enter e filter, hit \f(CW\*(C`/\*(C'\fR, enter a filter string and finish with -\&\f(CW\*(C`ENTER\*(C'\fR. Use \f(CW\*(C`SPACE\*(C'\fR to select/deselect rows, use \f(CW\*(C`a\*(C'\fR to select all -(visible) rows. -.PP -Commit your selection with \f(CW\*(C`q\*(C'\fR. The selected rows are being fed to -the requested output mode as usual. Abort with \f(CW\*(C`CTRL\-c\*(C'\fR, in which -case the results of the interactive mode are being ignored and all -rows are being fed to output. -.SS "\s-1COLUMNS\s0" -.IX Subsection "COLUMNS" -The parameter \fB\-c\fR can be used to specify, which columns to -display. By default tablizer numerizes the header names and these -numbers can be used to specify which header to display, see example -above. -.PP -However, beside numbers, you can also use regular expressions with -\&\fB\-c\fR, also separated by comma. And you can mix column numbers with -regexps. -.PP -Lets take this table: -.PP -.Vb 4 -\& PID TTY TIME CMD -\& 14001 pts/0 00:00:00 bash -\& 42871 pts/0 00:00:00 ps -\& 42872 pts/0 00:00:00 sed -.Ve -.PP -We want to see only the \s-1CMD\s0 column and use a regex for this: -.PP -.Vb 6 -\& ps | tablizer \-s \*(Aq\es+\*(Aq \-c C -\& CMD(4) -\& bash -\& ps -\& tablizer -\& sed -.Ve -.PP -where \*(L"C\*(R" is our regexp which matches \s-1CMD.\s0 -.PP -If a column specifier doesn't look like a regular expression, matching -against header fields will be case insensitive. So, if you have a -field with the name \f(CW\*(C`ID\*(C'\fR then these will all match: \f(CW\*(C`\-c id\*(C'\fR, \f(CW\*(C`\-c -Id\*(C'\fR. The same rule applies to the options \f(CW\*(C`\-T\*(C'\fR and \f(CW\*(C`\-F\*(C'\fR. -.SS "\s-1TRANSPOSE FIELDS USING REGEXPS\s0" -.IX Subsection "TRANSPOSE FIELDS USING REGEXPS" -You can manipulate field contents using regular expressions. You have -to tell tablizer which field[s] to operate on using the option \f(CW\*(C`\-T\*(C'\fR -and the search/replace pattern using \f(CW\*(C`\-R\*(C'\fR. The number of columns and -patterns must match. -.PP -A search/replace pattern consists of the following elements: -.PP -.Vb 1 -\& /search\-regexp/replace\-string/ -.Ve -.PP -The separator can be any valid character. Especially if you want to -use a regexp containing the \f(CW\*(C`/\*(C'\fR character, eg: -.PP -.Vb 1 -\& |search\-regexp|replace\-string| -.Ve -.PP -Example: -.PP -.Vb 7 -\& cat t/testtable2 -\& NAME DURATION -\& x 10 -\& a 100 -\& z 0 -\& u 4 -\& k 6 -\& -\& cat t/testtable2 | tablizer \-T2 \-R \*(Aq/^\ed/4/\*(Aq \-n -\& NAME DURATION -\& x 40 -\& a 400 -\& z 4 -\& u 4 -\& k 4 -.Ve -.SS "\s-1OUTPUT MODES\s0" -.IX Subsection "OUTPUT MODES" -There might be cases when the tabular output of a program is way too -large for your current terminal but you still need to see every -column. In such cases the \fB\-o extended\fR or \fB\-X\fR option can be -useful which enables \fIextended mode\fR. In this mode, each row will be -printed vertically, header left, value right, aligned by the field -widths. Here's an example: -.PP -.Vb 6 -\& kubectl get pods | ./tablizer \-o extended -\& NAME: repldepl\-7bcd8d5b64\-7zq4l -\& READY: 1/1 -\& STATUS: Running -\& RESTARTS: 1 (71m ago) -\& AGE: 5h28m -.Ve -.PP -You can of course still use a regex to reduce the number of rows -displayed. -.PP -The option \fB\-o shell\fR can be used if the output has to be processed -by the shell, it prints variable assignments for each cell, one line -per row: -.PP -.Vb 4 -\& kubectl get pods | ./tablizer \-o extended ./tablizer \-o shell -\& NAME="repldepl\-7bcd8d5b64\-7zq4l" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" -\& NAME="repldepl\-7bcd8d5b64\-m48n8" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" -\& NAME="repldepl\-7bcd8d5b64\-q2bf4" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" -.Ve -.PP -You can use this in an eval loop. -.PP -Beside normal ascii mode (the default) and extended mode there are -more output modes available: \fBorgtbl\fR which prints an Emacs org-mode -table and \fBmarkdown\fR which prints a Markdown table, \fByaml\fR, which -prints yaml encoding and \s-1CSV\s0 mode, which prints a comma separated -value file. -.SS "\s-1PUT FIELDS TO CLIPBOARD\s0" -.IX Subsection "PUT FIELDS TO CLIPBOARD" -You can let tablizer put fields to the clipboard using the option -\&\f(CW\*(C`\-y\*(C'\fR. This best fits the use-case when the result of your filtering -yields just one row. For example: -.PP -.Vb 1 -\& cloudctl cluster ls | tablizer \-yid matchbox -.Ve -.PP -If \*(L"matchbox\*(R" matches one cluster, you can immediately use the id of -that cluster somewhere else and paste it. Of course, if there are -multiple matches, then all id's will be put into the clipboard -separated by one space. -.SS "\s-1ENVIRONMENT VARIABLES\s0" -.IX Subsection "ENVIRONMENT VARIABLES" -\&\fBtablizer\fR supports certain environment variables which use can use -to influence program behavior. Commandline flags have always -precedence over environment variables. -.IP " \- enable numbering of header fields, like \fB\-n\fR." 4 -.IX Item " - enable numbering of header fields, like -n." -.PD 0 -.IP " \- comma separated list of columns to output, like \fB\-c\fR" 4 -.IX Item " - comma separated list of columns to output, like -c" -.IP "<\s-1NO_COLORS\s0> \- disable colorization of matches, like \fB\-N\fR" 4 -.IX Item " - disable colorization of matches, like -N" -.PD -.SS "\s-1COMPLETION\s0" -.IX Subsection "COMPLETION" -Shell completion for command line options can be enabled by using the -\&\fB\-\-completion\fR flag. The required parameter is the name of your -shell. Currently supported are: bash, zsh, fish and powershell. -.PP -Detailed instructions: -.IP "Bash:" 4 -.IX Item "Bash:" -.Vb 1 -\& source <(tablizer \-\-completion bash) -.Ve -.Sp -To load completions for each session, execute once: -.Sp -.Vb 2 -\& # Linux: -\& $ tablizer \-\-completion bash > /etc/bash_completion.d/tablizer -\& -\& # macOS: -\& $ tablizer \-\-completion bash > $(brew \-\-prefix)/etc/bash_completion.d/tablizer -.Ve -.IP "Zsh:" 4 -.IX Item "Zsh:" -If shell completion is not already enabled in your environment, -you will need to enable it. You can execute the following once: -.Sp -.Vb 1 -\& echo "autoload \-U compinit; compinit" >> ~/.zshrc -.Ve -.Sp -To load completions for each session, execute once: -.Sp -.Vb 1 -\& $ tablizer \-\-completion zsh > "${fpath[1]}/_tablizer" -.Ve -.Sp -You will need to start a new shell for this setup to take effect. -.IP "fish:" 4 -.IX Item "fish:" -.Vb 1 -\& tablizer \-\-completion fish | source -.Ve -.Sp -To load completions for each session, execute once: -.Sp -.Vb 1 -\& tablizer \-\-completion fish > ~/.config/fish/completions/tablizer.fish -.Ve -.IP "PowerShell:" 4 -.IX Item "PowerShell:" -.Vb 1 -\& tablizer \-\-completion powershell | Out\-String | Invoke\-Expression -.Ve -.Sp -To load completions for every new session, run: -.Sp -.Vb 1 -\& tablizer \-\-completion powershell > tablizer.ps1 -.Ve -.Sp -and source this file from your PowerShell profile. -.SH "CONFIGURATION AND COLORS" -.IX Header "CONFIGURATION AND COLORS" -YOu can put certain configuration values into a configuration file in -\&\s-1HCL\s0 format. By default tablizer looks for -\&\f(CW\*(C`$HOME/.config/tablizer/config\*(C'\fR, but you can provide one using the -parameter \f(CW\*(C`\-f\*(C'\fR. -.PP -In the configuration the following variables can be defined: -.PP -.Vb 8 -\& BG = "lightGreen" -\& FG = "white" -\& HighlightBG = "lightGreen" -\& HighlightFG = "white" -\& NoHighlightBG = "white" -\& NoHighlightFG = "lightGreen" -\& HighlightHdrBG = "red" -\& HighlightHdrFG = "white" -.Ve -.PP -The following color definitions are available: -.PP -black, blue, cyan, darkGray, default, green, lightBlue, lightCyan, -lightGreen, lightMagenta, lightRed, lightWhite, lightYellow, -magenta, red, white, yellow -.PP -The Variables \fB\s-1FG\s0\fR and \fB\s-1BG\s0\fR are being used to highlight matches. The -other *FG and *BG variables are for colored table output (enabled with -the \f(CW\*(C`\-L\*(C'\fR parameter). -.PP -Colorization can be turned off completely either by setting the -parameter \f(CW\*(C`\-N\*(C'\fR or the environment variable \fB\s-1NO_COLOR\s0\fR to a true value. -.SH "BUGS" -.IX Header "BUGS" -In order to report a bug, unexpected behavior, feature requests -or to submit a patch, please open an issue on github: -. -.SH "LICENSE" -.IX Header "LICENSE" -This software is licensed under the \s-1GNU GENERAL PUBLIC LICENSE\s0 version 3. -.PP -Copyright (c) 2022\-2024 by Thomas von Dein -.PP -This software uses the following \s-1GO\s0 modules: -.IP "repr (https://github.com/alecthomas/repr)" 4 -.IX Item "repr (https://github.com/alecthomas/repr)" -Released under the \s-1MIT\s0 License, Copyright (c) 2016 Alec Thomas -.IP "cobra (https://github.com/spf13/cobra)" 4 -.IX Item "cobra (https://github.com/spf13/cobra)" -Released under the Apache 2.0 license, Copyright 2013\-2022 The Cobra Authors -.IP "dateparse (github.com/araddon/dateparse)" 4 -.IX Item "dateparse (github.com/araddon/dateparse)" -Released under the \s-1MIT\s0 License, Copyright (c) 2015\-2017 Aaron Raddon -.IP "color (github.com/gookit/color)" 4 -.IX Item "color (github.com/gookit/color)" -Released under the \s-1MIT\s0 License, Copyright (c) 2016 inhere -.IP "tablewriter (github.com/olekukonko/tablewriter)" 4 -.IX Item "tablewriter (github.com/olekukonko/tablewriter)" -Released under the \s-1MIT\s0 License, Copyright (c) 201 by Oleku Konko -.IP "yaml (gopkg.in/yaml.v3)" 4 -.IX Item "yaml (gopkg.in/yaml.v3)" -Released under the \s-1MIT\s0 License, Copyright (c) 2006\-2011 Kirill Simonov -.IP "bubble-table (https://github.com/Evertras/bubble\-table)" 4 -.IX Item "bubble-table (https://github.com/Evertras/bubble-table)" -Released under the \s-1MIT\s0 License, Copyright (c) 2022 Brandon Fulljames -.SH "AUTHORS" -.IX Header "AUTHORS" -Thomas von Dein \fBtom \s-1AT\s0 vondein \s-1DOT\s0 org\fR diff --git a/tablizer.pod b/tablizer.pod deleted file mode 100644 index f616fec..0000000 --- a/tablizer.pod +++ /dev/null @@ -1,563 +0,0 @@ -=head1 NAME - -tablizer - Manipulate tabular output of other programs - -=head1 SYNOPSIS - - Usage: - tablizer [regex,...] [-r file] [flags] - - Operational Flags: - -c, --columns string Only show the speficied columns (separated by ,) - -v, --invert-match select non-matching rows - -n, --numbering Enable header numbering - -N, --no-color Disable pattern highlighting - -H, --no-headers Disable headers display - -s, --separator Custom field separator (maybe char, string or :class:) - -k, --sort-by Sort by column (default: 1) - -z, --fuzzy Use fuzzy search [experimental] - -F, --filter Filter given field with regex, can be used multiple times - -T, --transpose-columns string Transpose the speficied columns (separated by ,) - -R, --regex-transposer Apply /search/replace/ regexp to fields given in -T - -j, --json Read JSON input (must be array of hashes) - -I, --interactive Interactively filter and select rows - -g, --auto-headers Generate headers if there are none present in input - -x, --custom-headers a,b,... Use custom headers, separated by comma - - Output Flags (mutually exclusive): - -X, --extended Enable extended output - -M, --markdown Enable markdown table output - -O, --orgtbl Enable org-mode table output - -S, --shell Enable shell evaluable output - -Y, --yaml Enable yaml output - -J, --jsonout Enable JSON output - -C, --csv Enable CSV output - -A, --ascii Default output mode, ascii tabular - -L, --hightlight-lines Use alternating background colors for tables - -o, --ofs Output field separator, used by -A and -C. - -y, --yank-columns Yank specified columns (separated by ,) to clipboard, - space separated - - Sort Mode Flags (mutually exclusive): - -a, --sort-age sort according to age (duration) string - -D, --sort-desc Sort in descending order (default: ascending) - -i, --sort-numeric sort according to string numerical value - -t, --sort-time sort according to time string - - Other Flags: - -r --read-file Use as input instead of STDIN - --completion Generate the autocompletion script for - -f, --config Configuration file (default: ~/.config/tablizer/config) - -d, --debug Enable debugging - -h, --help help for tablizer - -m, --man Display manual page - -V, --version Print program version - - -=head1 DESCRIPTION - -Many programs generate tabular output. But sometimes you need to -post-process these tables, you may need to remove one or more columns -or you may want to filter for some pattern (See L) or you -may need the output in another program and need to parse it somehow. -Standard unix tools such as awk(1), grep(1) or column(1) may help, but -sometimes it's a tedious business. - -Let's take the output of the tool kubectl. It contains cells with -withespace and they do not separate columns by TAB characters. This is -not easy to process. - -You can use B to do these and more things. - -B analyses the header fields of a table, registers the -column positions of each header field and separates columns by those -positions. - -Without any options it reads its input from C, but you can also -specify a file as a parameter. If you want to reduce the output by -some regular expression, just specify it as its first parameter. You -may also use the B<-v> option to exclude all rows which match the -pattern. Hence: - - # read from STDIN - kubectl get pods | tablizer - - # read a file - tablizer -r filename - - # search for pattern in a file (works like grep) - tablizer regex -r filename - - # search for pattern in STDIN - kubectl get pods | tablizer regex - -The output looks like the original one. You can add the option B<-n>, -then every header field will have a numer associated with it, e.g.: - - NAME(1) READY(2) STATUS(3) RESTARTS(4) AGE(5) - -These numbers denote the column and you can use them to specify which -columns you want to have in your output (see L: - - kubectl get pods | tablizer -c1,3 - -You can specify the numbers in any order but output will always follow -the original order. - -However, you may also just use the header names instead of numbers, -eg: - - kubectl get pods | tablizer -cname,status - -You can also use regular expressions with B<-c>, eg: - - kubectl get pods | tablizer -c '[ae]' - -By default tablizer shows a header containing the names of each -column. This can be disabled using the B<-H> option. Be aware that -this only affects tabular output modes. Shell, Extended, Yaml and CSV -output modes always use the column names. - -By default, if a B has been speficied, matches will be -highlighted. You can disable this behavior with the B<-N> option. - -Use the B<-k> option to specify by which column to sort the tabular -data (as in GNU sort(1)). The default sort column is the first -one. You can specify column numbers or names. Column numbers start -with 1, names are case insensitive. You can specify multiple columns -separated by comma to sort, but the type must be the same. For example -if you want to sort numerically, all columns must be numbers. If you -use column numbers, then be aware, that these are the numbers before -column extraction. For example if you have a table with 4 columns and -specify C<-c4>, then only 1 column (the fourth) will be printed, -however if you want to sort by this column, you'll have to specify -C<-k4>. - -The default sort order is ascending. You can change this to -descending order using the option B<-D>. The default sort order is by -alphanumeric string, but there are other sort modes: - -=over - -=item B<-a --sort-age> - -Sorts duration strings like "1d4h32m51s". - -=item B<-i --sort-numeric> - -Sorts numeric fields. - -=item B<-t --sort-time> - -Sorts timestamps. - -=back - -Finally the B<-d> option enables debugging output which is mostly -useful for the developer. - -=head2 SEPARATOR - -The option B<-s> can be a single character, in which case the CSV -parser will be invoked. You can also specify a string as -separator. The string will be interpreted as literal string unless it -is a valid go regular expression. For example: - - -s '\t{2,}\' - -is being used as a regexp and will match two or more consecutive tabs. - - -s 'foo' - -on the other hand is no regular expression and will be used literally. - -To make live easier, there are a couple of predefined regular -expressions, which you can specify as classes: - -=over - -* :tab: - -Matches a tab and eats spaces around it. - -* :spaces: - -Matches 2 or more spaces. - -* :pipe: - -Matches a pipe character and eats spaces around it. - -* :default: - -Matches 2 or more spaces or tab. This is the default separator if none -is specified. - -* :nonword: - -Matches a non-word character. - -* :nondigit: - -Matches a non-digit character. - -* :special: - -Matches one or more special chars like brackets, dollar sign, slashes etc. - -* :nonprint: - -Matches one or more non-printable characters. - - -=back - -=head2 PATTERNS AND FILTERING - -You can reduce the rows being displayed by using one or more regular -expression patterns. The regexp language being used is the one of -GOLANG, refer to the syntax cheat sheet here: -L. - -If you want to read a more comprehensive documentation about the -topic and have perl installed you can read it with: - - perldoc perlre - -Or read it online: L. But please note -that the GO regexp engine does NOT support all perl regex terms, -especially look-ahead and look-behind. - -If you want to supply flags to a regex, then surround it with slashes -and append the flag. The following flags are supported: - - i => case insensitive - ! => negative match - -Example for a case insensitive search: - - kubectl get pods -A | tablizer "/account/i" - -If you use the C flag, then the regex match will be negated, that -is, if a line in the input matches the given regex, but C is -supplied, tablizer will NOT include it in the output. - -For example, here we want to get all lines matching "foo" but not -"bar": - - cat table | tablizer foo '/bar/!' - -This would match a line "foo zorro" but not "foo bar". - -The flags can also be combined. - -You can also use the experimental fuzzy search feature by providing the -option B<-z>, in which case the pattern is regarded as a fuzzy search -term, not a regexp. - -Sometimes you want to filter by one or more columns. You can do that -using the B<-F> option. The option can be specified multiple times and -has the following format: - - fieldname=regexp - -Fieldnames (== columns headers) are case insensitive. - -If you specify more than one filter, both filters have to match (AND -operation). - -These field filters can also be negated: - - fieldname!=regexp - -If the option B<-v> is specified, the filtering is inverted. - -=head2 INTERACTIVE FILTERING - -You can also use the interactive mode, enabled with C<-I> to filter -and select rows. This mode is complementary, that is, other filter -options are still being respected. - -To enter e filter, hit C, enter a filter string and finish with -C. Use C to select/deselect rows, use C to select all -(visible) rows. - -Commit your selection with C. The selected rows are being fed to -the requested output mode as usual. Abort with C, in which -case the results of the interactive mode are being ignored and all -rows are being fed to output. - -=head2 COLUMNS - -The parameter B<-c> can be used to specify, which columns to -display. By default tablizer numerizes the header names and these -numbers can be used to specify which header to display, see example -above. - -However, beside numbers, you can also use regular expressions with -B<-c>, also separated by comma. And you can mix column numbers with -regexps. - -Lets take this table: - - PID TTY TIME CMD - 14001 pts/0 00:00:00 bash - 42871 pts/0 00:00:00 ps - 42872 pts/0 00:00:00 sed - -We want to see only the CMD column and use a regex for this: - - ps | tablizer -s '\s+' -c C - CMD(4) - bash - ps - tablizer - sed - -where "C" is our regexp which matches CMD. - -If a column specifier doesn't look like a regular expression, matching -against header fields will be case insensitive. So, if you have a -field with the name C then these will all match: C<-c id>, C<-c -Id>. The same rule applies to the options C<-T> and C<-F>. - - -=head2 TRANSPOSE FIELDS USING REGEXPS - -You can manipulate field contents using regular expressions. You have -to tell tablizer which field[s] to operate on using the option C<-T> -and the search/replace pattern using C<-R>. The number of columns and -patterns must match. - -A search/replace pattern consists of the following elements: - - /search-regexp/replace-string/ - -The separator can be any valid character. Especially if you want to -use a regexp containing the C character, eg: - - |search-regexp|replace-string| - -Example: - - cat t/testtable2 - NAME DURATION - x 10 - a 100 - z 0 - u 4 - k 6 - - cat t/testtable2 | tablizer -T2 -R '/^\d/4/' -n - NAME DURATION - x 40 - a 400 - z 4 - u 4 - k 4 - -=head2 OUTPUT MODES - -There might be cases when the tabular output of a program is way too -large for your current terminal but you still need to see every -column. In such cases the B<-o extended> or B<-X> option can be -useful which enables I. In this mode, each row will be -printed vertically, header left, value right, aligned by the field -widths. Here's an example: - - kubectl get pods | ./tablizer -o extended - NAME: repldepl-7bcd8d5b64-7zq4l - READY: 1/1 - STATUS: Running - RESTARTS: 1 (71m ago) - AGE: 5h28m - -You can of course still use a regex to reduce the number of rows -displayed. - -The option B<-o shell> can be used if the output has to be processed -by the shell, it prints variable assignments for each cell, one line -per row: - - kubectl get pods | ./tablizer -o extended ./tablizer -o shell - NAME="repldepl-7bcd8d5b64-7zq4l" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" - NAME="repldepl-7bcd8d5b64-m48n8" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" - NAME="repldepl-7bcd8d5b64-q2bf4" READY="1/1" STATUS="Running" RESTARTS="9 (47m ago)" AGE="4d23h" - -You can use this in an eval loop. - -Beside normal ascii mode (the default) and extended mode there are -more output modes available: B which prints an Emacs org-mode -table and B which prints a Markdown table, B, which -prints yaml encoding and CSV mode, which prints a comma separated -value file. - -=head2 PUT FIELDS TO CLIPBOARD - -You can let tablizer put fields to the clipboard using the option -C<-y>. This best fits the use-case when the result of your filtering -yields just one row. For example: - - cloudctl cluster ls | tablizer -yid matchbox - -If "matchbox" matches one cluster, you can immediately use the id of -that cluster somewhere else and paste it. Of course, if there are -multiple matches, then all id's will be put into the clipboard -separated by one space. - -=head2 ENVIRONMENT VARIABLES - -B supports certain environment variables which use can use -to influence program behavior. Commandline flags have always -precedence over environment variables. - -=over - -=item - enable numbering of header fields, like B<-n>. - -=item - comma separated list of columns to output, like B<-c> - -=item - disable colorization of matches, like B<-N> - -=back - -=head2 COMPLETION - -Shell completion for command line options can be enabled by using the -B<--completion> flag. The required parameter is the name of your -shell. Currently supported are: bash, zsh, fish and powershell. - -Detailed instructions: - -=over - -=item Bash: - - source <(tablizer --completion bash) - -To load completions for each session, execute once: - - # Linux: - $ tablizer --completion bash > /etc/bash_completion.d/tablizer - - # macOS: - $ tablizer --completion bash > $(brew --prefix)/etc/bash_completion.d/tablizer - -=item Zsh: - -If shell completion is not already enabled in your environment, -you will need to enable it. You can execute the following once: - - echo "autoload -U compinit; compinit" >> ~/.zshrc - -To load completions for each session, execute once: - - $ tablizer --completion zsh > "${fpath[1]}/_tablizer" - -You will need to start a new shell for this setup to take effect. - -=item fish: - - tablizer --completion fish | source - -To load completions for each session, execute once: - - tablizer --completion fish > ~/.config/fish/completions/tablizer.fish - -=item PowerShell: - - tablizer --completion powershell | Out-String | Invoke-Expression - -To load completions for every new session, run: - - tablizer --completion powershell > tablizer.ps1 - -and source this file from your PowerShell profile. - -=back - -=head1 CONFIGURATION AND COLORS - -YOu can put certain configuration values into a configuration file in -HCL format. By default tablizer looks for -C<$HOME/.config/tablizer/config>, but you can provide one using the -parameter C<-f>. - -In the configuration the following variables can be defined: - - BG = "lightGreen" - FG = "white" - HighlightBG = "lightGreen" - HighlightFG = "white" - NoHighlightBG = "white" - NoHighlightFG = "lightGreen" - HighlightHdrBG = "red" - HighlightHdrFG = "white" - -The following color definitions are available: - -black, blue, cyan, darkGray, default, green, lightBlue, lightCyan, -lightGreen, lightMagenta, lightRed, lightWhite, lightYellow, -magenta, red, white, yellow - -The Variables B and B are being used to highlight matches. The -other *FG and *BG variables are for colored table output (enabled with -the C<-L> parameter). - -Colorization can be turned off completely either by setting the -parameter C<-N> or the environment variable B to a true value. - - - -=head1 BUGS - -In order to report a bug, unexpected behavior, feature requests -or to submit a patch, please open an issue on github: -L. - -=head1 LICENSE - -This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3. - -Copyright (c) 2022-2024 by Thomas von Dein - -This software uses the following GO modules: - -=over 4 - -=item repr (https://github.com/alecthomas/repr) - -Released under the MIT License, Copyright (c) 2016 Alec Thomas - -=item cobra (https://github.com/spf13/cobra) - -Released under the Apache 2.0 license, Copyright 2013-2022 The Cobra Authors - -=item dateparse (github.com/araddon/dateparse) - -Released under the MIT License, Copyright (c) 2015-2017 Aaron Raddon - -=item color (github.com/gookit/color) - -Released under the MIT License, Copyright (c) 2016 inhere - -=item tablewriter (github.com/olekukonko/tablewriter) - -Released under the MIT License, Copyright (c) 201 by Oleku Konko - -=item yaml (gopkg.in/yaml.v3) - -Released under the MIT License, Copyright (c) 2006-2011 Kirill Simonov - -=item bubble-table (https://github.com/Evertras/bubble-table) - -Released under the MIT License, Copyright (c) 2022 Brandon Fulljames - -=back - -=head1 AUTHORS - -Thomas von Dein B - -=cut - diff --git a/vhsdemo/Makefile b/vhsdemo/Makefile deleted file mode 100644 index 320590b..0000000 --- a/vhsdemo/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.PHONY: demo check clean-demo - -VHS = vhs - -clean-demo: - -%.gif: %.tape - @echo "vhs $<" - env PATH=..:$(PATH) vhs $< - -check: - ls -l ../tablizer - -demo: check clean-demo demo.gif - diff --git a/vhsdemo/demo.gif b/vhsdemo/demo.gif deleted file mode 100644 index eef7f9e..0000000 Binary files a/vhsdemo/demo.gif and /dev/null differ diff --git a/vhsdemo/demo.tape b/vhsdemo/demo.tape deleted file mode 100644 index d200ea3..0000000 --- a/vhsdemo/demo.tape +++ /dev/null @@ -1,157 +0,0 @@ -# -*-sh-*- - -Output demo.gif -Set FontSize 20 -Set Width 1200 -Set Height 1000 -Set Theme { "name": "Whimsy", "black": "#535178", "red": "#ef6487", "green": "#5eca89", "yellow": "#fdd877", "blue": "#65aef7", "magenta": "#aa7ff0", "cyan": "#43c1be", "white": "#ffffff", "brightBlack": "#535178", "brightRed": "#ef6487", "brightGreen": "#5eca89", "brightYellow": "#fdd877", "brightBlue": "#65aef7", "brightMagenta": "#aa7ff0", "brightCyan": "#43c1be", "brightWhite": "#ffffff", "background": "#29283b", "foreground": "#b3b0d6", "selection": "#3d3c58", "cursor": "#b3b0d6" } -Set WindowBar Colorful -Set BorderRadius 10 -Set Shell zsh -Set FontFamily "IBM Plex Mono" -Set CursorBlink false -Set PlaybackSpeed 1 -Set TypingSpeed .05 - -# initialize -Hide -Type `PROMPT=''` -Enter -Type "setopt interactivecomments" -Enter -Type "autoload -U colors && colors" -Enter -Type `PS1="%{$fg[magenta]%}demo> %{$reset_color%}"` -Enter -Type "clear" -Enter -Show - -Type "# Our input data" -Enter -Sleep 1s -Type "cat input | head -10" -Enter -Sleep 2s - -Enter -Type "# Filter over all rows" -Enter -Sleep 1s -Type "tablizer Central < input" -Enter -Sleep 2s - -Enter -Type "# Filter over all rows case insensitive" -Enter -Sleep 1s -Type "tablizer '/penc/i' < input" -Enter -Sleep 2s - -Enter -Type "# Filter over specific column" -Enter -Sleep 1s -Type "tablizer -Fcost=4.99 < input" -Enter -Sleep 2s - -Enter -Type "# Filter by regex on specific column" -Enter -Sleep 1s -Type "tablizer -Funits=Pen. < input" -Enter -Sleep 2s - -Enter -Type "# Output as markdown" -Enter -Sleep 1s -Type "tablizer -Funits=Pen. -M < input" -Enter -Sleep 2s - -Enter -Type "# Output as CSV" -Enter -Sleep 1s -Type "tablizer -Funits=Pen. -C < input" -Enter -Sleep 2s - -Enter -Type "# Output as shell evaluable" -Enter -Sleep 1s -Type "tablizer -Funits=Pen. -S < input" -Enter -Sleep 2s -Type "bat eval.sh" -Enter -Sleep 2s -Type "tablizer -Funits=Pen. -S < input | ./eval.sh" -Enter -Sleep 2s - -Enter -Type "# Reduce columns" -Enter -Sleep 1s -Type "tablizer -Funits=Pen. -c region,customer,units,count < input" -Enter -Sleep 2s - -Enter -Type "# Sort by COUNT column numerically " -Enter -Sleep 1s -Type "tablizer -Funits=Pen. -c region,customer,units,count -kcount -i < input" -Enter -Sleep 2s - -Enter -Type "# Do further filtering interactively" -Enter -Sleep 1s -Type "tablizer -Funits=Pen. -c region,customer,units,count -I -O < input" -Enter -Sleep 2s -Type "?" -Sleep 2s -Type "/" -Sleep 2s -Type "J" -Sleep 1s -Type "o" -Sleep 1s -Type "n" -Sleep 1s -Type "e" -Sleep 1s -Type "s" -Sleep 1s -Enter -Sleep 2s -Tab -Sleep 1s -Tab -Sleep 1s -Tab -Sleep 1s -Tab -Type "n" -Sleep 2s -Space -Sleep 1s -Down -Sleep 1s -Down -Sleep 1s -Space -Sleep 2s -Type "q" - -Sleep 10s diff --git a/vhsdemo/eval.sh b/vhsdemo/eval.sh deleted file mode 100755 index 2bfa0b0..0000000 --- a/vhsdemo/eval.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -while read LINE; do - eval "$LINE"; echo "$Customer ordered $Count ${Units}s" -done diff --git a/vhsdemo/input b/vhsdemo/input deleted file mode 100644 index 012758a..0000000 --- a/vhsdemo/input +++ /dev/null @@ -1,44 +0,0 @@ -Date Region Customer Units Count Cost Total -2016-01-06 East Jones Pencil 95 1.99 189.05 -2016-01-23 Central Kivell Binder 50 19.99 999.50 -2016-02-09 Central Jardine Pencil 36 4.99 179.64 -2016-02-26 Central Gill Pen 27 19.99 539.73 -2016-03-15 West Sorvino Pencil 56 2.99 167.44 -2016-04-01 East Jones Binder 60 4.99 299.40 -2016-04-18 Central Andrews Pencil 75 1.99 149.25 -2016-05-05 Central Jardine Pencil 90 4.99 449.10 -2016-05-22 West Thompson Pencil 32 1.99 63.68 -2016-06-08 East Jones Binder 60 8.99 539.40 -2016-06-25 Central Morgan Pencil 90 4.99 449.10 -2016-07-12 East Howard Binder 29 1.99 57.71 -2016-07-29 East Parent Binder 81 19.99 1619.19 -2016-08-15 East Jones Pencil 35 4.99 174.65 -2016-09-01 Central Smith Desk 2 125.00 250.00 -2016-09-18 East Jones Pen Set 16 15.99 255.84 -2016-10-05 Central Morgan Binder 28 8.99 251.72 -2016-10-22 East Jones Pen 64 8.99 575.36 -2016-11-08 East Parent Pen 15 19.99 299.85 -2016-11-25 Central Kivell Pen Set 96 4.99 479.04 -2016-12-12 Central Smith Pencil 67 1.29 86.43 -2016-12-29 East Parent Pen Set 74 15.99 1183.26 -2017-01-15 Central Gill Binder 46 8.99 413.54 -2017-02-01 Central Smith Binder 87 15.00 1305.00 -2017-02-18 East Jones Binder 4 4.99 19.96 -2017-03-07 West Sorvino Binder 7 19.99 139.93 -2017-03-24 Central Jardine Pen Set 50 4.99 249.50 -2017-04-10 Central Andrews Pencil 66 1.99 131.34 -2017-04-27 East Howard Pen 96 4.99 479.04 -2017-05-14 Central Gill Pencil 53 1.29 68.37 -2017-05-31 Central Gill Binder 80 8.99 719.20 -2017-06-17 Central Kivell Desk 5 125.00 625.00 -2017-07-04 East Jones Pen Set 62 4.99 309.38 -2017-07-21 Central Morgan Pen Set 55 12.49 686.95 -2017-08-07 Central Kivell Pen Set 42 23.95 1005.90 -2017-08-24 West Sorvino Desk 3 275.00 825.00 -2017-09-10 Central Gill Pencil 7 1.29 9.03 -2017-09-27 West Sorvino Pen 76 1.99 151.24 -2017-10-14 West Thompson Binder 57 19.99 1139.43 -2017-10-31 Central Andrews Pencil 14 1.29 18.06 -2017-11-17 Central Jardine Binder 11 4.99 54.89 -2017-12-04 Central Jardine Binder 94 19.99 1879.06 -2017-12-21 Central Andrews Binder 28 4.99 139.72