1
0
mirror of https://github.com/gryf/.vim.git synced 2025-12-17 19:40:29 +01:00

Added unittest for vimblogger_ft, corrected documentation, make pygment optional

This commit is contained in:
2010-12-13 06:12:33 +01:00
parent 388bc749e7
commit a4fad23f26
12 changed files with 1275 additions and 173 deletions

View File

@@ -27,10 +27,6 @@
'snippets' snipMate.txt /*'snippets'* 'snippets' snipMate.txt /*'snippets'*
.snippet snipMate.txt /*.snippet* .snippet snipMate.txt /*.snippet*
.snippets snipMate.txt /*.snippets* .snippets snipMate.txt /*.snippets*
:AcpDisable acp.txt /*:AcpDisable*
:AcpEnable acp.txt /*:AcpEnable*
:AcpLock acp.txt /*:AcpLock*
:AcpUnlock acp.txt /*:AcpUnlock*
:CVSEdit vcscommand.txt /*:CVSEdit* :CVSEdit vcscommand.txt /*:CVSEdit*
:CVSEditors vcscommand.txt /*:CVSEditors* :CVSEditors vcscommand.txt /*:CVSEditors*
:CVSUnedit vcscommand.txt /*:CVSUnedit* :CVSUnedit vcscommand.txt /*:CVSUnedit*
@@ -40,6 +36,8 @@
:CVSWatchOn vcscommand.txt /*:CVSWatchOn* :CVSWatchOn vcscommand.txt /*:CVSWatchOn*
:CVSWatchRemove vcscommand.txt /*:CVSWatchRemove* :CVSWatchRemove vcscommand.txt /*:CVSWatchRemove*
:CVSWatchers vcscommand.txt /*:CVSWatchers* :CVSWatchers vcscommand.txt /*:CVSWatchers*
:Date: vimblogger_ft.txt /*:Date:*
:DeleteBlogArticle vimblogger_ft.txt /*:DeleteBlogArticle*
:DelimitMateReload delimitMate.txt /*:DelimitMateReload* :DelimitMateReload delimitMate.txt /*:DelimitMateReload*
:DelimitMateSwitch delimitMate.txt /*:DelimitMateSwitch* :DelimitMateSwitch delimitMate.txt /*:DelimitMateSwitch*
:DelimitMateTest delimitMate.txt /*:DelimitMateTest* :DelimitMateTest delimitMate.txt /*:DelimitMateTest*
@@ -77,9 +75,15 @@
:FufTag fuf.txt /*:FufTag* :FufTag fuf.txt /*:FufTag*
:FufTagWithCursorWord fuf.txt /*:FufTagWithCursorWord* :FufTagWithCursorWord fuf.txt /*:FufTagWithCursorWord*
:FufTaggedFile fuf.txt /*:FufTaggedFile* :FufTaggedFile fuf.txt /*:FufTaggedFile*
:Id: vimblogger_ft.txt /*:Id:*
:Loremipsum loremipsum.txt /*:Loremipsum* :Loremipsum loremipsum.txt /*:Loremipsum*
:Loreplace loremipsum.txt /*:Loreplace* :Loreplace loremipsum.txt /*:Loreplace*
:Mark mark.txt /*:Mark* :Mark mark.txt /*:Mark*
:Modified: vimblogger_ft.txt /*:Modified:*
:PreviewBlogArticle vimblogger_ft.txt /*:PreviewBlogArticle*
:SendBlogArticle vimblogger_ft.txt /*:SendBlogArticle*
:Tags: vimblogger_ft.txt /*:Tags:*
:Title: vimblogger_ft.txt /*:Title:*
:VCSAdd vcscommand.txt /*:VCSAdd* :VCSAdd vcscommand.txt /*:VCSAdd*
:VCSAnnotate vcscommand.txt /*:VCSAnnotate* :VCSAnnotate vcscommand.txt /*:VCSAnnotate*
:VCSBlame vcscommand.txt /*:VCSBlame* :VCSBlame vcscommand.txt /*:VCSBlame*
@@ -122,9 +126,16 @@
:VimwikiToggleListItem vimwiki.txt /*:VimwikiToggleListItem* :VimwikiToggleListItem vimwiki.txt /*:VimwikiToggleListItem*
:VimwikiUISelect vimwiki.txt /*:VimwikiUISelect* :VimwikiUISelect vimwiki.txt /*:VimwikiUISelect*
:VimwikiVSplitLink vimwiki.txt /*:VimwikiVSplitLink* :VimwikiVSplitLink vimwiki.txt /*:VimwikiVSplitLink*
AnsiEsc AnsiEsc.txt /*AnsiEsc*
AnsiEsc-contents AnsiEsc.txt /*AnsiEsc-contents*
AnsiEsc-copyright AnsiEsc.txt /*AnsiEsc-copyright*
AnsiEsc-history AnsiEsc.txt /*AnsiEsc-history*
AnsiEsc-manual AnsiEsc.txt /*AnsiEsc-manual*
AnsiEsc.txt AnsiEsc.txt /*AnsiEsc.txt*
ExtractSnips() snipMate.txt /*ExtractSnips()* ExtractSnips() snipMate.txt /*ExtractSnips()*
ExtractSnipsFile() snipMate.txt /*ExtractSnipsFile()* ExtractSnipsFile() snipMate.txt /*ExtractSnipsFile()*
Filename() snipMate.txt /*Filename()* Filename() snipMate.txt /*Filename()*
Id vimblogger_ft.txt /*Id*
ResetSnippets() snipMate.txt /*ResetSnippets()* ResetSnippets() snipMate.txt /*ResetSnippets()*
ShowMarksClearAll showmarks.txt /*ShowMarksClearAll* ShowMarksClearAll showmarks.txt /*ShowMarksClearAll*
ShowMarksClearMark showmarks.txt /*ShowMarksClearMark* ShowMarksClearMark showmarks.txt /*ShowMarksClearMark*
@@ -156,21 +167,6 @@ VCSCommandSplit vcscommand.txt /*VCSCommandSplit*
VCSCommandVCSTypeOverride vcscommand.txt /*VCSCommandVCSTypeOverride* VCSCommandVCSTypeOverride vcscommand.txt /*VCSCommandVCSTypeOverride*
VimwikiWeblinkHandler vimwiki.txt /*VimwikiWeblinkHandler* VimwikiWeblinkHandler vimwiki.txt /*VimwikiWeblinkHandler*
abc fuf.txt /*abc* abc fuf.txt /*abc*
acp acp.txt /*acp*
acp-about acp.txt /*acp-about*
acp-author acp.txt /*acp-author*
acp-changelog acp.txt /*acp-changelog*
acp-commands acp.txt /*acp-commands*
acp-contact acp.txt /*acp-contact*
acp-installation acp.txt /*acp-installation*
acp-introduction acp.txt /*acp-introduction*
acp-options acp.txt /*acp-options*
acp-perl-omni acp.txt /*acp-perl-omni*
acp-snipMate acp.txt /*acp-snipMate*
acp-thanks acp.txt /*acp-thanks*
acp-usage acp.txt /*acp-usage*
acp.txt acp.txt /*acp.txt*
autocomplpop acp.txt /*autocomplpop*
b:VCSCommandCommand vcscommand.txt /*b:VCSCommandCommand* b:VCSCommandCommand vcscommand.txt /*b:VCSCommandCommand*
b:VCSCommandOriginalBuffer vcscommand.txt /*b:VCSCommandOriginalBuffer* b:VCSCommandOriginalBuffer vcscommand.txt /*b:VCSCommandOriginalBuffer*
b:VCSCommandSourceFile vcscommand.txt /*b:VCSCommandSourceFile* b:VCSCommandSourceFile vcscommand.txt /*b:VCSCommandSourceFile*
@@ -204,6 +200,7 @@ delimitMateSyntax delimitMate.txt /*delimitMateSyntax*
delimitMateTodo delimitMate.txt /*delimitMateTodo* delimitMateTodo delimitMate.txt /*delimitMateTodo*
delimitMateVisualWrapping delimitMate.txt /*delimitMateVisualWrapping* delimitMateVisualWrapping delimitMate.txt /*delimitMateVisualWrapping*
delimitMate_WithinEmptyPair delimitMate.txt /*delimitMate_WithinEmptyPair* delimitMate_WithinEmptyPair delimitMate.txt /*delimitMate_WithinEmptyPair*
docinfo vimblogger_ft.txt /*docinfo*
ds surround.txt /*ds* ds surround.txt /*ds*
fuf fuf.txt /*fuf* fuf fuf.txt /*fuf*
fuf#setOneTimeVariables() fuf.txt /*fuf#setOneTimeVariables()* fuf#setOneTimeVariables() fuf.txt /*fuf#setOneTimeVariables()*
@@ -272,32 +269,14 @@ fuf-usage fuf.txt /*fuf-usage*
fuf-vimrc-example fuf.txt /*fuf-vimrc-example* fuf-vimrc-example fuf.txt /*fuf-vimrc-example*
fuf.txt fuf.txt /*fuf.txt* fuf.txt fuf.txt /*fuf.txt*
fuzzyfinder fuf.txt /*fuzzyfinder* fuzzyfinder fuf.txt /*fuzzyfinder*
g:acp_behavior acp.txt /*g:acp_behavior* g:blogger_browser vimblogger_ft.txt /*g:blogger_browser*
g:acp_behavior-command acp.txt /*g:acp_behavior-command* g:blogger_confirm_del vimblogger_ft.txt /*g:blogger_confirm_del*
g:acp_behavior-completefunc acp.txt /*g:acp_behavior-completefunc* g:blogger_draft vimblogger_ft.txt /*g:blogger_draft*
g:acp_behavior-meets acp.txt /*g:acp_behavior-meets* g:blogger_login vimblogger_ft.txt /*g:blogger_login*
g:acp_behavior-onPopupClose acp.txt /*g:acp_behavior-onPopupClose* g:blogger_maxarticles vimblogger_ft.txt /*g:blogger_maxarticles*
g:acp_behavior-repeat acp.txt /*g:acp_behavior-repeat* g:blogger_name vimblogger_ft.txt /*g:blogger_name*
g:acp_behaviorCssOmniPropertyLength acp.txt /*g:acp_behaviorCssOmniPropertyLength* g:blogger_pass vimblogger_ft.txt /*g:blogger_pass*
g:acp_behaviorCssOmniValueLength acp.txt /*g:acp_behaviorCssOmniValueLength* g:blogger_stylesheets vimblogger_ft.txt /*g:blogger_stylesheets*
g:acp_behaviorFileLength acp.txt /*g:acp_behaviorFileLength*
g:acp_behaviorHtmlOmniLength acp.txt /*g:acp_behaviorHtmlOmniLength*
g:acp_behaviorKeywordCommand acp.txt /*g:acp_behaviorKeywordCommand*
g:acp_behaviorKeywordIgnores acp.txt /*g:acp_behaviorKeywordIgnores*
g:acp_behaviorKeywordLength acp.txt /*g:acp_behaviorKeywordLength*
g:acp_behaviorPerlOmniLength acp.txt /*g:acp_behaviorPerlOmniLength*
g:acp_behaviorPythonOmniLength acp.txt /*g:acp_behaviorPythonOmniLength*
g:acp_behaviorRubyOmniMethodLength acp.txt /*g:acp_behaviorRubyOmniMethodLength*
g:acp_behaviorRubyOmniSymbolLength acp.txt /*g:acp_behaviorRubyOmniSymbolLength*
g:acp_behaviorSnipmateLength acp.txt /*g:acp_behaviorSnipmateLength*
g:acp_behaviorUserDefinedFunction acp.txt /*g:acp_behaviorUserDefinedFunction*
g:acp_behaviorUserDefinedMeets acp.txt /*g:acp_behaviorUserDefinedMeets*
g:acp_behaviorXmlOmniLength acp.txt /*g:acp_behaviorXmlOmniLength*
g:acp_completeOption acp.txt /*g:acp_completeOption*
g:acp_completeoptPreview acp.txt /*g:acp_completeoptPreview*
g:acp_enableAtStartup acp.txt /*g:acp_enableAtStartup*
g:acp_ignorecaseOption acp.txt /*g:acp_ignorecaseOption*
g:acp_mappingDriven acp.txt /*g:acp_mappingDriven*
g:fuf_abbrevMap fuf.txt /*g:fuf_abbrevMap* g:fuf_abbrevMap fuf.txt /*g:fuf_abbrevMap*
g:fuf_autoPreview fuf.txt /*g:fuf_autoPreview* g:fuf_autoPreview fuf.txt /*g:fuf_autoPreview*
g:fuf_bookmarkdir_keyDelete fuf.txt /*g:fuf_bookmarkdir_keyDelete* g:fuf_bookmarkdir_keyDelete fuf.txt /*g:fuf_bookmarkdir_keyDelete*
@@ -440,18 +419,6 @@ mark-usage mark.txt /*mark-usage*
mark.txt mark.txt /*mark.txt* mark.txt mark.txt /*mark.txt*
mark.vim mark.txt /*mark.vim* mark.vim mark.txt /*mark.vim*
multi_snip snipMate.txt /*multi_snip* multi_snip snipMate.txt /*multi_snip*
project project.txt /*project*
project-adding-mappings project.txt /*project-adding-mappings*
project-example project.txt /*project-example*
project-flags project.txt /*project-flags*
project-inheritance project.txt /*project-inheritance*
project-invoking project.txt /*project-invoking*
project-mappings project.txt /*project-mappings*
project-plugin project.txt /*project-plugin*
project-settings project.txt /*project-settings*
project-syntax project.txt /*project-syntax*
project-tips project.txt /*project-tips*
project.txt project.txt /*project.txt*
py2stdlib py2stdlib.txt /*py2stdlib* py2stdlib py2stdlib.txt /*py2stdlib*
py2stdlib-__future__ py2stdlib.txt /*py2stdlib-__future__* py2stdlib-__future__ py2stdlib.txt /*py2stdlib-__future__*
py2stdlib-__main__ py2stdlib.txt /*py2stdlib-__main__* py2stdlib-__main__ py2stdlib.txt /*py2stdlib-__main__*
@@ -903,6 +870,7 @@ vcscommand-ssh-wrapper vcscommand.txt /*vcscommand-ssh-wrapper*
vcscommand-statusline vcscommand.txt /*vcscommand-statusline* vcscommand-statusline vcscommand.txt /*vcscommand-statusline*
vcscommand.txt vcscommand.txt /*vcscommand.txt* vcscommand.txt vcscommand.txt /*vcscommand.txt*
vgS surround.txt /*vgS* vgS surround.txt /*vgS*
vimblogger_ft.txt vimblogger_ft.txt /*vimblogger_ft.txt*
vimwiki vimwiki.txt /*vimwiki* vimwiki vimwiki.txt /*vimwiki*
vimwiki-calendar vimwiki.txt /*vimwiki-calendar* vimwiki-calendar vimwiki.txt /*vimwiki-calendar*
vimwiki-changelog vimwiki.txt /*vimwiki-changelog* vimwiki-changelog vimwiki.txt /*vimwiki-changelog*

View File

@@ -15,7 +15,7 @@ Other requirements:
- Python (tested with version 2.6, should work also in others) - Python (tested with version 2.6, should work also in others)
- gdata http://code.google.com/p/gdata-python-client - gdata http://code.google.com/p/gdata-python-client
- docutils http://docutils.sourceforge.net - docutils http://docutils.sourceforge.net
- pygments http://pygments.org - pygments http://pygments.org (optional)
- Blogger account - Blogger account
----------------------------------------------------------------------- -----------------------------------------------------------------------
@@ -138,22 +138,22 @@ tincidunt, luctus a, sodales eget, leo. Sed ligula augue, cursus et.
----->8----- ----->8-----
reST document (optionally) starts with *docinfo* section (first several reST document (optionally) starts with *docinfo* section (first several
lines, that are starting from *:* character) separaded from other lines, that are starting from ":" character) separaded from other
content with one empty line. content with one empty line.
Docinfo items holds article attributes, and are updated automatically Docinfo items holds article attributes, and are updated automatically
every each of upload to blogger, which is triggered by every each of upload to blogger, which is triggered by
*:SendBlogArticle* command. ":SendBlogArticle" command.
*:Id:* *:Id:*
Holds article id on blogger side. If not defined, new article will Holds article id on blogger side. If not defined, new article will
be created (even if there is already existing one with the very same be created (even if there is already existing one with the very same
content). If wrong id is entered (or an Id of deleted article), content). If wrong Id is entered (or an Id of deleted article),
exception will be raised, and no action on blogger side will be exception will be raised, and no action on blogger side will be
performed. performed.
*:Title:* *:Title:*
Holds article title. Can be changed when *:Id:* is obtained. Holds article title. Can be changed when |:Id:| is obtained.
*:Date:* *:Date:*
This is published date in RFC 3339 This is published date in RFC 3339
@@ -173,8 +173,9 @@ All other items are ignored.
After docinfo block, article body should be placed using markup for After docinfo block, article body should be placed using markup for
reStructuredText. reStructuredText.
Additionally, there is sourcecode directive, simple syntax highlighter Additionally, if pytgments is installed, there is sourcecode directive, simple
using Pygments module. Very simple usage could be as follows: syntax highlighter using Pygments module. Very simple usage could be as
follows:
-----8<----- -----8<-----
.. sourcecode:: python .. sourcecode:: python
@@ -184,7 +185,7 @@ using Pygments module. Very simple usage could be as follows:
----->8----- ----->8-----
Note: All headings for generated HTML by *:SendBlogArticle* will be Note: All headings for generated HTML by |:SendBlogArticle| will be
shifted by 3, so the first heading will become <h3>, second <h4> and so shifted by 3, so the first heading will become <h3>, second <h4> and so
on, to fit into blogger template (well, most of them). Remember, that on, to fit into blogger template (well, most of them). Remember, that
HTML allow up to 6 level of headings, while reST doesn't have this HTML allow up to 6 level of headings, while reST doesn't have this

View File

@@ -1 +1 @@
# module vimblogger # module rst2blogger

View File

@@ -1,46 +1,12 @@
# vim: fileencoding=utf8 """
# File: blogger.py
# Blogger interface to make easy way to create/update articles for specified Author: Roman 'gryf' Dobosz
# blog. Description: This is blogger activity connected module. It is using gdata[1]
# blogger module to provide add/modify/delete articles interface.
# It is assumed one way communication only, so you may create or update an
# article from reST source files. There is no way to recreate article from [1] http://code.google.com/p/gdata-python-client
# html to reST format. """
#
# requirements:
#
# - Vim compiled with +python
# - python 2.x (tested with 2.6)
# - modules
# - gdata (http://code.google.com/p/gdata-python-client)
# - docutils (http://docutils.sourceforge.net)
#
# USE CASES:
# 1. Create new post
#
# use reST template:
# ===8<---
# :Title: Blog post title
# :Date: optional publish date (for example: 2010-11-28 18:47:05),
# default: now()
# :Modified: optional, default: None
# :Tags: comma separated blog tags
#
# .. more
#
# --->8===
#
# All four docinfo are optional, however it is nice to give at least a title
# to the article :)
#
#
#
# which is provided under templates directory or as a
# snipMate shoortcut (see .vim/snippets/rst.snippets)
#
#-----------------------------------------------------------------------------
#
import datetime import datetime
import re import re
@@ -51,6 +17,7 @@ from gdata.blogger.data import BlogPost
class VimBlogger(object): class VimBlogger(object):
""" """
Communicate with blogger through gdata.blogger modules
""" """
DATE_PATTERN = re.compile(r"^(\d{4}-\d{2}-\d{2})" DATE_PATTERN = re.compile(r"^(\d{4}-\d{2}-\d{2})"
"T(\d{2}:\d{2}:\d{2})(\.\d{3})?[+-]" "T(\d{2}:\d{2}:\d{2})(\.\d{3})?[+-]"
@@ -59,12 +26,9 @@ class VimBlogger(object):
TIME_FORMAT = "%H:%M:%S" TIME_FORMAT = "%H:%M:%S"
TZ_FORMAT = "%H:%M" TZ_FORMAT = "%H:%M"
# TODO: dodać usuwanie artykułów (prosta lista, wybieramy art,
# potwierdzamy)
# TODO: Dokumentacja jako vimdoc!
def __init__(self, blogname, login, password): def __init__(self, blogname, login, password):
""" """
Initialization.
""" """
self.draft = True self.draft = True
self.blog_id = None self.blog_id = None
@@ -79,7 +43,6 @@ class VimBlogger(object):
""" """
Return list of articles Return list of articles
""" """
feed = self.client.get_posts(self.blog_id) feed = self.client.get_posts(self.blog_id)
posts = [] posts = []
@@ -91,7 +54,6 @@ class VimBlogger(object):
self._extract_date(entry.published.text))) self._extract_date(entry.published.text)))
return posts return posts
def create_article(self, html_doc, attrs=None): def create_article(self, html_doc, attrs=None):
""" """
Create new article Create new article
@@ -118,7 +80,7 @@ class VimBlogger(object):
new_post.published = atom.data.Published(text=attrs['date']) new_post.published = atom.data.Published(text=attrs['date'])
if self.draft: if self.draft:
new_post.control = atom.data.Control(\ new_post.control = atom.data.Control(\
draft=atom.data.Draft(text='yes')) draft=atom.data.Draft(text='yes'))
return self.client.post(new_post, BLOG_POST_URL % self.blog_id) return self.client.post(new_post, BLOG_POST_URL % self.blog_id)
@@ -137,7 +99,7 @@ class VimBlogger(object):
post = self._get_post(attrs['id']) post = self._get_post(attrs['id'])
post.content = atom.data.Content(text=html_doc, type="html") post.content = atom.data.Content(text=html_doc, type="html")
# update publish date # update published date
if 'date' in attrs and attrs['date'] and \ if 'date' in attrs and attrs['date'] and \
self._check_date(attrs['date']): self._check_date(attrs['date']):
post.published = atom.data.Published(text=attrs['date']) post.published = atom.data.Published(text=attrs['date'])
@@ -168,9 +130,9 @@ class VimBlogger(object):
self.client.delete(post) self.client.delete(post)
return None return None
def _get_post(self, post_id): def _get_post(self, post_id):
""" """
Return post with specified ID
""" """
post_href = self.blog.get_post_link().href post_href = self.blog.get_post_link().href
return self.client.get_feed(post_href + "/%s" % post_id, return self.client.get_feed(post_href + "/%s" % post_id,
@@ -227,18 +189,13 @@ class VimBlogger(object):
return True return True
def _update_date(self, post, attrs):
"""
Update articles published date
"""
def _authorize(self, login, password): def _authorize(self, login, password):
""" """
Try to authorize in Google service. Try to authorize in Google service.
Authorization is kept in client object. In case of wrong credentials, Authorization is kept in client object. In case of wrong credentials,
exception is thrown. exception is thrown.
""" """
source = 'Blogger_Python_Sample-2.0' source = 'Vim rst2blogger interface'
service = 'blogger' service = 'blogger'
self.client.client_login(login, self.client.client_login(login,
@@ -255,4 +212,3 @@ class VimBlogger(object):
self.blog_id = blog.get_blog_id() self.blog_id = blog.get_blog_id()
self.blog = blog self.blog = blog
break break

View File

@@ -1,4 +1,11 @@
# vim: fileencoding=utf8 # vim: fileencoding=utf8
"""
File: main.py
Author: Roman 'gryf' Dobosz
Description: main file to provide fuctionality between vim and moudles rest
and blogger
"""
import webbrowser import webbrowser
from xml.dom import minidom from xml.dom import minidom
from xml.parsers.expat import ExpatError from xml.parsers.expat import ExpatError
@@ -35,7 +42,7 @@ class Rst2Blogger(object):
systems' web browser systems' web browser
""" """
bufcontent = "\n".join(self.buff) bufcontent = "\n".join(self.buff)
name = vim.current.buffer.name name = self.buff.name
name = name[:-4] + ".html" name = name[:-4] + ".html"
html = blogPreview(bufcontent, self.stylesheets) html = blogPreview(bufcontent, self.stylesheets)
@@ -51,7 +58,10 @@ class Rst2Blogger(object):
return "Generated HTML has been written to %s" % name return "Generated HTML has been written to %s" % name
def post(self): def post(self):
bufcontent = "\n".join(vim.current.buffer) """
Do post article
"""
bufcontent = "\n".join(self.buff)
html, attrs = blogArticleString(bufcontent) html, attrs = blogArticleString(bufcontent)
parse_msg = self._check_html(html, True) parse_msg = self._check_html(html, True)
@@ -60,7 +70,8 @@ class Rst2Blogger(object):
return "There are errors in generated document" return "There are errors in generated document"
if not self.password: if not self.password:
self.password = vim.eval('inputsecret("Enter your gmail password: ")') self.password = \
vim.eval('inputsecret("Enter your gmail password: ")')
blog = VimBlogger(self.blogname, self.login, self.password) blog = VimBlogger(self.blogname, self.login, self.password)
blog.draft = self.draft > 0 blog.draft = self.draft > 0
@@ -74,9 +85,6 @@ class Rst2Blogger(object):
msg = "New article with id %s has been created" % \ msg = "New article with id %s has been created" % \
post.get_post_id() post.get_post_id()
if not post:
return "There is something fishy with creating new article."
for item, value in (('id', post.get_post_id()), for item, value in (('id', post.get_post_id()),
('date', post.published.text), ('date', post.published.text),
('title', post.title.text), ('title', post.title.text),
@@ -92,14 +100,15 @@ class Rst2Blogger(object):
delete delete
""" """
if not self.password: if not self.password:
self.password = vim.eval('inputsecret("Enter your gmail password: ")') self.password = \
vim.eval('inputsecret("Enter your gmail password: ")')
blog = VimBlogger(self.blogname, self.login, self.password) blog = VimBlogger(self.blogname, self.login, self.password)
posts = blog.get_articles(self.maxarticles) posts = blog.get_articles(self.maxarticles)
msg = u"inputlist([" msg = u"inputlist(["
for index, entries in enumerate(posts): for index, entries in enumerate(posts):
line = "%2d %s %s" % (index+1, line = "%2d %s %s" % (index + 1,
entries[1], entries[1],
entries[2]) entries[2])
msg += u'"' + line.replace('"', '\\"') + u'",' msg += u'"' + line.replace('"', '\\"') + u'",'
@@ -109,8 +118,9 @@ class Rst2Blogger(object):
choice = int(vim.eval(msg)) choice = int(vim.eval(msg))
if choice: if choice:
art = posts[choice-1] art = posts[choice - 1]
msg = 'confirm("You are about to delete article \'%s\'. Are you sure?"' msg = 'confirm("You are about to delete article \'%s\'. '
msg += 'Are you sure?"'
msg = unicode(msg % art[1]).encode(self.vim_encoding) msg = unicode(msg % art[1]).encode(self.vim_encoding)
msg += ', "&No\n&Yes")' msg += ', "&No\n&Yes")'
@@ -120,14 +130,10 @@ class Rst2Blogger(object):
choice = 2 choice = 2
if choice == 2: if choice == 2:
result = blog.delete_article(art[0]) blog.delete_article(art[0])
if result is None: return "Article deleted"
return "Article deleted"
else:
return result
return "No articles deleted" return "No articles deleted"
def _update_docinfo(self, attr, val): def _update_docinfo(self, attr, val):
""" """
Update current buffer with attributes value Update current buffer with attributes value
@@ -183,6 +189,9 @@ class Rst2Blogger(object):
returns empty string if parses succeed, else exception message. returns empty string if parses succeed, else exception message.
""" """
# minidom doesn't understand html entities like '&nbsp;' For checking
# purpose it is perfectly ok, to switch them with '&amp;'
html = html.replace("&nbsp;", "&amp;")
if add_container: if add_container:
html = "<div>" + html + "</div>" html = "<div>" + html + "</div>"
@@ -193,5 +202,3 @@ class Rst2Blogger(object):
message = str(ex) message = str(ex)
return message return message

View File

@@ -1,3 +1,10 @@
"""
File: rest.py
Author: Roman 'gryf' Dobosz
Description: This module is responsible for conversion between reST and HTML
with some goods added.
"""
import re import re
from docutils import core from docutils import core
@@ -5,35 +12,40 @@ from docutils import nodes
from docutils.parsers.rst import directives, Directive from docutils.parsers.rst import directives, Directive
from docutils.writers.html4css1 import Writer, HTMLTranslator from docutils.writers.html4css1 import Writer, HTMLTranslator
from pygments import highlight try:
from pygments.lexers import get_lexer_by_name, TextLexer from pygments import highlight
from pygments.formatters import HtmlFormatter from pygments.lexers import get_lexer_by_name, TextLexer
from pygments.formatters import HtmlFormatter
class Pygments(Directive):
"""
Source code syntax highlighting.
"""
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
has_content = True
def run(self):
self.assert_has_content()
try:
lexer = get_lexer_by_name(self.arguments[0])
except ValueError:
# no lexer found - use the text one instead of an exception
lexer = TextLexer()
# take an arbitrary option if more than one is given
formatter = HtmlFormatter(noclasses=True)
parsed = highlight(u'\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]
directives.register_directive('sourcecode', Pygments)
except ImportError:
pass
class Attrs(object): class Attrs(object):
ATTRS = {} ATTRS = {}
class Pygments(Directive):
"""
Source code syntax highlighting.
"""
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
has_content = True
def run(self):
self.assert_has_content()
try:
lexer = get_lexer_by_name(self.arguments[0])
except ValueError:
# no lexer found - use the text one instead of an exception
lexer = TextLexer()
# take an arbitrary option if more than one is given
formatter = HtmlFormatter(noclasses=True)
parsed = highlight(u'\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]
directives.register_directive('sourcecode', Pygments)
class CustomHTMLTranslator(HTMLTranslator): class CustomHTMLTranslator(HTMLTranslator):
""" """
@@ -41,7 +53,6 @@ class CustomHTMLTranslator(HTMLTranslator):
There are couple of customizations for docinfo fields behaviour and There are couple of customizations for docinfo fields behaviour and
abbreviations and acronyms. abbreviations and acronyms.
""" """
def __init__(self, document): def __init__(self, document):
""" """
Set some nice defaults for articles translations Set some nice defaults for articles translations
@@ -143,6 +154,7 @@ class CustomHTMLTranslator(HTMLTranslator):
else: else:
self.body.append(self.starttag(node, 'abbr', '')) self.body.append(self.starttag(node, 'abbr', ''))
class NoHeaderHTMLTranslator(CustomHTMLTranslator): class NoHeaderHTMLTranslator(CustomHTMLTranslator):
""" """
Special subclass for generating only body of an article Special subclass for generating only body of an article
@@ -154,7 +166,7 @@ class NoHeaderHTMLTranslator(CustomHTMLTranslator):
CustomHTMLTranslator.__init__(self, document) CustomHTMLTranslator.__init__(self, document)
self.head = [] self.head = []
self.meta = [] self.meta = []
self.head_prefix = ['','','','',''] self.head_prefix = ['', '', '', '', '']
self.body_prefix = [] self.body_prefix = []
self.body_suffix = [] self.body_suffix = []
self.stylesheet = [] self.stylesheet = []
@@ -173,11 +185,13 @@ class NoHeaderHTMLTranslator(CustomHTMLTranslator):
""" """
Attrs.ATTRS['date'] = node.astext() Attrs.ATTRS['date'] = node.astext()
class PreviewHTMLTranslator(CustomHTMLTranslator): class PreviewHTMLTranslator(CustomHTMLTranslator):
""" """
Class for display article in the browser as a preview. Class for display article in the browser as a preview.
""" """
CSS = [] CSS = []
def __init__(self, document): def __init__(self, document):
""" """
Alter levels for the heading tags, define custom, blog specific Alter levels for the heading tags, define custom, blog specific
@@ -193,7 +207,6 @@ class PreviewHTMLTranslator(CustomHTMLTranslator):
for css in self.default_stylesheets] for css in self.default_stylesheets]
self.body_ = [] self.body_ = []
def depart_docinfo(self, node): def depart_docinfo(self, node):
""" """
Overwrite body with some custom one. body_ will hold the first heading Overwrite body with some custom one. body_ will hold the first heading
@@ -203,7 +216,7 @@ class PreviewHTMLTranslator(CustomHTMLTranslator):
def visit_field(self, node): def visit_field(self, node):
""" """
Additional 'keyword' for the ODF metadata Make title visible as a heading
""" """
key, node_ = [n.astext() for n in node] key, node_ = [n.astext() for n in node]
key = key.lower() key = key.lower()
@@ -212,6 +225,7 @@ class PreviewHTMLTranslator(CustomHTMLTranslator):
self.body_.append('<h1 class="post-title entry-title">' self.body_.append('<h1 class="post-title entry-title">'
'<a href="#">%s</a></h1>\n' % self.encode(node_)) '<a href="#">%s</a></h1>\n' % self.encode(node_))
class BlogBodyWriter(Writer): class BlogBodyWriter(Writer):
""" """
Custom Writer class for generating HTML partial with the article Custom Writer class for generating HTML partial with the article
@@ -224,6 +238,7 @@ class BlogBodyWriter(Writer):
self.document.settings.output_encoding = "utf-8" self.document.settings.output_encoding = "utf-8"
Writer.translate(self) Writer.translate(self)
class BlogPreviewWriter(Writer): class BlogPreviewWriter(Writer):
""" """
Custom Writer class for generating full HTML of the article Custom Writer class for generating full HTML of the article
@@ -253,6 +268,7 @@ def blogPreview(string, stylesheets=None):
html_output = html_output.replace("<!-- more -->", "\n<!-- more -->\n") html_output = html_output.replace("<!-- more -->", "\n<!-- more -->\n")
return html_output return html_output
def blogArticleString(string): def blogArticleString(string):
""" """
Returns partial HTML of the article, and attribute dictionary Returns partial HTML of the article, and attribute dictionary
@@ -269,4 +285,3 @@ def blogArticleString(string):
attrs[key] = Attrs.ATTRS[key] attrs[key] = Attrs.ATTRS[key]
return html_output, attrs return html_output, attrs

View File

@@ -0,0 +1 @@
# module rst2blogger.tests

View File

@@ -0,0 +1,267 @@
# vim: set fileencoding=utf-8
import sys
import os
from datetime import datetime
from tempfile import mkstemp
LOGIN = "John"
PASS = "secret"
REST_ARTICLE = u""":Title: Title — This is a test
:Date: 2010-12-12T12:36:36+01:00
:Tags: this is a test, Blogger, rest
.. meta::
:description: meta are completely ignored in blogger parsers
`Amet`, convallis sollicitudin, commodo a, purus. Nulla vitae eros a diam
blandit **mollis**. Proin luctus ``ls --color ~/`` feugiat eros.
.. more
Pellentesque habitant morbi tristique senectus et *netus* et malesuada fames
ac turpis egestas. Duis ultricies urna: ``easy_install pygments``. Etiam enim
urna, pharetra suscipit, varius et, congue quis, odio. Donec `NES <Nintendo
Entertainment System>`:acronym: lobortis, elit bibendum euismod faucibus,
velit nibh egestas libero, vitae pellentesque elit augue ut massa.
test empty `acronym`:acronym: and `abbrev`:abbreviation:
Section 1
---------
Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a,
facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus
orci luctus et ultrices posuere cubilia Curae; `WTF? <What the
fcuk?>`:abbreviation: Duis imperdiet eleifend arcu. Cras magna ligula,
consequat at, tempor non, posuere.
Subsection 1.1
..............
.. sourcecode:: python
import vim
print vim.current.buffer.name
.. sourcecode:: unknown_lexer
Cras dignissim vulputate metus.
Phasellus eu quam. Quisque interdum cursus purus. In.
End.
"""
class Eval(object):
"""
Communication class
"""
value = ""
blog = None
gdata_delete = 0
class Dummy(sys.__class__):
"""
Dummy class, for faking modules and other objects, not directly needed
"""
def __getattr__(self, attrname):
""" The dummy class should have no attribute """
if attrname == 'util':
return Dummy("util")
return None
# fake vim module.
sys.modules["vim"] = Dummy("vim")
class MockBuffer(list):
"""
Vim buffer-like class
"""
def append(self, val, line=None):
"""
Override append method to mimic vim.buffer append behaviour
"""
if line is None:
super(MockBuffer, self).append(val)
else:
super(MockBuffer, self).insert(line, val)
class Mock(object):
"""
Generic all-purpose mock class
"""
pass
import vim
vim.command = lambda x: None
vim.current = Mock()
vim.current.buffer = MockBuffer(REST_ARTICLE.split("\n"))
fdesc, vim.current.buffer.name = mkstemp()
vim.current.buffer.name += ".rst"
os.close(fdesc) # close descriptor, only filename is needed
def mock_vim_eval(string):
ints = ("g:blogger_draft", "g:blogger_maxarticles",
"g:blogger_confirm_del")
if string in ints:
return "0"
elif string == "g:blogger_stylesheets":
return []
else:
return Eval.value
vim.eval = mock_vim_eval
class MockBlog(object):
"""
Mock blog class
"""
def __init__(self, name, id):
self.name = name
self.id = id
def get_blog_name(self):
return self.name
def get_blog_id(self):
return self.id
def get_post_link(self):
link = Mock()
link.href = "http://www.mock.org"
return link
def get_post_id(self):
return self.id
class MockPost(object):
"""
Mock class imitating posts
"""
def __init__(self):
self.category = Mock()
self.category = []
self.id = None
self.title = Mock()
self.title.text = ""
self.published = Mock()
self.published.text = ""
def add_label(self, label):
item = Mock()
item.term = label
self.category.append(item)
def get_post_id(self):
return self.id
class MockBlogFeed(object):
"""
Mock class for feed objects
"""
def __init__(self, *args, **kwargs):
self.entry = []
if Eval.blog:
for bid, bname in {1: 'one', 3: 'test', 7: 'blog_name'}.items():
blog = MockBlog(bname, bid)
self.entry.append(blog)
class MockPostFeed(object):
"""
Mock class for feed objects
"""
def __init__(self, *args, **kwargs):
self.entry = []
from atom.data import Id, Updated
from gdata.blogger.client import BloggerClient
BloggerClient.get_blogs = lambda x: MockBlogFeed()
from gdata.client import BadAuthentication
def mock_client_login(self, login, password, source=None, service=None):
"""
Mock method for client login.
"""
if login != LOGIN or password != PASS:
raise BadAuthentication("Incorrect username or password")
BloggerClient.client_login = mock_client_login
def mock_client_post(self, post, url=None):
"""
Mimic post method
"""
if Eval.value == 10:
return None
new_id = Id(text='1234567890')
post.id = new_id
date = datetime.utcnow()
milli = str(date.microsecond)[:3]
date = date.strftime("%Y-%m-%dT%H:%M:%S")
date = date + ".%s+00:00" % milli
post.updated = Updated(text=date)
return post
BloggerClient.post = mock_client_post
BloggerClient.update = mock_client_post
def mock_client_delete(self, post):
"""
Mock delete method
"""
if not post:
raise AttributeError("%s object has no attribute 'etag'" % type(post))
if Eval.gdata_delete:
return "404 Mock"
return None
BloggerClient.delete = mock_client_delete
def mock_client_get_posts(self, blog_id):
"""
Mock get_posts method
"""
posts = (('title1', 1, "2000-01-01T00:04:00.001+01:00"),
('title2', 2, "2001-01-01T00:02:19.001+01:00"),
('title3', 3, "2002-01-01T00:01:00.001+01:00"),
('title4', 4, "2006-01-01T00:02:00.001+02:00"))
feed = MockPostFeed()
for p in posts:
a = MockPost()
a.id = p[1]
a.title.text = p[0]
a.published.text = p[2]
feed.entry.append(a)
return feed
BloggerClient.get_posts = mock_client_get_posts
def mock_client_get_feed(self, uri, desired_class=None):
"""
Mock get_feed method
"""
post = MockPost()
post.add_label('test1')
return post
BloggerClient.get_feed = mock_client_get_feed
from gdata.blogger.data import BlogPost
def mock_get_post_id(self):
return self.id.text
BlogPost.get_post_id = mock_get_post_id

View File

@@ -0,0 +1,514 @@
import os
import sys
import unittest
this_dir = os.path.dirname(os.path.abspath(__file__))
this_dir = os.path.abspath(os.path.join(this_dir, "../.."))
sys.path.insert(0, this_dir)
from rst2blogger.tests import shared
from rst2blogger.blogger import VimBlogger
class TestCheckDates(unittest.TestCase):
"""
Tests for method VimBlogger._check_date
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimb = VimBlogger(None, shared.LOGIN, shared.PASS)
def test_happy_case_CET(self):
"""
Test on good date string on Central and East Europe
"""
date = "2000-01-01T00:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
def test_happy_case_HST(self):
"""
Test on good date string on Hawaii Time Zone
"""
date = "2000-01-01T00:00:00.001-10:00"
self.assertTrue(self.vimb._check_date(date))
def test_happy_case_GMT(self):
"""
Test UTC date string
"""
date = "2000-01-01T00:00:00.001-00:00"
self.assertTrue(self.vimb._check_date(date))
def test_without_milliseconds(self):
"""
Test on date string without milliseconds
"""
date = "2000-01-01T00:00:00+01:00"
self.assertTrue(self.vimb._check_date(date))
def test_wrong_tz_format(self):
"""
Test date with wrong timezone format (hour have no leading 0)
"""
date = "2000-01-01T00:00:00.001+1:00"
self.assertFalse(self.vimb._check_date(date))
# Test date with wrong timezone format (minute have only one digit)
date = "2000-01-01T00:00:00.001+01:0"
self.assertFalse(self.vimb._check_date(date))
# Test date with wrong timezone format (hours and minutes hasn't been
# separated by colon)
date = "2000-01-01T00:00:00.001+0100"
self.assertFalse(self.vimb._check_date(date))
def test_wrong_milliseconds(self):
"""
Test date with wrong format of milliseconds (.01 instead of .010)
"""
date = "2000-01-01T00:00:00.01+01:00"
self.assertFalse(self.vimb._check_date(date))
# Test date with wrong format of milliseconds (.1 instead of .100)
date = "2000-01-01T00:00:00.1+01:00"
self.assertFalse(self.vimb._check_date(date))
# Test date with spolied format (dot for milliseconds, but no digits)
date = "2000-01-01T00:00:00.+01:00"
self.assertFalse(self.vimb._check_date(date))
def test_good_milliseconds(self):
"""
Test date with correct format of milliseconds
"""
date = "2000-01-01T00:00:00.000+01:00"
self.assertTrue(self.vimb._check_date(date), date + " is incorrect")
date = "2000-01-01T00:00:00.999+01:00"
self.assertTrue(self.vimb._check_date(date), date + " is incorrect")
def test_wrong_hours(self):
"""
Test date with wrong hours value
"""
date = "2000-01-01T24:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
def test_good_hours(self):
"""
Test date with correct hours values
"""
date = "2000-01-01T00:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date), date + " is incorrect")
date = "2000-01-01T23:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date), date + " is incorrect")
def test_wrong_minutes(self):
"""
Test date with wrong minutes value
"""
date = "2000-01-01T00:60:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
date = "2000-01-01T00:000:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
date = "2000-01-01T00:1:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
def test_good_minutes(self):
"""
Test date with correct minutes values
"""
date = "2000-01-01T00:01:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
date = "2000-01-01T00:59:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
def test_wrong_seconds(self):
"""
Test date with wrong seconds value
"""
date = "2000-01-01T00:00:60.001+01:00"
self.assertFalse(self.vimb._check_date(date))
def test_good_seconds(self):
"""
Test date with good seconds values
"""
for second in range(60):
date = "2000-01-01T00:00:%0.2d.001+01:00" % second
self.assertTrue(self.vimb._check_date(date))
def test_wrong_days(self):
"""
Test date with incorrect days (january has always 31 days, no month
has lower number than 1)
"""
date = "2000-01-32T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
date = "2000-01-00T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
def test_good_days(self):
"""
Test date with correct days (january has always 31 days)
"""
date = "2000-01-01T00:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
date = "2000-01-31T00:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
def test_wrong_month(self):
"""
Test date with wrong month
"""
date = "2000-00-01T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
date = "2000-13-01T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
date = "2000-1-01T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
date = "2000-001-01T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
def test_good_month(self):
"""
Test date with correct months
"""
date = "2000-01-01T00:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
date = "2000-12-01T00:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
def test_wrong_year(self):
"""
Test date with wrong year
"""
date = "0000-01-01T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
date = "10000-01-01T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
date = "900-01-01T00:00:00.001+01:00"
self.assertFalse(self.vimb._check_date(date))
def test_good_year(self):
"""
Test date with correct years
"""
date = "0001-01-01T00:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
date = "9999-01-01T00:00:00.001+01:00"
self.assertTrue(self.vimb._check_date(date))
class TestAuthorize(unittest.TestCase):
"""
Test method VimBlogger._authorize
"""
def setUp(self):
"""
Create VimBlogger object (with good credentials, yes :>)
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
def test_happy_case(self):
"""
Try to login with good credentials
"""
self.assertTrue(self.vimob._authorize(shared.LOGIN,
shared.PASS) is None)
def test_wrong_login(self):
"""
Try to login with wrong login
"""
self.assertRaises(shared.BadAuthentication, self.vimob._authorize,
'joe', shared.PASS)
def test_wrong_pass(self):
"""
Try to login with wrong password
"""
self.assertRaises(shared.BadAuthentication, self.vimob._authorize,
'joe', shared.PASS)
class TestAddTag(unittest.TestCase):
"""
Test method VimBlogger._add_tag
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
self.post = shared.MockPost()
def test_add_tag(self):
"""
Add items to existing categories. List should be uniq.
"""
self.vimob._add_tag(self.post, 'item')
self.assertTrue(len(self.post.category) == 1)
# Item number should not change on the same label
self.vimob._add_tag(self.post, 'item')
self.assertTrue(len(self.post.category) == 1)
self.vimob._add_tag(self.post, 'item2')
self.assertTrue(len(self.post.category) == 2)
class TestExtractDate(unittest.TestCase):
"""
Test method VimBlogger._extract_date
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
def test_extract_date(self):
"""
Date should be already verified by _check_date method, so only
extraction is tested
"""
date = "2000-01-01T00:00:00.001-10:00"
# wrong scenario
self.assertFalse(self.vimob._extract_date('wrong_date_string'))
# only date should be returned
self.assertEqual(self.vimob._extract_date(date), "2000-01-01")
# date and time should be returned
self.assertEqual(self.vimob._extract_date(date, True),
"2000-01-01 00:00:00")
class TestGetPost(unittest.TestCase):
"""
Test method VimBlogger._get_post
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
self.vimob.blog = shared.Mock()
link = shared.Mock()
link.href = "mock.com"
link.feed = shared.Mock()
self.vimob.blog.get_post_link = lambda: link
def test_get_post(self):
"""
Nothing really to test here. Maybe in the future :)
"""
result = self.vimob._get_post('1234')
self.assertEqual(type(result), shared.MockPost)
class TestSetBlog(unittest.TestCase):
"""
Test method VimBlogger._set_blog
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
for bid, bname in {1: 'one', 3: 'test', 7: 'blog_name'}.items():
blog = shared.MockBlog(bname, bid)
self.vimob.feed.entry.append(blog)
def test_set_blog(self):
"""
Test setting a blog
"""
self.vimob._set_blog("no_valid_blog_name")
self.assertEqual(self.vimob.blog_id, None)
self.assertEqual(self.vimob.blog, None)
self.vimob._set_blog("blog_name")
self.assertEqual(self.vimob.blog_id, 7)
self.assertEqual(self.vimob.blog.get_blog_name(), 'blog_name')
self.vimob._set_blog("test")
self.assertEqual(self.vimob.blog_id, 3)
self.assertEqual(self.vimob.blog.get_blog_name(), 'test')
self.vimob._set_blog("one")
self.assertEqual(self.vimob.blog_id, 1)
self.assertEqual(self.vimob.blog.get_blog_name(), 'one')
class TestCreateArticle(unittest.TestCase):
"""
Test method VimBlogger.create_article
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
def test_create_simple_article(self):
"""
Test creation of article with minimum requirements
"""
html = "<p>article</p>"
post = self.vimob.create_article(html)
self.vimob.draft = True
self.assertEqual(post.id.text, '1234567890')
self.assertEqual(post.content.text, html)
self.assertEqual(post.published, None)
self.assertTrue(post.updated is not None)
self.assertEqual(post.title.text, "")
self.assertEqual(post.category, [])
self.assertEqual(post.control.draft.text, "yes")
def test_create_article(self):
"""
Test creation of article with full attrs
"""
html = u"<p>article \xe2\x80\x94 article</p>"
labels = "tag with spaces|vim|python|blogger".split("|")
attrs = {"title": u'Title \xe2\x80\x94 title',
"tags": ", ".join(labels),
"date": "2010-12-10T14:18:32+00:00"}
self.vimob.draft = False
post = self.vimob.create_article(html, attrs)
self.assertEqual(post.id.text, '1234567890')
self.assertEqual(post.content.text, html)
self.assertEqual(post.published.text, attrs['date'])
self.assertTrue(post.updated is not None)
self.assertEqual(post.title.text, attrs['title'])
self.assertEqual(len(post.category), 4)
for label in post.category:
self.assertTrue(label.term in labels)
del(labels[labels.index(label.term)])
self.assertEqual(post.control, None)
class TestDeleteArticle(unittest.TestCase):
"""
Test method VimBlogger.create_article
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
for bid, bname in {1: 'one', 3: 'test', 7: 'blog_name'}.items():
blog = shared.MockBlog(bname, bid)
self.vimob.feed.entry.append(blog)
self.vimob._set_blog('test')
def test_delete_non_existing_article(self):
"""
Test removing article without id
"""
self.assertEqual(self.vimob.delete_article(None),
"No article id provided")
def test_delete_article(self):
"""
Test removing article
"""
html = u"<p>article \xe2\x80\x94 article</p>"
labels = "tag with spaces|vim|python|blogger".split("|")
attrs = {"title": u'Title \xe2\x80\x94 title',
"tags": ", ".join(labels),
"date": "2010-12-10T14:18:32+00:00"}
self.vimob.draft = False
post = self.vimob.create_article(html, attrs)
self.assertEqual(self.vimob.delete_article(post.id.text), None)
class TestGetArticles(unittest.TestCase):
"""
Test method VimBlogger.get_articles
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
def test_get_articles(self):
"""
Test removing article without id
"""
articles = self.vimob.get_articles()
self.assertEqual(len(articles), 4)
articles = self.vimob.get_articles(maxarticles=2)
self.assertEqual(len(articles), 2)
class TestUpdateArticle(unittest.TestCase):
"""
Test method VimBlogger.update_article
"""
def setUp(self):
"""
Create VimBlogger object
"""
self.vimob = VimBlogger(None, shared.LOGIN, shared.PASS)
for bid, bname in {1: 'one', 3: 'test', 7: 'blog_name'}.items():
blog = shared.MockBlog(bname, bid)
self.vimob.feed.entry.append(blog)
self.vimob._set_blog('test')
def test_wrong_argument_types(self):
"""
Test update_article method with wrong argument types
"""
self.assertRaises(TypeError, self.vimob.update_article, None, None)
def test_no_id_in_attrs(self):
"""
Test update_article method with no id in attrs
"""
self.assertRaises(Exception, self.vimob.update_article,
'<p>update</p>', [])
def test_update(self):
"""
Test update_article method with no id in attrs
"""
attrs = {'id': 1234567890, 'title': 'update',
'date': '2001-01-01T00:02:19.001+01:00',
'tags': "tag1, tag2, tag3"}
post = self.vimob.update_article('<p>update</p>', attrs)
self.assertEqual(post.title.text, 'update')
self.assertEqual(post.id.text, '1234567890')
self.assertEqual(post.content.text, '<p>update</p>')
self.assertTrue(post.updated.text is not None)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,294 @@
# vim: set fileencoding=utf-8
import os
import sys
import unittest
import webbrowser
webbrowser.open = lambda x: None
this_dir = os.path.dirname(os.path.abspath(__file__))
this_dir = os.path.abspath(os.path.join(this_dir, "../.."))
sys.path.insert(0, this_dir)
from rst2blogger.tests.shared import LOGIN, PASS, Eval, MockBuffer
from rst2blogger.main import Rst2Blogger
from gdata.client import BadAuthentication
class TestRst2Blogger(unittest.TestCase):
"""
Tests for vim - rest - blogger interface
"""
def setUp(self):
"""
Create Rst2Blogger object
"""
self.obj = Rst2Blogger()
def test_object_creation(self):
"""
Create Rst2Blogger object and test it.
"""
self.assertTrue(self.obj is not None)
self.assertEqual(self.obj.docinfo_len, 3)
self.assertEqual(self.obj.login, "")
self.assertEqual(self.obj.password, "")
self.assertEqual(self.obj.blogname, "")
self.assertEqual(self.obj.buffer_encoding, "")
self.assertEqual(self.obj.vim_encoding, "")
self.assertEqual(self.obj.maxarticles, 0)
self.assertEqual(self.obj.draft, 0)
self.assertEqual(self.obj.confirm_del, 0)
self.assertEqual(self.obj.stylesheets, [])
class TestRst2BloggerSetDocinfoLen(unittest.TestCase):
"""
Test _set_docinfo_len method on different docinfo configurations
"""
def setUp(self):
"""
Create Rst2Blogger object
"""
self.obj = Rst2Blogger()
def test_set_docinfo_len(self):
"""
Test with no defined docinfo
"""
self.obj.buff = self.obj.buff[4:]
self.obj._set_docinfo_len()
self.assertEqual(self.obj.docinfo_len, 0)
def test_set_docinfo_len2(self):
"""
Test with one docinfo entry
"""
self.obj.buff = self.obj.buff[:1] + [''] + self.obj.buff[4:]
self.obj._set_docinfo_len()
self.assertEqual(self.obj.docinfo_len, 1)
def test_set_docinfo_len3(self):
"""
Test with wrong docinfo definition
"""
self.obj.buff = self.obj.buff[:1] + self.obj.buff[4:]
self.obj._set_docinfo_len()
self.assertEqual(self.obj.docinfo_len, 0)
class TestCheckHtml(unittest.TestCase):
"""
Check HTML parser
"""
def setUp(self):
"""
Create Rst2Blogger object
"""
self.obj = Rst2Blogger()
def test_check_html1(self):
"""
Parse (generated) html string, should return empty string
"""
html = "<html><head><title>test</title></head><body></body></html>"
self.assertEqual(self.obj._check_html(html), "")
self.assertEqual(self.obj._check_html(html, True), "")
def test_check_html2(self):
"""
Parse html fragment string
"""
html = "<p>first paragraph</p><p>another paragraph</p>"
self.assertEqual(self.obj._check_html(html),
"junk after document element: line 1, column 22")
self.assertEqual(self.obj._check_html(html, True), "")
def test_check_html3(self):
"""
Parse wrong html string (crossed tags)
"""
html = "<p>first paragraph<b></p>another paragraph</b>"
self.assertEqual(self.obj._check_html(html),
"mismatched tag: line 1, column 23")
self.assertEqual(self.obj._check_html(html, True),
"mismatched tag: line 1, column 28")
class TestRst2BloggerDelete(unittest.TestCase):
"""
Test delete method
"""
def setUp(self):
"""
Create Rst2Blogger object
"""
self.obj = Rst2Blogger()
self.obj.login = LOGIN
self.obj.password = PASS
self.obj.blogname = "test"
self.obj.vim_encoding = "utf-8"
def test_delete_without_password(self):
"""
Delete article, while password is incorrect/nonexistend
"""
self.obj.password = ""
self.assertRaises(BadAuthentication, self.obj.delete)
def test_delete(self):
"""
Delete article. Set confirmation attribute.
"""
self.obj.confirm_del = 1
Eval.value = 2 # set choice to answer "Y" for confirmation
Eval.blog = "test"
self.assertEqual(self.obj.delete(), "Article deleted")
def test_delete2(self):
"""
Delete article. Set confirmation attribute. Refuse to delete.
"""
self.obj.confirm_del = 1
Eval.value = 1 # set choice to answer "N" for confirmation
Eval.blog = "test"
self.assertEqual(self.obj.delete(), "No articles deleted")
def test_delete3(self):
"""
Delete article. Unset confirmation attribute. Delete returns something
else then None.
"""
Eval.value = 2
Eval.blog = "test"
Eval.gdata_delete = 1
self.assertEqual(self.obj.delete(), "Article deleted")
class TestRst2BloggerPost(unittest.TestCase):
"""
Test post method
"""
def setUp(self):
"""
Create Rst2Blogger object
"""
self.obj = Rst2Blogger()
self.obj.login = LOGIN
self.obj.password = PASS
self.obj.blogname = "test"
self.obj.vim_encoding = "utf-8"
self.obj.buffer_encoding = "utf-8"
# create copy of the buffer list and assign copy to the buff attribute
self._rest = MockBuffer(self.obj.buff[:])
self.obj.buff = self._rest
def test_without_password(self):
"""
Post article, while password is incorrect/nonexistend
"""
self.obj.password = ""
self.assertRaises(BadAuthentication, self.obj.post)
def test_with_wrong_data(self):
"""
Try to post not well formed html
"""
self.obj.buff.append('')
self.obj.buff.append('.. raw:: html')
self.obj.buff.append('')
self.obj.buff.append(' <p>foo<b>bar</p>baz</b>')
self.obj.buff.append('')
self.obj.post()
self.assertEqual(self.obj.post(),
'There are errors in generated document')
def test_post_create(self):
"""
Try to post well formed html, as a new article
"""
self.assertEqual(self.obj.post(),
'New article with id 1234567890 has been created')
def test_post_update(self):
"""
Try to post well formed html, as a new article
"""
self.obj.buff.append(':Id: 1234567890', 0)
self.assertEqual(self.obj.post(),
"Article 'Title \xe2\x80\x94 This is a test' "
"has been updated")
class TestRst2BloggerUpdateDocinfo(unittest.TestCase):
"""
Test _update_docinfo
"""
def setUp(self):
"""
Create Rst2Blogger object
"""
self.obj = Rst2Blogger()
self.obj.login = LOGIN
self.obj.password = PASS
self.obj.blogname = "test"
self.obj.vim_encoding = "utf-8"
self.obj.buffer_encoding = "utf-8"
# create copy of the buffer list and assign copy to the buff attribute
self._rest = MockBuffer(self.obj.buff[:])
self.obj.buff = self._rest
def test_with_empty_docinfo(self):
"""
Try to post not well formed html
"""
self.obj.buff = MockBuffer(self.obj.buff[4:])
self.obj.docinfo_len = 0
self.obj._update_docinfo('title', 'title2')
class TestRst2BloggerPreview(unittest.TestCase):
"""
Test preview
"""
def setUp(self):
"""
Create Rst2Blogger object
"""
self.obj = Rst2Blogger()
self.obj.login = LOGIN
self.obj.password = PASS
self.obj.blogname = "test"
def tearDown(self):
"""
Remove leftovers in fs
"""
try:
os.unlink(self.obj.buff.name[:-4])
except OSError:
pass
try:
os.unlink(self.obj.buff.name[:-4] + ".html")
except OSError:
pass
def test_preview_open_in_browser(self):
"""
Try to post not well formed html
"""
Eval.value = 1
print self.obj.preview()
def test_preview_save_to_file(self):
"""
Try to post not well formed html
"""
Eval.value = 0
name = self.obj.buff.name[:-4] + ".html"
self.assertEqual(self.obj.preview(),
"Generated HTML has been written to %s" % name)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,79 @@
# vim: set fileencoding=utf-8
import os
import sys
import unittest
import re
this_dir = os.path.dirname(os.path.abspath(__file__))
this_dir = os.path.abspath(os.path.join(this_dir, "../.."))
sys.path.insert(0, this_dir)
from rst2blogger.rest import blogArticleString, blogPreview
from rst2blogger.tests.shared import REST_ARTICLE
class TestBlogPreview(unittest.TestCase):
"""
Test generating HTML out of prepared reST text. It tests only for some
aspects of the entire thing, because it is not intendend to test all of
reST directives.
"""
def test_content(self):
"""
Simple case, check output
"""
html_out = blogPreview(REST_ARTICLE)
self.assertTrue(len(html_out) > 0)
self.assertTrue("<html" in html_out)
self.assertTrue("</html>" in html_out)
self.assertTrue("<?xml version=\"1.0\" encoding=\"utf-8\"" in
html_out)
self.assertTrue("\n\n<!-- more -->\n\n" in html_out)
self.assertTrue("<title>Title — This is a test</title>" in html_out)
self.assertTrue('type="text/css"' not in html_out)
self.assertTrue(re.search(r"<h1.*><a href=\"#\">Title — This is a"
" test</a></h1>", html_out))
self.assertTrue(re.search(r"<h2>Section 1</h2>", html_out))
self.assertTrue(re.search(r"<h3>Subsection 1.1</h3>", html_out))
self.assertTrue("description" not in html_out)
def test_stylesheets(self):
"""
Test output for stylesheets
"""
html_out = blogPreview(REST_ARTICLE, ["css/style1.css",
"css/blogger1.css"])
self.assertTrue('type="text/css"' in html_out)
match = re.search(r'<link rel="stylesheet" '
'href=".*" type="text/css" />', html_out)
self.assertTrue(match is not None)
self.assertEqual(len(match.span()), 2)
class TestBlogArticleString(unittest.TestCase):
"""
Test blogArticleString function, wich should return part of html and
dictionary with attributes.
"""
def test_blogArticleString(self):
html_out, attrs = blogArticleString(REST_ARTICLE)
self.assertEqual(len(attrs), 3)
self.assertTrue(len(html_out) > 0)
self.assertTrue("<html" not in html_out)
self.assertTrue("</html>" not in html_out)
self.assertTrue("<?xml version=\"1.0\" encoding=\"utf-8\"" not in
html_out)
self.assertTrue("\n\n<!-- more -->\n\n" in html_out)
self.assertTrue("<title>Title — This is a test</title>" not in
html_out)
self.assertTrue('type="text/css"' not in html_out)
self.assertTrue(re.search(r"<h4>Section 1</h4>", html_out))
self.assertTrue(re.search(r"<h5>Subsection 1.1</h5>", html_out))
self.assertTrue("description" not in html_out)
self.assertEqual(attrs['title'], u"Title — This is a test")
self.assertEqual(attrs['date'], "2010-12-12T12:36:36+01:00")
self.assertEqual(attrs['tags'], "this is a test, Blogger, rest")
if __name__ == "__main__":
unittest.main()

View File

@@ -25,7 +25,7 @@ if !exists("g:blogger_login")
endif endif
if !exists("g:blogger_pass") if !exists("g:blogger_pass")
let g:blogger_pass = "Kurcz4czek" let g:blogger_pass = ""
endif endif
if !exists("g:blogger_draft") if !exists("g:blogger_draft")