Skip to content

Commit

Permalink
refactor: minor restructure and basic housekeeping
Browse files Browse the repository at this point in the history
  - Document main package.
  - Update Go version to 1.18.
  - Unify cli and docs packages.
  - Rename persist package to services to allow for more idiomatic
    names.
  - Simplify error handling.
  - Separate the task of fetching the kernel version and asserting that
    it is compatible with the application.
  - Simplify logic that checks whether the kernel version is compatible
    with the application.
  - Replace `fmt.Sprint` calls with the more performant
    `strconv.FormatInt(int64(t, 10))` for writing to the threshold
    variable.
  - Create a new test suite for the threshold package including fuzz
    tests.
  - Update documentation year.
  - Fix grammar errors in the README.
  - Delete the superficial `main_test.go`.
  • Loading branch information
Tshaka Eric Lekholoane committed May 29, 2022
1 parent 4890f87 commit 4500eb3
Show file tree
Hide file tree
Showing 16 changed files with 271 additions and 211 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ bat
!bat/
*.zip

# Development
.vscode

# Binaries for programs and plugins
*.exe
*.exe~
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Tshaka Eric Lekholoane
Copyright (c) 2022 Tshaka Eric Lekholoane

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ DESCRIPTION

## About

The goal is to replicate the functionality of the [ASUS Battery Health Charging](https://www.asus.com/us/support/FAQ/1032726/) utility for ASUS laptops on Windows which aims prolong the battery's life-span <a href="https://electrek.co/2017/09/01/tesla-battery-expert-recommends-daily-battery-pack-charging/"><sup>1</sup></a> <a href="https://batteryuniversity.com/learn/article/how_to_prolong_lithium_based_batteries"><sup>2</sup></a>.
The goal is to replicate the functionality of the [ASUS Battery Health Charging](https://www.asus.com/us/support/FAQ/1032726/) utility for ASUS laptops on Windows which aims to prolong the battery's life-span <a href="https://electrek.co/2017/09/01/tesla-battery-expert-recommends-daily-battery-pack-charging/"><sup>1</sup></a> <a href="https://batteryuniversity.com/learn/article/how_to_prolong_lithium_based_batteries"><sup>2</sup></a>.

## Disclaimer

This has been reported to work with some ASUS and [Lenovo ThinkPad](https://github.com/tshakalekholoane/bat/discussions/23) laptops only. For Dell systems, see [smbios-utils](https://github.com/dell/libsmbios), particularly the `smbios-battery-ctl` command, or install it using your package manager. For other manufacturers there is also [TLP](https://linrunner.de/tlp/).
This has been reported to only work with some ASUS and [Lenovo ThinkPad](https://github.com/tshakalekholoane/bat/discussions/23) laptops only. For Dell systems, see [smbios-utils](https://github.com/dell/libsmbios), particularly the `smbios-battery-ctl` command, or install it using your package manager. For other manufacturers there is also [TLP](https://linrunner.de/tlp/).

There have also been some [problems setting the charging threshold inside of a virtual machine](https://github.com/tshakalekholoane/bat/issues/3#issuecomment-858581495).

## Installation

Precompiled binaries (Linux x86-64) are available from the [GitHub releases page](https://github.com/tshakalekholoane/bat/releases), the latest of which can be downloaded from [here](https://github.com/tshakalekholoane/bat/releases/download/0.8.4/bat).
Precompiled binaries (Linux x86-64) are available from the [GitHub releases page](https://github.com/tshakalekholoane/bat/releases), the latest of which can be downloaded from [here](https://github.com/tshakalekholoane/bat/releases/download/0.9/bat).

After downloading the binary, give it permission to execute on your system by running the following command. For example, assuming the binary is located in the user's Downloads folder:

Expand All @@ -65,17 +65,17 @@ $ go build ./cmd/bat/
# Print the current battery charging threshold.
$ bat --threshold

# Set a new charging threshold, say 80%.
# (requires superuser permissions).
# Set a new charging threshold, say 80% (requires superuser
# permissions).
$ sudo bat --threshold 80

# Persist the current charging threshold setting between restarts
# Persist the current charging threshold setting between restarts
# (requires superuser permissions).
$ sudo bat --persist
```

## Requirements

Linux kernel version later than 5.4 which is the [earliest version to expose the battery charging threshold variable](https://github.com/torvalds/linux/commit/7973353e92ee1e7ca3b2eb361a4b7cb66c92abee).
Linux kernel version later than 5.4-rc1 which is the [earliest version to expose the battery charging threshold variable](https://github.com/torvalds/linux/commit/7973353e92ee1e7ca3b2eb361a4b7cb66c92abee).

To persist the threshold setting between restarts, the application relies on [systemd](https://systemd.io/), particularly a version later than 244, and [Bash](https://www.gnu.org/software/bash/) which are bundled with most Linux distributions.
1 change: 1 addition & 0 deletions cmd/bat/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Binary bat is a battery management utility for Linux laptops.
package main

import "tshaka.co/bat/internal/cli"
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module tshaka.co/bat

go 1.17
go 1.18
90 changes: 53 additions & 37 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,82 @@
package cli

import (
_ "embed"
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"strconv"
"strings"
"syscall"

"tshaka.co/bat/internal/docs"
"tshaka.co/bat/internal/file"
"tshaka.co/bat/internal/persist"
"tshaka.co/bat/internal/services"
"tshaka.co/bat/internal/threshold"
)

// common error messages
// Common error messages.
const (
incompat = "This program is most likely not compatible with your system. " +
"See\nhttps://github.com/tshakalekholoane/bat#disclaimer for details."
permissionDenied = "Permission denied. Try running this command using sudo."
)

// Documentation.
var (
//go:embed help.txt
help string
//go:embed version.txt
version string
)

// errPermissionDenied indicates that the user has insufficient
// permissions to perform an action.
var errPermissionDenied = syscall.EACCES

// context is a helper function that returns an io.Writer and a status
// code the program should exit with after performing an action.
func context(fatal bool) (io.Writer, int) {
var out io.Writer = os.Stdout
var code int
// context is a helper function that returns an io.Writer and exit code,
// os.Stderr and 1 if fatal is true and os.Stdout and 0 otherwise.
func context(fatal bool) (out io.Writer, code int) {
if fatal {
out = os.Stderr
code = 1
} else {
out = os.Stdout
}
return
}

// page filters the string doc through the less pager.
func page(doc string) {
cmd := exec.Command(
"less",
"--no-init",
"--quit-if-one-screen",
"--IGNORE-CASE",
"--RAW-CONTROL-CHARS",
)
cmd.Stdin = strings.NewReader(doc)
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
log.Fatalln(err)
}
return out, code
os.Exit(0)
}

// reportf formats according to a format specifier and writes to either
// standard error or standard output depending on the context.
func reportf(fatal bool, format string, a ...interface{}) {
// reportf formats a according to a format specifier and prints it to
// standard error if fatal is true or to standard output otherwise.
func reportf(fatal bool, format string, a ...any) {
out, code := context(fatal)
fmt.Fprintf(out, format, a...)
os.Exit(code)
}

// reportln formats using the default formats for its operands, appends
// a new line and, writes to standard output.
func reportln(fatal bool, a ...interface{}) {
// reportln formats a using the default formats for its operands,
// appends a new line and, writes to standard error if fatal is true or
// standard output otherwise.
func reportln(fatal bool, a ...any) {
reportf(fatal, "%v\n", a...)
}

Expand All @@ -69,25 +96,20 @@ func show(v string) {
// Run executes the application.
func Run() {
if len(os.Args) == 1 {
docs.Usage()
os.Exit(0)
page(help)
}

switch os.Args[1] {
case "-c", "--capacity":
show("capacity")
case "-h", "--help":
err := docs.Usage()
if err != nil {
log.Fatalln(err)
}
os.Exit(0)
page(help)
case "-p", "--persist":
err := persist.WriteServices()
if err != nil {
if err := services.Write(); err != nil {
switch {
case errors.Is(err, persist.ErrBashNotFound):
reportln(true, "Cannot find Bash on your system.")
case errors.Is(err, persist.ErrIncompatSystemd):
case errors.Is(err, services.ErrBashNotFound):
reportln(true, "Could not find Bash on your system.")
case errors.Is(err, services.ErrIncompatSystemd):
reportln(true, "Requires systemd version 244-rc1 or later.")
case errors.Is(err, file.ErrNotFound):
reportln(true, incompat)
Expand All @@ -99,8 +121,7 @@ func Run() {
}
reportln(false, "Persistence of the current charging threshold enabled.")
case "-r", "--reset":
err := persist.DeleteServices()
if err != nil {
if err := services.Delete(); err != nil {
if errors.Is(err, errPermissionDenied) {
reportln(true, permissionDenied)
}
Expand All @@ -121,11 +142,10 @@ func Run() {
}
log.Fatal(err)
}
if t < 1 || t > 100 {
if !threshold.IsValid(t) {
reportln(true, "Number should be between 1 and 100.")
}
err = threshold.Set(t)
if err != nil {
if err := threshold.Set(t); err != nil {
switch {
case errors.Is(err, threshold.ErrIncompatKernel):
reportln(true, "Requires Linux kernel version 5.4 or later.")
Expand All @@ -145,11 +165,7 @@ func Run() {
show("charge_control_end_threshold")
}
case "-v", "--version":
err := docs.Version()
if err != nil {
log.Fatal(err)
}
os.Exit(0)
page(version)
default:
reportf(
true,
Expand Down
2 changes: 1 addition & 1 deletion internal/docs/help.txt → internal/cli/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ SUPPORT
REFERENCE
https://wiki.archlinux.org/title/Laptop/ASUS#Battery_charge_threshold

13 DECEMBER 2021
28 MAY 2022
3 changes: 3 additions & 0 deletions internal/cli/version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bat 0.9
Copyright (c) 2022 Tshaka Eric Lekholoane.
MIT Licence.
47 changes: 0 additions & 47 deletions internal/docs/docs.go

This file was deleted.

3 changes: 0 additions & 3 deletions internal/docs/version.txt

This file was deleted.

13 changes: 6 additions & 7 deletions internal/file/file.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package file implements a helper function that returns the value of a
// Package file contains helpers related to reading the contents of a
// /sys/class/power_supply/BAT?/ variable.
package file

Expand All @@ -11,19 +11,18 @@ import (
var ErrNotFound = errors.New("file: virtual file not found")

// Contents returns the contents of a virtual file in
// /sys/class/power_supply/BAT?/ as a sequence of bytes.
func Contents(v string) ([]byte, error) {
v = "/sys/class/power_supply/BAT?/" + v
matches, err := filepath.Glob(v)
// /sys/class/power_supply/BAT?/ as a slice of bytes.
func Contents(f string) ([]byte, error) {
matches, err := filepath.Glob("/sys/class/power_supply/BAT?/" + f)
if err != nil {
return nil, err
}
if len(matches) == 0 {
return nil, ErrNotFound
}
f, err := os.ReadFile(matches[0])
val, err := os.ReadFile(matches[0])
if err != nil {
return nil, err
}
return f, nil
return val, nil
}
Loading

0 comments on commit 4500eb3

Please sign in to comment.