textileをhtml5slidesなスライドショーにする

動機

  1. googleのhtml5slidesを使って発表している人をよく見る
  2. 議事録等に利用するredmineではtextileフォーマットをよく使う
  3. textileをスライドにできれば議事録も楽になるのではないか
  4. 作ってみよう

始めはflaskで実装しましたがサーバ立てるのは面倒だと思い、javascriptになおしました。textileフォーマットのテキストをどこかにアップロードして、ブックマークレットを呼び出せばスライドショーできます。

使い方

  1. textile to slidesのリンク先のブックマークレットをブックマークします
  2. textileのテキストを開きます(例: example.textile.txt )
  3. ブックマークレットを実行します

実装方針

html5slidesの構造は、html/body/section/articleのようになっています。sectionの中にarticle要素(スライド)を並べることでスライドを作成できます。

textileフォーマットにはページ区切りの要素がないため、H1やH2などのヘッダ要素を区切りにしてarticle要素内に包含するようにしました。textileフォーマットでは、h1. XXXXのように書くとヘッダ要素を作成できます。

すなわち

h1. aaa

ppp

h1. bbb

h2. ccc

↑これを、↓こうします。

<article>
  <h1>aaa</h1>
    <p>ppp</p>
</article>
<article>
  <h1>bbb</h1>
</article>
<article>
  <h2>ccc</h2>
</article>

実装

(python) flask+lxml+PyTextile+html5slidesで実装

https://bitbucket.org/joma/textiletoslides.flask/

  1. POSTデータやURL先のテキストからデータソースを読み込み
  2. PyTextileでHTMLに変換
  3. H1,2,3要素から次のH1,2,3までarticleに追加
  4. テンプレートで出力

flaskだとサーバ動かす必要があり、それじゃちょっと...ということでjavascriptに路線変更しました。

(javascript) textile javascript+html5slidesで実装

http://let.hatelabo.jp/joma/let/gYC-yfWK8PDZaQ

chromeなどでは、テキストファイルをhtml/body/pre要素として表示するので、それをデータソースとしました。

  1. html/body/pre[0]をデータソースとして読み込み
  2. Javascript TextileでHTMLに変換
  3. H1,2,3要素から次のH1,2,3までarticleに追加
  4. html5slidesのhandleDomLoaded関数を呼び出し

DjangoのORMだけを使う

global_settings + myproject.settings を使う方法です。あまり検証していないので間違っていたらごめんなさい。

ライブラリパスを通す&DJANGO_SETTINGS_MODULEの指定

プロジェクトやアプリケーションへのパスを通し、DJANGO_SETTINGS_MODULEを指定します。

export PYTHONPATH=$PYTHONPATH:/path/to/myproject/..
export DJANGO_SETTINGS_MODULE='myproject.settings'

既存のライブラリパスにプロジェクトやアプリケーションがある場合は、前者のexportは必要ありません。

settingsをインポートしてORMの実行

モデルをインポートする前にsettingsオブジェクトを用意する必要があります。

from django.conf import LazySettings
settings = LazySettings() # settings.configure()はいらないみたいです

from myproject.myapp.models import MyModel

m = MyModel()
...

あとは、従来のORMの使い方と同じです。LazySettingsっていう名前なのでバッドノウハウかもしれませんが、Djangoの○○だけ使いたい!ってときにいいかもしれませんね。

rfeedfinderを修正

Fastladderのオープンソース版を試していると、feedの登録&取得に失敗するケースがあります(例: http://www.ruby-lang.org/ja/ )。根本をたどってみると、feedを探索するライブラリ、rfeedfinder(ver.0.9.13)に問題があるようです。具体的には、下記のisAValidURL関数です。

  def self.isAValidURL?(url_to_check)
    return false if url_to_check == nil

    # The protocols that we allow are the following
    protocol_whitelist = ["http", "https"]
    # I guess we could have included some more, but that doesn't really
    # make sense anyway as these are the ones that should be used.
    # We'll see if the need arises and then add more later if needed.

    re = Regexp.new("(#{protocol_whitelist.join('|')}):" + \
      "\/\/([[:alpha:][:digit:].]{2,})([.]{1})([[:alpha:]]{2,4})(\/)")

    # For the sake of the regular expression check we add a back slash
    # at the end of the URL
    url_to_check += "/"
    return true unless (re =~ url_to_check) == nil
    false
  end

正規表現https?://hogehoge.example.com/ などの正規なURLであることを判別しているようです。しかし、この正規表現ではハイフンやポート番号に対応していません。というわけで、URIモジュールを使って修正しました。makeFullURI関数も難ありだったので、適宜直しています。

Index: lib/rfeedfinder.rb
===================================================================
--- lib/rfeedfinder.rb  (revision 1)
+++ lib/rfeedfinder.rb  (working copy)
@@ -314,11 +314,23 @@

   protected
   def self.makeFullURI(uri)
-    uri = uri.strip.sub(/^feed(.*)/, 'http\1').downcase
-    if /^http|https/.match(uri)
+    begin
+      uri_parsed = URI.parse(uri)
+    rescue URI::Error
       return uri
+    end
+
+    case uri_parsed.scheme
+    when "http", "https"
+      return uri
+    when "feed"
+      uri = uri.strip.sub(/^feed(.*)/, 'http\1').downcase
+      return uri
+    when nil
+      # when uri does not start with 'protocol-scheme://', add 'http://'
+      return "http://" << uri
     else
-      return "http://" << uri
+      return uri
     end
   end

@@ -481,14 +493,16 @@
     # make sense anyway as these are the ones that should be used.
     # We'll see if the need arises and then add more later if needed.

-    re = Regexp.new("(#{protocol_whitelist.join('|')}):" + \
-      "\/\/([[:alpha:][:digit:].]{2,})([.]{1})([[:alpha:]]{2,4})(\/)")
-
-    # For the sake of the regular expression check we add a back slash
-    # at the end of the URL
-    url_to_check += "/"
-    return true unless (re =~ url_to_check) == nil
-    false
+    begin
+      uri = URI.parse(url_to_check)
+      if protocol_whitelist.index(uri.scheme)
+        return true
+      else
+        return false
+      end
+    rescue URI::Error
+      return false
+    end
   end

makeFullURIのdowncaseについてはこのままだとまずいらしい(たとえば、URLがcase-sensitiveなとき)ですが、rfeedfinder作者の意図が分からないので判断は保留で。

char-hints-mod2.jsをいじって昔のvimperatorのクイックヒント風に

クイックヒントで絞り込みをせず数字で直接ヒントの文字を入力することが多くなったと感じるようになって、昔のvimperatorのようにクイックヒントの文字列をアルファベットの組み合わせにしたいと思ったのが動機です(どこかでそういう設定にする旨を見た気がするのですが忘失しました;;)。

調べてみると char-hints-mod2というプラグインがあり、それを修正しました(作者様には感謝感謝。

修正箇所

おおざっぱですが、基本的には大文字に変換していたところを小文字変換にし、マッチングの部分を小文字でマッチするようにしました。

  • toUpperCase -> toLowerCase
  • A-Z -> a-z
 39     const DEFAULT_HINTCHARS = "asdfghjkl"; // ホームポジションメインで

 48         var hintchars = options.hintchars.toLowerCase();

 58         var hintchars = options.hintchars.toLowerCase();

 93             commandline.command = hintString.replace(/[a-z]+/g, "");

 97                 if(/^[a-z]$/.test(hintString[i])) {

感想

直感的な絞り込みが恋しくなるときもありますが、キーストローク数が平均2回なのでだいぶ打つのが楽になりました。

参考リンク

python-ldapを使ってldapsearchもどき

simple bind(パスワード認証)限定なldapクライアントがほしかったのでpython ldapを使ってldapsearchもどきを作りました。

ソース

## python-ldap,python-pit are required
import ldap,ldif
from pit import Pit
import sys,optparse,getpass

## configuration
defaults = Pit.get('ldap.config',{'require' : {
                    'uri':'ldap://ldap.example.com',
                    'base_dn':'dc=example,dc=com',
                    'bind_dn':'uid=hoge,dc=example,dc=com',
                    'bind_password':'Password for binding',
                    }})

## Options. like LDAPSEARCH(1)
optparser = optparse.OptionParser()
optparser.add_option('-b', '--basedn', dest='base_dn', default=defaults['base_dn'])
optparser.add_option('-D', '--binddn', dest='bind_dn', default=defaults['bind_dn'])
optparser.add_option('-H', dest='ldap_uri', default=defaults['uri'])
optparser.add_option('-x', action='store_true', dest='is_anonymous')
optparser.add_option('-w', dest='bind_password', default=defaults['bind_password'])
optparser.add_option('-W', action='store_true', dest='prompt_password')

opts, args = optparser.parse_args()
ldap_uri = opts.ldap_uri
base_dn = opts.base_dn
bind_dn = opts.bind_dn
bind_password = opts.bind_password
if opts.is_anonymous:
    bind_dn = ''
    bind_password = ''
elif opts.prompt_password:
    bind_password = getpass.getpass("Enter LDAP Password: ")

## connect to ldap server
lo = ldap.initialize(ldap_uri)
lo.simple_bind_s(bind_dn, bind_password)

## search
# args are search fileter and attributes you want
if len(args) > 0:
    result = lo.search_s(base_dn, ldap.SCOPE_SUBTREE, args[0], args[0:])
# search all
else:
    result = lo.search_s(base_dn, ldap.SCOPE_SUBTREE)

## output ldif
ldif_writer=ldif.LDIFWriter(sys.stdout)
for dn,entry in result:
    ldif_writer.unparse(dn,entry)

使い方

ldapsearchに似た感じです。Pitでデフォルトのbase DNなどを保存します。

パスワードを聞かれる検索
python pyldapsearch.py -W
フィルタや欲しい属性の指定
python pyldapsearch.py '(uid=joma)' sn mail
匿名検索
python pyldapsearch.py -x
いろいろオプションつけて検索
python pyldapsearch.py -b 'dc=example,dc=com' -D 'uid=joma,dc=example,dc=com' -H 'ldap://ldap.example.com' -w password

メモ

  • search_sの_sはsynchronous、同期処理
  • searchの範囲は、SCOPE_BASE,SCOPE_ONELEVEL,SCOPE_SUBTREE(それぞれ、自分、自分と子供まで、自分以下のツリー全体)
  • Pitは環境変数EDITORの設定が必要

Djangoのテンプレートエンジンを使ってcsvデータをフォーマットして出力する

ある程度量のあるLDIFを作る必要があり、一つ一つ手打ちしていくのもめんどくさい&ミスが恐いので、Excelでデータをまとめ、csvに変換後Djangoのテンプレートエンジンを使って生成しました。

ソース

csv2format.py
import sys,csv
from django.conf import settings
settings.configure(TEMPLATE_DIRS=('./',)) # テンプレートファイルの置き場所
from django.template import Context,loader

if len(sys.argv) != 3:
    print "usage: template.py template_file csv_file"
    sys.exit()

format = (  # csvのフォーマットを指定
            'uidNumber',
            'uid',
            'givenName',
            'sn',
            'mail',
            'userPassword',
)

tmpl = loader.get_template(sys.argv[1])
csvdata = csv.reader(open(sys.argv[2]))

for row in csvdata:
    data = Context(dict(zip(format,row))) # formatと値の辞書を作成
    print tmpl.render(data)

使い方

 % python csv2format.py テンプレートファイル csvファイル

以下のファイルを読み込ませて実行してみます。

ldap.tmpl (テンプレートファイル)

{{要素名}} が値に変換されて出力されます。

dn: uid={{uid}},ou=People,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
uid: {{uid}}
cn: {{givenName}} {{sn}}
givenName: {{givenName}}
sn: {{sn}}
mail: {{mail}}
shadowMax: 99999
shadowWarning: 7
uidNumber: {{uidNumber}}
gidNumber: 100
loginShell: /bin/bash
homeDirectory: /home/{{uid}}
userPassword:: {{userPassword}}
users.csv
1001,joma,Namae,MYOUJI,[email protected],password-string
実行結果
[joma@brain:~]% python csv2format.py ldap.tmpl users.csv
dn: uid=joma,ou=People,dc=example,dc=com
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
uid: joma
cn: Namae MYOUJI
givenName: Namae
sn: MYOUJI
mail: [email protected]
shadowMax: 99999
shadowWarning: 7
uidNumber: 1001
gidNumber: 100
loginShell: /bin/bash
homeDirectory: /home/joma
userPassword:: password-string

感想

  • LDIF以外にも使えて便利かも
  • スーパーpre記法がLDIFにも対応してることに感心

yes or no なプロンプト

先日練習のために書いたTwitterクライアントでポスト前に確認を入れようと思い、yes or noなプロンプトを実装しました。といっても、一通り組んだあと参考になるスクリプトを見つけたので、その引用です。

ソース

引用元は、python-virtinst.noarch パッケージ(RPM)の virtinst/cli.py

def prompt_for_input(prompt = "", val = None):
    if val is not None:
        return val
    print prompt + " ",
    return sys.stdin.readline().strip()

def yes_or_no(s):
    s = s.lower()
    if s in ("y", "yes", "1", "true", "t"):
        return True
    elif s in ("n", "no", "0", "false", "f"):
        return False
    raise ValueError, "A yes or no response is required"

yesな回答がタプルにあればtrue、noな回答がタプルにあればfalse、それ以外は例外を返すような流れですね。s.lower()でcase insensitiveにするところもポイントでしょうか。

使用例

while 1:
    res = prompt_for_input("Yes or No?")
    try:
        if yes_or_no(res):
            print "yes"
        else:
            print "no"
        break
    except ValueError, e:
        print "ERROR: ", e

yesかnoであればbreakでループを抜け、例外であればループの繰り返し。

感想

このyes or noプロンプトはシンプルだけど、プログラミングの練習には意外と効果的かもしれない。

P.S.

今回は該当部分を手直ししましたが、実際はこんな関数が定義されていました。

def _(s)
    return s

res = prompt_for_input(_("Yes or No?")) のような感じで使われていました。この文字列をごにょごにょ処理するときに使うのでしょうか。てっきりpythonネイティブな関数と勘違いしてハマるところでした。