Compare commits

..

1 Commits

Author SHA1 Message Date
9aea74595a
ci: trigger goreportcard refresh
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-25 16:23:24 +02:00
23 changed files with 85 additions and 620 deletions

@ -3,26 +3,6 @@
def main(ctx): def main(ctx):
return [ 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", "kind": "pipeline",
"type": "docker", "type": "docker",
@ -173,7 +153,7 @@ def main(ctx):
] ]
}, },
{ {
"name": "go test", "name": "go vet|test",
"image": "docker.io/nixos/nix:2.8.0-amd64", "image": "docker.io/nixos/nix:2.8.0-amd64",
"pull": "if-not-exists", "pull": "if-not-exists",
"depends_on": ["set up cachix"], "depends_on": ["set up cachix"],
@ -192,6 +172,7 @@ def main(ctx):
} }
], ],
"commands": [ "commands": [
"nix develop --command go vet ./...",
"nix develop --command go test -cover ./..." "nix develop --command go test -cover ./..."
] ]
}, },
@ -297,7 +278,7 @@ def main(ctx):
] ]
}, },
{ {
"name": "test", "name": "vet|test",
"image": "docker.io/immawanderer/archlinux-go-fyne:linux-amd64", "image": "docker.io/immawanderer/archlinux-go-fyne:linux-amd64",
"pull": "always", "pull": "always",
"depends_on": ["pull archlinux"], "depends_on": ["pull archlinux"],
@ -308,6 +289,7 @@ def main(ctx):
} }
], ],
"commands": [ "commands": [
"go vet ./...",
"go test -cover ./..." "go test -cover ./..."
] ]
}, },
@ -374,7 +356,7 @@ def main(ctx):
] ]
}, },
{ {
"name": "test", "name": "vet|test",
"image": "docker.io/immawanderer/fedora-go-fyne:linux-amd64", "image": "docker.io/immawanderer/fedora-go-fyne:linux-amd64",
"pull": "always", "pull": "always",
"depends_on": ["pull fedora"], "depends_on": ["pull fedora"],
@ -385,6 +367,7 @@ def main(ctx):
} }
], ],
"commands": [ "commands": [
"go vet ./...",
"go test -cover ./..." "go test -cover ./..."
] ]
}, },
@ -428,15 +411,16 @@ def main(ctx):
"type": "docker", "type": "docker",
"name": "goreportcard refresh", "name": "goreportcard refresh",
"clone": {"disable": True}, "clone": {"disable": True},
"depends_on": ["golangci-lint"], "depends_on": ["nix", "archlinux", "fedora"],
"trigger": { "trigger": {
"ref": { "ref": {
"exclude": [
"refs/pulls/**",
"refs/heads/**"
],
"include": [ "include": [
"refs/tags/**", "refs/tags/**",
"refs/heads/development" "refs/heads/development"
],
"exclude": [
"refs/pull/**"
] ]
} }
}, },
@ -448,12 +432,12 @@ def main(ctx):
"commands": [ "commands": [
"uname -r", "uname -r",
"curl --version", "curl --version",
"curl " + "curl \
"-sS " + -sS \
"-X POST " + -X POST \
"-F \"repo=git.dotya.ml/${DRONE_REPO}\" " + -F \"repo=git.dotya.ml/${DRONE_REPO}\" \
"https://goreportcard.com/checks " + https://goreportcard.com/checks \
"-o /dev/null" -o /dev/null"
] ]
} }
] ]

@ -1,17 +0,0 @@
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

5
.gitattributes vendored

@ -1,3 +1,2 @@
*.go diff=golang *.go diff=golang
*.md diff=markdown *.md diff=markdown
*.starlark linguist-language=Starlark

@ -1,93 +0,0 @@
# 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,10 +45,20 @@ repos:
- repo: https://github.com/dnephin/pre-commit-golang - repo: https://github.com/dnephin/pre-commit-golang
rev: v0.5.0 rev: v0.5.0
hooks: hooks:
- id: go-mod-tidy - id: go-fmt
- id: go-unit-tests - id: go-vet
- id: go-imports
- id: go-cyclo
args: [-over=15]
- id: golangci-lint - id: golangci-lint
- id: go-unit-tests
- id: go-build - 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
- repo: local - repo: local
hooks: hooks:
- id: nix-build - id: nix-build
@ -56,6 +66,6 @@ repos:
entry: nix build .#go-xkcdreader entry: nix build .#go-xkcdreader
pass_filenames: false pass_filenames: false
# trigger this hook on changes to any of nix (also flake.lock) files # trigger this hook on changes to any of nix (also flake.lock) files
# and go's mod or sum files # and go's src, mod or sum files
files: '\.*.(nix|lock|mod|sum)$' files: '\.*.(nix|lock|go|mod|sum)$'
language: system language: system

4
.vimrc-example Normal file

@ -0,0 +1,4 @@
augroup vimgo_local
au!
au FileType go nmap <leader>gr :term ++hidden ++open nixGL go run -v .<cr>
augroup END

@ -5,16 +5,15 @@ package cmd
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// appName is the name of the app. // appName is the name of the app
const appName = "go-xkcdreader" const appName = "go-xkcdreader"
// Root is the main go-xkcdreader command. // Root is the main go-xkcdreader command
var Root = &cobra.Command{ var Root = &cobra.Command{
Use: appName, Use: appName,
Short: "an offline-capable xkcd webcomic reader written in Go", Short: "an offline-capable xkcd webcomic reader written in Go",
@ -25,12 +24,12 @@ var Root = &cobra.Command{
}, },
} }
// GetAppName returns the name of the application. // GetAppName returns the name of the application
func GetAppName() string { func GetAppName() string {
return appName return appName
} }
// Execute is the entrypoint of cobra's poison. // Execute is the entrypoint of cobra's poison
func Execute() { func Execute() {
if err := Root.Execute(); err != nil { if err := Root.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
@ -39,14 +38,13 @@ func Execute() {
} }
// help redefines the original help func, essentially adding a way to exit the // 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) { func help(*cobra.Command, []string) {
err := Root.Usage() err := Root.Usage()
if err != nil { if err != nil {
log.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
os.Exit(0) 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) { func TestExecuteFunc(t *testing.T) {
Execute() Execute()
} }

@ -10,10 +10,10 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// version holds app version string. // version holds app version string
var version = "v0.0.13" 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 shortVersion = false
var cmdVersion = &cobra.Command{ var cmdVersion = &cobra.Command{
@ -21,7 +21,7 @@ var cmdVersion = &cobra.Command{
Short: "Print version information and exit", Short: "Print version information and exit",
Long: `All software has versions. This is ` + appName + `'s`, Long: `All software has versions. This is ` + appName + `'s`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
fmt.Fprintln(os.Stderr, getVersion()) fmt.Println(getVersion())
return nil return nil
}, },
PersistentPostRun: func(cmd *cobra.Command, args []string) { PersistentPostRun: func(cmd *cobra.Command, args []string) {
@ -30,21 +30,20 @@ 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 { func getVersion() string {
if !shortVersion { if !shortVersion {
return appName + " - " + version return appName + " - " + version
} }
return GetShortVersion() return GetShortVersion()
} }
// GetShortVersion returns a bare version string. // GetShortVersion returns a bare version string
func GetShortVersion() string { func GetShortVersion() string {
return version return version
} }
// the init func registers the commands and flags with cobra. // the init func registers the commands and flags with cobra
func init() { func init() {
cmdVersion.Flags().BoolVarP(&shortVersion, "short", "s", false, "print just the version string and nothing else") cmdVersion.Flags().BoolVarP(&shortVersion, "short", "s", false, "print just the version string and nothing else")
Root.AddCommand(cmdVersion) Root.AddCommand(cmdVersion)

@ -5,24 +5,17 @@ package cmd
import ( import (
"bytes" "bytes"
"errors"
"os" "os"
"regexp" "regexp"
"testing" "testing"
) )
func TestNixFlake_GosrcVersionStringEquivalence(t *testing.T) { func TestNixFlake_GosrcVersionStringEquivalence(t *testing.T) {
t.Parallel()
want := GetShortVersion() want := GetShortVersion()
fData, err := os.ReadFile("../flake.nix") 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) if err != nil {
t.Errorf("Failed to open flake.nix: %q", err)
} }
got := bytes.Contains(fData, []byte(" version = \""+want+"\"")) got := bytes.Contains(fData, []byte(" version = \""+want+"\""))
@ -33,8 +26,6 @@ func TestNixFlake_GosrcVersionStringEquivalence(t *testing.T) {
} }
func TestVersionStringFormat(t *testing.T) { func TestVersionStringFormat(t *testing.T) {
t.Parallel()
v := version v := version
// this expression matches the format of "vX.Y.Z" where {X,Y,Z} are digits // this expression matches the format of "vX.Y.Z" where {X,Y,Z} are digits
// (such as 0 or 123) // (such as 0 or 123)
@ -55,7 +46,7 @@ func TestGetVersion(t *testing.T) {
} }
} }
// set shortVersion variable manually. // set shortVersion variable manually
func TestGetVersionWithShortVersionVar(t *testing.T) { func TestGetVersionWithShortVersionVar(t *testing.T) {
shortVersion = true shortVersion = true
want := version want := version
@ -67,7 +58,7 @@ func TestGetVersionWithShortVersionVar(t *testing.T) {
} }
} }
// explicitly get short version. // explicitly get short version
func TestGetShortVersion(t *testing.T) { func TestGetShortVersion(t *testing.T) {
want := version want := version
got := GetShortVersion() got := GetShortVersion()

@ -16,21 +16,6 @@
"type": "github" "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": { "nixgl": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -70,7 +55,6 @@
"root": { "root": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"nix-filter": "nix-filter",
"nixgl": "nixgl", "nixgl": "nixgl",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }

@ -10,17 +10,11 @@
flake = false; flake = false;
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nix-filter = {
url = "github:numtide/nix-filter";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = { self, nixgl, nix-filter, nixpkgs, ... }: outputs = { self, nixgl, nixpkgs, ... }:
let let
projname = "go-xkcdreader";
# to work with older version of flakes # to work with older version of flakes
lastModifiedDate = lastModifiedDate =
self.lastModifiedDate or self.lastModified or "19700101"; self.lastModifiedDate or self.lastModified or "19700101";
@ -45,10 +39,6 @@
}); });
in in
rec { rec {
formatter = forAllSystems (system:
nixpkgsFor.${system}.nixpkgs-fmt
);
packages = forAllSystems (system: packages = forAllSystems (system:
let let
pkgs = nixpkgsFor.${system}; pkgs = nixpkgsFor.${system};
@ -56,7 +46,7 @@
in in
rec { rec {
go-xkcdreader = with pkgs; buildGoModule rec { go-xkcdreader = with pkgs; buildGoModule rec {
pname = "${projname}"; pname = "go-xkcdreader";
buildInputs = [ buildInputs = [
gcc gcc
libglvnd # instead of libGL libglvnd # instead of libGL
@ -76,10 +66,6 @@
]; ];
nativeBuildInputs = [ pkgconfig ]; nativeBuildInputs = [ pkgconfig ];
overrideModAttrs = _: {
# GOPROXY = "direct";
};
inherit version; inherit version;
doCheck = false; doCheck = false;
# use go.mod for managing go deps, instead of vendor-only dir # use go.mod for managing go deps, instead of vendor-only dir
@ -93,35 +79,15 @@
modSha256 = lib.fakeSha256; modSha256 = lib.fakeSha256;
# dont't forget to update vendorSha256 whenever go.mod or go.sum change # dont't forget to update vendorSha256 whenever go.mod or go.sum change
vendorSha256 = "sha256-LvdcTbj8cFlaIBsq1VLfLF2Tu9HiZzGO8igD766nMLE="; vendorSha256 = "sha256-oHOMkvQhMFsAGgMcAHvxZp1vcDSVLmUYhft+cvnMd6M=";
# In 'nix develop', we don't need a copy of the source tree # In 'nix develop', we don't need a copy of the source tree
# in the Nix store. # in the Nix store.
src = nix-filter.lib.filter { src = lib.cleanSource ./.;
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 = { meta = {
description = "an offline-capable xkcd webcomic reader written in Go"; description = "an offline-capable xkcd webcomic reader written in Go";
homepage = "https://git.dotya.ml/wanderer/${projname}"; homepage = "https://git.dotya.ml/wanderer/go-xkcdreader";
license = lib.licenses.gpl3; license = lib.licenses.gpl3;
maintainers = [ "wanderer" ]; maintainers = [ "wanderer" ];
platforms = lib.platforms.linux ++ lib.platforms.darwin; platforms = lib.platforms.linux ++ lib.platforms.darwin;
@ -135,7 +101,7 @@
go-xkcdreader = { go-xkcdreader = {
type = "app"; type = "app";
program = program =
"${self.packages.${system}.${projname}}/bin/${projname}"; "${self.packages.${system}.go-xkcdreader}/bin/go-xkcdreader";
}; };
default = go-xkcdreader; default = go-xkcdreader;
}); });
@ -168,7 +134,7 @@
nix-store --query --references $(nix-instantiate shell.nix) | \ nix-store --query --references $(nix-instantiate shell.nix) | \
xargs nix-store --realise | \ xargs nix-store --realise | \
xargs nix-store --query --requisites | \ xargs nix-store --query --requisites | \
cachix push ${projname} cachix push go-xkcdreader
''; '';
add-license = pkgs.writeShellScriptBin "add-license" '' add-license = pkgs.writeShellScriptBin "add-license" ''
go run github.com/google/addlicense@v1.0.0 -v \ go run github.com/google/addlicense@v1.0.0 -v \
@ -179,13 +145,12 @@
{ {
default = with pkgs; mkShell default = with pkgs; mkShell
{ {
name = "${projname}-" + version; name = "go-xkcdreader-" + version;
GOFLAGS = "-buildmode=pie -trimpath -mod=readonly -modcacherw"; GOFLAGS = "-buildmode=pie -trimpath -mod=readonly -modcacherw";
GOLDFLAGS = "-s -w -X cmd.version=${version}"; GOLDFLAGS = "-s -w -X cmd.version=${version}";
CGO_CFLAGS = "-g2 -Og -mtune=generic"; CGO_CFLAGS = "-g2 -Og -mtune=generic";
CGO_LDFLAGS = "-Wl,-O1,-sort-common,-as-needed,-z,relro,-z,now,-flto -pthread"; CGO_LDFLAGS = "-Wl,-O1,-sort-common,-as-needed,-z,relro,-z,now,-flto -pthread";
GOPROXY = "direct";
shellHook = '' shellHook = ''
echo " -- in go-xkcdreader shell..." echo " -- in go-xkcdreader shell..."
@ -206,8 +171,8 @@
## if you wish to use this, uncomment the related block in ## if you wish to use this, uncomment the related block in
## overlay.nix and the next line ## overlay.nix and the next line
# goPkgs.dominikh.go-tools # goPkgs.dominikh.go-tools
# goPkgs.patchelf-x86_64 goPkgs.patchelf-x86_64
# goPkgs.patchelf-aarch64 goPkgs.patchelf-aarch64
## ad-hoc cmds ## ad-hoc cmds
gob gob

1
go.mod

@ -4,7 +4,6 @@ go 1.17
require ( require (
fyne.io/fyne/v2 v2.1.4 fyne.io/fyne/v2 v2.1.4
gitea.com/jolheiser/xkcd v0.0.2
github.com/spf13/cobra v1.4.0 github.com/spf13/cobra v1.4.0
) )

2
go.sum

@ -1,7 +1,5 @@
fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc= 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= 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.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 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= github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=

@ -4,7 +4,7 @@
package main package main
import ( import (
"log" "fmt"
"git.dotya.ml/wanderer/go-xkcdreader/cmd" "git.dotya.ml/wanderer/go-xkcdreader/cmd"
"git.dotya.ml/wanderer/go-xkcdreader/xkcdreader" "git.dotya.ml/wanderer/go-xkcdreader/xkcdreader"
@ -15,9 +15,9 @@ var version = cmd.GetShortVersion()
func main() { func main() {
cmd.Execute() cmd.Execute()
log.Println("Starting " + cmd.GetAppName() + " " + version) fmt.Println("Starting " + cmd.GetAppName() + " " + version)
xkcdreader.RunApp() xkcdreader.RunApp()
log.Println("Exited") fmt.Println("Exited")
} }

@ -10,5 +10,4 @@ import (
// does this test even make sense? // does this test even make sense?
func TestExecMain(t *testing.T) { func TestExecMain(t *testing.T) {
go main() go main()
t.Log("Main executed successfully")
} }

@ -4,7 +4,7 @@
package xkcdreader package xkcdreader
import ( import (
// for embedding standard license header. // for embedding standard license header
_ "embed" _ "embed"
"log" "log"
@ -24,10 +24,8 @@ type project struct {
license string license string
} }
var ( var authorInfo = "Adam Mirre <a_mirre at utb dot cz>"
authorInfo = "Adam Mirre <a_mirre at utb dot cz>" var projectURL = "https://git.dotya.ml/wanderer/go-xkcdreader"
projectURL = "https://git.dotya.ml/wanderer/go-xkcdreader"
)
//go:embed assets/standard-license-header.txt //go:embed assets/standard-license-header.txt
var l string var l string

@ -4,30 +4,22 @@
package xkcdreader package xkcdreader
import ( import (
"fmt"
"log" "log"
"os"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/app" "fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"git.dotya.ml/wanderer/go-xkcdreader/cmd" "git.dotya.ml/wanderer/go-xkcdreader/cmd"
"git.dotya.ml/wanderer/go-xkcdreader/xkcdreader/xkcd"
) )
const appGreeting = "welcome to go-xkcdreader" const appGreeting = "welcome to go-xkcdreader"
var ( var a fyne.App
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() { func RunApp() {
// initialize the fyne application // initialize the fyne application
newApp() newApp()
@ -35,9 +27,6 @@ func RunApp() {
goxkcdreader := getApp() goxkcdreader := getApp()
w := goxkcdreader.NewWindow(cmd.GetAppName()) w := goxkcdreader.NewWindow(cmd.GetAppName())
// mark this window as the main window
w.SetMaster()
centered := container.New( centered := container.New(
layout.NewHBoxLayout(), layout.NewHBoxLayout(),
layout.NewSpacer(), layout.NewSpacer(),
@ -58,7 +47,6 @@ func RunApp() {
func newApp() { func newApp() {
a = app.New() a = app.New()
log.Println("Created a new fyne application") log.Println("Created a new fyne application")
} }
@ -66,16 +54,18 @@ func getApp() fyne.App {
return a return a
} }
// makeGreeting creates a greeting label. // makeGreeting creates a greeting label
func makeGreeting() *widget.Label { func makeGreeting() *widget.Label {
w := widget.NewLabel(appGreeting) w := widget.NewLabel(appGreeting)
w.TextStyle.Monospace = true w.TextStyle.Monospace = true
return w return w
} }
func makeToolbar() *widget.Toolbar { func makeToolbar() *widget.Toolbar {
toolbar := widget.NewToolbar( toolbar := widget.NewToolbar(
widget.NewToolbarAction(theme.SearchIcon(), func() {
log.Println("Search")
}),
widget.NewToolbarSpacer(), widget.NewToolbarSpacer(),
widget.NewToolbarAction(theme.HelpIcon(), func() { widget.NewToolbarAction(theme.HelpIcon(), func() {
log.Println("Display help") log.Println("Display help")
@ -85,17 +75,14 @@ func makeToolbar() *widget.Toolbar {
}), }),
widget.NewToolbarAction(theme.InfoIcon(), aboutWindow), widget.NewToolbarAction(theme.InfoIcon(), aboutWindow),
) )
return toolbar return toolbar
} }
func makeTabs() *container.AppTabs { func makeTabs() *container.AppTabs {
tabs := container.NewAppTabs( tabs := container.NewAppTabs(
container.NewTabItem("browse", makeBrowseUI()), container.NewTabItem("xkcd comic", makeBrowseUI()),
container.NewTabItem("what if?", widget.NewLabel("serious answers to absurd questions and absurd advice for common concerns from xkcd's Randall Munroe")), 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 return tabs
} }
@ -103,126 +90,23 @@ func makeBrowseUI() *fyne.Container {
// container for the image and surrounding elements // container for the image and surrounding elements
imgC := container.New( imgC := container.New(
layout.NewHBoxLayout(), layout.NewHBoxLayout(),
widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() { widget.NewButton("previous", func() {
log.Println("Previous comic") log.Println("Previous comic")
}), }),
layout.NewSpacer(), layout.NewSpacer(),
container.NewCenter( widget.NewLabel("img placeholder"),
// TODO(me): dynamically replace placeholder text with image once
// fetched...
// widget.NewLabel("img placeholder"),
getImg(latestComic.Img, false),
),
layout.NewSpacer(), layout.NewSpacer(),
widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() { widget.NewButton("next", func() {
log.Println("Next comic") log.Println("Next comic")
}), }))
)
browseTabLabel := "Latest comic..."
browseUI := container.New( browseUI := container.New(
layout.NewVBoxLayout(), layout.NewVBoxLayout(),
widget.NewLabel(browseTabLabel),
layout.NewSpacer(), 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, imgC,
layout.NewSpacer(),
container.NewCenter(
// comic "alt text"
&widget.Label{
Text: latestComic.Alt,
TextStyle: fyne.TextStyle{Italic: true},
},
),
) )
return browseUI 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,8 +6,6 @@ package xkcdreader
import ( import (
"testing" "testing"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"git.dotya.ml/wanderer/go-xkcdreader/cmd" "git.dotya.ml/wanderer/go-xkcdreader/cmd"
) )
@ -21,7 +19,7 @@ func TestGreetingText(t *testing.T) {
} }
func TestToolbar(t *testing.T) { func TestToolbar(t *testing.T) {
wantItems := 4 wantItems := 5
gotToolbar := makeToolbar() gotToolbar := makeToolbar()
if len(gotToolbar.Items) != wantItems { if len(gotToolbar.Items) != wantItems {
@ -30,6 +28,7 @@ func TestToolbar(t *testing.T) {
} }
func TestTabs(t *testing.T) { func TestTabs(t *testing.T) {
// wantTabsNum := 2 // wantTabsNum := 2
// gotTabs := makeTabs() // gotTabs := makeTabs()
// //
@ -116,109 +115,3 @@ func TestGetApp(t *testing.T) {
t.Error("Failed to get application pointer using getApp()") 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)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

@ -1,39 +0,0 @@
// 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
}

@ -1,37 +0,0 @@
// 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
}

@ -1,54 +0,0 @@
// 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()
}
}