<gh_stars>0
#!/usr/bin/env python3
import pathlib
import os.path
import json
import markdown
import xml.dom.minidom as minidom
from datetime import datetime, timezone, timedelta
import rsserver.server

class RSSConstructorException(Exception):
	"""Exception thrown during RSS object generating.
	
	This exception is thrown when, for example, the structure
	requirements for building an item object or a feed object
	aren't met.
	
	"""
	pass

class RSSFeed:
	"""Represents a whole RSS feed.

	Attributes:
		info (dict): The feed's information, loaded from server_info.json
		items (list of RSSItem): The feed's items.
	
	"""
	
	def __init__(self, path):
		"""Loads all the feed's date from the specified path.

		The feed's info gets loaded from a `server_info.json`, and the items
		get loaded from the items folder.

		Args:
			path (str): The path for loading all the information from.

		"""
		
		if os.path.isdir(path):
			path_info = path + "/server_info.json"
			path_items = path + "/items"

			if os.path.isfile(path_info) and os.path.isdir(path_items):
				file_info = open(path_info)
				self.info = json.load(file_info)
				self.items = []

				plib_items = pathlib.Path(path_items)
				for file in plib_items.iterdir():
					if file.is_dir():
						pathtoitem = full_path(file)
						self.items.append(RSSItem(pathtoitem))

				self.items.sort()
			else:
				raise RSSConstructorException("Required information is missing")
		else:
			raise RSSConstructorException("Please specify a directory")

	def generatedoc(self):
		"""Generates the DOM document

		xml.dom.minidom is used through the whole process, since CDATA nodes are
		required inside description nodes in items, due to the fact that the
		content gets translated from Markdown to HTML.

		Returns:
			xml.dom.minidom.Document: The feed's document

		"""
		
		document = minidom.Document()
		root = document.createElement("rss")
		root.setAttribute("version", "2.0")
		document.appendChild(root)
		cont = document.createElement("channel")
		root.appendChild(cont)

		title = document.createElement("title")
		title.appendChild(document.createTextNode(self.info["title"]))
		cont.appendChild(title)
		link = document.createElement("link")
		link.appendChild(document.createTextNode(
			"http://" +
			rsserver.server.address + ":" + str(rsserver.server.port)
			))
		cont.appendChild(link)
		description = document.createElement("description")
		description.appendChild(document.createTextNode(self.info["description"]))
		cont.appendChild(description)

		for item in self.items:
			node = item.getnode(document)
			cont.appendChild(node)

		return document

class RSSItem:
	"""Represents a single item inside an RSS feed.

	Attributes:
		name (str): The name of the original folder.
		info (dict): The item's information, sourced from JSON file.
		content (str): The item's content, written in Markdown.

	"""
	def __init__(self, path):
		"""
		Loads the required files from the selected folder, then stores
		their content.

		Note:
			For initializing, the files `info.json` and `content.md` are
			required.
		
		Args:
			path (str): The folder's path to be loaded.

		Raises:
			RSSConstructorException: If the path doesn't exist, is not a folder,
				or doesn't contain the required files.

		"""
		path_info = path + "/info.json"
		path_content = path + "/content.md"
		parts = path.split("/")
		self.name = parts[len(parts) - 1]

		if os.path.isfile(path_info) and os.path.isfile(path_content):
			file_info = open(path_info)
			file_content = open(path_content)

			self.info = json.load(file_info)
			self.content = file_content.read()

			file_info.close()
			file_content.close()
		else:
			raise RSSConstructorException("Needed files not found.")

	def __gt__(self, other):
		"""For ordering items by reverse date.

		The first items to appear in a RSS feed are the most recent ones.
		Thus, they are ordered from newest to oldest,

		Args:
			other (RSSItem): The other item to compare

		Returns:
			True if the other's date is more recent, False otherwise

		"""
		self_date = dateobj(self.info["date"], self.info["time"],
		self.info["timezone"])
		other_date = dateobj(other.info["date"], other.info["time"],
		other.info["timezone"])
		return self_date < other_date

	def renderHTML(self):
		"""Generates a HTML document with the contents of the item.

		The item's "link" could be attempted to open in a browser
		(which is the original intention of the <link> node), so generating
		a simple page might fill the gap.

		The HTML code is constructed with strings because Markdown function
		returns a string, and there was no incentive to make a XHTML document
		from scratch.

		Returns:
			A basic HTML document for displaying in a web browser.

		"""
		title = self.info["title"]
		body = "<h1>" + title + "</h1>"
		body += markdown.markdown(self.content)
		head = "<meta charset='UTF-8'>"
		
		#Getting feed's name back from server_info.json
		base_info_path = rsserver.server.base_directory + "/server_info.json"
		base_info = open(base_info_path)
		base_info_dict = json.load(base_info)
		base_info.close()
		title += " - " + base_info_dict["title"]

		head += "<title>" + title +"</title>"
		return "<html><head>" + head + "</head><body>" + body + "</body></html>"
	
	def getnode(self, document):
		"""Generates a node for a document.

		It uses all the values gathered in the info dictionary to build the
		nodes, so having all keys and values set correctly is necessary for
		this method to work correctly.

		Args:
			document (xml.dom.minidom.Document): The document to which the final node
				will be added

		Returns:
			xml.dom.minidom.Element: This item's node

		"""
		
		link = "http://" + rsserver.server.address + ":"
		link += str(rsserver.server.port) + "/" + self.name
		
		node = document.createElement("item")
		
		title = document.createElement("title")
		title_text = document.createTextNode(self.info["title"])
		title.appendChild(title_text)
		node.appendChild(title)
		#Link nodes, which use the same text node
		link_text = document.createTextNode(link)
		link_node = document.createElement("link")
		link_node.appendChild(link_text.cloneNode(True))
		guid = document.createElement("guid")
		guid.setAttribute("isPermaLink", "true")
		guid.appendChild(link_text)
		node.appendChild(link_node)
		node.appendChild(guid)

		description = document.createElement("description")
		content = document.createCDATASection(markdown.markdown(self.content))
		description.appendChild(content)
		node.appendChild(description)

		pubdate = document.createElement("pubDate")
		pubdate.appendChild(document.createTextNode(genedate(self.info)))
		node.appendChild(pubdate)

		for c in self.info["categories"]:
			category = document.createElement("category")
			category.appendChild(document.createTextNode(c))
			node.appendChild(category)

		return node

def full_path(path):
	"""Support function, reassembles a path from a Path object.

	Used for reconstructing a directory Path object's original path.

	Args;
		path (pathlib.Path): The path object.

	Returns:
		str: Its original path.

	"""
	result = ""
	if isinstance(path, pathlib.Path):
		parts = path.parts
		for x in parts:
			result += x + "/"
	
	return result[0:len(result)-1]

def dateobj(stringdate, stringtime, stringtz):
	"""Support function, creates a datetime object from an item's info attribute.

	Generates a full datetime object with date, time and timezone information
	from the item's dictionary.

	Args:
		stringdate (str): The string representing the date in the format YYYY-MM-DD
			(Y: year, M: month, D: day)
		stringtime (str): The string representing the time in the format HH:MM:SS
			(H: hours, M: minutes, S: seconds)
		stringtz (str): The string representing the timezone in the format xHHMM
			(x: Plus (+) or minus (-) symbol, H: hours, M: minutes)

	Returns:
		datetime.datetime: A datetime object with the required data filled

	"""
	tz_multipliers = {"+": 1, "-": -1}
	
	date_num = [int(n) for n in stringdate.split("-")]
	time_num = [int(n) for n in stringtime.split(":")]
	tz_hours = int(stringtz[1:3]) * tz_multipliers[stringtz[0]]
	tz_minutes = int(stringtz[3:5]) * tz_multipliers[stringtz[0]]
	delta = timedelta(hours=tz_hours, minutes=tz_minutes)

	return datetime(date_num[0], date_num[1], date_num[2],
	time_num[0], time_num[1], time_num[2], tzinfo=timezone(delta))

def genedate(info):
	"""Generates RFC 822 compliant date string

	Creates, from an item's info attribute, the string required for
	the pubDate node, which must be compliant with the standard
	RFC 822: https://www.w3.org/Protocols/rfc822/#z28

	Args:
		info (dict): A dictionary sourced from an item object

	Returns:
		The pubDate string

	"""
	days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
	months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
	"Aug", "Sep", "Oct", "Nov", "Dec"]

	obj = dateobj(info["date"], info["time"], info["timezone"])
	pubdate = days[obj.weekday()] + ", " + str(obj.day)
	pubdate += " " + months[obj.month - 1] + " " + str(obj.year)
	pubdate += " " + info["time"] + " " + info["timezone"]

	return pubdate
#!/usr/bin/env python3
import pathlib
import os.path
import json
import markdown
import xml.dom.minidom as minidom
from datetime import datetime, timezone, timedelta
import rsserver.server

class RSSConstructorException(Exception):
	"""Exception thrown during RSS object generating.
	
	This exception is thrown when, for example, the structure
	requirements for building an item object or a feed object
	aren't met.
	
	"""
	pass

class RSSFeed:
	"""Represents a whole RSS feed.

	Attributes:
		info (dict): The feed's information, loaded from server_info.json
		items (list of RSSItem): The feed's items.
	
	"""
	
	def __init__(self, path):
		"""Loads all the feed's date from the specified path.

		The feed's info gets loaded from a `server_info.json`, and the items
		get loaded from the items folder.

		Args:
			path (str): The path for loading all the information from.

		"""
		
		if os.path.isdir(path):
			path_info = path + "/server_info.json"
			path_items = path + "/items"

			if os.path.isfile(path_info) and os.path.isdir(path_items):
				file_info = open(path_info)
				self.info = json.load(file_info)
				self.items = []

				plib_items = pathlib.Path(path_items)
				for file in plib_items.iterdir():
					if file.is_dir():
						pathtoitem = full_path(file)
						self.items.append(RSSItem(pathtoitem))

				self.items.sort()
			else:
				raise RSSConstructorException("Required information is missing")
		else:
			raise RSSConstructorException("Please specify a directory")

	def generatedoc(self):
		"""Generates the DOM document

		xml.dom.minidom is used through the whole process, since CDATA nodes are
		required inside description nodes in items, due to the fact that the
		content gets translated from Markdown to HTML.

		Returns:
			xml.dom.minidom.Document: The feed's document

		"""
		
		document = minidom.Document()
		root = document.createElement("rss")
		root.setAttribute("version", "2.0")
		document.appendChild(root)
		cont = document.createElement("channel")
		root.appendChild(cont)

		title = document.createElement("title")
		title.appendChild(document.createTextNode(self.info["title"]))
		cont.appendChild(title)
		link = document.createElement("link")
		link.appendChild(document.createTextNode(
			"http://" +
			rsserver.server.address + ":" + str(rsserver.server.port)
			))
		cont.appendChild(link)
		description = document.createElement("description")
		description.appendChild(document.createTextNode(self.info["description"]))
		cont.appendChild(description)

		for item in self.items:
			node = item.getnode(document)
			cont.appendChild(node)

		return document

class RSSItem:
	"""Represents a single item inside an RSS feed.

	Attributes:
		name (str): The name of the original folder.
		info (dict): The item's information, sourced from JSON file.
		content (str): The item's content, written in Markdown.

	"""
	def __init__(self, path):
		"""
		Loads the required files from the selected folder, then stores
		their content.

		Note:
			For initializing, the files `info.json` and `content.md` are
			required.
		
		Args:
			path (str): The folder's path to be loaded.

		Raises:
			RSSConstructorException: If the path doesn't exist, is not a folder,
				or doesn't contain the required files.

		"""
		path_info = path + "/info.json"
		path_content = path + "/content.md"
		parts = path.split("/")
		self.name = parts[len(parts) - 1]

		if os.path.isfile(path_info) and os.path.isfile(path_content):
			file_info = open(path_info)
			file_content = open(path_content)

			self.info = json.load(file_info)
			self.content = file_content.read()

			file_info.close()
			file_content.close()
		else:
			raise RSSConstructorException("Needed files not found.")

	def __gt__(self, other):
		"""For ordering items by reverse date.

		The first items to appear in a RSS feed are the most recent ones.
		Thus, they are ordered from newest to oldest,

		Args:
			other (RSSItem): The other item to compare

		Returns:
			True if the other's date is more recent, False otherwise

		"""
		self_date = dateobj(self.info["date"], self.info["time"],
		self.info["timezone"])
		other_date = dateobj(other.info["date"], other.info["time"],
		other.info["timezone"])
		return self_date < other_date

	def renderHTML(self):
		"""Generates a HTML document with the contents of the item.

		The item's "link" could be attempted to open in a browser
		(which is the original intention of the <link> node), so generating
		a simple page might fill the gap.

		The HTML code is constructed with strings because Markdown function
		returns a string, and there was no incentive to make a XHTML document
		from scratch.

		Returns:
			A basic HTML document for displaying in a web browser.

		"""
		title = self.info["title"]
		body = "<h1>" + title + "</h1>"
		body += markdown.markdown(self.content)
		head = "<meta charset='UTF-8'>"
		
		#Getting feed's name back from server_info.json
		base_info_path = rsserver.server.base_directory + "/server_info.json"
		base_info = open(base_info_path)
		base_info_dict = json.load(base_info)
		base_info.close()
		title += " - " + base_info_dict["title"]

		head += "<title>" + title +"</title>"
		return "<html><head>" + head + "</head><body>" + body + "</body></html>"
	
	def getnode(self, document):
		"""Generates a node for a document.

		It uses all the values gathered in the info dictionary to build the
		nodes, so having all keys and values set correctly is necessary for
		this method to work correctly.

		Args:
			document (xml.dom.minidom.Document): The document to which the final node
				will be added

		Returns:
			xml.dom.minidom.Element: This item's node

		"""
		
		link = "http://" + rsserver.server.address + ":"
		link += str(rsserver.server.port) + "/" + self.name
		
		node = document.createElement("item")
		
		title = document.createElement("title")
		title_text = document.createTextNode(self.info["title"])
		title.appendChild(title_text)
		node.appendChild(title)
		#Link nodes, which use the same text node
		link_text = document.createTextNode(link)
		link_node = document.createElement("link")
		link_node.appendChild(link_text.cloneNode(True))
		guid = document.createElement("guid")
		guid.setAttribute("isPermaLink", "true")
		guid.appendChild(link_text)
		node.appendChild(link_node)
		node.appendChild(guid)

		description = document.createElement("description")
		content = document.createCDATASection(markdown.markdown(self.content))
		description.appendChild(content)
		node.appendChild(description)

		pubdate = document.createElement("pubDate")
		pubdate.appendChild(document.createTextNode(genedate(self.info)))
		node.appendChild(pubdate)

		for c in self.info["categories"]:
			category = document.createElement("category")
			category.appendChild(document.createTextNode(c))
			node.appendChild(category)

		return node

def full_path(path):
	"""Support function, reassembles a path from a Path object.

	Used for reconstructing a directory Path object's original path.

	Args;
		path (pathlib.Path): The path object.

	Returns:
		str: Its original path.

	"""
	result = ""
	if isinstance(path, pathlib.Path):
		parts = path.parts
		for x in parts:
			result += x + "/"
	
	return result[0:len(result)-1]

def dateobj(stringdate, stringtime, stringtz):
	"""Support function, creates a datetime object from an item's info attribute.

	Generates a full datetime object with date, time and timezone information
	from the item's dictionary.

	Args:
		stringdate (str): The string representing the date in the format YYYY-MM-DD
			(Y: year, M: month, D: day)
		stringtime (str): The string representing the time in the format HH:MM:SS
			(H: hours, M: minutes, S: seconds)
		stringtz (str): The string representing the timezone in the format xHHMM
			(x: Plus (+) or minus (-) symbol, H: hours, M: minutes)

	Returns:
		datetime.datetime: A datetime object with the required data filled

	"""
	tz_multipliers = {"+": 1, "-": -1}
	
	date_num = [int(n) for n in stringdate.split("-")]
	time_num = [int(n) for n in stringtime.split(":")]
	tz_hours = int(stringtz[1:3]) * tz_multipliers[stringtz[0]]
	tz_minutes = int(stringtz[3:5]) * tz_multipliers[stringtz[0]]
	delta = timedelta(hours=tz_hours, minutes=tz_minutes)

	return datetime(date_num[0], date_num[1], date_num[2],
	time_num[0], time_num[1], time_num[2], tzinfo=timezone(delta))

def genedate(info):
	"""Generates RFC 822 compliant date string

	Creates, from an item's info attribute, the string required for
	the pubDate node, which must be compliant with the standard
	RFC 822: https://www.w3.org/Protocols/rfc822/#z28

	Args:
		info (dict): A dictionary sourced from an item object

	Returns:
		The pubDate string

	"""
	days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
	months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
	"Aug", "Sep", "Oct", "Nov", "Dec"]

	obj = dateobj(info["date"], info["time"], info["timezone"])
	pubdate = days[obj.weekday()] + ", " + str(obj.day)
	pubdate += " " + months[obj.month - 1] + " " + str(obj.year)
	pubdate += " " + info["time"] + " " + info["timezone"]

	return pubdate
#!/usr/bin/env python3 Exception thrown during RSS object generating. 	 	This exception is thrown when, for example, the structure 	requirements for building an item object or a feed object 	aren't met. Represents a whole RSS feed.  	Attributes: 		info (dict): The feed's information, loaded from server_info.json 		items (list of RSSItem): The feed's items. Loads all the feed's date from the specified path.  		The feed's info gets loaded from a `server_info.json`, and the items 		get loaded from the items folder.  		Args: 			path (str): The path for loading all the information from. Generates the DOM document  		xml.dom.minidom is used through the whole process, since CDATA nodes are 		required inside description nodes in items, due to the fact that the 		content gets translated from Markdown to HTML.  		Returns: 			xml.dom.minidom.Document: The feed's document Represents a single item inside an RSS feed.  	Attributes: 		name (str): The name of the original folder. 		info (dict): The item's information, sourced from JSON file. 		content (str): The item's content, written in Markdown. Loads the required files from the selected folder, then stores 		their content.  		Note: 			For initializing, the files `info.json` and `content.md` are 			required. 		 		Args: 			path (str): The folder's path to be loaded.  		Raises: 			RSSConstructorException: If the path doesn't exist, is not a folder, 				or doesn't contain the required files. For ordering items by reverse date.  		The first items to appear in a RSS feed are the most recent ones. 		Thus, they are ordered from newest to oldest,  		Args: 			other (RSSItem): The other item to compare  		Returns: 			True if the other's date is more recent, False otherwise Generates a HTML document with the contents of the item.  		The item's "link" could be attempted to open in a browser 		(which is the original intention of the <link> node), so generating 		a simple page might fill the gap.  		The HTML code is constructed with strings because Markdown function 		returns a string, and there was no incentive to make a XHTML document 		from scratch.  		Returns: 			A basic HTML document for displaying in a web browser. #Getting feed's name back from server_info.json Generates a node for a document.  		It uses all the values gathered in the info dictionary to build the 		nodes, so having all keys and values set correctly is necessary for 		this method to work correctly.  		Args: 			document (xml.dom.minidom.Document): The document to which the final node 				will be added  		Returns: 			xml.dom.minidom.Element: This item's node #Link nodes, which use the same text node Support function, reassembles a path from a Path object.  	Used for reconstructing a directory Path object's original path.  	Args; 		path (pathlib.Path): The path object.  	Returns: 		str: Its original path. Support function, creates a datetime object from an item's info attribute.  	Generates a full datetime object with date, time and timezone information 	from the item's dictionary.  	Args: 		stringdate (str): The string representing the date in the format YYYY-MM-DD 			(Y: year, M: month, D: day) 		stringtime (str): The string representing the time in the format HH:MM:SS 			(H: hours, M: minutes, S: seconds) 		stringtz (str): The string representing the timezone in the format xHHMM 			(x: Plus (+) or minus (-) symbol, H: hours, M: minutes)  	Returns: 		datetime.datetime: A datetime object with the required data filled Generates RFC 822 compliant date string  	Creates, from an item's info attribute, the string required for 	the pubDate node, which must be compliant with the standard 	RFC 822: https://www.w3.org/Protocols/rfc822/#z28  	Args: 		info (dict): A dictionary sourced from an item object  	Returns: 		The pubDate string