mirror of
https://github.com/Jguer/yay
synced 2024-10-31 13:42:27 +00:00
bbeef9d237
As mentioned it is possible for stuff to clash in cache if you were to -G a package from the repos, then -S an AUR package by the same name. To avoid that give abs cloning its own directory. It is still possible for a clash to occur if there was a package named abs. However currently there is not. Idealy the aur cloning would also get it's own subdir, but that will invalidate everyone's cache so leaving it for a time with more breaking changes. This is also under its own config option so that AURDEST does not interfere with it.
921 lines
17 KiB
Go
921 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"html"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Jguer/yay/v9/pkg/types"
|
|
rpc "github.com/mikkeloscar/aur"
|
|
)
|
|
|
|
// Parses command line arguments in a way we can interact with programmatically but
|
|
// also in a way that can easily be passed to pacman later on.
|
|
type arguments struct {
|
|
op string
|
|
options map[string]string
|
|
globals map[string]string
|
|
doubles types.StringSet // Tracks args passed twice such as -yy and -dd
|
|
targets []string
|
|
}
|
|
|
|
func makeArguments() *arguments {
|
|
return &arguments{
|
|
"",
|
|
make(map[string]string),
|
|
make(map[string]string),
|
|
make(types.StringSet),
|
|
make([]string, 0),
|
|
}
|
|
}
|
|
|
|
func (parser *arguments) copyGlobal() (cp *arguments) {
|
|
cp = makeArguments()
|
|
for k, v := range parser.globals {
|
|
cp.globals[k] = v
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (parser *arguments) copy() (cp *arguments) {
|
|
cp = makeArguments()
|
|
|
|
cp.op = parser.op
|
|
|
|
for k, v := range parser.options {
|
|
cp.options[k] = v
|
|
}
|
|
|
|
for k, v := range parser.globals {
|
|
cp.globals[k] = v
|
|
}
|
|
|
|
cp.targets = make([]string, len(parser.targets))
|
|
copy(cp.targets, parser.targets)
|
|
|
|
for k, v := range parser.doubles {
|
|
cp.doubles[k] = v
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (parser *arguments) delArg(options ...string) {
|
|
for _, option := range options {
|
|
delete(parser.options, option)
|
|
delete(parser.globals, option)
|
|
delete(parser.doubles, option)
|
|
}
|
|
}
|
|
|
|
func (parser *arguments) needRoot() bool {
|
|
if parser.existsArg("h", "help") {
|
|
return false
|
|
}
|
|
|
|
switch parser.op {
|
|
case "D", "database":
|
|
if parser.existsArg("k", "check") {
|
|
return false
|
|
}
|
|
return true
|
|
case "F", "files":
|
|
if parser.existsArg("y", "refresh") {
|
|
return true
|
|
}
|
|
return false
|
|
case "Q", "query":
|
|
if parser.existsArg("k", "check") {
|
|
return true
|
|
}
|
|
return false
|
|
case "R", "remove":
|
|
if parser.existsArg("p", "print", "print-format") {
|
|
return false
|
|
}
|
|
return true
|
|
case "S", "sync":
|
|
if parser.existsArg("y", "refresh") {
|
|
return true
|
|
}
|
|
if parser.existsArg("p", "print", "print-format") {
|
|
return false
|
|
}
|
|
if parser.existsArg("s", "search") {
|
|
return false
|
|
}
|
|
if parser.existsArg("l", "list") {
|
|
return false
|
|
}
|
|
if parser.existsArg("g", "groups") {
|
|
return false
|
|
}
|
|
if parser.existsArg("i", "info") {
|
|
return false
|
|
}
|
|
if parser.existsArg("c", "clean") && mode == modeAUR {
|
|
return false
|
|
}
|
|
return true
|
|
case "U", "upgrade":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (parser *arguments) addOP(op string) (err error) {
|
|
if parser.op != "" {
|
|
err = fmt.Errorf("only one operation may be used at a time")
|
|
return
|
|
}
|
|
|
|
parser.op = op
|
|
return
|
|
}
|
|
|
|
func (parser *arguments) addParam(option string, arg string) (err error) {
|
|
if !isArg(option) {
|
|
return fmt.Errorf("invalid option '%s'", option)
|
|
}
|
|
|
|
if isOp(option) {
|
|
err = parser.addOP(option)
|
|
return
|
|
}
|
|
|
|
switch {
|
|
case parser.existsArg(option):
|
|
parser.doubles[option] = struct{}{}
|
|
case isGlobal(option):
|
|
parser.globals[option] = arg
|
|
default:
|
|
parser.options[option] = arg
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (parser *arguments) addArg(options ...string) (err error) {
|
|
for _, option := range options {
|
|
err = parser.addParam(option, "")
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Multiple args acts as an OR operator
|
|
func (parser *arguments) existsArg(options ...string) bool {
|
|
for _, option := range options {
|
|
_, exists := parser.options[option]
|
|
if exists {
|
|
return true
|
|
}
|
|
|
|
_, exists = parser.globals[option]
|
|
if exists {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (parser *arguments) getArg(options ...string) (arg string, double bool, exists bool) {
|
|
existCount := 0
|
|
|
|
for _, option := range options {
|
|
var value string
|
|
|
|
value, exists = parser.options[option]
|
|
|
|
if exists {
|
|
arg = value
|
|
existCount++
|
|
_, exists = parser.doubles[option]
|
|
|
|
if exists {
|
|
existCount++
|
|
}
|
|
|
|
}
|
|
|
|
value, exists = parser.globals[option]
|
|
|
|
if exists {
|
|
arg = value
|
|
existCount++
|
|
_, exists = parser.doubles[option]
|
|
|
|
if exists {
|
|
existCount++
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
double = existCount >= 2
|
|
exists = existCount >= 1
|
|
|
|
return
|
|
}
|
|
|
|
func (parser *arguments) addTarget(targets ...string) {
|
|
parser.targets = append(parser.targets, targets...)
|
|
}
|
|
|
|
func (parser *arguments) clearTargets() {
|
|
parser.targets = make([]string, 0)
|
|
}
|
|
|
|
// Multiple args acts as an OR operator
|
|
func (parser *arguments) existsDouble(options ...string) bool {
|
|
for _, option := range options {
|
|
_, exists := parser.doubles[option]
|
|
if exists {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (parser *arguments) formatArgs() (args []string) {
|
|
var op string
|
|
|
|
if parser.op != "" {
|
|
op = formatArg(parser.op)
|
|
}
|
|
|
|
args = append(args, op)
|
|
|
|
for option, arg := range parser.options {
|
|
if option == "--" {
|
|
continue
|
|
}
|
|
|
|
formattedOption := formatArg(option)
|
|
args = append(args, formattedOption)
|
|
|
|
if hasParam(option) {
|
|
args = append(args, arg)
|
|
}
|
|
|
|
if parser.existsDouble(option) {
|
|
args = append(args, formattedOption)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (parser *arguments) formatGlobals() (args []string) {
|
|
for option, arg := range parser.globals {
|
|
formattedOption := formatArg(option)
|
|
args = append(args, formattedOption)
|
|
|
|
if hasParam(option) {
|
|
args = append(args, arg)
|
|
}
|
|
|
|
if parser.existsDouble(option) {
|
|
args = append(args, formattedOption)
|
|
}
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
func formatArg(arg string) string {
|
|
if len(arg) > 1 {
|
|
arg = "--" + arg
|
|
} else {
|
|
arg = "-" + arg
|
|
}
|
|
|
|
return arg
|
|
}
|
|
|
|
func isArg(arg string) bool {
|
|
switch arg {
|
|
case "-", "--":
|
|
case "ask":
|
|
case "D", "database":
|
|
case "Q", "query":
|
|
case "R", "remove":
|
|
case "S", "sync":
|
|
case "T", "deptest":
|
|
case "U", "upgrade":
|
|
case "F", "files":
|
|
case "V", "version":
|
|
case "h", "help":
|
|
case "Y", "yay":
|
|
case "P", "show":
|
|
case "G", "getpkgbuild":
|
|
case "b", "dbpath":
|
|
case "r", "root":
|
|
case "v", "verbose":
|
|
case "arch":
|
|
case "cachedir":
|
|
case "color":
|
|
case "config":
|
|
case "debug":
|
|
case "gpgdir":
|
|
case "hookdir":
|
|
case "logfile":
|
|
case "noconfirm":
|
|
case "confirm":
|
|
case "disable-download-timeout":
|
|
case "sysroot":
|
|
case "d", "nodeps":
|
|
case "assume-installed":
|
|
case "dbonly":
|
|
case "noprogressbar":
|
|
case "noscriptlet":
|
|
case "p", "print":
|
|
case "print-format":
|
|
case "asdeps":
|
|
case "asexplicit":
|
|
case "ignore":
|
|
case "ignoregroup":
|
|
case "needed":
|
|
case "overwrite":
|
|
case "f", "force":
|
|
case "c", "changelog":
|
|
case "deps":
|
|
case "e", "explicit":
|
|
case "g", "groups":
|
|
case "i", "info":
|
|
case "k", "check":
|
|
case "l", "list":
|
|
case "m", "foreign":
|
|
case "n", "native":
|
|
case "o", "owns":
|
|
case "file":
|
|
case "q", "quiet":
|
|
case "s", "search":
|
|
case "t", "unrequired":
|
|
case "u", "upgrades":
|
|
case "cascade":
|
|
case "nosave":
|
|
case "recursive":
|
|
case "unneeded":
|
|
case "clean":
|
|
case "sysupgrade":
|
|
case "w", "downloadonly":
|
|
case "y", "refresh":
|
|
case "x", "regex":
|
|
case "machinereadable":
|
|
//yay options
|
|
case "aururl":
|
|
case "save":
|
|
case "afterclean", "cleanafter":
|
|
case "noafterclean", "nocleanafter":
|
|
case "devel":
|
|
case "nodevel":
|
|
case "timeupdate":
|
|
case "notimeupdate":
|
|
case "topdown":
|
|
case "bottomup":
|
|
case "completioninterval":
|
|
case "sortby":
|
|
case "searchby":
|
|
case "redownload":
|
|
case "redownloadall":
|
|
case "noredownload":
|
|
case "rebuild":
|
|
case "rebuildall":
|
|
case "rebuildtree":
|
|
case "norebuild":
|
|
case "batchinstall":
|
|
case "nobatchinstall":
|
|
case "answerclean":
|
|
case "noanswerclean":
|
|
case "answerdiff":
|
|
case "noanswerdiff":
|
|
case "answeredit":
|
|
case "noansweredit":
|
|
case "answerupgrade":
|
|
case "noanswerupgrade":
|
|
case "gitclone":
|
|
case "nogitclone":
|
|
case "gpgflags":
|
|
case "mflags":
|
|
case "gitflags":
|
|
case "builddir":
|
|
case "editor":
|
|
case "editorflags":
|
|
case "makepkg":
|
|
case "makepkgconf":
|
|
case "nomakepkgconf":
|
|
case "pacman":
|
|
case "tar":
|
|
case "git":
|
|
case "gpg":
|
|
case "requestsplitn":
|
|
case "sudoloop":
|
|
case "nosudoloop":
|
|
case "provides":
|
|
case "noprovides":
|
|
case "pgpfetch":
|
|
case "nopgpfetch":
|
|
case "upgrademenu":
|
|
case "noupgrademenu":
|
|
case "cleanmenu":
|
|
case "nocleanmenu":
|
|
case "diffmenu":
|
|
case "nodiffmenu":
|
|
case "editmenu":
|
|
case "noeditmenu":
|
|
case "useask":
|
|
case "nouseask":
|
|
case "combinedupgrade":
|
|
case "nocombinedupgrade":
|
|
case "a", "aur":
|
|
case "repo":
|
|
case "removemake":
|
|
case "noremovemake":
|
|
case "askremovemake":
|
|
case "complete":
|
|
case "stats":
|
|
case "news":
|
|
case "gendb":
|
|
case "currentconfig":
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func handleConfig(option, value string) bool {
|
|
switch option {
|
|
case "aururl":
|
|
config.AURURL = value
|
|
case "save":
|
|
shouldSaveConfig = true
|
|
case "afterclean", "cleanafter":
|
|
config.CleanAfter = true
|
|
case "noafterclean", "nocleanafter":
|
|
config.CleanAfter = false
|
|
case "devel":
|
|
config.Devel = true
|
|
case "nodevel":
|
|
config.Devel = false
|
|
case "timeupdate":
|
|
config.TimeUpdate = true
|
|
case "notimeupdate":
|
|
config.TimeUpdate = false
|
|
case "topdown":
|
|
config.SortMode = topDown
|
|
case "bottomup":
|
|
config.SortMode = bottomUp
|
|
case "completioninterval":
|
|
n, err := strconv.Atoi(value)
|
|
if err == nil {
|
|
config.CompletionInterval = n
|
|
}
|
|
case "sortby":
|
|
config.SortBy = value
|
|
case "searchby":
|
|
config.SearchBy = value
|
|
case "noconfirm":
|
|
config.NoConfirm = true
|
|
case "config":
|
|
config.PacmanConf = value
|
|
case "redownload":
|
|
config.ReDownload = "yes"
|
|
case "redownloadall":
|
|
config.ReDownload = "all"
|
|
case "noredownload":
|
|
config.ReDownload = "no"
|
|
case "rebuild":
|
|
config.ReBuild = "yes"
|
|
case "rebuildall":
|
|
config.ReBuild = "all"
|
|
case "rebuildtree":
|
|
config.ReBuild = "tree"
|
|
case "norebuild":
|
|
config.ReBuild = "no"
|
|
case "batchinstall":
|
|
config.BatchInstall = true
|
|
case "nobatchinstall":
|
|
config.BatchInstall = false
|
|
case "answerclean":
|
|
config.AnswerClean = value
|
|
case "noanswerclean":
|
|
config.AnswerClean = ""
|
|
case "answerdiff":
|
|
config.AnswerDiff = value
|
|
case "noanswerdiff":
|
|
config.AnswerDiff = ""
|
|
case "answeredit":
|
|
config.AnswerEdit = value
|
|
case "noansweredit":
|
|
config.AnswerEdit = ""
|
|
case "answerupgrade":
|
|
config.AnswerUpgrade = value
|
|
case "noanswerupgrade":
|
|
config.AnswerUpgrade = ""
|
|
case "gitclone":
|
|
config.GitClone = true
|
|
case "nogitclone":
|
|
config.GitClone = false
|
|
case "gpgflags":
|
|
config.GpgFlags = value
|
|
case "mflags":
|
|
config.MFlags = value
|
|
case "gitflags":
|
|
config.GitFlags = value
|
|
case "builddir":
|
|
config.BuildDir = value
|
|
case "absdir":
|
|
config.ABSDir = value
|
|
case "editor":
|
|
config.Editor = value
|
|
case "editorflags":
|
|
config.EditorFlags = value
|
|
case "makepkg":
|
|
config.MakepkgBin = value
|
|
case "makepkgconf":
|
|
config.MakepkgConf = value
|
|
case "nomakepkgconf":
|
|
config.MakepkgConf = ""
|
|
case "pacman":
|
|
config.PacmanBin = value
|
|
case "tar":
|
|
config.TarBin = value
|
|
case "git":
|
|
config.GitBin = value
|
|
case "gpg":
|
|
config.GpgBin = value
|
|
case "requestsplitn":
|
|
n, err := strconv.Atoi(value)
|
|
if err == nil && n > 0 {
|
|
config.RequestSplitN = n
|
|
}
|
|
case "sudoloop":
|
|
config.SudoLoop = true
|
|
case "nosudoloop":
|
|
config.SudoLoop = false
|
|
case "provides":
|
|
config.Provides = true
|
|
case "noprovides":
|
|
config.Provides = false
|
|
case "pgpfetch":
|
|
config.PGPFetch = true
|
|
case "nopgpfetch":
|
|
config.PGPFetch = false
|
|
case "upgrademenu":
|
|
config.UpgradeMenu = true
|
|
case "noupgrademenu":
|
|
config.UpgradeMenu = false
|
|
case "cleanmenu":
|
|
config.CleanMenu = true
|
|
case "nocleanmenu":
|
|
config.CleanMenu = false
|
|
case "diffmenu":
|
|
config.DiffMenu = true
|
|
case "nodiffmenu":
|
|
config.DiffMenu = false
|
|
case "editmenu":
|
|
config.EditMenu = true
|
|
case "noeditmenu":
|
|
config.EditMenu = false
|
|
case "useask":
|
|
config.UseAsk = true
|
|
case "nouseask":
|
|
config.UseAsk = false
|
|
case "combinedupgrade":
|
|
config.CombinedUpgrade = true
|
|
case "nocombinedupgrade":
|
|
config.CombinedUpgrade = false
|
|
case "a", "aur":
|
|
mode = modeAUR
|
|
case "repo":
|
|
mode = modeRepo
|
|
case "removemake":
|
|
config.RemoveMake = "yes"
|
|
case "noremovemake":
|
|
config.RemoveMake = "no"
|
|
case "askremovemake":
|
|
config.RemoveMake = "ask"
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func isOp(op string) bool {
|
|
switch op {
|
|
case "V", "version":
|
|
case "D", "database":
|
|
case "F", "files":
|
|
case "Q", "query":
|
|
case "R", "remove":
|
|
case "S", "sync":
|
|
case "T", "deptest":
|
|
case "U", "upgrade":
|
|
// yay specific
|
|
case "Y", "yay":
|
|
case "P", "show":
|
|
case "G", "getpkgbuild":
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func isGlobal(op string) bool {
|
|
switch op {
|
|
case "b", "dbpath":
|
|
case "r", "root":
|
|
case "v", "verbose":
|
|
case "arch":
|
|
case "cachedir":
|
|
case "color":
|
|
case "config":
|
|
case "debug":
|
|
case "gpgdir":
|
|
case "hookdir":
|
|
case "logfile":
|
|
case "noconfirm":
|
|
case "confirm":
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func hasParam(arg string) bool {
|
|
switch arg {
|
|
case "dbpath", "b":
|
|
case "root", "r":
|
|
case "sysroot":
|
|
case "config":
|
|
case "ignore":
|
|
case "assume-installed":
|
|
case "overwrite":
|
|
case "ask":
|
|
case "cachedir":
|
|
case "hookdir":
|
|
case "logfile":
|
|
case "ignoregroup":
|
|
case "arch":
|
|
case "print-format":
|
|
case "gpgdir":
|
|
case "color":
|
|
//yay params
|
|
case "aururl":
|
|
case "mflags":
|
|
case "gpgflags":
|
|
case "gitflags":
|
|
case "builddir":
|
|
case "absdir":
|
|
case "editor":
|
|
case "editorflags":
|
|
case "makepkg":
|
|
case "makepkgconf":
|
|
case "pacman":
|
|
case "tar":
|
|
case "git":
|
|
case "gpg":
|
|
case "requestsplitn":
|
|
case "answerclean":
|
|
case "answerdiff":
|
|
case "answeredit":
|
|
case "answerupgrade":
|
|
case "completioninterval":
|
|
case "sortby":
|
|
case "searchby":
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Parses short hand options such as:
|
|
// -Syu -b/some/path -
|
|
func (parser *arguments) parseShortOption(arg string, param string) (usedNext bool, err error) {
|
|
if arg == "-" {
|
|
err = parser.addArg("-")
|
|
return
|
|
}
|
|
|
|
arg = arg[1:]
|
|
|
|
for k, _char := range arg {
|
|
char := string(_char)
|
|
|
|
if hasParam(char) {
|
|
if k < len(arg)-1 {
|
|
err = parser.addParam(char, arg[k+1:])
|
|
} else {
|
|
usedNext = true
|
|
err = parser.addParam(char, param)
|
|
}
|
|
|
|
break
|
|
} else {
|
|
err = parser.addArg(char)
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Parses full length options such as:
|
|
// --sync --refresh --sysupgrade --dbpath /some/path --
|
|
func (parser *arguments) parseLongOption(arg string, param string) (usedNext bool, err error) {
|
|
if arg == "--" {
|
|
err = parser.addArg(arg)
|
|
return
|
|
}
|
|
|
|
arg = arg[2:]
|
|
|
|
switch split := strings.SplitN(arg, "=", 2); {
|
|
case len(split) == 2:
|
|
err = parser.addParam(split[0], split[1])
|
|
case hasParam(arg):
|
|
err = parser.addParam(arg, param)
|
|
usedNext = true
|
|
default:
|
|
err = parser.addArg(arg)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (parser *arguments) parseStdin() error {
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
scanner.Split(bufio.ScanLines)
|
|
|
|
for scanner.Scan() {
|
|
parser.addTarget(scanner.Text())
|
|
}
|
|
|
|
return os.Stdin.Close()
|
|
}
|
|
|
|
func (parser *arguments) parseCommandLine() (err error) {
|
|
args := os.Args[1:]
|
|
usedNext := false
|
|
|
|
if len(args) < 1 {
|
|
_, err = parser.parseShortOption("-Syu", "")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
} else {
|
|
for k, arg := range args {
|
|
var nextArg string
|
|
|
|
if usedNext {
|
|
usedNext = false
|
|
continue
|
|
}
|
|
|
|
if k+1 < len(args) {
|
|
nextArg = args[k+1]
|
|
}
|
|
|
|
switch {
|
|
case parser.existsArg("--"):
|
|
parser.addTarget(arg)
|
|
case strings.HasPrefix(arg, "--"):
|
|
usedNext, err = parser.parseLongOption(arg, nextArg)
|
|
case strings.HasPrefix(arg, "-"):
|
|
usedNext, err = parser.parseShortOption(arg, nextArg)
|
|
default:
|
|
parser.addTarget(arg)
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if parser.op == "" {
|
|
parser.op = "Y"
|
|
}
|
|
|
|
if parser.existsArg("-") {
|
|
var file *os.File
|
|
err = parser.parseStdin()
|
|
parser.delArg("-")
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
file, err = os.Open("/dev/tty")
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
os.Stdin = file
|
|
}
|
|
|
|
cmdArgs.extractYayOptions()
|
|
return
|
|
}
|
|
|
|
func (parser *arguments) extractYayOptions() {
|
|
for option, value := range parser.options {
|
|
if handleConfig(option, value) {
|
|
parser.delArg(option)
|
|
}
|
|
}
|
|
|
|
for option, value := range parser.globals {
|
|
if handleConfig(option, value) {
|
|
parser.delArg(option)
|
|
}
|
|
}
|
|
|
|
rpc.AURURL = strings.TrimRight(config.AURURL, "/") + "/rpc.php?"
|
|
config.AURURL = strings.TrimRight(config.AURURL, "/")
|
|
}
|
|
|
|
// Crude html parsing, good enough for the arch news
|
|
// This is only displayed in the terminal so there should be no security
|
|
// concerns
|
|
func parseNews(str string) string {
|
|
var buffer bytes.Buffer
|
|
var tagBuffer bytes.Buffer
|
|
var escapeBuffer bytes.Buffer
|
|
inTag := false
|
|
inEscape := false
|
|
|
|
for _, char := range str {
|
|
if inTag {
|
|
if char == '>' {
|
|
inTag = false
|
|
switch tagBuffer.String() {
|
|
case "code":
|
|
buffer.WriteString(cyanCode)
|
|
case "/code":
|
|
buffer.WriteString(resetCode)
|
|
case "/p":
|
|
buffer.WriteRune('\n')
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
tagBuffer.WriteRune(char)
|
|
continue
|
|
}
|
|
|
|
if inEscape {
|
|
if char == ';' {
|
|
inEscape = false
|
|
escapeBuffer.WriteRune(char)
|
|
s := html.UnescapeString(escapeBuffer.String())
|
|
buffer.WriteString(s)
|
|
continue
|
|
}
|
|
|
|
escapeBuffer.WriteRune(char)
|
|
continue
|
|
}
|
|
|
|
if char == '<' {
|
|
inTag = true
|
|
tagBuffer.Reset()
|
|
continue
|
|
}
|
|
|
|
if char == '&' {
|
|
inEscape = true
|
|
escapeBuffer.Reset()
|
|
escapeBuffer.WriteRune(char)
|
|
continue
|
|
}
|
|
|
|
buffer.WriteRune(char)
|
|
}
|
|
|
|
buffer.WriteString(resetCode)
|
|
return buffer.String()
|
|
}
|