Compare commits
40 Commits
feature-tr
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
be97641245 | |||
d2b5afe907 | |||
f58122320b | |||
06485ba003 | |||
8ed7525297 | |||
e11fd7fb72 | |||
4c67075f5f | |||
103bbfa528 | |||
7121d8f91a | |||
f3e481395e | |||
c94b671d9d | |||
e2b327932e | |||
14ba7b18bf | |||
755a7c07b1 | |||
0b5559336e | |||
ed72a0db66 | |||
34936a5f72 | |||
c09507658a | |||
5e6f02a843 | |||
9dab23ded7 | |||
53858ccda3 | |||
c2fab52268 | |||
1f62d3dec6 | |||
5e6a8f9001 | |||
12c7003570 | |||
7540300857 | |||
f26701d416 | |||
31f475acb6 | |||
b28dae3bd0 | |||
c79f4e811b | |||
6a2470db54 | |||
d32b44aedd | |||
c190f3f6f2 | |||
100b961494 | |||
53be19912a | |||
37bdd08e41 | |||
bd58ca6608 | |||
6372fd93fd | |||
325a1ca50d | |||
a859916f1c |
@ -3,6 +3,26 @@
|
||||
|
||||
def main(ctx):
|
||||
return [
|
||||
{
|
||||
"kind": "pipeline",
|
||||
"type": "docker",
|
||||
"name": "golangci-lint",
|
||||
"steps": [
|
||||
{
|
||||
"name": "golangci-lint",
|
||||
"image": "docker.io/immawanderer/archlinux-go-fyne:linux-amd64",
|
||||
"pull": "always",
|
||||
"commands": [
|
||||
"curl -sSfL " +
|
||||
"https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"+
|
||||
" | sh -s -- -b $(go env GOPATH)/bin v1.46.2",
|
||||
"export PATH=\"$(go env GOPATH)/bin:$PATH\"",
|
||||
"golangci-lint --version",
|
||||
"golangci-lint run -v --timeout 5m"
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "pipeline",
|
||||
"type": "docker",
|
||||
@ -153,7 +173,7 @@ def main(ctx):
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "go vet|test",
|
||||
"name": "go test",
|
||||
"image": "docker.io/nixos/nix:2.8.0-amd64",
|
||||
"pull": "if-not-exists",
|
||||
"depends_on": ["set up cachix"],
|
||||
@ -172,7 +192,6 @@ def main(ctx):
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
"nix develop --command go vet ./...",
|
||||
"nix develop --command go test -cover ./..."
|
||||
]
|
||||
},
|
||||
@ -278,7 +297,7 @@ def main(ctx):
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "vet|test",
|
||||
"name": "test",
|
||||
"image": "docker.io/immawanderer/archlinux-go-fyne:linux-amd64",
|
||||
"pull": "always",
|
||||
"depends_on": ["pull archlinux"],
|
||||
@ -289,7 +308,6 @@ def main(ctx):
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
"go vet ./...",
|
||||
"go test -cover ./..."
|
||||
]
|
||||
},
|
||||
@ -356,7 +374,7 @@ def main(ctx):
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "vet|test",
|
||||
"name": "test",
|
||||
"image": "docker.io/immawanderer/fedora-go-fyne:linux-amd64",
|
||||
"pull": "always",
|
||||
"depends_on": ["pull fedora"],
|
||||
@ -367,7 +385,6 @@ def main(ctx):
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
"go vet ./...",
|
||||
"go test -cover ./..."
|
||||
]
|
||||
},
|
||||
@ -405,6 +422,41 @@ def main(ctx):
|
||||
"temp": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "pipeline",
|
||||
"type": "docker",
|
||||
"name": "goreportcard refresh",
|
||||
"clone": {"disable": True},
|
||||
"depends_on": ["golangci-lint"],
|
||||
"trigger": {
|
||||
"ref": {
|
||||
"include": [
|
||||
"refs/tags/**",
|
||||
"refs/heads/development"
|
||||
],
|
||||
"exclude": [
|
||||
"refs/pull/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"name": "trigger",
|
||||
"image": "docker.io/curlimages/curl:7.83.1",
|
||||
"pull": "if-not-exists",
|
||||
"commands": [
|
||||
"uname -r",
|
||||
"curl --version",
|
||||
"curl " +
|
||||
"-sS " +
|
||||
"-X POST " +
|
||||
"-F \"repo=git.dotya.ml/${DRONE_REPO}\" " +
|
||||
"https://goreportcard.com/checks " +
|
||||
"-o /dev/null"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
17
.example.vimrc
Normal file
17
.example.vimrc
Normal file
@ -0,0 +1,17 @@
|
||||
augroup vimgo_local
|
||||
au!
|
||||
|
||||
function! Runhidden()
|
||||
cd `git rev-parse --show-toplevel`
|
||||
echo '...running go-xkcdreader'
|
||||
term ++hidden ++open nixGL go run -tags wayland -v .
|
||||
endfunction
|
||||
|
||||
au FileType go nmap <leader>gr :exec Runhidden()<cr>
|
||||
au FileType go nmap <leader>r :exec Runhidden()<cr>
|
||||
au FileType go nmap <leader>gc <Plug>(go-coverage-toggle)
|
||||
|
||||
" if using vim-go, this sets gofumpt as the main formatter
|
||||
let g:go_gopls_gofumpt=1
|
||||
let g:go_fmt_command='gofumpt'
|
||||
augroup END
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1,2 +1,3 @@
|
||||
*.go diff=golang
|
||||
*.md diff=markdown
|
||||
*.starlark linguist-language=Starlark
|
||||
|
93
.golangci.yml
Normal file
93
.golangci.yml
Normal file
@ -0,0 +1,93 @@
|
||||
# Copyright 2022 wanderer <a_mirre at utb dot cz>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
---
|
||||
run:
|
||||
go: 1.17.10
|
||||
tests: true
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- dupl
|
||||
- deadcode
|
||||
- decorder
|
||||
- dogsled
|
||||
- exportloopref
|
||||
- forbidigo
|
||||
- gas
|
||||
- gocognit
|
||||
- goconst
|
||||
- gocritic
|
||||
- godot
|
||||
- govet
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- ineffassign
|
||||
# - ifshort
|
||||
- misspell
|
||||
# - prealloc # disable for now as it might be premature optimisation
|
||||
- revive
|
||||
- unconvert
|
||||
- unparam
|
||||
- varcheck
|
||||
- whitespace
|
||||
- wsl
|
||||
|
||||
linter-settings:
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
disabled-checks:
|
||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- whyNoLint
|
||||
- wrapperFunc
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
lang-version: "1.18"
|
||||
govet:
|
||||
check-shadowing: true
|
||||
revive:
|
||||
severity: warning
|
||||
confidence: 0.8
|
||||
errorCode: 1
|
||||
warningCode: 1
|
||||
rules:
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: exported
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
- name: indent-error-flow
|
||||
- name: errorf
|
||||
- name: duplicated-imports
|
||||
- name: modifies-value-receiver
|
||||
...
|
@ -45,20 +45,10 @@ repos:
|
||||
- repo: https://github.com/dnephin/pre-commit-golang
|
||||
rev: v0.5.0
|
||||
hooks:
|
||||
- id: go-fmt
|
||||
- id: go-vet
|
||||
- id: go-imports
|
||||
- id: go-cyclo
|
||||
args: [-over=15]
|
||||
- id: golangci-lint
|
||||
- id: go-unit-tests
|
||||
- id: go-build
|
||||
- id: go-mod-tidy
|
||||
- repo: https://github.com/tekwizely/pre-commit-golang
|
||||
rev: v1.0.0-beta.5
|
||||
hooks:
|
||||
- id: go-revive-mod
|
||||
- id: go-sec-mod
|
||||
- id: go-unit-tests
|
||||
- id: golangci-lint
|
||||
- id: go-build
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: nix-build
|
||||
@ -66,6 +56,6 @@ repos:
|
||||
entry: nix build .#go-xkcdreader
|
||||
pass_filenames: false
|
||||
# trigger this hook on changes to any of nix (also flake.lock) files
|
||||
# and go's src, mod or sum files
|
||||
files: '\.*.(nix|lock|go|mod|sum)$'
|
||||
# and go's mod or sum files
|
||||
files: '\.*.(nix|lock|mod|sum)$'
|
||||
language: system
|
||||
|
@ -1,4 +0,0 @@
|
||||
augroup vimgo_local
|
||||
au!
|
||||
au FileType go nmap <leader>gr :term ++hidden ++open nixGL go run -v .<cr>
|
||||
augroup END
|
14
cmd/root.go
14
cmd/root.go
@ -5,15 +5,16 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// appName is the name of the app
|
||||
// appName is the name of the app.
|
||||
const appName = "go-xkcdreader"
|
||||
|
||||
// Root is the main go-xkcdreader command
|
||||
// Root is the main go-xkcdreader command.
|
||||
var Root = &cobra.Command{
|
||||
Use: appName,
|
||||
Short: "an offline-capable xkcd webcomic reader written in Go",
|
||||
@ -24,12 +25,12 @@ var Root = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// GetAppName returns the name of the application
|
||||
// GetAppName returns the name of the application.
|
||||
func GetAppName() string {
|
||||
return appName
|
||||
}
|
||||
|
||||
// Execute is the entrypoint of cobra's poison
|
||||
// Execute is the entrypoint of cobra's poison.
|
||||
func Execute() {
|
||||
if err := Root.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
@ -38,13 +39,14 @@ func Execute() {
|
||||
}
|
||||
|
||||
// help redefines the original help func, essentially adding a way to exit the
|
||||
// application properly after showing help message
|
||||
// application properly after showing help message.
|
||||
func help(*cobra.Command, []string) {
|
||||
err := Root.Usage()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ func TestExecuteRootCmd(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// is this valid? dunno
|
||||
// is this valid? dunno...
|
||||
func TestExecuteFunc(t *testing.T) {
|
||||
Execute()
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// version holds app version string
|
||||
// version holds app version string.
|
||||
var version = "v0.0.13"
|
||||
|
||||
// used to determine whether to print short or long version string
|
||||
// used to determine whether to print short or long version string.
|
||||
var shortVersion = false
|
||||
|
||||
var cmdVersion = &cobra.Command{
|
||||
@ -21,7 +21,7 @@ var cmdVersion = &cobra.Command{
|
||||
Short: "Print version information and exit",
|
||||
Long: `All software has versions. This is ` + appName + `'s`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println(getVersion())
|
||||
fmt.Fprintln(os.Stderr, getVersion())
|
||||
return nil
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
@ -30,20 +30,21 @@ var cmdVersion = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// get (short if -s/--short flag is supplied) version
|
||||
// get (short if -s/--short flag is supplied) version.
|
||||
func getVersion() string {
|
||||
if !shortVersion {
|
||||
return appName + " - " + version
|
||||
}
|
||||
|
||||
return GetShortVersion()
|
||||
}
|
||||
|
||||
// GetShortVersion returns a bare version string
|
||||
// GetShortVersion returns a bare version string.
|
||||
func GetShortVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
// the init func registers the commands and flags with cobra
|
||||
// the init func registers the commands and flags with cobra.
|
||||
func init() {
|
||||
cmdVersion.Flags().BoolVarP(&shortVersion, "short", "s", false, "print just the version string and nothing else")
|
||||
Root.AddCommand(cmdVersion)
|
||||
|
@ -5,19 +5,26 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNixFlake_GosrcVersionStringEquivalence(t *testing.T) {
|
||||
want := GetShortVersion()
|
||||
fData, err := os.ReadFile("../flake.nix")
|
||||
t.Parallel()
|
||||
|
||||
want := GetShortVersion()
|
||||
|
||||
fData, err := os.ReadFile("../flake.nix")
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
t.Errorf("Failed to open flake.nix: %q", err)
|
||||
}
|
||||
|
||||
t.Errorf("Error reading flake: %q", err)
|
||||
}
|
||||
|
||||
got := bytes.Contains(fData, []byte(" version = \""+want+"\""))
|
||||
|
||||
if !got {
|
||||
@ -26,6 +33,8 @@ func TestNixFlake_GosrcVersionStringEquivalence(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVersionStringFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
v := version
|
||||
// this expression matches the format of "vX.Y.Z" where {X,Y,Z} are digits
|
||||
// (such as 0 or 123)
|
||||
@ -46,7 +55,7 @@ func TestGetVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// set shortVersion variable manually
|
||||
// set shortVersion variable manually.
|
||||
func TestGetVersionWithShortVersionVar(t *testing.T) {
|
||||
shortVersion = true
|
||||
want := version
|
||||
@ -58,7 +67,7 @@ func TestGetVersionWithShortVersionVar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// explicitly get short version
|
||||
// explicitly get short version.
|
||||
func TestGetShortVersion(t *testing.T) {
|
||||
want := version
|
||||
got := GetShortVersion()
|
||||
|
16
flake.lock
16
flake.lock
@ -16,6 +16,21 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1653590866,
|
||||
"narHash": "sha256-E4yKIrt/S//WfW5D9IhQ1dVuaAy8RE7EiCMfnbrOC78=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "3e81a637cdf9f6e9b39aeb4d6e6394d1ad158e16",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixgl": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@ -55,6 +70,7 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixgl": "nixgl",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
|
55
flake.nix
55
flake.nix
@ -10,11 +10,17 @@
|
||||
flake = false;
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
nix-filter = {
|
||||
url = "github:numtide/nix-filter";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixgl, nixpkgs, ... }:
|
||||
outputs = { self, nixgl, nix-filter, nixpkgs, ... }:
|
||||
let
|
||||
|
||||
projname = "go-xkcdreader";
|
||||
|
||||
# to work with older version of flakes
|
||||
lastModifiedDate =
|
||||
self.lastModifiedDate or self.lastModified or "19700101";
|
||||
@ -39,6 +45,10 @@
|
||||
});
|
||||
in
|
||||
rec {
|
||||
formatter = forAllSystems (system:
|
||||
nixpkgsFor.${system}.nixpkgs-fmt
|
||||
);
|
||||
|
||||
packages = forAllSystems (system:
|
||||
let
|
||||
pkgs = nixpkgsFor.${system};
|
||||
@ -46,7 +56,7 @@
|
||||
in
|
||||
rec {
|
||||
go-xkcdreader = with pkgs; buildGoModule rec {
|
||||
pname = "go-xkcdreader";
|
||||
pname = "${projname}";
|
||||
buildInputs = [
|
||||
gcc
|
||||
libglvnd # instead of libGL
|
||||
@ -66,6 +76,10 @@
|
||||
];
|
||||
nativeBuildInputs = [ pkgconfig ];
|
||||
|
||||
overrideModAttrs = _: {
|
||||
# GOPROXY = "direct";
|
||||
};
|
||||
|
||||
inherit version;
|
||||
doCheck = false;
|
||||
# use go.mod for managing go deps, instead of vendor-only dir
|
||||
@ -79,15 +93,35 @@
|
||||
|
||||
modSha256 = lib.fakeSha256;
|
||||
# dont't forget to update vendorSha256 whenever go.mod or go.sum change
|
||||
vendorSha256 = "sha256-oHOMkvQhMFsAGgMcAHvxZp1vcDSVLmUYhft+cvnMd6M=";
|
||||
vendorSha256 = "sha256-LvdcTbj8cFlaIBsq1VLfLF2Tu9HiZzGO8igD766nMLE=";
|
||||
|
||||
# In 'nix develop', we don't need a copy of the source tree
|
||||
# in the Nix store.
|
||||
src = lib.cleanSource ./.;
|
||||
src = nix-filter.lib.filter {
|
||||
root = lib.cleanSource ./.;
|
||||
exclude = [
|
||||
./flake.nix
|
||||
./flake.lock
|
||||
./default.nix
|
||||
./shell.nix
|
||||
|
||||
./check-fmt
|
||||
./README.md
|
||||
./LICENSE
|
||||
|
||||
./.drone.starlark
|
||||
./.envrc
|
||||
./.example.vimrc
|
||||
./.gitattributes
|
||||
./.gitignore
|
||||
./.golangci.yml
|
||||
./.pre-commit-config.yaml
|
||||
];
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "an offline-capable xkcd webcomic reader written in Go";
|
||||
homepage = "https://git.dotya.ml/wanderer/go-xkcdreader";
|
||||
homepage = "https://git.dotya.ml/wanderer/${projname}";
|
||||
license = lib.licenses.gpl3;
|
||||
maintainers = [ "wanderer" ];
|
||||
platforms = lib.platforms.linux ++ lib.platforms.darwin;
|
||||
@ -101,7 +135,7 @@
|
||||
go-xkcdreader = {
|
||||
type = "app";
|
||||
program =
|
||||
"${self.packages.${system}.go-xkcdreader}/bin/go-xkcdreader";
|
||||
"${self.packages.${system}.${projname}}/bin/${projname}";
|
||||
};
|
||||
default = go-xkcdreader;
|
||||
});
|
||||
@ -134,7 +168,7 @@
|
||||
nix-store --query --references $(nix-instantiate shell.nix) | \
|
||||
xargs nix-store --realise | \
|
||||
xargs nix-store --query --requisites | \
|
||||
cachix push go-xkcdreader
|
||||
cachix push ${projname}
|
||||
'';
|
||||
add-license = pkgs.writeShellScriptBin "add-license" ''
|
||||
go run github.com/google/addlicense@v1.0.0 -v \
|
||||
@ -145,12 +179,13 @@
|
||||
{
|
||||
default = with pkgs; mkShell
|
||||
{
|
||||
name = "go-xkcdreader-" + version;
|
||||
name = "${projname}-" + version;
|
||||
|
||||
GOFLAGS = "-buildmode=pie -trimpath -mod=readonly -modcacherw";
|
||||
GOLDFLAGS = "-s -w -X cmd.version=${version}";
|
||||
CGO_CFLAGS = "-g2 -Og -mtune=generic";
|
||||
CGO_LDFLAGS = "-Wl,-O1,-sort-common,-as-needed,-z,relro,-z,now,-flto -pthread";
|
||||
GOPROXY = "direct";
|
||||
|
||||
shellHook = ''
|
||||
echo " -- in go-xkcdreader shell..."
|
||||
@ -171,8 +206,8 @@
|
||||
## if you wish to use this, uncomment the related block in
|
||||
## overlay.nix and the next line
|
||||
# goPkgs.dominikh.go-tools
|
||||
goPkgs.patchelf-x86_64
|
||||
goPkgs.patchelf-aarch64
|
||||
# goPkgs.patchelf-x86_64
|
||||
# goPkgs.patchelf-aarch64
|
||||
|
||||
## ad-hoc cmds
|
||||
gob
|
||||
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.17
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.1.4
|
||||
gitea.com/jolheiser/xkcd v0.0.2
|
||||
github.com/spf13/cobra v1.4.0
|
||||
)
|
||||
|
||||
|
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
||||
fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
|
||||
fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
|
||||
gitea.com/jolheiser/xkcd v0.0.2 h1:HJP83YwSKxSYcoNfpb1ZpAfBvkUAnN+YgeukraXtfrc=
|
||||
gitea.com/jolheiser/xkcd v0.0.2/go.mod h1:aDa2vX54wLaX8Ra5CGN2GWBX13UWAGJKGGddzHl/hks=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||
|
6
main.go
6
main.go
@ -4,7 +4,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.dotya.ml/wanderer/go-xkcdreader/cmd"
|
||||
"git.dotya.ml/wanderer/go-xkcdreader/xkcdreader"
|
||||
@ -15,9 +15,9 @@ var version = cmd.GetShortVersion()
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
|
||||
fmt.Println("Starting " + cmd.GetAppName() + " " + version)
|
||||
log.Println("Starting " + cmd.GetAppName() + " " + version)
|
||||
|
||||
xkcdreader.RunApp()
|
||||
|
||||
fmt.Println("Exited")
|
||||
log.Println("Exited")
|
||||
}
|
||||
|
@ -10,4 +10,5 @@ import (
|
||||
// does this test even make sense?
|
||||
func TestExecMain(t *testing.T) {
|
||||
go main()
|
||||
t.Log("Main executed successfully")
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
package xkcdreader
|
||||
|
||||
import (
|
||||
// for embedding standard license header
|
||||
// for embedding standard license header.
|
||||
_ "embed"
|
||||
"log"
|
||||
|
||||
@ -24,8 +24,10 @@ type project struct {
|
||||
license string
|
||||
}
|
||||
|
||||
var authorInfo = "Adam Mirre <a_mirre at utb dot cz>"
|
||||
var projectURL = "https://git.dotya.ml/wanderer/go-xkcdreader"
|
||||
var (
|
||||
authorInfo = "Adam Mirre <a_mirre at utb dot cz>"
|
||||
projectURL = "https://git.dotya.ml/wanderer/go-xkcdreader"
|
||||
)
|
||||
|
||||
//go:embed assets/standard-license-header.txt
|
||||
var l string
|
||||
|
@ -4,22 +4,30 @@
|
||||
package xkcdreader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"git.dotya.ml/wanderer/go-xkcdreader/cmd"
|
||||
"git.dotya.ml/wanderer/go-xkcdreader/xkcdreader/xkcd"
|
||||
)
|
||||
|
||||
const appGreeting = "welcome to go-xkcdreader"
|
||||
|
||||
var a fyne.App
|
||||
var (
|
||||
a fyne.App
|
||||
latestComic = xkcd.GetLatest()
|
||||
)
|
||||
|
||||
// RunApp performs sets up and runs the main application
|
||||
// RunApp performs sets up and runs the main application.
|
||||
func RunApp() {
|
||||
// initialize the fyne application
|
||||
newApp()
|
||||
@ -27,6 +35,9 @@ func RunApp() {
|
||||
goxkcdreader := getApp()
|
||||
w := goxkcdreader.NewWindow(cmd.GetAppName())
|
||||
|
||||
// mark this window as the main window
|
||||
w.SetMaster()
|
||||
|
||||
centered := container.New(
|
||||
layout.NewHBoxLayout(),
|
||||
layout.NewSpacer(),
|
||||
@ -47,6 +58,7 @@ func RunApp() {
|
||||
|
||||
func newApp() {
|
||||
a = app.New()
|
||||
|
||||
log.Println("Created a new fyne application")
|
||||
}
|
||||
|
||||
@ -54,18 +66,16 @@ func getApp() fyne.App {
|
||||
return a
|
||||
}
|
||||
|
||||
// makeGreeting creates a greeting label
|
||||
// makeGreeting creates a greeting label.
|
||||
func makeGreeting() *widget.Label {
|
||||
w := widget.NewLabel(appGreeting)
|
||||
w.TextStyle.Monospace = true
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func makeToolbar() *widget.Toolbar {
|
||||
toolbar := widget.NewToolbar(
|
||||
widget.NewToolbarAction(theme.SearchIcon(), func() {
|
||||
log.Println("Search")
|
||||
}),
|
||||
widget.NewToolbarSpacer(),
|
||||
widget.NewToolbarAction(theme.HelpIcon(), func() {
|
||||
log.Println("Display help")
|
||||
@ -75,14 +85,17 @@ func makeToolbar() *widget.Toolbar {
|
||||
}),
|
||||
widget.NewToolbarAction(theme.InfoIcon(), aboutWindow),
|
||||
)
|
||||
|
||||
return toolbar
|
||||
}
|
||||
|
||||
func makeTabs() *container.AppTabs {
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem("xkcd comic", makeBrowseUI()),
|
||||
container.NewTabItem("browse", makeBrowseUI()),
|
||||
container.NewTabItem("what if?", widget.NewLabel("serious answers to absurd questions and absurd advice for common concerns from xkcd's Randall Munroe")),
|
||||
container.NewTabItemWithIcon("search", theme.SearchIcon(), makeSearchTab()),
|
||||
)
|
||||
|
||||
return tabs
|
||||
}
|
||||
|
||||
@ -90,23 +103,126 @@ func makeBrowseUI() *fyne.Container {
|
||||
// container for the image and surrounding elements
|
||||
imgC := container.New(
|
||||
layout.NewHBoxLayout(),
|
||||
widget.NewButton("previous", func() {
|
||||
widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() {
|
||||
log.Println("Previous comic")
|
||||
}),
|
||||
layout.NewSpacer(),
|
||||
widget.NewLabel("img placeholder"),
|
||||
container.NewCenter(
|
||||
// TODO(me): dynamically replace placeholder text with image once
|
||||
// fetched...
|
||||
// widget.NewLabel("img placeholder"),
|
||||
getImg(latestComic.Img, false),
|
||||
),
|
||||
layout.NewSpacer(),
|
||||
widget.NewButton("next", func() {
|
||||
widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() {
|
||||
log.Println("Next comic")
|
||||
}))
|
||||
}),
|
||||
)
|
||||
|
||||
browseTabLabel := "Latest comic..."
|
||||
browseUI := container.New(
|
||||
layout.NewVBoxLayout(),
|
||||
widget.NewLabel(browseTabLabel),
|
||||
layout.NewSpacer(),
|
||||
container.NewCenter(
|
||||
container.NewVBox(
|
||||
&widget.Label{
|
||||
Text: "published on " +
|
||||
fmt.Sprint(
|
||||
latestComic.Year, "-",
|
||||
latestComic.Month, "-",
|
||||
latestComic.Day,
|
||||
),
|
||||
TextStyle: fyne.TextStyle{Italic: true},
|
||||
},
|
||||
container.NewHBox(
|
||||
&widget.Label{
|
||||
Text: latestComic.Title,
|
||||
TextStyle: fyne.TextStyle{Bold: true},
|
||||
},
|
||||
&widget.Label{
|
||||
Text: "(#" + fmt.Sprint(latestComic.Num) + ")",
|
||||
TextStyle: fyne.TextStyle{Monospace: true},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
imgC,
|
||||
layout.NewSpacer(),
|
||||
container.NewCenter(
|
||||
// comic "alt text"
|
||||
&widget.Label{
|
||||
Text: latestComic.Alt,
|
||||
TextStyle: fyne.TextStyle{Italic: true},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
return browseUI
|
||||
}
|
||||
|
||||
// getComicLatest fetches latest comic, updates latestComic if necessary.
|
||||
func getComicLatest() {
|
||||
comic := xkcd.GetLatest()
|
||||
|
||||
if comic == nil {
|
||||
// TODO(me): properly handle the situation (i.e. bail early)
|
||||
log.Println("error getting latest comic")
|
||||
}
|
||||
|
||||
// update latesComic, if necessary
|
||||
if latestComic != comic {
|
||||
latestComic = comic
|
||||
}
|
||||
}
|
||||
|
||||
// get img from filesystem, resize it and return as *canvas.Image.
|
||||
func getImg(imgURI string, local bool) *canvas.Image {
|
||||
if local {
|
||||
_, err := os.Stat(imgURI)
|
||||
// properly handle error, perhaps panic?...don't panic, I know..
|
||||
if err != nil {
|
||||
log.Println("failed to read file " + imgURI)
|
||||
return canvas.NewImageFromFile("")
|
||||
}
|
||||
|
||||
img := canvas.NewImageFromFile(imgURI)
|
||||
|
||||
img.SetMinSize(
|
||||
fyne.Size{
|
||||
Height: 383,
|
||||
Width: 273,
|
||||
},
|
||||
)
|
||||
|
||||
img.ScaleMode = canvas.ImageScaleSmooth
|
||||
img.FillMode = canvas.ImageFillContain
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
preImg, err := storage.ParseURI(imgURI)
|
||||
if err != nil {
|
||||
log.Printf("error parsing comic URI: %q", err.Error())
|
||||
}
|
||||
|
||||
// get the actual image
|
||||
img := canvas.NewImageFromURI(preImg)
|
||||
|
||||
if img.Resource == nil {
|
||||
log.Println("error fetching the image file of the latest comic")
|
||||
}
|
||||
|
||||
// set basic img properties
|
||||
img.SetMinSize(
|
||||
fyne.Size{
|
||||
// subject to change, this is what I consider to be sane defaults
|
||||
// atm...
|
||||
Height: 650,
|
||||
Width: 750,
|
||||
},
|
||||
)
|
||||
|
||||
img.ScaleMode = canvas.ImageScaleSmooth
|
||||
img.FillMode = canvas.ImageFillContain
|
||||
|
||||
return img
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ package xkcdreader
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"git.dotya.ml/wanderer/go-xkcdreader/cmd"
|
||||
)
|
||||
|
||||
@ -19,7 +21,7 @@ func TestGreetingText(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToolbar(t *testing.T) {
|
||||
wantItems := 5
|
||||
wantItems := 4
|
||||
gotToolbar := makeToolbar()
|
||||
|
||||
if len(gotToolbar.Items) != wantItems {
|
||||
@ -28,7 +30,6 @@ func TestToolbar(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTabs(t *testing.T) {
|
||||
|
||||
// wantTabsNum := 2
|
||||
// gotTabs := makeTabs()
|
||||
//
|
||||
@ -115,3 +116,109 @@ func TestGetApp(t *testing.T) {
|
||||
t.Error("Failed to get application pointer using getApp()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImgMinSize(t *testing.T) {
|
||||
// init
|
||||
newApp()
|
||||
|
||||
// this is relative to the test file
|
||||
imgPath := "assets/comic.png"
|
||||
dimens := struct {
|
||||
h float32
|
||||
w float32
|
||||
}{
|
||||
h: 383.0,
|
||||
w: 273.0,
|
||||
}
|
||||
|
||||
got := getImg(imgPath, true)
|
||||
got.SetMinSize(
|
||||
fyne.Size{
|
||||
Height: 383.0,
|
||||
Width: 273.0,
|
||||
},
|
||||
)
|
||||
|
||||
if got.MinSize().Height != dimens.h {
|
||||
t.Errorf("Failed to get img w/ proper height, want: %f, got: %f", dimens.h, got.Size().Height)
|
||||
}
|
||||
|
||||
if got.MinSize().Width != dimens.w {
|
||||
t.Errorf("Failed to get img w/ proper width, want: %f, got: %f", dimens.w, got.Size().Width)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImg(t *testing.T) {
|
||||
// init
|
||||
newApp()
|
||||
|
||||
// this is relative to the test file
|
||||
imgPath := "assets/comic.png"
|
||||
|
||||
got := getImg(imgPath, true)
|
||||
got.SetMinSize(
|
||||
fyne.Size{
|
||||
Height: 383.0,
|
||||
Width: 273.0,
|
||||
},
|
||||
)
|
||||
|
||||
testImg := canvas.NewImageFromFile(imgPath)
|
||||
|
||||
gotA := got.Image
|
||||
testA := testImg.Image
|
||||
|
||||
if gotA != nil && testA != nil {
|
||||
t.Fatal("test img or fetched img are nil")
|
||||
}
|
||||
|
||||
if gotA != testA {
|
||||
t.Fatal("Images differ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetComicLatest(t *testing.T) {
|
||||
want := latestComic
|
||||
|
||||
// attempt comic refresh
|
||||
getComicLatest()
|
||||
|
||||
got := latestComic
|
||||
|
||||
if want.Img != got.Img ||
|
||||
want.Alt != got.Alt ||
|
||||
want.Day != got.Day ||
|
||||
want.Link != got.Link ||
|
||||
want.Month != got.Month ||
|
||||
want.News != got.News ||
|
||||
want.SafeTitle != got.SafeTitle ||
|
||||
want.Title != got.Title ||
|
||||
want.Transcript != got.Transcript ||
|
||||
want.Year != got.Year {
|
||||
t.Error("latestComic contains stale comic, comic data differ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetComicLatest_MinSize(t *testing.T) {
|
||||
want := struct {
|
||||
height float32
|
||||
width float32
|
||||
}{
|
||||
height: 650.0,
|
||||
width: 750.0,
|
||||
}
|
||||
|
||||
getComicLatest()
|
||||
|
||||
got := getImg(latestComic.Img, false)
|
||||
|
||||
if want.height != got.MinSize().Height {
|
||||
t.Errorf("Latest comic height differs, want: %f, got: %f",
|
||||
want.height, got.MinSize().Height)
|
||||
}
|
||||
|
||||
if want.width != got.MinSize().Width {
|
||||
t.Errorf("Latest comic width differs, want: %f, got: %f",
|
||||
want.width, got.MinSize().Width)
|
||||
}
|
||||
}
|
||||
|
BIN
xkcdreader/assets/comic.png
Normal file
BIN
xkcdreader/assets/comic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
39
xkcdreader/search.go
Normal file
39
xkcdreader/search.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2022 wanderer <a_mirre at utb dot cz>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package xkcdreader
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func makeSearchTab() *fyne.Container {
|
||||
c := container.NewMax(
|
||||
container.NewVBox(
|
||||
widget.NewLabel("You want me to look in..."),
|
||||
container.NewHBox(
|
||||
widget.NewCheck("xkcd", func(value bool) {
|
||||
// TODO(me): have this preference saved in a config or sth
|
||||
log.Println("'search in xkcd' set to", value)
|
||||
}),
|
||||
widget.NewCheck("what if?", func(value bool) {
|
||||
log.Println("'search in what if?' set to", value)
|
||||
})),
|
||||
makeSearchEntry(),
|
||||
),
|
||||
)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func makeSearchEntry() *widget.Entry {
|
||||
s := widget.NewEntry()
|
||||
s.SetPlaceHolder("Search phrase, comic number...")
|
||||
s.Resize(fyne.Size{Height: 100})
|
||||
|
||||
return s
|
||||
}
|
37
xkcdreader/xkcd/xkcd.go
Normal file
37
xkcdreader/xkcd/xkcd.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2022 wanderer <a_mirre at utb dot cz>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package xkcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"gitea.com/jolheiser/xkcd"
|
||||
)
|
||||
|
||||
var xkcdClient = xkcd.New()
|
||||
|
||||
// GetLatest grabs the latest available xkcd comic.
|
||||
func GetLatest() *xkcd.Comic {
|
||||
comic, err := xkcdClient.Current(context.Background())
|
||||
if err != nil {
|
||||
// TODO(me): handle this
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return comic
|
||||
}
|
||||
|
||||
// GetComic grabs xkcd comic no. "num", as provided by the caller.
|
||||
func GetComic(num int) *xkcd.Comic {
|
||||
comic, err := xkcdClient.Comic(context.Background(), num)
|
||||
if err != nil {
|
||||
// TODO(me): handle this
|
||||
log.Println(err)
|
||||
|
||||
comic = nil
|
||||
}
|
||||
|
||||
return comic
|
||||
}
|
54
xkcdreader/xkcd/xkcd_test.go
Normal file
54
xkcdreader/xkcd/xkcd_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2022 wanderer <a_mirre at utb dot cz>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package xkcd
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"gitea.com/jolheiser/xkcd"
|
||||
)
|
||||
|
||||
func TestGetComic(t *testing.T) {
|
||||
comicNum := 2625
|
||||
comic := GetComic(comicNum)
|
||||
|
||||
comic2625 := xkcd.Comic{
|
||||
Month: "5",
|
||||
Num: 2625,
|
||||
Link: "",
|
||||
Year: "2022",
|
||||
News: "",
|
||||
SafeTitle: "Field Topology",
|
||||
Transcript: "",
|
||||
Alt: "The combination croquet set/10-lane pool can also be used for some varieties of foosball and Skee-Ball.",
|
||||
Img: "https://imgs.xkcd.com/comics/field_topology.png",
|
||||
Title: "Field Topology",
|
||||
Day: "27",
|
||||
}
|
||||
|
||||
if *comic != comic2625 {
|
||||
t.Log("Comic does not match test data")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetComic_Bad(t *testing.T) {
|
||||
comicNum := math.MaxInt32
|
||||
comic := GetComic(comicNum)
|
||||
|
||||
if comic != nil {
|
||||
t.Logf("No comic should have been returned for comicNum: %d", comicNum)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatest_Bad(t *testing.T) {
|
||||
comic := GetLatest()
|
||||
|
||||
if comic == nil {
|
||||
t.Log("Could not get latest comic")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user