parser.go 18 KB

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