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

First attempt to write reST -> blogger interface

This commit is contained in:
2010-11-29 06:02:26 +01:00
parent 0a1e903e9e
commit f39c090921
6 changed files with 546 additions and 166 deletions

View File

@@ -27,6 +27,10 @@
'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*
@@ -152,6 +156,21 @@ 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*
@@ -253,6 +272,32 @@ 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:acp_behavior-command acp.txt /*g:acp_behavior-command*
g:acp_behavior-completefunc acp.txt /*g:acp_behavior-completefunc*
g:acp_behavior-meets acp.txt /*g:acp_behavior-meets*
g:acp_behavior-onPopupClose acp.txt /*g:acp_behavior-onPopupClose*
g:acp_behavior-repeat acp.txt /*g:acp_behavior-repeat*
g:acp_behaviorCssOmniPropertyLength acp.txt /*g:acp_behaviorCssOmniPropertyLength*
g:acp_behaviorCssOmniValueLength acp.txt /*g:acp_behaviorCssOmniValueLength*
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*

96
ftplugin/rst/blogger.vim Normal file
View File

@@ -0,0 +1,96 @@
" Blogger vim interface.
" Provide some convinient functions for creating preview from the reST file
" and to send articles to blog.
if exists("b:did_rst_plugin")
finish " load only once
else
let b:did_rst_plugin = 1
endif
if !exists("g:blogger_browser")
let g:blogger_browser = 0
endif
if !exists("g:blogger_name")
let g:blogger_name = ""
endif
if !exists("g:blogger_login")
let g:blogger_login= ""
endif
if !exists("g:blogger_pass")
let g:blogger_pass = ""
endif
map <F5> :call <SID>Restify()<cr>
map <F6> :call <SID>Rst2Blogger()<cr>
if !exists('*s:Restify')
python << EOF
#{{{
import os
import sys
import webbrowser
import vim
scriptdir = os.path.dirname(vim.eval('expand("<sfile>")'))
sys.path.insert(0, scriptdir)
from bloggervim.rest import blogPreview, blogArticleString
from bloggervim.blogger import VimBlogger
#}}}
EOF
" Translate reSt text into html fragment suitable for preview in browser.
fun <SID>Restify()
python << EOF
# {{{
bufcontent = "\n".join(vim.current.buffer)
name = vim.current.buffer.name
name = name[:-4] + ".html"
html = blogPreview(bufcontent)
output_file = open(name, "w")
output_file.write(html)
output_file.close()
if vim.eval("g:blogger_browser"):
webbrowser.open(name)
print "Generated HTML has been opened in browser"
else:
print "Generated HTML has been written to %s" % name
#}}}
EOF
endfun
" Generate headless html, gather title, dates and tags from filed list and
" then send it to blog.
fun <SID>Rst2Blogger()
python << EOF
#{{{
bufcontent = "\n".join(vim.current.buffer)
name = vim.current.buffer.name
html, attrs = blogArticleString(bufcontent)
login = vim.eval("g:blogger_login")
password = vim.eval("g:blogger_pass")
blogname = vim.eval("g:blogger_name")
if not password:
password = vim.eval('inputsecret("Enter your gmail password: ")')
title = 'title' in attrs and attrs['title'] or None
date = 'date' in attrs and attrs['date'] or None
tags = 'tags' in attrs and attrs['tags'] or ""
tags = [tag.strip() for tag in tags.split(',')]
modified = 'modified' in attrs and attrs['modified'] or None
blog = VimBlogger(blogname, login, password)
print blog.create_article(title, html, tags=tags)
#}}}
EOF
endfun
endif

View File

@@ -0,0 +1,130 @@
# vim: fileencoding=utf8
#
# Blogger interface to make easy way to create/update articles for specified
# blog.
#
# 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
# 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)
# - pytz (http://pytz.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)
#
# vim.eval('inputsecret("Password: ")')
# echomsg expand("%:p")
#-----------------------------------------------------------------------------
#
import getpass # TODO: remove
import time
import datetime
import pytz
import atom
from gdata.blogger.client import BloggerClient
class VimBlogger(object):
"""
"""
def __init__(self, blogname, login, password):
"""
"""
self.blog = None
self.client = BloggerClient()
self._authorize(login, password)
self.feed = self.client.get_blogs()
self._set_blog(blogname)
#self._get_arts(blogname)
def _set_blog(self, blogname):
"""
"""
for blog in self.feed.entry:
if blog.get_blog_name() == blogname:
self.blog = blog
break
def _get_arts(self, blogname):
"""
"""
feed = self.client.get_posts(self.blog.get_blog_id())
for entry in feed.entry:
print entry.title.text
#
import ipdb; ipdb.set_trace()
#
# entry.content obiekt zawiera ciało artykułu (entry.content.text
# posiada czystą formę która mnie interesuje najbardziej, do której
# można pisać
#
# entry.category - lista wszystkich kategorii (blogowych tagów), które
# post posiada. Są to elementy klasy atom.data.Category, które
# łatwiutko stworzyć i dodać do posta:
# import atom
# cat1 = atom.data.Category()
# cat1.term = "nowy tag dla bloggera"
# entry.category.append(cat1)
#
# entry.title przechowuje tytuł posta
def _authorize(self, login, password):
"""
"""
source = 'Blogger_Python_Sample-2.0'
service = 'blogger'
self.client.client_login(login,
password,
source=source,
service=service)
def create_article(self, title, html_doc, tags=None):
"""
"""
blog_id = self.blog.get_blog_id()
if tags is None:
tags = []
return self.client.add_post(blog_id, title, html_doc, labels=tags,
draft=True)
def update_article(self, title, html_doc, tags=None):
"""
"""
pass
if __name__ == "__main__":
p = getpass.getpass("Password: ")
b = VimBlogger("rdobosz", "gryf73@gmail.com", p)

View File

@@ -0,0 +1,263 @@
import re
from docutils import core
from docutils import nodes
from docutils.parsers.rst import directives, Directive
from docutils.writers.html4css1 import Writer, HTMLTranslator
from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer
from pygments.formatters import HtmlFormatter
class Attrs(object):
ATTRS = {}
class Pygments(Directive):
"""
Source code syntax hightlighting.
"""
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):
"""
Base class for reST files translations.
There are couple of customizations for docinfo fields behaviour and
abbreviations and acronyms.
"""
def __init__(self, document):
"""
Set some nice defaults for articles translations
"""
HTMLTranslator.__init__(self, document)
self.initial_header_level = 4
def visit_section(self, node):
"""
Don't affect document, just keep track of the section levels
"""
self.section_level += 1
def depart_section(self, node):
self.section_level -= 1
def visit_meta(self, node):
pass
def depart_meta(self, node):
pass
def visit_document(self, node):
pass
def depart_document(self, node):
pass
def depart_docinfo(self, node):
"""
Reset body, remove unnecesairy content.
"""
self.body = []
def visit_date(self, node):
pass
def depart_date(self, node):
pass
def visit_literal(self, node):
"""
This is almos the same as the original one from HTMLTranslator class.
The only difference is in used HTML tag: it uses 'code' instead of
'tt'
"""
self.body.append(self.starttag(node, 'code', ''))
text = node.astext()
for token in self.words_and_spaces.findall(text):
if token.strip():
# Protect text like "--an-option" and the regular expression
# ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping
if self.sollbruchstelle.search(token):
self.body.append('<span class="pre">%s</span>'
% self.encode(token))
else:
self.body.append(self.encode(token))
elif token in ('\n', ' '):
# Allow breaks at whitespace:
self.body.append(token)
else:
# Protect runs of multiple spaces; the last space can wrap:
self.body.append('&nbsp;' * (len(token) - 1) + ' ')
self.body.append('</code>')
# Content already processed:
raise nodes.SkipNode
def visit_acronym(self, node):
"""
Define missing acronym HTML tag
"""
node_text = node.children[0].astext()
node_text = node_text.replace('\n', ' ')
patt = re.compile(r'^(.+)\s<(.+)>')
if patt.match(node_text):
node.children[0] = nodes.Text(patt.match(node_text).groups()[0])
self.body.append(\
self.starttag(node, 'acronym',
'', title=patt.match(node_text).groups()[1]))
else:
self.body.append(self.starttag(node, 'acronym', ''))
def visit_abbreviation(self, node):
"""
Define missing abbr HTML tag
"""
node_text = node.children[0].astext()
node_text = node_text.replace('\n', ' ')
patt = re.compile(r'^(.+)\s<(.+)>')
if patt.match(node_text):
node.children[0] = nodes.Text(patt.match(node_text).groups()[0])
self.body.append(\
self.starttag(node, 'abbr',
'', title=patt.match(node_text).groups()[1]))
else:
self.body.append(self.starttag(node, 'abbr', ''))
class NoHeaderHTMLTranslator(CustomHTMLTranslator):
"""
Special subclass for generating only body of an article
"""
def __init__(self, document):
"""
Remove all needless parts of HTML document.
"""
CustomHTMLTranslator.__init__(self, document)
self.head = []
self.meta = []
self.head_prefix = ['','','','','']
self.body_prefix = []
self.body_suffix = []
self.stylesheet = []
self.generator = ('')
def visit_field(self, node):
"""
Harvest docinfo fields and store it in global dictionary.
"""
key, val = [n.astext() for n in node]
key = key.lower()
Attrs.ATTRS[key] = val
def visit_date(self, node):
"""
Store published date in global dictionary.
"""
Attrs.ATTRS['date'] = node.astext()
class PreviewHTMLTranslator(CustomHTMLTranslator):
"""
Class for dislpay article in the browser as a preview.
"""
def __init__(self, document):
"""
Alter levels for the heading tags, define custom, blog specific
stylesheets. Note, that style_custom is present only locally to adjust
way of display the page
"""
CustomHTMLTranslator.__init__(self, document)
self.initial_header_level = 1
self.section_level = 1
# order of css files is important
self.default_stylesheets = ["css/widget_css_2_bundle.css",
"css/style_custom.css",
"css/style_blogger.css"]
self.stylesheet = [self.stylesheet_link % self.encode(css) \
for css in self.default_stylesheets]
self.body_ = []
def depart_docinfo(self, node):
"""
Overwrite body with some custom one. body_ will hold the first heading
with title of the document.
"""
self.body = self.body_
def visit_field(self, node):
"""
Additional 'keyword' for the ODF metadata
"""
key, node_ = [n.astext() for n in node]
key = key.lower()
if key == 'title':
self.head.append('<title>%s</title>\n' % self.encode(node_))
self.body_.append('<h1 class="post-title entry-title">'
'<a href="#">%s</a></h1>\n' % self.encode(node_))
class BlogBodyWriter(Writer):
"""
Custom Writer class for generating HTML partial with the article
"""
def __init__(self):
Writer.__init__(self)
self.translator_class = NoHeaderHTMLTranslator
def translate(self):
self.document.settings.output_encoding = "utf-8"
Writer.translate(self)
class BlogPreviewWriter(Writer):
"""
Custom Writer class for generating full HTML of the article
"""
def __init__(self):
Writer.__init__(self)
self.translator_class = PreviewHTMLTranslator
def translate(self):
self.document.settings.output_encoding = "utf-8"
Writer.translate(self)
def blogPreview(string):
"""
Returns partial HTML of the article, and attribute dictionary
string argument is an article in reST
"""
html_output = core.publish_string(string, writer=BlogPreviewWriter())
html_output = html_output.strip()
html_output = html_output.replace("<!-- more -->", "\n<!-- more -->\n")
return html_output
def blogArticleString(string):
"""
Returns partial HTML of the article, and attribute dictionary
string argument is an article in reST
"""
# reset ATTRS
Attrs.ATTRS = {}
html_output = core.publish_string(string, writer=BlogBodyWriter())
html_output = html_output.strip()
html_output = html_output.replace("<!-- more -->", "\n<!-- more -->\n")
return html_output, Attrs.ATTRS

View File

@@ -5,169 +5,6 @@ setlocal spell
setlocal smartindent setlocal smartindent
setlocal autoindent setlocal autoindent
setlocal formatoptions=tcq "set VIms default setlocal formatoptions=tcq "set VIms default
let g:blogger_login="gryf73"
if exists("b:did_rst_plugin") let g:blogger_name="rdobosz"
finish " only load once let g:blogger_browser=1
else
let b:did_rst_plugin = 1
endif
map <F5> :call <SID>Rst2Blogger()<cr>
if !exists('*s:Rst2Blogger')
" Simple function, that translates reSt text into html with specified format,
" suitable to copy and paste into blogger post.
fun <SID>Rst2Blogger()
python << EOF
import re
from docutils import core
from docutils import nodes
from docutils.parsers.rst import directives, Directive
from docutils.writers.html4css1 import Writer, HTMLTranslator
from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer
from pygments.formatters import HtmlFormatter
import vim
class Pygments(Directive):
"""
Source code syntax hightlighting.
"""
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 NoHeaderHTMLTranslator(HTMLTranslator):
def __init__(self, document):
HTMLTranslator.__init__(self,document)
self.head_prefix = ['','','','','']
self.body_prefix = []
self.body_suffix = []
self.stylesheet = []
self.head = []
self.meta = []
self.generator = ('')
self.initial_header_level = 2
self.section_level = 2
def visit_document(self, node):
pass
def depart_document(self, node):
pass
def visit_section(self, node):
pass
def depart_section(self, node):
pass
def visit_acronym(self, node):
node_text = node.children[0].astext()
node_text = node_text.replace('\n', ' ')
patt = re.compile(r'^(.+)\s<(.+)>')
if patt.match(node_text):
node.children[0] = nodes.Text(patt.match(node_text).groups()[0])
self.body.append(\
self.starttag(node, 'acronym',
'', title=patt.match(node_text).groups()[1]))
else:
self.body.append(self.starttag(node, 'acronym', ''))
def visit_abbreviation(self, node):
node_text = node.children[0].astext()
node_text = node_text.replace('\n', ' ')
patt = re.compile(r'^(.+)\s<(.+)>')
if patt.match(node_text):
node.children[0] = nodes.Text(patt.match(node_text).groups()[0])
self.body.append(\
self.starttag(node, 'abbr',
'', title=patt.match(node_text).groups()[1]))
else:
self.body.append(self.starttag(node, 'abbr', ''))
_w = Writer()
_w.translator_class = NoHeaderHTMLTranslator
def blogify(string):
return core.publish_string(string, writer=_w)
bufcontent = "\n".join(vim.current.buffer)
name = vim.current.buffer.name
if name.lower().endswith(".rst"):
name = name[:-4] + ".html"
vim.command('new')
vim.current.buffer[:] = blogify(bufcontent).split("\n")
try:
vim.command(r'silent! %s/<tt class="docutils literal">/<code>/g')
vim.command(r'silent! %s/<\/tt>/<\/code>/g')
except:
pass
try:
vim.command(r'silent! %s/<!-- more -->/\r<!-- more -->\r/g')
except:
pass
vim.command('w %s' % name.replace(' ', '\ '))
vim.command('bd')
else:
print "Ihis is not reSt file. File should have '.rst' extension."
EOF
endfun
endif
if !exists('*s:Restify')
" This is similar to that above, but creates full html document
fun <SID>Restify()
python << EOF
from docutils import core
from docutils.writers.html4css1 import Writer, HTMLTranslator
import vim
_w = Writer()
_w.translator_class = HTMLTranslator
def reSTify(string):
return core.publish_string(string,writer=_w)
bufcontent = "\n".join(vim.current.buffer)
name = vim.current.buffer.name
if name.lower().endswith(".rst"):
name = name[:-4] + ".html"
vim.command('new')
vim.current.buffer[:] = reSTify(bufcontent).split("\n")
vim.command(r'silent %s/<tt class="docutils literal">/<code>/g')
vim.command(r'silent %s/<\/tt>/<\/code>/g')
vim.command('w %s' % name)
vim.command('bd')
else:
print "It's not reSt file!"
EOF
endfun
endif

View File

@@ -57,3 +57,12 @@ snippet figure
:figclass: ${3:custclass} :figclass: ${3:custclass}
$2 $2
snippet article
:Title: ${1:Blog post title}
:Date: ${2:`strftime("%Y-%m-%d %H:%M:%S")`}
:Modified: ${3}
:Tags: ${4:blogtag1, blogtag2}
${5}
.. more