diff --git a/quotearg.go b/quotearg.go new file mode 100644 index 0000000..83e7336 --- /dev/null +++ b/quotearg.go @@ -0,0 +1,384 @@ +package quotearg + +import ( + "strconv" + "unicode/utf8" +) + +const ( + QAElideNullBytes = 0x01 + QAElideOuterQuotes = 0x02 + QASplitTrigraphs = 0x04 +) + +const SizeMax = ^uint(0) + +type QuotingOptions struct { + Flags int + QuoteTheseToo uint + LeftQuote rune + RightQuote rune + Style QuotingStyle +} + +type QuotingStyle int + +const ( + LiteralQuotingStyle QuotingStyle = iota + ShellQuotingStyle + ShellAlwaysQuotingStyle + ShellEscapeQuotingStyle + ShellEscapeAlwaysQuotingStyle + CQuotingStyle + CMaybeQuotingStyle + EscapeQuotingStyle + LocaleQuotingStyle + CLocaleQuotingStyle + CustomQuotingStyle +) + +var QuotingStyleArgs = []string{ + "literal", + "shell", + "shell-always", + "shell-escape", + "shell-escape-always", + "c", + "c-maybe", + "escape", + "locale", + "clocale", + "", +} + +func GetTextQuote(s QuotingStyle) rune { + if s == CLocaleQuotingStyle { + return '\'' + } else { + return '"' + } +} + +func QuoteargBufferRestyled( + buffer []rune, + arg []rune, + style QuotingStyle, + flags int, + quoteTheseToo uint, + leftQuote rune, + rightQuote rune, +) { + var pos int + var elideOuterQuotes bool = (flags & QAElideOuterQuotes) != 0 + var pendingShellEscapeEnd bool + var escaping bool + var quoteRune rune + var backslashEscapes bool + var allCAndShellQuoteCompat bool = true + + store := func(c rune) { + buffer[pos] = c + pos++ + } + + startESC := func() { + if elideOuterQuotes { + goto ForceOuterQuotingStyle + } + escaping = true + if style == ShellAlwaysQuotingStyle && !pendingShellEscapeEnd { + store('\'') + store('$') + store('\'') + pendingShellEscapeEnd = true + } + store('\\') + } + + endESC := func() { + if pendingShellEscapeEnd && !escaping { + store('\'') + store('\'') + pendingShellEscapeEnd = false + } + } + + switch style { + case CMaybeQuotingStyle: + style = CQuotingStyle + elideOuterQuotes = true + fallthrough + case CQuotingStyle: + if !elideOuterQuotes { + store('"') + } + backslashEscapes = true + quoteRune = '"' + break + case EscapeQuotingStyle: + backslashEscapes = true + elideOuterQuotes = false + break + case LocaleQuotingStyle: + fallthrough + case CLocaleQuotingStyle: + fallthrough + case CustomQuotingStyle: + if style != CustomQuotingStyle { + leftQuote = GetTextQuote(style) + rightQuote = GetTextQuote(style) + } + if !elideOuterQuotes { + // todo figure out the voodoo + } + backslashEscapes = true + quoteRune = rightQuote + break + case ShellEscapeQuotingStyle: + backslashEscapes = true + fallthrough + case ShellQuotingStyle: + elideOuterQuotes = true + fallthrough + case ShellEscapeAlwaysQuotingStyle: + if !elideOuterQuotes { + backslashEscapes = true + } + fallthrough + case ShellAlwaysQuotingStyle: + style = ShellAlwaysQuotingStyle + if !elideOuterQuotes { + store('\'') + } + quoteRune = '\'' + break + default: + panic("invalid style") + } + + for i := 0; i < len(arg); i++ { + var esc rune + var isRightQuote bool + var cAndShellQuoteCompat bool + + quoteIsNext := arg[i+1] == quoteRune + + if backslashEscapes && + style != ShellAlwaysQuotingStyle && + quoteIsNext { + if elideOuterQuotes { + goto ForceOuterQuotingStyle + } + isRightQuote = true + } + + c := arg[i] + + switch c { + case 0x00: + if backslashEscapes { + startESC() + if style != ShellQuotingStyle && + i+1 < len(arg) && + '0' <= arg[i+1] && + arg[i+1] <= '9' { + store('0') + store('0') + } + c = '0' + } else if flags&QAElideNullBytes != 0 { + continue + } + break + case '?': + switch style { + case ShellAlwaysQuotingStyle: + if elideOuterQuotes { + goto ForceOuterQuotingStyle + } + break + case CQuotingStyle: + if flags&QASplitTrigraphs != 0 && + i+2 < len(arg) && + arg[i+1] == '?' { + switch arg[i+2] { + case '!', '\'', '(', ')', '-', '/', '<', '=', '>': + if elideOuterQuotes { + goto ForceOuterQuotingStyle + } + + c = arg[i+2] + i += 2 + store('?') + store('"') + store('"') + store('?') + break + default: + break + } + } + break + default: + break + } + break + case '\a': + esc = 'a' + goto CEscape + case '\b': + esc = 'b' + goto CEscape + case '\f': + esc = 'f' + goto CEscape + case '\n': + esc = 'n' + goto CAndShellEscape + case '\r': + esc = 'r' + goto CAndShellEscape + case '\t': + esc = 't' + goto CAndShellEscape + case '\v': + esc = 'v' + goto CEscape + case '\\': + esc = c + if style == ShellAlwaysQuotingStyle { + if elideOuterQuotes { + goto ForceOuterQuotingStyle + } + goto StoreC + } + + if backslashEscapes && elideOuterQuotes && quoteRune > 0 { + goto StoreC + } + + CAndShellEscape: + if style == ShellAlwaysQuotingStyle && elideOuterQuotes { + goto ForceOuterQuotingStyle + } + + CEscape: + if backslashEscapes { + c = esc + goto StoreEscape + } + break + + case '{', '}': + if len(arg) != 1 { + break + } + fallthrough + case '#', '~': + if i != 0 { + break + } + fallthrough + case ' ': + cAndShellQuoteCompat = true + fallthrough + case '!', '"', '$', '&', '(', ')', '*', ';', '<', '=', '>', '[': + fallthrough + case '^', '`', '|': + if style == ShellAlwaysQuotingStyle && elideOuterQuotes { + goto ForceOuterQuotingStyle + } + break + case '\'': + encounteredSingleQuote = true + cAndShellQuoteCompat = true + if style == ShellAlwaysQuotingStyle { + if elideOuterQuotes { + goto ForceOuterQuotingStyle + } + // buffersize voodoo + + store('\'') + store('\\') + store('\'') + pendingShellEscapeEnd = false + } + break + case '%', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5': + fallthrough + case '6', '7', '8', '9', ':', 'A', 'B', 'C', 'D', 'E', 'F': + fallthrough + case 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R': + fallthrough + case 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ']', '_', 'a', 'b': + fallthrough + case 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n': + fallthrough + case 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z': + cAndShellQuoteCompat = true + break + default: + printable := strconv.IsPrint(c) + cAndShellQuoteCompat = printable + if backslashEscapes && !printable { + startESC() + store('x') + switch { + case c < ' ': + store(byte(c) >> 4) + store(byte(c) & 0xF) + case c > utf8.MaxRune: + c = 0xFFFD + fallthrough + case c < 0x10000: + for s := 12; s >= 0; s -= 4 { + store(c >> uint(s) & 0xF) + } + default: + for s := 28; s >= 0; s -= 4 { + store(c >> uint(s) & 0xF) + } + } + goto StoreC + } else if isRightQuote { + store('\\') + isRightQuote = false + } + goto StoreC + } + } + + if !(((backslashEscapes && style != ShellAlwaysQuotingStyle) || + elideOuterQuotes) && !isRightQuote) { + goto StoreC + } + +StoreEscape: + startESC() + +StoreC: + endESC() + store(c) + + if !cAndShellQuoteCompat { + allCAndShellQuoteCompat = false + } + + if len == 0 && style == ShellAlwaysQuotingStyle && elideOuterQuotes { + goto ForceOuterQuotingStyle + } + +ForceOuterQuotingStyle: + if style == ShellAlwaysQuotingStyle && backslashEscapes { + style = ShellEscapeAlwaysQuotingStyle + } + QuoteargBufferRestyled( + buffer, + arg, + style, + flags & ^QAElideOuterQuotes, + 0, + leftQuote, + rightQuote, + ) +}