Windows7にMinGWがインストールできない?

※ 追記あり

gem で nokogiri をインストールしようとしたところ、次のようなエラーが出てインストールできなかった。

^o^ > gem install nokogiri
Temporarily enhancing PATH to include DevKit...
Building native extensions.  This could take a while...
ERROR:  Error installing nokogiri:
        ERROR: Failed to build gem native extension.

    C:/Ruby200-x64/bin/ruby.exe extconf.rb
checking for libxml/parser.h... no
-----
libxml2 is missing.  please visit http://nokogiri.org/tutorials/installing_nokog
iri.html for help with installing dependencies.
-----
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=C:/Ruby200-x64/bin/ruby
        --with-zlib-dir
        --without-zlib-dir
        --with-zlib-include
        --without-zlib-include=${zlib-dir}/include
        --with-zlib-lib
        --without-zlib-lib=${zlib-dir}/lib
        --with-iconv-dir
        --without-iconv-dir
        --with-iconv-include
        --without-iconv-include=${iconv-dir}/include
        --with-iconv-lib
        --without-iconv-lib=${iconv-dir}/lib
        --with-xml2-dir
        --without-xml2-dir
        --with-xml2-include
        --without-xml2-include=${xml2-dir}/include
        --with-xml2-lib
        --without-xml2-lib=${xml2-dir}/lib
        --with-xslt-dir
        --without-xslt-dir
        --with-xslt-include
        --without-xslt-include=${xslt-dir}/include
        --with-xslt-lib
        --without-xslt-lib=${xslt-dir}/lib
        --with-libxslt-config
        --without-libxslt-config
        --with-pkg-config
        --without-pkg-config
        --with-libxml-2.0-config
        --without-libxml-2.0-config
        --with-pkg-config
        --without-pkg-config
        --with-libiconv-config
        --without-libiconv-config
        --with-pkg-config
        --without-pkg-config


Gem files will remain installed in C:/Ruby200-x64/lib/ruby/gems/2.0.0/gems/nokog
iri-1.6.1 for inspection.
Results logged to C:/Ruby200-x64/lib/ruby/gems/2.0.0/gems/nokogiri-1.6.1/ext/nok
ogiri/gem_make.out

調べてみると、どうやら nokogiri はもはや Pure Ruby ではなく、おまけにほかのCライブラリに依存するらしい。
Windows の Ruby 2.0.0 に nokogiri をインストールする方法はこのページに詳しい。

 cf. WindowsのRuby 2.0でNokogiriを使う ― KaoriYa

というわけで、ここからが今日の本題。C のライブラリをビルドするためにまずは MinGW のインストールに挑戦する。
まずは、ダウンロードページから mingw-get-setup.exe をダウンロード。これを右クリックして管理者として実行する。
順調に進むかと思いきや、「”C:\MinGW\libexec\mingw-get\shlink.js” のスクリプト エンジン “JScript” が見つかりません。」というエラーがでてしまう。先週、有効にしたはずなのにおかしい。
とりあえず「OK」をクリックすると MinGW Installation Manager というパッケージ管理ツールのウィンドウが開く。ここでインストールしたいパッケージを選んで(よくわからんので Basic Setup にあるすべてを選んだ)、メニューから Installation → Apply Changes をクリックするとインストールが始まって、しばらくすると無事終わった(ように見える)。
ところが、スタートメニューにもデスクトップにもショートカットが作成されていない。これってやっぱり正常にインストールされてないってことか?

試しに簡単なプログラムを書いてみた。

#include

main () {
    printf("Hello, world.\n")
}

実行結果:

^o^ > gcc -o hello.exe hello.c

^o^ > hello.exe
Hello, world.

なんと、コンパイルができてしまった。どういうこと?

[追記]
gcc が使えたのは、MinGW が正常にインストールされているわけじゃなかった。Haskell Platform に含まれてる gcc が動いていた。

^o^ > which gcc
C:/Program Files (x86)/Haskell Platform/2012.4.0.0/mingw/bin/gcc.EXE

MinGW のほうにパスを通せばいいのかなあ。shlink.js が何をやってるのか調べてみようか。

スクリプト エンジン “JScript” が見つかりません。

Windows7 Home Premium での話。
以前 JScript で書いたスクリプトを実行しようとしたら、次のようにエラーになった。

^o^ > cscript arg1.js foo bar baz
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

CScript エラー: スクリプト "C:\Users\takatoh\Documents\w\sandbox\wsh\arg1.js" のス
クリプト エンジン "JScript" が見つかりません。

ググってみたらYahoo知恵袋の↓このページを見つけた。

 cf. http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13100243473

以下、作業のメモとして残しておく。
まず、コマンドプロンプトを管理者権限で起動する。スタート → プログラム → アクセサリ → コマンドプロンプト、を右クリックして、「管理者として実行」。で、次のようにコマンドを打ち込む。

^o^ > regsvr32 jscript.dll

すると、「jscript.dll の DllRegisterServer は成功しました。」というダイアログが出るので「OK」をクリック。これで JScript が動くようになる。

^o^ > cscript arg1.js foo bar baz
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

foo
bar
baz

Yahoo知恵袋の記事では、JScriptが損傷してるとか書いてあるけど、心当たりが無い。もしかしたら何かのときに損傷したのかも。

ちなみに、VBScript も同じ状態。

^o^ > cscript arg.vbs foo bar baz
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

CScript エラー: スクリプト "C:\Users\takatoh\Documents\w\sandbox\wsh\arg.vbs" のス
クリプト エンジン "VBScript" が見つかりません。

同じやり方では復旧できなかった。なんで?

^o^ > regsvr32 vbscript.dll

これで、成功したとダイアログには出る。が、実際にスクリプトを実行してみるとダメ。

^o^ > cscript args.vbs foo bar baz
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

CScript エラー: スクリプト "C:\Users\takatoh\Documents\w\sandbox\wsh\args.vbs" のス
クリプト エンジン "VBScript" が見つかりません。

まあ、VBScriptは使わないからいいか。

Rubyを2.0.0にバージョンアップした

もうすぐ2.1.0も出そうだというのに、いまさらながら2.0.0にバージョンアップした。
使うのはいつものとおり、One-Click Ruby Installer for Windows。ダウンロードページから、64bit版をダウンロードした。

 cf. http://rubyinstaller.org/downloads/

インストール自体はいたって簡単なので省略。あらかじめgemもいくつかインストールされていた。

^o^ > ruby -v
ruby 2.0.0p353 (2013-11-22) [x64-mingw32]

^o^ > gem list

*** LOCAL GEMS ***

bigdecimal (1.2.0)
io-console (0.4.2)
json (1.7.7)
minitest (4.3.2)
psych (2.0.0)
rake (0.9.6)
rdoc (4.0.0)
test-unit (2.0.0.0)

ついでなので、Development Kit も入れることにする。同じページから64bit版の DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe をダウンロード。自己解凍形式のexeファイルなので、C:\DevKit に解凍した。で、あとはインストール作業。次のようにした。

^o^ > cd c:\DevKit

^o^ > ruby dk.rb init
[INFO] found RubyInstaller v2.0.0 at C:/Ruby200-x64

Initialization complete! Please review and modify the auto-generated
'config.yml' file to ensure it contains the root directories to all
of the installed Rubies you want enhanced by the DevKit.

^o^ > ruby dk.rb install
[INFO] Updating convenience notice gem override for 'C:/Ruby200-x64'
[INFO] Installing 'C:/Ruby200-x64/lib/ruby/site_ruby/devkit.rb'

これでOKかな。

スクリプトの–versionオプションについて

※ 追記あり

argparse モジュールを使っているうち、気になった、というか気に入らないことがあったのでメモしておく。
とりあえず、こんなスクリプトがあったとする。

import argparse

parser = argparse.ArgumentParser(description='Hello world program.')
parser.add_argument('name', metavar='NAME', action='store',
                    help='specify name instead "world".')
parser.add_argument('-m', '--morning', dest='greeting', action='store_const',
                    const='Good morning', default='Hello', help='good morning')
parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1,
                    metavar='N', help='repeat N times')
args = parser.parse_args()

for n in range(args.times):
    print '%s, %s.' % (args.greeting, args.name)

これは以前 argparse を試してみたときのスクリプトにちょっと変更を加えたもので、どこを変えたかというと、位置引数 NAME のデフォルト値をなくして必須の引数にした点だ。

^o^ > python hello2.py -h
usage: hello2.py [-h] [-m] [-t N] NAME

Hello world program.

positional arguments:
  NAME             specify name instead "world".

optional arguments:
  -h, --help       show this help message and exit
  -m, --morning    good morning
  -t N, --times N  repeat N times

^o^ > python hello2.py Andy
Hello, Andy.

^o^ > python hello2.py -m Andy
Good morning, Andy.

^o^ > python hello2.py -m -t 3 Andy
Good morning, Andy.
Good morning, Andy.
Good morning, Andy.

^o^ > python hello2.py
usage: hello2.py [-h] [-m] [-t N] NAME
hello2.py: error: too few arguments

最後の実行例のように、NAME 引数を省略するとエラーになる。これは期待通りの動作。
で、もうひとつ覚えておいてほしいのは一番最初の -h オプションを指定した実行例。NAME 引数が無いにもかかわらずエラーにならずにヘルプが表示されている。これも期待通りの動作だ。

さて、本題はこれから。一通りの機能を実装し終わったら(そしてこれからも機能を拡張するつもりがあるなら)、スクリプトのバージョンを表示するオプションを追加したくなる。
というわけで、バージョンを表示する -v (–version) オプションを追加したのがこれだ。

import argparse

script_version = "v0.1.0"

parser = argparse.ArgumentParser(description='Hello world program.')
parser.add_argument('name', metavar='NAME', action='store',
                    help='specify name instead "world".')
parser.add_argument('-v', '--version', dest='version', action='store_true',
                    help='show version and exit')
parser.add_argument('-m', '--morning', dest='greeting', action='store_const',
                    const='Good morning', default='Hello', help='good morning')
parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1,
                    metavar='N', help='repeat N times')
args = parser.parse_args()

if args.version:
    print script_version
    exit()

for n in range(args.times):
    print '%s, %s.' % (args.greeting, args.name)

ところが、これは期待通りには動作しない。

^o^ > python hello2a.py -v
usage: hello2a.py [-h] [-v] [-m] [-t N] NAME
hello2a.py: error: too few arguments

NAME 引数が無いために、引数が足りないと怒られてしまう。
この問題は、NAME 引数を定義するところで、nargs=’?’ とすることでとりあえず回避できる。

import argparse

script_version = "v0.1.0"

parser = argparse.ArgumentParser(description='Hello world program.')
parser.add_argument('name', metavar='NAME', nargs='?', action='store',
                    help='specify name instead "world".')
parser.add_argument('-v', '--version', dest='version', action='store_true',
                    help='show version and exit')
parser.add_argument('-m', '--morning', dest='greeting', action='store_const',
                    const='Good morning', default='Hello', help='good morning')
parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1,
                    metavar='N', help='repeat N times')
args = parser.parse_args()

if args.version:
    print script_version
    exit()

for n in range(args.times):
    print '%s, %s.' % (args.greeting, args.name)
^o^ > python hello2b.py -v
v0.1.0

が、一方でこれは別の問題を引き起こす。NAME 引数が無くてもスクリプトが動いてしまうのだ。これは期待している動作とは違う。

^o^ > python hello2b.py
Hello, None.

NAME 引数がない場合はちゃんとエラーになってほしい。でもって -v オプションを指定したときには NAME 引数が無くてもエラーになってほしくない。思い出してほしいが -h オプションはまさにそのように動作している。どうにかして期待通りに動作するようにはできないものだろうか。argparse のドキュメントを見たもののどうも解決策はなさそうに見える。

 cf. http://docs.python.jp/2/library/argparse.html

で、結局、NAME 引数が指定されているかどうかを自前でチェックするようにした。

import argparse

script_version = "v0.1.0"

parser = argparse.ArgumentParser(description='Hello world program.')
parser.add_argument('name', metavar='NAME', nargs='?', action='store',
                    help='specify name instead "world".')
parser.add_argument('-v', '--version', dest='version', action='store_true',
                    help='show version and exit')
parser.add_argument('-m', '--morning', dest='greeting', action='store_const',
                    const='Good morning', default='Hello', help='good morning')
parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1,
                    metavar='N', help='repeat N times')
args = parser.parse_args()

if args.version:
    print script_version
    exit()

if args.name is None:
    print parser.prog + ": error: too few arguments. type " + parser.prog + " -h for help"
    exit()

for n in range(args.times):
    print '%s, %s.' % (args.greeting, args.name)

これで期待通り。

^o^ > python hello2c.py -v
v0.1.0

^o^ > python hello2c.py
hello2c.py: error: too few arguments. type hello2c.py -h for help

でもやっぱり、argparse で何とかできてほしいなあ。

[追記]
argparse のドキュメントをよくよく読んでみたら、やっぱり解決策があった。
add_argument() の引数に、action=”version” と version=バージョンナンバー を指定すればいい。直したのがこれ:

import argparse

script_version = "v0.1.1"

parser = argparse.ArgumentParser(description='Hello world program.')
parser.add_argument('name', metavar='NAME', action='store',
                    help='specify name instead "world".')
parser.add_argument('-v', '--version', action='version', version=script_version,
                    help='show version and exit')
parser.add_argument('-m', '--morning', dest='greeting', action='store_const',
                    const='Good morning', default='Hello', help='good morning')
parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1,
                    metavar='N', help='repeat N times')
args = parser.parse_args()

for n in range(args.times):
    print '%s, %s.' % (args.greeting, args.name)

実行結果:

^o^ > python hello2d.py -h
usage: hello2d.py [-h] [-v] [-m] [-t N] NAME

Hello world program.

positional arguments:
  NAME             specify name instead "world".

optional arguments:
  -h, --help       show this help message and exit
  -v, --version    show version and exit
  -m, --morning    good morning
  -t N, --times N  repeat N times

^o^ > python hello2d.py -v
v0.1.1

^o^ > python hello2d.py Andy
Hello, Andy.

^o^ > python hello2d.py
usage: hello2d.py [-h] [-v] [-m] [-t N] NAME
hello2d.py: error: too few arguments

YAMLの読み書き

Python で YAML を読み書きするには、PyYAML というパッケージがある。
というわけで、忘れないようにメモ。

インストールは、ほかのパッケージと同様、pip でインストールできる。

^o^ > pip install pyyaml
Downloading/unpacking pyyaml
  Downloading PyYAML-3.10.tar.gz (241kB): 241kB downloaded
  Running setup.py egg_info for package pyyaml

Installing collected packages: pyyaml
  Running setup.py install for pyyaml
    checking if libyaml is compilable
    Unable to find vcvarsall.bat
    skipping build_ext

Successfully installed pyyaml
Cleaning up...

まずはサンプルのYAMLファイルを用意する。

^o^ > type sample_dict.yaml
Python: Guido
Ruby: Matz
Perl: Larry

YAMLの読み込み

yaml.load関数を使う。

>>> import yaml
>>> f = open('sample_dict.yaml', 'r')
>>> data = yaml.load(f)
>>> f.close()
>>> data
{'Python': 'Guido', 'Ruby': 'Matz', 'Perl': 'Larry'}
>>> type(data)
<type 'dict'>

ちゃんと辞書型のデータになっているのがわかる。ファイルオブジェクトを作らなきゃいけないのが Ruby と比べてちょっと面倒だな。
あ、それから、インポートするのは、pyyaml じゃなくて yaml ね。

YAMLの書き出し

書き出すときには、yaml.dump関数。文字列が返ってくるので、これもやっぱりファイルオブジェクトを使ってファイルに書き出す。

>>> f = open('sample_dict2.yaml', 'w')
>>> f.write(yaml.dump(data))
>>> f.close()
>>> exit()

^o^ > type sample_dict2.yaml
{Perl: Larry, Python: Guido, Ruby: Matz}

ふむ、辞書(YAMLではマップって言うんだっけ?)のフォーマットが読み込んだファイルとは違うな。PyYAMLではこういうフォーマットにしかならないんだろうか。

リストの場合

リストでも試してみよう。

^o^ > type sample_list.yaml
- Python
- Ruby
- Perl

読み込み:

>>> import yaml
>>> f = open('sample_list.yaml', 'r')
>>> data = yaml.load(f)
>>> f.close()
>>> data
['Python', 'Ruby', 'Perl']
>>> type(data)
<type 'list'>

書き出し:

>>> f = open('sample_list2.yaml', 'w')
>>> f.write(yaml.dump(data))
>>> f.close()
>>> exit()

^o^ > type sample_list2.yaml
[Python, Ruby, Perl]

やっぱりフォーマットが違う。
まあ、データの量とかも関係があるのかも。何百行にもなるようなリストを上のようなフォーマットで出力されたら見難くてしょうがない。

Webページから画像を一括ダウンロードするPythonスクリプト

urllib とか BeautifulSoupでのスクレイピングとかの練習を兼ねて作ってみた。何に使うかは想像にお任せする;p

# coding: utf-8

import urllib
import re
import argparse
import os
from BeautifulSoup import BeautifulSoup

script_version = 'v0.0.1'
re_image = re.compile(".+\.(jpg|png|gif)")

def get_linked_images(soup):
    for a in soup("a"):
        for i in a("img"):
            a2 = i.parent
        if re_image.match(a2["href"]):
            image = a2["href"]
            file = url_to_filename(image)
            try:
                print image
                urllib.urlretrieve(image, file)
            except IOError:
                pass

def get_embeded_images(soup):
    for i in soup("img"):
        image = i["src"]
        if re_image.match(image):
            file = url_to_filename(image)
            try:
                print image
                urllib.urlretrieve(image, file)
            except IOError:
                pass

def url_to_filename(url):
    filename = url.split('/')[-1]
    filename = re.sub('\?.+', '', filename)
    if args.dir:
        filename = os.path.join(args.dir, filename)
    return filename

parser = argparse.ArgumentParser(description="Download images from web page.")
parser.add_argument('url', metavar='URL', nargs='?', action='store',
                    help='specify URL.')
parser.add_argument('-v', '--version', dest='version', action='store_true',
                    help='show version and exit')
parser.add_argument('-e', '--embeded-image', dest='embeded', action='store_true',
                    help='download embeded images(default)')
parser.add_argument('-l', '--linked-image', dest='linked', action='store_true',
                    help='download linked images')
parser.add_argument('-d', '--dir', dest='dir', metavar='DIR', action='store',
                    help='download into DIR')
args = parser.parse_args()

if args.version:
    print script_version
    exit()

url = args.url
if args.dir:
    os.makedirs(args.dir)

print "Download images from " + url + "\n"
res = urllib.urlopen(url).read()
soup = BeautifulSoup(res)
if args.linked:
    get_linked_images(soup)
else:
    get_enbeded_images(soup)

ハイフンでつながれた数字を連続する数列に変換するPythonスクリプト

ようするに、この間の逆をやろうってことだ。
 cf. 連続する数列をハイフンでまとめるPythonスクリプト – blog.panickblanket.com

import re

def expand_num(s):
    a = re.split(", *", s)
    r = re.compile("\d+-\d+")

    def f(s1):
        a, b = map(int, s1.split('-'))
        return range(a, b+1)

    def g(x):
        if r.match(x):
            return f(x)
        else:
            return [int(x)]

    return reduce(lambda x, y: x + g(y), a, [])

if __name__ == '__main__':
    print expand_num("1-3")
    print expand_num("1-3, 5, 7-8")
    print expand_num("1, 3-5, 7")

実行結果:

^o^ > python expand_num.py
[1, 2, 3]
[1, 2, 3, 5, 7, 8]
[1, 3, 4, 5, 7]