import std/strutils import utils/twtstr type BracketState = enum bsNone, bsInBracket proc getId(line: openArray[char]): string = result = "" var i = 0 var bs = bsNone while i < line.len: case (let c = line[i]; c) of AsciiAlphaNumeric, '-', '_', '.': result &= c.toLowerAscii() of ' ': result &= '-' of '[': bs = bsInBracket of ']': if bs == bsInBracket: if i + 1 < line.len and line[i + 1] == '(': inc i while i < line.len: let c = line[i] if c == '\\': inc i elif c == ')': break inc i bs = bsNone else: discard inc i type InlineFlag = enum ifItalic, ifBold, ifDel func startsWithScheme(s: string): bool = for i, c in s: if i > 0 and c == ':': return true if c notin AsciiAlphaNumeric: break false type ParseInlineContext = object i: int bracketChars: string bs: BracketState bracketRef: bool flags: set[InlineFlag] proc parseInTag(ctx: var ParseInlineContext; line: openArray[char]) = var buf = "" var i = ctx.i + 1 while i < line.len: let c = line[i] if c == '>': # done if buf.startsWithScheme(): # link stdout.write("" & buf & "") else: # tag stdout.write('<' & buf & '>') buf = "" break elif c == '<': stdout.write('<' & buf) buf = "" dec i break else: buf &= c inc i stdout.write(buf) ctx.i = i proc append(ctx: var ParseInlineContext; s: string) = if ctx.bs == bsInBracket: ctx.bracketChars &= s else: stdout.write(s) proc append(ctx: var ParseInlineContext; c: char) = if ctx.bs == bsInBracket: ctx.bracketChars &= c else: stdout.write(c) type CommentState = enum csNone, csDash, csDashDash proc parseComment(ctx: var ParseInlineContext; line: openArray[char]) = var i = ctx.i var cs = csNone var buf = "" while i < line.len: let c = line[i] if cs in {csNone, csDash} and c == '-': inc cs elif cs == csDashDash and c == '>': buf &= '>' break else: cs = csNone buf &= c inc i ctx.append(buf) ctx.i = i proc parseCode(ctx: var ParseInlineContext; line: openArray[char]) = let i = ctx.i + 1 let j = line.toOpenArray(i, line.high).find('`') if j != -1: ctx.append("") ctx.append(line.toOpenArray(i, i + j - 1).htmlEscape()) ctx.append("") ctx.i = i + j else: ctx.append('`') proc parseLinkDestination(url: var string; line: openArray[char]; i: int): int = var i = i var quote = false var parens = 0 let sc = line[i] if sc == '<': inc i while i < line.len: let c = line[i] if quote: quote = false elif sc == '<' and c == '>' or sc != '<' and c in AsciiWhitespace + {')'}: break elif c in {'<', '\n'} or c in Controls and sc != '<': return -1 elif c == '\\': quote = true elif c == '(': inc parens url &= c elif c == ')' and sc != '>': if parens == 0: break dec parens url &= c else: url &= c inc i if sc != '>' and parens != 0 or quote: return -1 return line.skipBlanks(i) proc parseTitle(title: var string; line: openArray[char]; i: int): int = let ec = line[i] var i = i + 1 var quote = false while i < line.len: let c = line[i] if quote: quote = false elif c == '\\': quote = true elif c == ec: inc i break else: title &= c inc i return line.skipBlanks(i) proc parseLink(ctx: var ParseInlineContext; line: openArray[char]) = let i = ctx.i + 1 if i >= line.len or line[i] != '(': #TODO reference links stdout.write('[' & ctx.bracketChars & ']') return var url = "" var j = url.parseLinkDestination(line, line.skipBlanks(i + 1)) var title = "" if j != -1 and j < line.len and line[j] in {'(', '"', '\''}: j = title.parseTitle(line, j) if j == -1 or j >= line.len or line[j] != ')': stdout.write('[' & ctx.bracketChars & ']') else: let url = url.htmlEscape() stdout.write("") stdout.write(ctx.bracketChars) stdout.write("") ctx.i = j proc parseImageAlt(text: var string; line: openArray[char]; i: int): int = var i = i var brackets = 0 while i < line.len: let c = line[i] if c == '\\': inc i elif c == '<': while i < line.len and line[i] != '>': text &= c inc i elif c == '[': inc brackets text &= c elif line[i] == ']': if brackets == 0: break dec brackets text &= c else: text &= c inc i return i proc parseImage(ctx: var ParseInlineContext; line: openArray[char]) = var text = "" let i = text.parseImageAlt(line, ctx.i + 2) if i == -1 or i + 1 >= line.len or line[i] != ']' or line[i + 1] != '(': ctx.append("![") return var url = "" var j = url.parseLinkDestination(line, line.skipBlanks(i + 2)) var title = "" if j != -1 and j < line.len and line[j] in {'(', '"', '\''}: j = title.parseTitle(line, j) if j == -1 or j >= line.len or line[j] != ')': ctx.append("![") else: ctx.append("" & text.htmlEscape())
    ctx.append("") ctx.i = j proc appendToggle(ctx: var ParseInlineContext; f: InlineFlag; s, e: string) = if f notin ctx.flags: ctx.flags.incl(f) ctx.append(s) else: ctx.flags.excl(f) ctx.append(e) proc parseInline(line: openArray[char]) = var ctx = ParseInlineContext() while ctx.i < line.len: let c = line[ctx.i] if c == '\\': inc ctx.i if ctx.i < line.len: ctx.append(line[ctx.i]) elif (ctx.i > 0 and line[ctx.i - 1] notin AsciiWhitespace or ctx.i + 1 < line.len and line[ctx.i + 1] notin AsciiWhitespace) and (c == '*' or c == '_' and (ctx.i == 0 or line[ctx.i - 1] notin AsciiAlphaNumeric or ctx.i + 1 >= line.len or line[ctx.i + 1] notin AsciiAlphaNumeric + {'_'})): if ctx.i + 1 < line.len and line[ctx.i + 1] == c: ctx.appendToggle(ifBold, "", "") inc ctx.i else: ctx.appendToggle(ifItalic, "", "") elif c == '`': ctx.parseCode(line) elif c == '~' and ctx.i + 1 < line.len and line[ctx.i + 1] == '~': ctx.appendToggle(ifDel, "", "") inc ctx.i elif c == '!' and ctx.i + 1 < line.len and line[ctx.i + 1] == '[': ctx.parseImage(line) elif c == '[': if ctx.bs == bsInBracket: stdout.write('[' & ctx.bracketChars) ctx.bracketChars = "" ctx.bs = bsInBracket ctx.bracketRef = ctx.i + 1 < line.len and line[ctx.i + 1] == '^' if ctx.bracketRef: inc ctx.i elif c == ']' and ctx.bs == bsInBracket: if ctx.bracketRef: let id = ctx.bracketChars.getId() stdout.write("" & ctx.bracketChars & "") else: ctx.parseLink(line) ctx.bracketChars = "" ctx.bracketRef = false ctx.bs = bsNone elif c == '<': ctx.parseInTag(line) elif ctx.i + 4 < line.len and line.toOpenArray(ctx.i, ctx.i + 3) == "") if i != -1: stdout.write(line.substr(0, i + 2)) state.blockType = btNone line.toOpenArray(i + 3, line.high).parseInline() else: stdout.write(line & '\n') proc main() = var line: string var state = ParseState(listDepth: -1) while state.reprocess or stdin.readLine(line): state.reprocess = false case state.blockType of btNone: state.parseNone(line) of btPre: state.parsePre(line) of btTabPre: state.parseTabPre(line) of btSpacePre: state.parseSpacePre(line) of btBlockquote: state.parseBlockquote(line) of btList: state.parseList(line) of btPar: state.parsePar(line) of btHTML: state.parseHTML(line) of btHTMLPre: state.parseHTMLPre(line) of btComment: state.parseComment(line) state.blockData.parseInline() main()