parser.go 19 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "html"
  7. "os"
  8. "strconv"
  9. "strings"
  10. "unicode"
  11. rpc "github.com/mikkeloscar/aur"
  12. )
  13. // A basic set implementation for strings.
  14. // This is used a lot so it deserves its own type.
  15. // Other types of sets are used throughout the code but do not have
  16. // their own typedef.
  17. // String sets and <type>sets should be used throughout the code when applicable,
  18. // they are a lot more flexible than slices and provide easy lookup.
  19. type stringSet map[string]struct{}
  20. func (set stringSet) set(v string) {
  21. set[v] = struct{}{}
  22. }
  23. func (set stringSet) get(v string) bool {
  24. _, exists := set[v]
  25. return exists
  26. }
  27. func (set stringSet) remove(v string) {
  28. delete(set, v)
  29. }
  30. func (set stringSet) toSlice() []string {
  31. slice := make([]string, 0, len(set))
  32. for v := range set {
  33. slice = append(slice, v)
  34. }
  35. return slice
  36. }
  37. func (set stringSet) copy() stringSet {
  38. newSet := make(stringSet)
  39. for str := range set {
  40. newSet.set(str)
  41. }
  42. return newSet
  43. }
  44. func sliceToStringSet(in []string) stringSet {
  45. set := make(stringSet)
  46. for _, v := range in {
  47. set.set(v)
  48. }
  49. return set
  50. }
  51. func makeStringSet(in ...string) stringSet {
  52. return sliceToStringSet(in)
  53. }
  54. // Parses command line arguments in a way we can interact with programmatically but
  55. // also in a way that can easily be passed to pacman later on.
  56. type arguments struct {
  57. op string
  58. options map[string]string
  59. globals map[string]string
  60. doubles stringSet // Tracks args passed twice such as -yy and -dd
  61. targets []string
  62. }
  63. func makeArguments() *arguments {
  64. return &arguments{
  65. "",
  66. make(map[string]string),
  67. make(map[string]string),
  68. make(stringSet),
  69. make([]string, 0),
  70. }
  71. }
  72. func (parser *arguments) copy() (cp *arguments) {
  73. cp = makeArguments()
  74. cp.op = parser.op
  75. for k, v := range parser.options {
  76. cp.options[k] = v
  77. }
  78. for k, v := range parser.globals {
  79. cp.globals[k] = v
  80. }
  81. cp.targets = make([]string, len(parser.targets))
  82. copy(cp.targets, parser.targets)
  83. for k, v := range parser.doubles {
  84. cp.doubles[k] = v
  85. }
  86. return
  87. }
  88. func (parser *arguments) delArg(options ...string) {
  89. for _, option := range options {
  90. delete(parser.options, option)
  91. delete(parser.globals, option)
  92. delete(parser.doubles, option)
  93. }
  94. }
  95. func (parser *arguments) needRoot() bool {
  96. if parser.existsArg("h", "help") {
  97. return false
  98. }
  99. switch parser.op {
  100. case "D", "database":
  101. if parser.existsArg("k", "check") {
  102. return false
  103. }
  104. return true
  105. case "F", "files":
  106. if parser.existsArg("y", "refresh") {
  107. return true
  108. }
  109. return false
  110. case "Q", "query":
  111. if parser.existsArg("k", "check") {
  112. return true
  113. }
  114. return false
  115. case "R", "remove":
  116. return true
  117. case "S", "sync":
  118. if parser.existsArg("y", "refresh") {
  119. return true
  120. }
  121. if parser.existsArg("p", "print", "print-format") {
  122. return false
  123. }
  124. if parser.existsArg("s", "search") {
  125. return false
  126. }
  127. if parser.existsArg("l", "list") {
  128. return false
  129. }
  130. if parser.existsArg("g", "groups") {
  131. return false
  132. }
  133. if parser.existsArg("i", "info") {
  134. return false
  135. }
  136. if parser.existsArg("c", "clean") && mode == modeAUR {
  137. return false
  138. }
  139. return true
  140. case "U", "upgrade":
  141. return true
  142. default:
  143. return false
  144. }
  145. }
  146. func (parser *arguments) addOP(op string) (err error) {
  147. if parser.op != "" {
  148. err = fmt.Errorf("only one operation may be used at a time")
  149. return
  150. }
  151. parser.op = op
  152. return
  153. }
  154. func (parser *arguments) addParam(option string, arg string) (err error) {
  155. if !isArg(option) {
  156. return fmt.Errorf("invalid option '%s'", option)
  157. }
  158. if isOp(option) {
  159. err = parser.addOP(option)
  160. return
  161. }
  162. if parser.existsArg(option) {
  163. parser.doubles[option] = struct{}{}
  164. } else if isGlobal(option) {
  165. parser.globals[option] = arg
  166. } else {
  167. parser.options[option] = arg
  168. }
  169. return
  170. }
  171. func (parser *arguments) addArg(options ...string) (err error) {
  172. for _, option := range options {
  173. err = parser.addParam(option, "")
  174. if err != nil {
  175. return
  176. }
  177. }
  178. return
  179. }
  180. // Multiple args acts as an OR operator
  181. func (parser *arguments) existsArg(options ...string) bool {
  182. for _, option := range options {
  183. _, exists := parser.options[option]
  184. if exists {
  185. return true
  186. }
  187. _, exists = parser.globals[option]
  188. if exists {
  189. return true
  190. }
  191. }
  192. return false
  193. }
  194. func (parser *arguments) getArg(options ...string) (arg string, double bool, exists bool) {
  195. existCount := 0
  196. for _, option := range options {
  197. var value string
  198. value, exists = parser.options[option]
  199. if exists {
  200. arg = value
  201. existCount++
  202. _, exists = parser.doubles[option]
  203. if exists {
  204. existCount++
  205. }
  206. }
  207. value, exists = parser.globals[option]
  208. if exists {
  209. arg = value
  210. existCount++
  211. _, exists = parser.doubles[option]
  212. if exists {
  213. existCount++
  214. }
  215. }
  216. }
  217. double = existCount >= 2
  218. exists = existCount >= 1
  219. return
  220. }
  221. func (parser *arguments) addTarget(targets ...string) {
  222. parser.targets = append(parser.targets, targets...)
  223. }
  224. func (parser *arguments) clearTargets() {
  225. parser.targets = make([]string, 0)
  226. }
  227. // Multiple args acts as an OR operator
  228. func (parser *arguments) existsDouble(options ...string) bool {
  229. for _, option := range options {
  230. _, exists := parser.doubles[option]
  231. if exists {
  232. return true
  233. }
  234. }
  235. return false
  236. }
  237. func (parser *arguments) formatArgs() (args []string) {
  238. var op string
  239. if parser.op != "" {
  240. op = formatArg(parser.op)
  241. }
  242. args = append(args, op)
  243. for option, arg := range parser.options {
  244. if option == "--" {
  245. continue
  246. }
  247. formattedOption := formatArg(option)
  248. args = append(args, formattedOption)
  249. if hasParam(option) {
  250. args = append(args, arg)
  251. }
  252. if parser.existsDouble(option) {
  253. args = append(args, formattedOption)
  254. }
  255. }
  256. return
  257. }
  258. func (parser *arguments) formatGlobals() (args []string) {
  259. for option, arg := range parser.globals {
  260. formattedOption := formatArg(option)
  261. args = append(args, formattedOption)
  262. if hasParam(option) {
  263. args = append(args, arg)
  264. }
  265. if parser.existsDouble(option) {
  266. args = append(args, formattedOption)
  267. }
  268. }
  269. return
  270. }
  271. func formatArg(arg string) string {
  272. if len(arg) > 1 {
  273. arg = "--" + arg
  274. } else {
  275. arg = "-" + arg
  276. }
  277. return arg
  278. }
  279. func isArg(arg string) bool {
  280. switch arg {
  281. case "-", "--":
  282. case "ask":
  283. case "D", "database":
  284. case "Q", "query":
  285. case "R", "remove":
  286. case "S", "sync":
  287. case "T", "deptest":
  288. case "U", "upgrade":
  289. case "F", "files":
  290. case "V", "version":
  291. case "h", "help":
  292. case "Y", "yay":
  293. case "P", "show":
  294. case "G", "getpkgbuild":
  295. case "b", "dbpath":
  296. case "r", "root":
  297. case "v", "verbose":
  298. case "arch":
  299. case "cachedir":
  300. case "color":
  301. case "config":
  302. case "debug":
  303. case "gpgdir":
  304. case "hookdir":
  305. case "logfile":
  306. case "noconfirm":
  307. case "confirm":
  308. case "disable-download-timeout":
  309. case "sysroot":
  310. case "d", "nodeps":
  311. case "assume-installed":
  312. case "dbonly":
  313. case "noprogressbar":
  314. case "noscriptlet":
  315. case "p", "print":
  316. case "print-format":
  317. case "asdeps":
  318. case "asexplicit":
  319. case "ignore":
  320. case "ignoregroup":
  321. case "needed":
  322. case "overwrite":
  323. case "f", "force":
  324. case "c", "changelog":
  325. case "deps":
  326. case "e", "explicit":
  327. case "g", "groups":
  328. case "i", "info":
  329. case "k", "check":
  330. case "l", "list":
  331. case "m", "foreign":
  332. case "n", "native":
  333. case "o", "owns":
  334. case "file":
  335. case "q", "quiet":
  336. case "s", "search":
  337. case "t", "unrequired":
  338. case "u", "upgrades":
  339. case "cascade":
  340. case "nosave":
  341. case "recursive":
  342. case "unneeded":
  343. case "clean":
  344. case "sysupgrade":
  345. case "w", "downloadonly":
  346. case "y", "refresh":
  347. case "x", "regex":
  348. case "machinereadable":
  349. //yay options
  350. case "aururl":
  351. case "save":
  352. case "afterclean", "cleanafter":
  353. case "noafterclean", "nocleanafter":
  354. case "devel":
  355. case "nodevel":
  356. case "timeupdate":
  357. case "notimeupdate":
  358. case "topdown":
  359. case "bottomup":
  360. case "completioninterval":
  361. case "sortby":
  362. case "redownload":
  363. case "redownloadall":
  364. case "noredownload":
  365. case "rebuild":
  366. case "rebuildall":
  367. case "rebuildtree":
  368. case "norebuild":
  369. case "answerclean":
  370. case "noanswerclean":
  371. case "answerdiff":
  372. case "noanswerdiff":
  373. case "answeredit":
  374. case "noansweredit":
  375. case "answerupgrade":
  376. case "noanswerupgrade":
  377. case "gitclone":
  378. case "nogitclone":
  379. case "gpgflags":
  380. case "mflags":
  381. case "gitflags":
  382. case "builddir":
  383. case "editor":
  384. case "editorflags":
  385. case "makepkg":
  386. case "makepkgconf":
  387. case "nomakepkgconf":
  388. case "pacman":
  389. case "tar":
  390. case "git":
  391. case "gpg":
  392. case "requestsplitn":
  393. case "sudoloop":
  394. case "nosudoloop":
  395. case "provides":
  396. case "noprovides":
  397. case "pgpfetch":
  398. case "nopgpfetch":
  399. case "upgrademenu":
  400. case "noupgrademenu":
  401. case "cleanmenu":
  402. case "nocleanmenu":
  403. case "diffmenu":
  404. case "nodiffmenu":
  405. case "editmenu":
  406. case "noeditmenu":
  407. case "useask":
  408. case "nouseask":
  409. case "combinedupgrade":
  410. case "nocombinedupgrade":
  411. case "a", "aur":
  412. case "repo":
  413. case "removemake":
  414. case "noremovemake":
  415. case "askremovemake":
  416. case "complete":
  417. case "stats":
  418. case "news":
  419. case "gendb":
  420. case "currentconfig":
  421. default:
  422. return false
  423. }
  424. return true
  425. }
  426. func handleConfig(option, value string) bool {
  427. switch option {
  428. case "aururl":
  429. config.AURURL = value
  430. case "save":
  431. shouldSaveConfig = true
  432. case "afterclean", "cleanafter":
  433. config.CleanAfter = true
  434. case "noafterclean", "nocleanafter":
  435. config.CleanAfter = false
  436. case "devel":
  437. config.Devel = true
  438. case "nodevel":
  439. config.Devel = false
  440. case "timeupdate":
  441. config.TimeUpdate = true
  442. case "notimeupdate":
  443. config.TimeUpdate = false
  444. case "topdown":
  445. config.SortMode = topDown
  446. case "bottomup":
  447. config.SortMode = bottomUp
  448. case "completioninterval":
  449. n, err := strconv.Atoi(value)
  450. if err == nil {
  451. config.CompletionInterval = n
  452. }
  453. case "sortby":
  454. config.SortBy = value
  455. case "noconfirm":
  456. config.NoConfirm = true
  457. case "config":
  458. config.PacmanConf = value
  459. case "redownload":
  460. config.ReDownload = "yes"
  461. case "redownloadall":
  462. config.ReDownload = "all"
  463. case "noredownload":
  464. config.ReDownload = "no"
  465. case "rebuild":
  466. config.ReBuild = "yes"
  467. case "rebuildall":
  468. config.ReBuild = "all"
  469. case "rebuildtree":
  470. config.ReBuild = "tree"
  471. case "norebuild":
  472. config.ReBuild = "no"
  473. case "answerclean":
  474. config.AnswerClean = value
  475. case "noanswerclean":
  476. config.AnswerClean = ""
  477. case "answerdiff":
  478. config.AnswerDiff = value
  479. case "noanswerdiff":
  480. config.AnswerDiff = ""
  481. case "answeredit":
  482. config.AnswerEdit = value
  483. case "noansweredit":
  484. config.AnswerEdit = ""
  485. case "answerupgrade":
  486. config.AnswerUpgrade = value
  487. case "noanswerupgrade":
  488. config.AnswerUpgrade = ""
  489. case "gitclone":
  490. config.GitClone = true
  491. case "nogitclone":
  492. config.GitClone = false
  493. case "gpgflags":
  494. config.GpgFlags = value
  495. case "mflags":
  496. config.MFlags = value
  497. case "gitflags":
  498. config.GitFlags = value
  499. case "builddir":
  500. config.BuildDir = value
  501. case "editor":
  502. config.Editor = value
  503. case "editorflags":
  504. config.EditorFlags = value
  505. case "makepkg":
  506. config.MakepkgBin = value
  507. case "makepkgconf":
  508. config.MakepkgConf = value
  509. case "nomakepkgconf":
  510. config.MakepkgConf = ""
  511. case "pacman":
  512. config.PacmanBin = value
  513. case "tar":
  514. config.TarBin = value
  515. case "git":
  516. config.GitBin = value
  517. case "gpg":
  518. config.GpgBin = value
  519. case "requestsplitn":
  520. n, err := strconv.Atoi(value)
  521. if err == nil && n > 0 {
  522. config.RequestSplitN = n
  523. }
  524. case "sudoloop":
  525. config.SudoLoop = true
  526. case "nosudoloop":
  527. config.SudoLoop = false
  528. case "provides":
  529. config.Provides = true
  530. case "noprovides":
  531. config.Provides = false
  532. case "pgpfetch":
  533. config.PGPFetch = true
  534. case "nopgpfetch":
  535. config.PGPFetch = false
  536. case "upgrademenu":
  537. config.UpgradeMenu = true
  538. case "noupgrademenu":
  539. config.UpgradeMenu = false
  540. case "cleanmenu":
  541. config.CleanMenu = true
  542. case "nocleanmenu":
  543. config.CleanMenu = false
  544. case "diffmenu":
  545. config.DiffMenu = true
  546. case "nodiffmenu":
  547. config.DiffMenu = false
  548. case "editmenu":
  549. config.EditMenu = true
  550. case "noeditmenu":
  551. config.EditMenu = false
  552. case "useask":
  553. config.UseAsk = true
  554. case "nouseask":
  555. config.UseAsk = false
  556. case "combinedupgrade":
  557. config.CombinedUpgrade = true
  558. case "nocombinedupgrade":
  559. config.CombinedUpgrade = false
  560. case "a", "aur":
  561. mode = modeAUR
  562. case "repo":
  563. mode = modeRepo
  564. case "removemake":
  565. config.RemoveMake = "yes"
  566. case "noremovemake":
  567. config.RemoveMake = "no"
  568. case "askremovemake":
  569. config.RemoveMake = "ask"
  570. default:
  571. return false
  572. }
  573. return true
  574. }
  575. func isOp(op string) bool {
  576. switch op {
  577. case "V", "version":
  578. case "D", "database":
  579. case "F", "files":
  580. case "Q", "query":
  581. case "R", "remove":
  582. case "S", "sync":
  583. case "T", "deptest":
  584. case "U", "upgrade":
  585. // yay specific
  586. case "Y", "yay":
  587. case "P", "show":
  588. case "G", "getpkgbuild":
  589. default:
  590. return false
  591. }
  592. return true
  593. }
  594. func isGlobal(op string) bool {
  595. switch op {
  596. case "b", "dbpath":
  597. case "r", "root":
  598. case "v", "verbose":
  599. case "arch":
  600. case "cachedir":
  601. case "color":
  602. case "config":
  603. case "debug":
  604. case "gpgdir":
  605. case "hookdir":
  606. case "logfile":
  607. case "noconfirm":
  608. case "confirm":
  609. default:
  610. return false
  611. }
  612. return true
  613. }
  614. func hasParam(arg string) bool {
  615. switch arg {
  616. case "dbpath", "b":
  617. case "root", "r":
  618. case "sysroot":
  619. case "config":
  620. case "ignore":
  621. case "assume-installed":
  622. case "overwrite":
  623. case "ask":
  624. case "cachedir":
  625. case "hookdir":
  626. case "logfile":
  627. case "ignoregroup":
  628. case "arch":
  629. case "print-format":
  630. case "gpgdir":
  631. case "color":
  632. //yay params
  633. case "aururl":
  634. case "mflags":
  635. case "gpgflags":
  636. case "gitflags":
  637. case "builddir":
  638. case "editor":
  639. case "editorflags":
  640. case "makepkg":
  641. case "makepkgconf":
  642. case "pacman":
  643. case "tar":
  644. case "git":
  645. case "gpg":
  646. case "requestsplitn":
  647. case "answerclean":
  648. case "answerdiff":
  649. case "answeredit":
  650. case "answerupgrade":
  651. case "completioninterval":
  652. case "sortby":
  653. default:
  654. return false
  655. }
  656. return true
  657. }
  658. // Parses short hand options such as:
  659. // -Syu -b/some/path -
  660. func (parser *arguments) parseShortOption(arg string, param string) (usedNext bool, err error) {
  661. if arg == "-" {
  662. err = parser.addArg("-")
  663. return
  664. }
  665. arg = arg[1:]
  666. for k, _char := range arg {
  667. char := string(_char)
  668. if hasParam(char) {
  669. if k < len(arg)-1 {
  670. err = parser.addParam(char, arg[k+1:])
  671. } else {
  672. usedNext = true
  673. err = parser.addParam(char, param)
  674. }
  675. break
  676. } else {
  677. err = parser.addArg(char)
  678. if err != nil {
  679. return
  680. }
  681. }
  682. }
  683. return
  684. }
  685. // Parses full length options such as:
  686. // --sync --refresh --sysupgrade --dbpath /some/path --
  687. func (parser *arguments) parseLongOption(arg string, param string) (usedNext bool, err error) {
  688. if arg == "--" {
  689. err = parser.addArg(arg)
  690. return
  691. }
  692. arg = arg[2:]
  693. split := strings.SplitN(arg, "=", 2)
  694. if len(split) == 2 {
  695. err = parser.addParam(split[0], split[1])
  696. } else if hasParam(arg) {
  697. err = parser.addParam(arg, param)
  698. usedNext = true
  699. } else {
  700. err = parser.addArg(arg)
  701. }
  702. return
  703. }
  704. func (parser *arguments) parseStdin() error {
  705. scanner := bufio.NewScanner(os.Stdin)
  706. scanner.Split(bufio.ScanLines)
  707. for scanner.Scan() {
  708. parser.addTarget(scanner.Text())
  709. }
  710. return os.Stdin.Close()
  711. }
  712. func (parser *arguments) parseCommandLine() (err error) {
  713. args := os.Args[1:]
  714. usedNext := false
  715. if len(args) < 1 {
  716. parser.parseShortOption("-Syu", "")
  717. } else {
  718. for k, arg := range args {
  719. var nextArg string
  720. if usedNext {
  721. usedNext = false
  722. continue
  723. }
  724. if k+1 < len(args) {
  725. nextArg = args[k+1]
  726. }
  727. if parser.existsArg("--") {
  728. parser.addTarget(arg)
  729. } else if strings.HasPrefix(arg, "--") {
  730. usedNext, err = parser.parseLongOption(arg, nextArg)
  731. } else if strings.HasPrefix(arg, "-") {
  732. usedNext, err = parser.parseShortOption(arg, nextArg)
  733. } else {
  734. parser.addTarget(arg)
  735. }
  736. if err != nil {
  737. return
  738. }
  739. }
  740. }
  741. if parser.op == "" {
  742. parser.op = "Y"
  743. }
  744. if parser.existsArg("-") {
  745. var file *os.File
  746. err = parser.parseStdin()
  747. parser.delArg("-")
  748. if err != nil {
  749. return
  750. }
  751. file, err = os.Open("/dev/tty")
  752. if err != nil {
  753. return
  754. }
  755. os.Stdin = file
  756. }
  757. cmdArgs.extractYayOptions()
  758. return
  759. }
  760. func (parser *arguments) extractYayOptions() {
  761. for option, value := range parser.options {
  762. if handleConfig(option, value) {
  763. parser.delArg(option)
  764. }
  765. }
  766. for option, value := range parser.globals {
  767. if handleConfig(option, value) {
  768. parser.delArg(option)
  769. }
  770. }
  771. rpc.AURURL = strings.TrimRight(config.AURURL, "/") + "/rpc.php?"
  772. config.AURURL = strings.TrimRight(config.AURURL, "/")
  773. }
  774. //parses input for number menus split by spaces or commas
  775. //supports individual selection: 1 2 3 4
  776. //supports range selections: 1-4 10-20
  777. //supports negation: ^1 ^1-4
  778. //
  779. //include and excule holds numbers that should be added and should not be added
  780. //respectively. other holds anything that can't be parsed as an int. This is
  781. //intended to allow words inside of number menus. e.g. 'all' 'none' 'abort'
  782. //of course the implementation is up to the caller, this function mearley parses
  783. //the input and organizes it
  784. func parseNumberMenu(input string) (intRanges, intRanges, stringSet, stringSet) {
  785. include := make(intRanges, 0)
  786. exclude := make(intRanges, 0)
  787. otherInclude := make(stringSet)
  788. otherExclude := make(stringSet)
  789. words := strings.FieldsFunc(input, func(c rune) bool {
  790. return unicode.IsSpace(c) || c == ','
  791. })
  792. for _, word := range words {
  793. var num1 int
  794. var num2 int
  795. var err error
  796. invert := false
  797. other := otherInclude
  798. if word[0] == '^' {
  799. invert = true
  800. other = otherExclude
  801. word = word[1:]
  802. }
  803. ranges := strings.SplitN(word, "-", 2)
  804. num1, err = strconv.Atoi(ranges[0])
  805. if err != nil {
  806. other.set(strings.ToLower(word))
  807. continue
  808. }
  809. if len(ranges) == 2 {
  810. num2, err = strconv.Atoi(ranges[1])
  811. if err != nil {
  812. other.set(strings.ToLower(word))
  813. continue
  814. }
  815. } else {
  816. num2 = num1
  817. }
  818. mi := min(num1, num2)
  819. ma := max(num1, num2)
  820. if !invert {
  821. include = append(include, makeIntRange(mi, ma))
  822. } else {
  823. exclude = append(exclude, makeIntRange(mi, ma))
  824. }
  825. }
  826. return include, exclude, otherInclude, otherExclude
  827. }
  828. // Crude html parsing, good enough for the arch news
  829. // This is only displayed in the terminal so there should be no security
  830. // concerns
  831. func parseNews(str string) string {
  832. var buffer bytes.Buffer
  833. var tagBuffer bytes.Buffer
  834. var escapeBuffer bytes.Buffer
  835. inTag := false
  836. inEscape := false
  837. for _, char := range str {
  838. if inTag {
  839. if char == '>' {
  840. inTag = false
  841. switch tagBuffer.String() {
  842. case "code":
  843. buffer.WriteString(cyanCode)
  844. case "/code":
  845. buffer.WriteString(resetCode)
  846. case "/p":
  847. buffer.WriteRune('\n')
  848. }
  849. continue
  850. }
  851. tagBuffer.WriteRune(char)
  852. continue
  853. }
  854. if inEscape {
  855. if char == ';' {
  856. inEscape = false
  857. escapeBuffer.WriteRune(char)
  858. s := html.UnescapeString(escapeBuffer.String())
  859. buffer.WriteString(s)
  860. continue
  861. }
  862. escapeBuffer.WriteRune(char)
  863. continue
  864. }
  865. if char == '<' {
  866. inTag = true
  867. tagBuffer.Reset()
  868. continue
  869. }
  870. if char == '&' {
  871. inEscape = true
  872. escapeBuffer.Reset()
  873. escapeBuffer.WriteRune(char)
  874. continue
  875. }
  876. buffer.WriteRune(char)
  877. }
  878. buffer.WriteString(resetCode)
  879. return buffer.String()
  880. }