Compare commits

...

40 Commits

Author SHA1 Message Date
be97641245
flake: exclude all non-go files
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-08 18:19:46 +02:00
d2b5afe907
flake,ci: reenable GOPROXY var default
All checks were successful
continuous-integration/drone/push Build is passing
...mainly due to arbitrarily failing ci builds, as this doesn't appear
to affect local builds/nix builds on remote builders, just fresh builds
in the ci.
the explanation for why this is happening might be that certain package
VCS's don't allow many/frequent connections from IP ranges other than
GOPROXY's (Google's).
that is sheer speculation, though.
however, it is still true that there are "no" failed ci builds with
GOPROXY env var set to its default value (left untouched), which does
indeed indicate that the above mentioned might be the root cause of the
issue after all.

this commit reverts:
    * f3e481395e
and partially reverts:
    * 14ba7b18bf

GOPROXY=direct has been kept for `nix-shell`/`nix develop` environments
as that is not where it poses an issue.
2022-07-08 17:13:13 +02:00
f58122320b
flake: add ${projname} var instead of a literal
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-07 22:47:43 +02:00
06485ba003
golangci-lint: rm prealloc,{rowserr,sqlclose}check
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-14 14:37:35 +02:00
8ed7525297
pre-commit: run tidy earlier [skip ci] 2022-06-14 13:43:31 +02:00
e11fd7fb72
pre-commit: rm govet,go-cyclo
...since they are both being run as part of golangci-lint checks

[skip ci]
2022-06-14 13:38:33 +02:00
4c67075f5f
flake: set formatter (for all supported systems)
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-13 23:28:52 +02:00
103bbfa528
flake(goModule): filter out non-pertinent files...
All checks were successful
continuous-integration/drone/push Build is passing
...such as flake files or {shell,default}.nix to reduce the necessity to
rebuild (when using `nix build`) after each change, even to non-go
files. more files can be added to an exclude list in the future.
2022-06-13 17:39:46 +02:00
7121d8f91a
golangci-lint: add custom linter settings...
All checks were successful
continuous-integration/drone Build is passing
continuous-integration/drone/push Build is passing
...for:
  * gocritic
  * gocyclo
  * govet
2022-06-12 02:20:13 +02:00
f3e481395e
ci(env): use GOPROXY=direct (follow-up of 14ba7b1)
All checks were successful
continuous-integration/drone Build is passing
continuous-integration/drone/push Build is passing
2022-06-11 06:50:21 +02:00
c94b671d9d
go(version): switch from log to fmt for printing
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-09 23:47:05 +02:00
e2b327932e
.gitattributes: register *.starlark with linguist
All checks were successful
continuous-integration/drone/push Build is passing
[skip-ci]
2022-06-05 01:29:54 +02:00
14ba7b18bf
flake,go: use GOPROXY=direct
All checks were successful
continuous-integration/drone/push Build is passing
* both for the flake-provided shell environment and the flake-provided
  go module build env

inspired by:
  https://drewdevault.com/2022/05/25/Google-has-been-DDoSing-sourcehut.html
2022-06-01 16:49:46 +02:00
755a7c07b1
app: use 'latestComic' to set comic fields in UI
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-31 21:21:06 +02:00
0b5559336e
cmd(version): check for more errors in the test
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-31 21:07:44 +02:00
ed72a0db66
go: get latest comic on app start
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-30 18:08:30 +02:00
34936a5f72
ci: run 'govet' with golangci-lint
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-29 23:43:56 +02:00
c09507658a
go: enable running certain tests in parallel
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-29 22:28:17 +02:00
5e6f02a843
flake: comment out patchelf (for now)
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-29 21:19:40 +02:00
9dab23ded7
go: add xkcd package
All checks were successful
continuous-integration/drone/push Build is passing
* utilise the john olheiser's neat xkcd package for comic fetching
  ref: gitea.com/jolheiser/xkcd
* add tests
* nix: bump module hash
2022-05-29 21:05:28 +02:00
53858ccda3
ci: refresh go report card after 'golangci-lint'
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-29 15:22:19 +02:00
c2fab52268
ci: run golangi-lint checks
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-28 21:02:40 +02:00
1f62d3dec6
pre-commit(go): use golangci-lint for most checks
[skip ci]
2022-05-28 20:58:56 +02:00
5e6a8f9001
rename: .vimrc-example -> .example.vimrc
All checks were successful
continuous-integration/drone/push Build is passing
[skip ci]
2022-05-28 19:44:00 +02:00
12c7003570
update .vimrc-example [skip ci] 2022-05-28 18:59:57 +02:00
7540300857
chore(app): fmt
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-28 16:54:45 +02:00
f26701d416
add .golangci.yml [skip ci] 2022-05-27 23:11:12 +02:00
31f475acb6
fix(app): implement wsl's formatting suggestions
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-27 23:08:17 +02:00
b28dae3bd0
fix(app): format using gofumpt
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-27 23:03:09 +02:00
c79f4e811b
fix: implement forbidigo's suggestion fmt.Println
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-27 22:28:43 +02:00
6a2470db54
fix: implement godot's suggestions on app comments
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-27 22:14:29 +02:00
d32b44aedd
app(main_test): log success
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-27 22:06:30 +02:00
c190f3f6f2
app: mark first window as the main one
All checks were successful
continuous-integration/drone Build is passing
continuous-integration/drone/push Build is passing
2022-05-27 17:43:35 +02:00
100b961494
app: add a way to get images (from FS for now)
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-26 20:42:35 +02:00
53be19912a
app: add stuff to 'browse' tab
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-25 23:33:27 +02:00
37bdd08e41
app: move 'search tab' creation to search.go
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-25 22:31:09 +02:00
bd58ca6608
pre-commit: alter when 'nix build' runs
All checks were successful
continuous-integration/drone/push Build is passing
* do not run 'nix build' on arbitrary go src file changes, instead
  (except for nix and flake related changes) only run on module-wide
  changes, i.e. when sum,mod files change as these require flake
  vendorSha256 updates or else they would fail to build.
2022-05-25 19:39:31 +02:00
6372fd93fd
app: mv 'search' to a tab
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-25 19:33:13 +02:00
325a1ca50d
app: use buttons with icons
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-25 18:59:13 +02:00
a859916f1c
ci: trigger goreportcard refresh
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-25 16:50:29 +02:00
23 changed files with 647 additions and 76 deletions

@ -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

@ -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

5
.gitattributes vendored

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

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

@ -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,17 +5,24 @@ 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 {
t.Errorf("Failed to open flake.nix: %q", err)
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+"\""))
@ -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,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"
}

@ -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

@ -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

@ -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=

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

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

@ -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
}

@ -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()
}
}