textileをhtml5slidesなスライドショーにする
動機
- googleのhtml5slidesを使って発表している人をよく見る
- 議事録等に利用するredmineではtextileフォーマットをよく使う
- textileをスライドにできれば議事録も楽になるのではないか
- 作ってみよう
始めはflaskで実装しましたがサーバ立てるのは面倒だと思い、javascriptになおしました。textileフォーマットのテキストをどこかにアップロードして、ブックマークレットを呼び出せばスライドショーできます。
使い方
- textile to slidesのリンク先のブックマークレットをブックマークします
- textileのテキストを開きます(例: example.textile.txt )
- ブックマークレットを実行します
実装方針
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/
- POSTデータやURL先のテキストからデータソースを読み込み
- PyTextileでHTMLに変換
- H1,2,3要素から次のH1,2,3までarticleに追加
- テンプレートで出力
flaskだとサーバ動かす必要があり、それじゃちょっと...ということでjavascriptに路線変更しました。
(javascript) textile javascript+html5slidesで実装
http://let.hatelabo.jp/joma/let/gYC-yfWK8PDZaQ
chromeなどでは、テキストファイルをhtml/body/pre要素として表示するので、それをデータソースとしました。
- html/body/pre[0]をデータソースとして読み込み
- Javascript TextileでHTMLに変換
- H1,2,3要素から次のH1,2,3までarticleに追加
- 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ネイティブな関数と勘違いしてハマるところでした。