Skip to main content

C++ of the Day #40 - Python의 OptionParser 클래스 따라잡기

텍스트 기반 프로그램을 만들때 필요한 기능중의 하나가 command line option들을 parsing 해주는 것입니다. 원래는 boost::program_options 라이브러리를 소개하는 글을 쓰려고 했습니다. 그런데 마침 Python을 사용하면서 알게 된 OptionParser 클래스가 좀 더 사용하기가 쉬운 것 같아 이 클래스를 C++ 버전으로 만들어 보았습니다.

boost::program_options 라이브러리와는 달리 옵션들의 grouping이나 설정 파일을 사용하는 기능은 구현되어 있지 않습니다.

그리고 옵션들의 타입은 모두 string이나 vector<string>을 사용합니다. boost::program_options과 같이 as<int> 하는 식의 함수를 제공할 수도 있었습니다만 이렇게 하려면 exception들을 정의해야 하기 때문에 생략하였습니다. 필요하다면 다음과 같이 boost::lexical_cast를 사용하면 됩니다.


int blocksize = boost::lexical_cast(opts.get(BlockSizeOpt));


그리고 OptionParser 클래스는 getopt_long을 사용하여 구현되었기 때문에 parsing하는 방법은 GNU coding standards를 따릅니다.

Command line option에는 short form과 long form이 있습니다. 예를 들어 -I는 short form, --include-path는 long form입니다. 또한 어떤 옵션은 여러개의 값을 가질 수도 있습니다. gcc의 -I 옵션은 여러개의 값을 가질 수 있는 옵션의 예입니다.

그럼 이제 OptionParser의 사용법을 하나씩 알아보겠습니다. 먼저 OptionParser를 다음과 같이 생성합니다.


OptionParser op("Usage: %prog [options] arg1 arg2 ...", // usage
"1.0", // version
"Report bugs to ." // message
);


첫번째 인자는 usage 문자열이고 다음으로 version, message 문자열을 넣어 줍니다. usage나 version 문자열에 %prog를 넣으면 이 부분은 argv[0]로 변경되어 출력됩니다. 마지막으로 message는 help 화면의 마지막에 출력될 메시지를 넣습니다. (조금 뒤에 나오는 예제 화면을 보시면 이해가 쉽습니다.)

다음으로 위에서 생성한 OptionParser를 사용하여 parse_args 함수를 호출합니다. 결과는 OptMap과 OptArgs에 각각 저장됩니다.


OptMap optmap;
OptArgs args;
boost::tie(optmap, args) = op.parse_args(argc, argv);


여기까지 작성하고 빌드하여 실행시킨 결과는 다음과 같습니다.


$ ./a.out --help
Usage: ./a.out [options] arg1 arg2 ...

Options:
-V, --version show program's version number and exit
-h, --help show this help message and exit

Report bugs to .

$ ./a.out --version
1.0


위의 결과에서 알 수 있듯이 --help와 --version 옵션은 OptionParser 클래스에 의해 자동적으로 제공됩니다.

그럼 이제 필요한 인자를 하나씩 만들어 보겠습니다. 다음 라인은 -a, --all 옵션을 추가하는 코드입니다. 마지막 문자열은 --help 화면에 출력될 help string입니다.


op.add_option('a', "all", "do not hide entries starting with .");


이 옵션이 입력되었는지 확인하는 방법은 parse_args에서 리턴된 OptMap의 test함수를 사용하는 것입니다.


if (optmap.test('a')) allflags = true;


test함수에서 사용되는 키는 add_option 호출시 short form에 사용된 값입니다. Long form만 제공하는 옵션을 만들기 위해서는 다음과 같이 별도의 값을 지정해야 하는데 이때 OptionParser의 UserOpt 이상의 값중에서 사용해야 합니다. 따라서 이런 옵션이 다수 있는 경우 다음과 같이 enum 을 만들어 사용하는 것이 좋습니다.


enum Opt
{
AuthorOpt = OptionParser::UserOpt,
...
};

add_option(AuthorOpt, "author", "print the author of each file")


이 값이 입력되었는지 확인하기 위해서는 위의 경우와 마찬가지로 이 값을 사용하여 test 함수를 호출하면 됩니다.

다음으로 --block-size=30 이라든지 --color=always와 같이 인자가 필요한 옵션을 추가하는 방법에 대해 알아보겠습니다.


.add_option(BlockSizeOpt, "block-size", "use SIZE-byte blocks", "SIZE")
.add_option(ColorOpt, "color",
"control whether color is used to distinguish file\n"
" types. WHEN may be `never', `always', or `auto'\n"
"[default: %default]",
"WHEN",
"auto")


인자가 필요한 경우 help string 뒤에 metavar 인자를 넘깁니다. 이 인자가 null string이 아닌 경우를 인자가 필요한 옵션으로 간주하여 처리합니다. 추가로 이 값은 --help 화면에 --block-size=SIZE와 같이 출력되는 역할을 합니다.

두번째 --color 옵션의 경우에는 metavar외에 추가로 인자를 하나 더 가지고 있는데 이 인자는 default value를 나타냅니다. Default value가 있는 옵션은 인자가 필요하지만 입력되지 않은 경우에도 에러로 처리되지 않고 default value가 입력된 것처럼 처리됩니다. 이 경우 help string의 %default 부분은 "auto"값으로 변경되어 출력됩니다.

마지막으로 gcc의 -I 옵션처럼 여러개의 값을 가질 수 있는 인자를 만들려면 add_option 함수 대신 add_multi_option 함수를 사용합니다. 사용 방법은 default value를 지정할 수 없다는 점과 metavar가 항상 null string이 아니어야 한다는 점을 제외하곤 동일합니다.

Parsing 결과에서 옵션의 인자를 얻는 방법은 다음과 같습니다. 리턴 값은 string입니다.


if (optmap.test(BlockSizeOpt)) {
blocksize = boost::lexical_cast(optmap.get(BlockSizeOpt));
}


여러개의 인자를 가지는 옵션의 결과는 vector<string>로 리턴됩니다.


template
std::ostream& operator<<(std::ostream& os, std::vector const& cont)
{
std::copy(cont.begin(), cont.end(), std::ostream_iterator(os, " "));
return os;
}

...

if (optmap.test('I')) {
cout << "-I=" << optmap.get_multi('I') << endl;
}


마지막으로 parse_args함수에서 리턴되는 tuple중 두번째 값인 OptArgs에는 옵션 이외의 인자들이 vector<string>의 형태로 리턴됩니다. 예를 들어 "gcc -o a.out 1.cpp 2.cpp" 를 parse_arg하면 OptArgs에는 1.cpp, 2.cpp 두개의 값이 들어 있게 됩니다.

다음 화면은 첨부된 test.cpp를 빌드하여 만든 실행 파일의 --help입니다.


Usage: ./a.out [options] arg1 arg2 ...

Options:
-V, --version show program's version number and exit
-h, --help show this help message and exit
-a, --all do not hide entries starting with .
--author print the author of each file
--block-size=SIZE use SIZE-byte blocks
-c with -lt: sort by, and show, ctime (time of last
modification of file status information)
with -l: show ctime and sort by name
otherwise: sort by ctime
-H, --dereference-command-line
follow symbolic links on the command line
--color=[WHEN] control whether color is used to distinguish file
types. WHEN may be `never', `always', or `auto'
[default: auto]
-I, --include-path=PATH
list of search path

Report bugs to .


.

구현된 내용에 대해 설명을 하려고 했는데 막상 만들고 나니 별로 설명할 내용이 없네요. :roll: 구현을 위해 boost::variant와 static_visitor의 사용하고 있습니다. 참고하실 분들은 한번 보세요.

소스 코드는 여기서 다운로드하시면 됩니다. :-)

Comments

Popular posts from this blog

1의 개수 세기

저도 간단한 알고리즘 문제 하나... :-)

어떤 수 n이 주어졌을때 1~n까지의 수를 쭈욱 썼을때 나오는 1의 개수를 구하는 문제입니다.

예를 들어 13이라는 수가 주어지면 1~13까지의 수 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13에서 1은 1, 10, 11, 12, 13에 나오며 그 개수는 6이 됩니다. 즉, f(13)=6.

원래 문제는 f(n)=n이 되는 1이 아닌 가장 작은 수를 구하는 문제인데 이 문제의 경우에는 처음부터 쭈욱 세어나가면 되기 때문에 간단히 다음과 같이 구현을 하면 됩니다. ((한가지 주의할 점은 이전에 찾았던 n-1값을 사용하지 않고 다시 처음부터 n까지 값을 계산하면 시간이 너무 많이 걸린다는 점입니다. 위의 코드에서는 static 변수를 사용하여 이전 값에 계속 더해나가는 방법을 사용했습니다.))


#include

int count1(int n)
{
static int cnt = 1; // not 0 because n starts from 2. see main.

while (n > 0) {
if ((n % 10) == 1) ++cnt;
n /= 10;
}

return cnt;
}

int main()
{
using namespace std;

int n = 2;

while (count1(n) != n) ++n;
cout << n << endl;
}


좀 재미가 없죠? 그래서 이번 문제는 어떤 수 n에 대해서 f(n)을 O(1)시간에 구하는 알고리즘을 만드는 것입니다. 관심있으신 분들은 한번 풀어보세요. 제가 만든 코드는 내일 올려보겠습니다.

C++ of the Day #9 - Boost.Python 사용하기 #1

Python은 가장 인기있는 interpret 언어중의 하나입니다. Python의 장점 중 하나는 C/C++ 모듈과 쉽게 연동할 수 있다는 점입니다. 물론 손으로 일일히 wrapper를 만드는 것은 손이 많이 가고 에러를 만들수 있는 작업이나 SWIG등과 같은 도구를 사용하면 쉽게 python 모듈을 만들 수 있습니다.

Boost.Python은 이런 SWIG와 같이 python 모듈을 쉽게 만들 수 있도록 도와주는 라이브러리로 순수 C++만을 사용한다는 점이 SWIG와 다른 점입니다. 그리고 개인적으로는 Boost 라이브러리에 포함되어 있는 것들이 왠지 좀 더 믿음직스러워서... :-)

이번 글에서는 Boost.Python 문서에 나와 있는 예제를 가지고 간단하게 python 모듈을 만드는 방법에 대해서 알아보겠습니다.

Requirements리눅스
이 글에서는 리눅스 환경에서의 사용 방법을 설명한다.Boost.Python 라이브러리 (1.33.1)
Boost 라이브러리를 다운로드받아 아래와 유사한 명령으로 라이브러리를 빌드한다.
bjam -sTOOLS=gcc -with-python install

bjam의 --prefix 옵션으로 라이브러리가 설치될 위치를 변경할 수 있다.Python 라이브러리 (2.4.3)
Python을 다운로드 받아 빌드하여 설치한다.
위의 경우와 유사하게 configure의 --prefix 옵션으로 설치될 위치를 변경할 수 있다.

Write C++ Code다음과 같이 코드를 작성한다.

// greet.cpp #include <stdexcept> char const* greet(unsigned x) { static char const* const msgs[] = { "hello", "Boost.Python", "world!" }; if (x > 2) throw std::range_error("greet: index out of range"…

Hello Wordpress, again.

한 두주일 정도 Textpattern을 사용해봤는데 다시 Wordpress로 돌아오기로 결정했습니다. 무엇보다 스킨 변경이 너무 복잡하고 사용자층이 Wordpress에 비해 너무 앏네요. 원하는 plugin도 찾기 어렵고... :-|

그동안 Textpattern에 썼던 글들은 모두 Wordpress로 옮겼습니다. 2개 있던 댓글도 옮겼는데 그중의 하난 제가 쓴... ;-)

애초에 wp-dokuwiki plugin이 무거워서 옮겼던 것이라 이 plugin은 설치를 안할 예정인데 몇가지 아쉬운 점이 있네요.

첫째는 code highlighting 기능인데 이 기능은 예전에 만들어 놨던 것을 조금 수정해서 쓰려고 준비중입니다. 두번째는 Footnote 기능인데 찾아보니 Footnotes 0.9 Plugin for WordPress 2.0.x라는게 있네요.

이정도면 비록 wiki syntax에 비할바는 아니지만 쓸만할 것 같습니다. :-)