~reesmichael1/roman

b02f932604e5b3cb96b5663c6d54b50b61903ea7 — Michael Rees 2 months ago 936ef31
Escape fields in subscriptions file

This fixes #20. Previously, feed titles that had a comma in them would
not work, but now, they can be added without issue.
3 files changed, 41 insertions(+), 44 deletions(-)

M roman.nimble
M src/romanpkg/subscriptions.nim
M src/romanpkg/types.nim
M roman.nimble => roman.nimble +1 -0
@@ 12,6 12,7 @@ binDir        = "bin"
# Dependencies

requires "argparse >= 0.9"
requires "csvtools >= 0.2"
requires "fab >= 0.4"
requires "feednim >= 0.2"
requires "nim >= 0.20.0"

M src/romanpkg/subscriptions.nim => src/romanpkg/subscriptions.nim +39 -43
@@ 1,8 1,10 @@
import algorithm
import os
import parsecsv
import sequtils
import strutils

import csvtools

import errors
import feeds
import paths


@@ 16,61 18,55 @@ proc newSubscription*(name, url: string, kind: FeedKind): Subscription {.
  Subscription(name: name, url: url, feedKind: kind)


proc isComment(line: string): bool =
  line.strip().startsWith("#")


proc getSubscriptions*(): seq[Subscription] {.raises: [RomanError].} =
  let subsFilePath = getSubsFilePath()
  if not existsFile(subsFilePath):
    initConfigDir()
    return
  try:
    var p: CsvParser
    p.open(subsFilePath)
    while p.readRow():
      if p.row.len > 0 and p.row[0].len > 0:
        if p.row[0][0] == '#':
          continue
      if p.row.len != 3:
    for row in csvRows(subsFilePath):
      if row[0].isComment(): continue
      if row.len != 3:
        raise newException(RomanError,
          "bad line in subscriptions file: " & $p.row)
      var kind: FeedKind
      case p.row[2]:
        of "rss":
          kind = RSS
        of "atom":
          kind = Atom
        else:
          raise newException(RomanError,
            "unrecognized type field in subscriptions file: " & p.row[2])

      result.add(newSubscription(p.row[0], p.row[1], kind))
          "bad line in subscriptions file: " & $row)

      let kind = parseEnum[FeedKind](row[2])
      result.add(newSubscription(row[0], row[1], kind))

    result.sort(proc(a, b: Subscription): int = cmpIgnoreCase(a.name, b.name))
  except:
    raise newException(RomanError, getCurrentExceptionMsg())


proc subscriptionToLine(sub: Subscription): string {.raises: [RomanError].} =
  var kind: string
  case sub.feedKind:
  of RSS:
    kind = "rss"
  of Atom:
    kind = "atom"
proc getFeedKindString(kind: FeedKind): string {.raises: [RomanError].} =
  case kind
  of Unknown:
    raise newException(RomanError, "unknown feed type")
  return sub.name & "," & sub.url & "," & kind
  else:
    return toLowerAscii(repr(kind))


proc subscriptionToLine(sub: Subscription): string {.raises: [RomanError].} =
  result = connect(@[sub.name, sub.url, getFeedKindString(sub.feedKind)],
    quoteAlways = true)


proc addFullSubscriptionToSubsFile(subscription: Subscription) {.raises: [RomanError].} =
  try:
    let subs = getSubscriptions()
    if subscription in subs:
    let sameURLSubs = subs.filterIt(it.url == subscription.url)
    if sameURLSubs.len > 0:
      raise newException(RomanError,
        "you are already subscribed to " & subscription.url & "!")
    var f: File
    let filename = getSubsFilePath()
    if f.open(filename, fmAppend):
      defer: f.close()
      f.writeLine(subscriptionToLine(subscription))
      f.write(subscriptionToLine(subscription))
  except IOError as e:
    raise newException(RomanError, e.msg)



@@ 83,13 79,20 @@ proc addSubscriptionToSubsFile*(url: string, feedKind: FeedKind) {.


proc subscriptionFromLine(line: string): Subscription {.raises: [RomanError].} =
  let fields = line.split(",")
  result.name = fields[0]
  result.url = fields[1]
  let fields = try:
    line.split(",").mapIt(unescape(it))
  except ValueError:
    raise newException(RomanError,
      "unescaped line in subscriptions file" & line)
  if fields.len != 3:
    raise newException(RomanError,
      "invalid line in subscriptions file: " & line)
  case fields[2]
  of "rss": result.feedKind = FeedKind.RSS
  of "atom": result.feedKind = FeedKind.Atom
  else: raise newException(RomanError, "invalid feed type: " & fields[2])
  result.name = fields[0]
  result.url = fields[1]


proc removeSubscriptionFromSubsFile*(sub: Subscription) {.


@@ 98,20 101,13 @@ proc removeSubscriptionFromSubsFile*(sub: Subscription) {.
    let filename = getSubsFilePath()
    let content = filename.readFile()
    let subsLines = content.splitLines()
    var kind: string
    case sub.feedKind:
    of RSS:
      kind = "rss"
    of Atom:
      kind = "atom"
    of Unknown:
      raise newException(RomanError,
        "trying to save subscription without knowing feed type")

    # TODO: make a backup of the contents to write in the event of an exception
    var f: File
    if f.open(filename, fmWrite):
      defer: f.close()
      for line in subsLines:
        if line.len > 0:
        if not line.isComment() and line.len > 0:
          let s = subscriptionFromLine(line)
          if s != sub:
            f.writeLine(line)

M src/romanpkg/types.nim => src/romanpkg/types.nim +1 -1
@@ 26,7 26,7 @@ type
    author*: Option[string]

  FeedKind* = enum
    RSS, Atom, Unknown
    RSS = "RSS", Atom = "Atom", Unknown

  Feed* = object
    kind*: FeedKind