From f45b14403744f99ae265cff2e05e98e74836e501 Mon Sep 17 00:00:00 2001 From: John Conway Date: Wed, 8 May 2019 10:37:10 +0100 Subject: Changed tothe Nimble package structure --- .DS_Store | Bin 6148 -> 6148 bytes .gitignore | 53 +++++++++++++ FeedNim.nimble | 12 +++ feed_nim.nim | 33 -------- modules/atom.nim | 187 ------------------------------------------- modules/jsonfeed.nim | 133 ------------------------------- modules/rss.nim | 198 ---------------------------------------------- src/feednim.nim | 7 ++ src/feednim/atom.nim | 187 +++++++++++++++++++++++++++++++++++++++++++ src/feednim/jsonfeed.nim | 133 +++++++++++++++++++++++++++++++ src/feednim/rss.nim | 198 ++++++++++++++++++++++++++++++++++++++++++++++ src/feednim/submodule.nim | 12 +++ tests/config.nims | 1 + tests/test1.nim | 12 +++ 14 files changed, 615 insertions(+), 551 deletions(-) create mode 100644 .gitignore create mode 100644 FeedNim.nimble delete mode 100644 feed_nim.nim delete mode 100644 modules/atom.nim delete mode 100644 modules/jsonfeed.nim delete mode 100644 modules/rss.nim create mode 100644 src/feednim.nim create mode 100644 src/feednim/atom.nim create mode 100644 src/feednim/jsonfeed.nim create mode 100644 src/feednim/rss.nim create mode 100644 src/feednim/submodule.nim create mode 100644 tests/config.nims create mode 100644 tests/test1.nim diff --git a/.DS_Store b/.DS_Store index 5008ddf..1e8c07c 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23f585f --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# ---> Kate +# Swap Files # +.*.kate-swp +.swp.* + +# ---> Nim +nimcache/ + +# ---> Vim +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ \ No newline at end of file diff --git a/FeedNim.nimble b/FeedNim.nimble new file mode 100644 index 0000000..e17914f --- /dev/null +++ b/FeedNim.nimble @@ -0,0 +1,12 @@ +# Package + +version = "0.1.0" +author = "John Conway" +description = "An Atom, RSS, and JSONfeed parser" +license = "MIT" +srcDir = "src" + + +# Dependencies + +requires "nim >= 0.19.4" diff --git a/feed_nim.nim b/feed_nim.nim deleted file mode 100644 index 2945fdf..0000000 --- a/feed_nim.nim +++ /dev/null @@ -1,33 +0,0 @@ -import httpclient - -import modules/atom -import modules/rss -import modules/jsonfeed - -proc loadAtom*(filename: string): Atom = ## Loads the Atom from the given ``filename``. - var Atom: string = readFile(filename) # Load the data from the file. - return parseAtom(Atom) - - -proc getAtom*(url: string): Atom = ## Gets the Atom over from the specified ``url``. - var Atom: string = newHttpClient().getContent(url) # Get the data. - return parseAtom(Atom) - - -proc loadRSS*(filename: string): Rss = ## Loads the RSS from the given ``filename``. - var rss: string = readFile(filename) # Load the data from the file. - return parseRSS(rss) - - -proc getRSS*(url: string): Rss = ## Gets the RSS over from the specified ``url``. - var rss: string = newHttpClient().getContent(url) # Get the data. - return parseRSS(rss) - -proc loadJsonFeed*(filename: string): JsonFeed = ## Loads the JSONFeed from the given ``filename``. - var jsonFeed: string = readFile(filename) # Load the data from the file. - return parseJSONFeed(jsonFeed) - - -proc getJsonFeed*(url: string): JsonFeed = ## Gets the JSONFeed over from the specified ``url``. - var jsonFeed: string = newHttpClient().getContent(url) # Get the data. - return parseJSONFeed(jsonFeed) \ No newline at end of file diff --git a/modules/atom.nim b/modules/atom.nim deleted file mode 100644 index cce9c44..0000000 --- a/modules/atom.nim +++ /dev/null @@ -1,187 +0,0 @@ -# Nim Atom Syndication Format module - -# Written by John Conway -# Released under the MIT open source license. - -import httpclient -import strutils -import sequtils -import xmlparser -import xmltree -import streams -import sugar - -type - Atom* = object - author: AtomAuthor # Sugar, not in Atom spec. Returns the first author. - id*: string # Required Atom field - title*: string # Required Atom field - updated*: string # Required Atom field - authors*: seq[AtomAuthor] # Pleuralised because the Atom spec allows more than one - category*: seq[string] - generator*: string - icon*: string - link*: AtomLink - logo*: string - rights*: string - subtitle*: string - entrys*: seq[AtomEntry] - - AtomAuthor* = object - name*: string # Required Atom field - url*: string - email*: string - - AtomImage* = object - url*: string - title*: string - link*: string - width*: string - height*: string - description*: string - - AtomLink* = object - href*: string - rel*: string - linktype*: string - hreflang*: string - title*: string - length*: string - - AtomEntry* = object - id*: string # Required Atom field - title*: string # Required Atom field - updated*: string # Required Atom field - author: AtomAuthor # Sugar, not in Atom spec. Returns the first author. - authors*: seq[AtomAuthor] # Pleuralised because the Atom spec allows more than one - category*: seq[string] - content*: string - contentSrc*: string - contentType*: string - link*: AtomLink - published*: string - rights*: string - source*: string - summary*: string - - -proc parseEntry( node: XmlNode) : AtomEntry = - var entry: AtomEntry = AtomEntry() - - # Fill the required fields - entry.id = node.child("id").innerText - entry.title = node.child("title").innerText - entry.updated = node.child("updated").innerText - - # Fill the optinal fields - if node.child("author") != nil: - for athr_node in node.findAll("author"): - var author: AtomAuthor = AtomAuthor() - author.name = athr_node.child("name").innerText - if athr_node.child("url") != nil: author.url = athr_node.child("url").innerText - if athr_node.child("email") != nil: author.email = athr_node.child("email").innerText - entry.authors.add(author) - - if node.child("category") != nil: - entry.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText) - - if node.child("content") != nil: - entry.content = node.child("content").innerText - if node.attrs != nil: - if node.attr("type") != "": entry.contentType = node.attr("type") - if node.attr("src") != "": entry.contentSrc = node.attr("src") - - if node.child("contributer") != nil: - for con_node in node.findAll("author"): - var author: AtomAuthor = AtomAuthor() - if con_node.child("name") != nil: author.name = con_node.child("name").innerText - if con_node.child("url") != nil: author.url = con_node.child("url").innerText - if con_node.child("email") != nil: author.email = con_node.child("email").innerText - entry.authors.add(author) - - if node.child("link") != nil: - if node.attrs != nil: - if node.attr("href") != "": entry.link.href = node.attr("href") - if node.attr("rel") != "": entry.link.rel = node.attr("rel") - if node.attr("type") != "": entry.link.linktype = node.attr("type") - if node.attr("hreflang") != "": entry.link.rel = node.attr("hreflang") - if node.attr("title") != "": entry.link.rel = node.attr("title") - if node.attr("length") != "": entry.link.rel = node.attr("length") - - if node.child("published") != nil: entry.published = node.child("published").innerText - - if node.child("rights") != nil: entry.rights = node.child("rights").innerText - - if node.child("summary") != nil: entry.summary = node.child("summary").innerText - - # SUGAR an easy way to access an author - if entry.authors.len() > 0: - entry.author = entry.authors[0] - else: - entry.author = AtomAuthor() - - return entry - -proc parseAtom*(data: string): Atom = - ## Parses the Atom from the given string. - - # Parse into XML. - let node: XmlNode = parseXML(newStringStream(data)) - - # Create the return object. - var atom: Atom = Atom() - - # Fill in the required fields - atom.id = node.child("id").innerText - atom.title = node.child("title").innerText - atom.updated = node.child("updated").innerText - - # Fill in the optional fields - if node.child("author") != nil: - for athr_node in node.findAll("author"): - var author: AtomAuthor = AtomAuthor() - author.name = athr_node.child("name").innerText - if athr_node.child("url") != nil: author.url = athr_node.child("url").innerText - if athr_node.child("email") != nil: author.email = athr_node.child("email").innerText - atom.authors.add(author) - - if node.child("category") != nil: - atom.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText) - - if node.child("generator") != nil: atom.generator = node.child("generator").innerText - - if node.child("icon") != nil: atom.icon = node.child("icon").innerText - - if node.child("link") != nil: - if node.attrs != nil: - if node.attr("href") != "": atom.link.href = node.attr("href") - if node.attr("rel") != "": atom.link.rel = node.attr("rel") - if node.attr("type") != "": atom.link.linktype = node.attr("type") - if node.attr("hreflang") != "": atom.link.rel = node.attr("hreflang") - if node.attr("title") != "": atom.link.rel = node.attr("title") - if node.attr("length") != "": atom.link.rel = node.attr("length") - - if node.child("logo") != nil: atom.logo = node.child("logo").innerText - - if node.child("rights") != nil: atom.rights = node.child("rights").innerText - - if node.child("subtitle") != nil: atom.subtitle = node.child("subtitle").innerText - - if atom.authors.len() > 0: - atom.author = atom.authors[0] - else: - atom.author = AtomAuthor() - - # If there are no entrys: - if node.child("entry") == nil: - atom.entrys = @[] - return atom - - # Otherwise, add the entrys. - if node.child("entry") != nil: - atom.entrys = map( node.findAll("entry"), parseEntry ) - - # Return the Atom data. - return atom - - diff --git a/modules/jsonfeed.nim b/modules/jsonfeed.nim deleted file mode 100644 index 8ff246f..0000000 --- a/modules/jsonfeed.nim +++ /dev/null @@ -1,133 +0,0 @@ -# Nim JSONFeed Syndication module - -# Written by John Conway -# Released under the MIT open source license. - -import strutils -import sequtils -import json -import streams -import sugar - -type - JSONFeed* = object - author*: JSONFeedAuthor - version*: string - title*: string - home_page_url*: string - feed_url*: string - description*: string - next_url*: string - icon*: string - favicon*: string - expired*: bool - hubs*: seq[JSONFeedHub] - items*: seq[JSONFeedItem] - - JSONFeedHub* = object - hubType: string - url: string - - JSONFeedAuthor* = object - name*: string - url*: string - avatar*: string - - JSONFeedItem* = object - author*: JSONFeedAuthor - id*: string - url*: string - external_url*: string - title*: string - content_html*: string - content_text*: string - summary*: string - image*: string - banner_image*: string - date_published*: string - date_modified*: string - tags: seq[string] - attachments*: seq[JSONFeedAttachment] - - JSONFeedAttachment* = object - url*: string - mime_type*: string - title*: string - size_in_bytes*: string - duration_in_seconds*: string - -proc parseItem( node: JsonNode) : JSONFeedItem = - var item: JSONFeedItem = JSONFeedItem() - - if node.getOrDefault( "author" ) != nil: - let author = node["author"] - item.author.name = getStr( node.getOrDefault "name" ) - item.author.url = getStr( node.getOrDefault "url" ) - item.author.avatar = getStr( node.getOrDefault "avatar" ) - - item.id = getStr( node.getOrDefault "id" ) - item.url = getStr( node.getOrDefault "url" ) - item.external_url = getStr( node.getOrDefault "external_url" ) - item.title = getStr( node.getOrDefault "title" ) - item.content_html = getStr( node.getOrDefault "content_html" ) - item.content_text = getStr( node.getOrDefault "content_text" ) - item.summary = getStr( node.getOrDefault "summary" ) - item.image = getStr( node.getOrDefault "image" ) - item.banner_image = getStr( node.getOrDefault "banner_image" ) - item.date_published = getStr( node.getOrDefault "date_published" ) - item.date_modified = getStr( node.getOrDefault "date_modified" ) - - if node.getOrDefault( "tags" ) != nil: - for tag in node["tags"]: - item.tags.add( $tag ) - - if node.getOrDefault( "attachments" ) != nil: - for jattach in node["attachments"]: - var attachment: JSONFeedAttachment = JSONFeedAttachment() - attachment.url = getStr( jattach.getOrDefault "url" ) - attachment.mime_type = getStr( jattach.getOrDefault "mime_type" ) - attachment.title = getStr( jattach.getOrDefault "title" ) - attachment.size_in_bytes = getStr( jattach.getOrDefault "size_in_bytes" ) - attachment.duration_in_seconds = getStr( jattach.getOrDefault "duration_in_seconds" ) - - item.attachments.add( attachment ) - - return item - -proc parseJSONFeed*(data: string): JSONFeed = - let node = data.parseJson() - var feed: JSONFeed = JSONFeed() - - if node.getOrDefault( "author" ) != nil: - let author = node["author"] - feed.author.name = getStr( author.getOrDefault "name" ) - feed.author.url = getStr( author.getOrDefault "url" ) - feed.author.avatar = getStr( author.getOrDefault "avatar" ) - - feed.version = getStr( node.getOrDefault "version" ) - feed.title = getStr( node.getOrDefault "title" ) - feed.home_page_url = getStr( node.getOrDefault "home_page_url" ) - feed.feed_url = getStr( node.getOrDefault "feed_url" ) - feed.description = getStr( node.getOrDefault "description" ) # What is this? - feed.next_url = getStr( node.getOrDefault "next_url" ) - feed.icon = getStr( node.getOrDefault "icon" ) - feed.favicon = getStr( node.getOrDefault "favicon" ) - - if node.getOrDefault( "expired" ) != nil: - feed.expired = node["expired"].getBool() - - if node.getOrDefault( "hubs" ) != nil: - for jhub in node["hubs"]: - var hub: JSONFeedHub = JSONFeedHub() - hub.hubType = getStr( jhub.getOrDefault "url" ) - hub.url = getStr( jhub.getOrDefault "mime_type" ) - - feed.hubs.add( hub ) - - feed.items = @[] - if node.getOrDefault( "items" ) != nil: - for item in node["items"]: - feed.items.add item.parseItem() - - - return feed \ No newline at end of file diff --git a/modules/rss.nim b/modules/rss.nim deleted file mode 100644 index abd4883..0000000 --- a/modules/rss.nim +++ /dev/null @@ -1,198 +0,0 @@ -# Nim RSS (Really Simple Syndication) module - -# Orginally written by Adam Chesak. -# Rewritten by John Conway - -# Released under the MIT open source license. - -import httpclient -import strutils -import sequtils -import xmlparser -import xmltree -import streams -import sugar - -type - RSS* = object - title*: string - link*: string - description*: string - language*: string - copyright*: string - managingEditor*: string - webMaster*: string - pubDate*: string - lastBuildDate*: string - category*: seq[string] - generator*: string - docs*: string - cloud*: RSSCloud - ttl*: string - image*: RSSImage - rating*: string - textInput*: RSSTextInput - skipHours*: seq[string] - skipDays*: seq[string] - items*: seq[RSSItem] - - RSSEnclosure* = object - url*: string - length*: string - enclosureType*: string - - RSSCloud* = object - domain*: string - port*: string - path*: string - registerProcedure*: string - protocol*: string - - RSSImage* = object - url*: string - title*: string - link*: string - width*: string - height*: string - description*: string - - RSSTextInput* = object - title*: string - description*: string - name*: string - link*: string - - RSSItem* = object - title*: string - link*: string - description*: string - author*: string - category*: seq[string] - comments*: string - enclosure*: RSSEnclosure - guid*: string - pubDate*: string - sourceUrl*: string - sourceText*: string - -proc parseItem( node: XmlNode) : RSSItem = - var item: RSSItem = RSSItem() - if node.child("title") != nil: item.title = node.child("title").innerText - - if node.child("link") != nil: item.link = node.child("link").innerText - - if node.child("description") != nil: item.description = node.child("description").innerText - - for key in @["author", "dc:creator"]: - if node.child(key) != nil: item.author = node.child(key).innerText - - if node.child("category") != nil: - item.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText) - - if node.child("comments") != nil: item.comments = node.child("comments").innerText - - if node.child("enclosure") != nil: - var encl: RSSEnclosure = RSSEnclosure() - encl.url = node.child("enclosure").attr("url") - encl.length = node.child("enclosure").attr("length") - encl.enclosureType = node.child("enclosure").attr("type") - item.enclosure = encl - - if node.child("guid") != nil: item.guid = node.child("guid").innerText - - if node.child("pubDate") != nil: item.pubDate = node.child("pubDate").innerText - - if node.child("source") != nil: - item.sourceUrl = node.child("source").attr("url") - item.sourceText = node.child("source").innerText - - return item - -proc parseRSS*(data: string): RSS = - ## Parses the RSS from the given string. - - # Parse into XML. - let root: XmlNode = parseXML(newStringStream(data)) - let channel: XmlNode = root.child("channel") - - # Create the return object. - var rss: RSS = RSS() - - # Fill the required fields. - rss.title = channel.child("title").innerText - rss.link = channel.child("link").innerText - rss.description = channel.child("description").innerText - - # Fill the optional fields. - for key in @["language", "dc:language"]: - if channel.child(key) != nil: - rss.language = channel.child(key).innerText - - if channel.child("copyright") != nil: rss.copyright = channel.child("copyright").innerText - - if channel.child("managingEditor") != nil: rss.managingEditor = channel.child("managingEditor").innerText - - if channel.child("webMaster") != nil: rss.webMaster = channel.child("webMaster").innerText - - for key in @["pubDate", "dc:date"]: - if channel.child(key) != nil: - rss.pubDate = channel.child(key).innerText - - if channel.child("lastBuildDate") != nil: rss.lastBuildDate = channel.child("lastBuildDate").innerText - - if channel.child("category") != nil: rss.category = map(channel.findAll("category"), (x: XmlNode) -> string => x.innerText) - - for key in @["generator", "dc:publisher"]: - if channel.child(key) != nil: rss.generator = channel.child(key).innerText - - if channel.child("docs") != nil: rss.docs = channel.child("docs").innerText - - if channel.child("cloud") != nil: - var cloud: RSSCloud = RSSCloud() - cloud.domain = channel.child("cloud").attr("domain") - cloud.port = channel.child("cloud").attr("port") - cloud.path = channel.child("cloud").attr("path") - cloud.registerProcedure = channel.child("cloud").attr("registerProcedure") - cloud.protocol = channel.child("cloud").attr("protocol") - rss.cloud = cloud - - if channel.child("ttl") != nil: rss.ttl = channel.child("ttl").innerText - - if channel.child("image") != nil: - var image: RSSImage = RSSImage() - let img = channel.child("image") - if img.child("url") != nil: image.url = img.child("url").innerText - if img.attr("rdf:resource") != "" and img.attr("rdf:resource") != "": image.url = img.attr("rdf:resource") - if img.child("title") != nil: image.title = img.child("title").innerText - if img.child("link") != nil: image.link = img.child("link").innerText - if img.child("width") != nil: image.width = img.child("width").innerText - if img.child("height") != nil: image.height = img.child("height").innerText - if img.child("description") != nil: image.description = img.child("description").innerText - rss.image = image - - if channel.child("rating") != nil: rss.rating = channel.child("rating").innerText - - if channel.child("textInput") != nil: - var textInput: RSSTextInput = RSSTextInput() - textInput.title = channel.child("textInput").child("title").innerText - textInput.description = channel.child("textInput").child("description").innerText - textInput.name = channel.child("textInput").child("name").innerText - textInput.link = channel.child("textInput").child("link").innerText - rss.textInput = textInput - - if channel.child("skipHours") != nil: - rss.skipHours = map(channel.findAll("hour"), (x: XmlNode) -> string => x.innerText) - if channel.child("skipDays") != nil: - rss.skipDays = map(channel.findAll("day"), (x: XmlNode) -> string => x.innerText) - - # If there are no items: - if channel.child("item") == nil and root.child("item") == nil: - rss.items = @[] - return rss - - # Otherwise, add the items. - if channel.child("item") != nil: rss.items = map(channel.findAll("item"), parseItem) - else: rss.items = map(root.findAll("item"), parseItem) - - # Return the RSS data. - return rss \ No newline at end of file diff --git a/src/feednim.nim b/src/feednim.nim new file mode 100644 index 0000000..4b2a270 --- /dev/null +++ b/src/feednim.nim @@ -0,0 +1,7 @@ +# This is just an example to get you started. A typical library package +# exports the main API in this file. Note that you cannot rename this file +# but you can remove it if you wish. + +proc add*(x, y: int): int = + ## Adds two files together. + return x + y diff --git a/src/feednim/atom.nim b/src/feednim/atom.nim new file mode 100644 index 0000000..cce9c44 --- /dev/null +++ b/src/feednim/atom.nim @@ -0,0 +1,187 @@ +# Nim Atom Syndication Format module + +# Written by John Conway +# Released under the MIT open source license. + +import httpclient +import strutils +import sequtils +import xmlparser +import xmltree +import streams +import sugar + +type + Atom* = object + author: AtomAuthor # Sugar, not in Atom spec. Returns the first author. + id*: string # Required Atom field + title*: string # Required Atom field + updated*: string # Required Atom field + authors*: seq[AtomAuthor] # Pleuralised because the Atom spec allows more than one + category*: seq[string] + generator*: string + icon*: string + link*: AtomLink + logo*: string + rights*: string + subtitle*: string + entrys*: seq[AtomEntry] + + AtomAuthor* = object + name*: string # Required Atom field + url*: string + email*: string + + AtomImage* = object + url*: string + title*: string + link*: string + width*: string + height*: string + description*: string + + AtomLink* = object + href*: string + rel*: string + linktype*: string + hreflang*: string + title*: string + length*: string + + AtomEntry* = object + id*: string # Required Atom field + title*: string # Required Atom field + updated*: string # Required Atom field + author: AtomAuthor # Sugar, not in Atom spec. Returns the first author. + authors*: seq[AtomAuthor] # Pleuralised because the Atom spec allows more than one + category*: seq[string] + content*: string + contentSrc*: string + contentType*: string + link*: AtomLink + published*: string + rights*: string + source*: string + summary*: string + + +proc parseEntry( node: XmlNode) : AtomEntry = + var entry: AtomEntry = AtomEntry() + + # Fill the required fields + entry.id = node.child("id").innerText + entry.title = node.child("title").innerText + entry.updated = node.child("updated").innerText + + # Fill the optinal fields + if node.child("author") != nil: + for athr_node in node.findAll("author"): + var author: AtomAuthor = AtomAuthor() + author.name = athr_node.child("name").innerText + if athr_node.child("url") != nil: author.url = athr_node.child("url").innerText + if athr_node.child("email") != nil: author.email = athr_node.child("email").innerText + entry.authors.add(author) + + if node.child("category") != nil: + entry.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText) + + if node.child("content") != nil: + entry.content = node.child("content").innerText + if node.attrs != nil: + if node.attr("type") != "": entry.contentType = node.attr("type") + if node.attr("src") != "": entry.contentSrc = node.attr("src") + + if node.child("contributer") != nil: + for con_node in node.findAll("author"): + var author: AtomAuthor = AtomAuthor() + if con_node.child("name") != nil: author.name = con_node.child("name").innerText + if con_node.child("url") != nil: author.url = con_node.child("url").innerText + if con_node.child("email") != nil: author.email = con_node.child("email").innerText + entry.authors.add(author) + + if node.child("link") != nil: + if node.attrs != nil: + if node.attr("href") != "": entry.link.href = node.attr("href") + if node.attr("rel") != "": entry.link.rel = node.attr("rel") + if node.attr("type") != "": entry.link.linktype = node.attr("type") + if node.attr("hreflang") != "": entry.link.rel = node.attr("hreflang") + if node.attr("title") != "": entry.link.rel = node.attr("title") + if node.attr("length") != "": entry.link.rel = node.attr("length") + + if node.child("published") != nil: entry.published = node.child("published").innerText + + if node.child("rights") != nil: entry.rights = node.child("rights").innerText + + if node.child("summary") != nil: entry.summary = node.child("summary").innerText + + # SUGAR an easy way to access an author + if entry.authors.len() > 0: + entry.author = entry.authors[0] + else: + entry.author = AtomAuthor() + + return entry + +proc parseAtom*(data: string): Atom = + ## Parses the Atom from the given string. + + # Parse into XML. + let node: XmlNode = parseXML(newStringStream(data)) + + # Create the return object. + var atom: Atom = Atom() + + # Fill in the required fields + atom.id = node.child("id").innerText + atom.title = node.child("title").innerText + atom.updated = node.child("updated").innerText + + # Fill in the optional fields + if node.child("author") != nil: + for athr_node in node.findAll("author"): + var author: AtomAuthor = AtomAuthor() + author.name = athr_node.child("name").innerText + if athr_node.child("url") != nil: author.url = athr_node.child("url").innerText + if athr_node.child("email") != nil: author.email = athr_node.child("email").innerText + atom.authors.add(author) + + if node.child("category") != nil: + atom.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText) + + if node.child("generator") != nil: atom.generator = node.child("generator").innerText + + if node.child("icon") != nil: atom.icon = node.child("icon").innerText + + if node.child("link") != nil: + if node.attrs != nil: + if node.attr("href") != "": atom.link.href = node.attr("href") + if node.attr("rel") != "": atom.link.rel = node.attr("rel") + if node.attr("type") != "": atom.link.linktype = node.attr("type") + if node.attr("hreflang") != "": atom.link.rel = node.attr("hreflang") + if node.attr("title") != "": atom.link.rel = node.attr("title") + if node.attr("length") != "": atom.link.rel = node.attr("length") + + if node.child("logo") != nil: atom.logo = node.child("logo").innerText + + if node.child("rights") != nil: atom.rights = node.child("rights").innerText + + if node.child("subtitle") != nil: atom.subtitle = node.child("subtitle").innerText + + if atom.authors.len() > 0: + atom.author = atom.authors[0] + else: + atom.author = AtomAuthor() + + # If there are no entrys: + if node.child("entry") == nil: + atom.entrys = @[] + return atom + + # Otherwise, add the entrys. + if node.child("entry") != nil: + atom.entrys = map( node.findAll("entry"), parseEntry ) + + # Return the Atom data. + return atom + + diff --git a/src/feednim/jsonfeed.nim b/src/feednim/jsonfeed.nim new file mode 100644 index 0000000..8ff246f --- /dev/null +++ b/src/feednim/jsonfeed.nim @@ -0,0 +1,133 @@ +# Nim JSONFeed Syndication module + +# Written by John Conway +# Released under the MIT open source license. + +import strutils +import sequtils +import json +import streams +import sugar + +type + JSONFeed* = object + author*: JSONFeedAuthor + version*: string + title*: string + home_page_url*: string + feed_url*: string + description*: string + next_url*: string + icon*: string + favicon*: string + expired*: bool + hubs*: seq[JSONFeedHub] + items*: seq[JSONFeedItem] + + JSONFeedHub* = object + hubType: string + url: string + + JSONFeedAuthor* = object + name*: string + url*: string + avatar*: string + + JSONFeedItem* = object + author*: JSONFeedAuthor + id*: string + url*: string + external_url*: string + title*: string + content_html*: string + content_text*: string + summary*: string + image*: string + banner_image*: string + date_published*: string + date_modified*: string + tags: seq[string] + attachments*: seq[JSONFeedAttachment] + + JSONFeedAttachment* = object + url*: string + mime_type*: string + title*: string + size_in_bytes*: string + duration_in_seconds*: string + +proc parseItem( node: JsonNode) : JSONFeedItem = + var item: JSONFeedItem = JSONFeedItem() + + if node.getOrDefault( "author" ) != nil: + let author = node["author"] + item.author.name = getStr( node.getOrDefault "name" ) + item.author.url = getStr( node.getOrDefault "url" ) + item.author.avatar = getStr( node.getOrDefault "avatar" ) + + item.id = getStr( node.getOrDefault "id" ) + item.url = getStr( node.getOrDefault "url" ) + item.external_url = getStr( node.getOrDefault "external_url" ) + item.title = getStr( node.getOrDefault "title" ) + item.content_html = getStr( node.getOrDefault "content_html" ) + item.content_text = getStr( node.getOrDefault "content_text" ) + item.summary = getStr( node.getOrDefault "summary" ) + item.image = getStr( node.getOrDefault "image" ) + item.banner_image = getStr( node.getOrDefault "banner_image" ) + item.date_published = getStr( node.getOrDefault "date_published" ) + item.date_modified = getStr( node.getOrDefault "date_modified" ) + + if node.getOrDefault( "tags" ) != nil: + for tag in node["tags"]: + item.tags.add( $tag ) + + if node.getOrDefault( "attachments" ) != nil: + for jattach in node["attachments"]: + var attachment: JSONFeedAttachment = JSONFeedAttachment() + attachment.url = getStr( jattach.getOrDefault "url" ) + attachment.mime_type = getStr( jattach.getOrDefault "mime_type" ) + attachment.title = getStr( jattach.getOrDefault "title" ) + attachment.size_in_bytes = getStr( jattach.getOrDefault "size_in_bytes" ) + attachment.duration_in_seconds = getStr( jattach.getOrDefault "duration_in_seconds" ) + + item.attachments.add( attachment ) + + return item + +proc parseJSONFeed*(data: string): JSONFeed = + let node = data.parseJson() + var feed: JSONFeed = JSONFeed() + + if node.getOrDefault( "author" ) != nil: + let author = node["author"] + feed.author.name = getStr( author.getOrDefault "name" ) + feed.author.url = getStr( author.getOrDefault "url" ) + feed.author.avatar = getStr( author.getOrDefault "avatar" ) + + feed.version = getStr( node.getOrDefault "version" ) + feed.title = getStr( node.getOrDefault "title" ) + feed.home_page_url = getStr( node.getOrDefault "home_page_url" ) + feed.feed_url = getStr( node.getOrDefault "feed_url" ) + feed.description = getStr( node.getOrDefault "description" ) # What is this? + feed.next_url = getStr( node.getOrDefault "next_url" ) + feed.icon = getStr( node.getOrDefault "icon" ) + feed.favicon = getStr( node.getOrDefault "favicon" ) + + if node.getOrDefault( "expired" ) != nil: + feed.expired = node["expired"].getBool() + + if node.getOrDefault( "hubs" ) != nil: + for jhub in node["hubs"]: + var hub: JSONFeedHub = JSONFeedHub() + hub.hubType = getStr( jhub.getOrDefault "url" ) + hub.url = getStr( jhub.getOrDefault "mime_type" ) + + feed.hubs.add( hub ) + + feed.items = @[] + if node.getOrDefault( "items" ) != nil: + for item in node["items"]: + feed.items.add item.parseItem() + + + return feed \ No newline at end of file diff --git a/src/feednim/rss.nim b/src/feednim/rss.nim new file mode 100644 index 0000000..abd4883 --- /dev/null +++ b/src/feednim/rss.nim @@ -0,0 +1,198 @@ +# Nim RSS (Really Simple Syndication) module + +# Orginally written by Adam Chesak. +# Rewritten by John Conway + +# Released under the MIT open source license. + +import httpclient +import strutils +import sequtils +import xmlparser +import xmltree +import streams +import sugar + +type + RSS* = object + title*: string + link*: string + description*: string + language*: string + copyright*: string + managingEditor*: string + webMaster*: string + pubDate*: string + lastBuildDate*: string + category*: seq[string] + generator*: string + docs*: string + cloud*: RSSCloud + ttl*: string + image*: RSSImage + rating*: string + textInput*: RSSTextInput + skipHours*: seq[string] + skipDays*: seq[string] + items*: seq[RSSItem] + + RSSEnclosure* = object + url*: string + length*: string + enclosureType*: string + + RSSCloud* = object + domain*: string + port*: string + path*: string + registerProcedure*: string + protocol*: string + + RSSImage* = object + url*: string + title*: string + link*: string + width*: string + height*: string + description*: string + + RSSTextInput* = object + title*: string + description*: string + name*: string + link*: string + + RSSItem* = object + title*: string + link*: string + description*: string + author*: string + category*: seq[string] + comments*: string + enclosure*: RSSEnclosure + guid*: string + pubDate*: string + sourceUrl*: string + sourceText*: string + +proc parseItem( node: XmlNode) : RSSItem = + var item: RSSItem = RSSItem() + if node.child("title") != nil: item.title = node.child("title").innerText + + if node.child("link") != nil: item.link = node.child("link").innerText + + if node.child("description") != nil: item.description = node.child("description").innerText + + for key in @["author", "dc:creator"]: + if node.child(key) != nil: item.author = node.child(key).innerText + + if node.child("category") != nil: + item.category = map(node.findAll("category"), (x: XmlNode) -> string => x.innerText) + + if node.child("comments") != nil: item.comments = node.child("comments").innerText + + if node.child("enclosure") != nil: + var encl: RSSEnclosure = RSSEnclosure() + encl.url = node.child("enclosure").attr("url") + encl.length = node.child("enclosure").attr("length") + encl.enclosureType = node.child("enclosure").attr("type") + item.enclosure = encl + + if node.child("guid") != nil: item.guid = node.child("guid").innerText + + if node.child("pubDate") != nil: item.pubDate = node.child("pubDate").innerText + + if node.child("source") != nil: + item.sourceUrl = node.child("source").attr("url") + item.sourceText = node.child("source").innerText + + return item + +proc parseRSS*(data: string): RSS = + ## Parses the RSS from the given string. + + # Parse into XML. + let root: XmlNode = parseXML(newStringStream(data)) + let channel: XmlNode = root.child("channel") + + # Create the return object. + var rss: RSS = RSS() + + # Fill the required fields. + rss.title = channel.child("title").innerText + rss.link = channel.child("link").innerText + rss.description = channel.child("description").innerText + + # Fill the optional fields. + for key in @["language", "dc:language"]: + if channel.child(key) != nil: + rss.language = channel.child(key).innerText + + if channel.child("copyright") != nil: rss.copyright = channel.child("copyright").innerText + + if channel.child("managingEditor") != nil: rss.managingEditor = channel.child("managingEditor").innerText + + if channel.child("webMaster") != nil: rss.webMaster = channel.child("webMaster").innerText + + for key in @["pubDate", "dc:date"]: + if channel.child(key) != nil: + rss.pubDate = channel.child(key).innerText + + if channel.child("lastBuildDate") != nil: rss.lastBuildDate = channel.child("lastBuildDate").innerText + + if channel.child("category") != nil: rss.category = map(channel.findAll("category"), (x: XmlNode) -> string => x.innerText) + + for key in @["generator", "dc:publisher"]: + if channel.child(key) != nil: rss.generator = channel.child(key).innerText + + if channel.child("docs") != nil: rss.docs = channel.child("docs").innerText + + if channel.child("cloud") != nil: + var cloud: RSSCloud = RSSCloud() + cloud.domain = channel.child("cloud").attr("domain") + cloud.port = channel.child("cloud").attr("port") + cloud.path = channel.child("cloud").attr("path") + cloud.registerProcedure = channel.child("cloud").attr("registerProcedure") + cloud.protocol = channel.child("cloud").attr("protocol") + rss.cloud = cloud + + if channel.child("ttl") != nil: rss.ttl = channel.child("ttl").innerText + + if channel.child("image") != nil: + var image: RSSImage = RSSImage() + let img = channel.child("image") + if img.child("url") != nil: image.url = img.child("url").innerText + if img.attr("rdf:resource") != "" and img.attr("rdf:resource") != "": image.url = img.attr("rdf:resource") + if img.child("title") != nil: image.title = img.child("title").innerText + if img.child("link") != nil: image.link = img.child("link").innerText + if img.child("width") != nil: image.width = img.child("width").innerText + if img.child("height") != nil: image.height = img.child("height").innerText + if img.child("description") != nil: image.description = img.child("description").innerText + rss.image = image + + if channel.child("rating") != nil: rss.rating = channel.child("rating").innerText + + if channel.child("textInput") != nil: + var textInput: RSSTextInput = RSSTextInput() + textInput.title = channel.child("textInput").child("title").innerText + textInput.description = channel.child("textInput").child("description").innerText + textInput.name = channel.child("textInput").child("name").innerText + textInput.link = channel.child("textInput").child("link").innerText + rss.textInput = textInput + + if channel.child("skipHours") != nil: + rss.skipHours = map(channel.findAll("hour"), (x: XmlNode) -> string => x.innerText) + if channel.child("skipDays") != nil: + rss.skipDays = map(channel.findAll("day"), (x: XmlNode) -> string => x.innerText) + + # If there are no items: + if channel.child("item") == nil and root.child("item") == nil: + rss.items = @[] + return rss + + # Otherwise, add the items. + if channel.child("item") != nil: rss.items = map(channel.findAll("item"), parseItem) + else: rss.items = map(root.findAll("item"), parseItem) + + # Return the RSS data. + return rss \ No newline at end of file diff --git a/src/feednim/submodule.nim b/src/feednim/submodule.nim new file mode 100644 index 0000000..1af4e81 --- /dev/null +++ b/src/feednim/submodule.nim @@ -0,0 +1,12 @@ +# This is just an example to get you started. Users of your library will +# import this file by writing ``import FeedNim/submodule``. Feel free to rename or +# remove this file altogether. You may create additional modules alongside +# this file as required. + +type + Submodule* = object + name*: string + +proc initSubmodule*(): Submodule = + ## Initialises a new ``Submodule`` object. + Submodule(name: "Anonymous") diff --git a/tests/config.nims b/tests/config.nims new file mode 100644 index 0000000..3bb69f8 --- /dev/null +++ b/tests/config.nims @@ -0,0 +1 @@ +switch("path", "$projectDir/../src") \ No newline at end of file diff --git a/tests/test1.nim b/tests/test1.nim new file mode 100644 index 0000000..0ff07eb --- /dev/null +++ b/tests/test1.nim @@ -0,0 +1,12 @@ +# This is just an example to get you started. You may wish to put all of your +# tests into a single file, or separate them into multiple `test1`, `test2` +# etc. files (better names are recommended, just make sure the name starts with +# the letter 't'). +# +# To run these tests, simply execute `nimble test`. + +import unittest + +import FeedNim +test "can add": + check add(5, 5) == 10 -- cgit v1.2.3