Be lost in thought…

Nineye’s personal weblog!!!

x11을 통해 원격으로 Android 프로젝트 개발을 해보자

Published by Nineye under development tools on August 30, 2010

Android 프로젝트를 진행하게 되어서 개발 환경을 구축하게 되었는데, 이것이 그다지 간단한 작업이 아니었다. 물론, 안드로이드 개발자 사이트에 개발 환경을 구축하는 방법을 자세히 설명해 놓았지만, 3번 이상은 하지 않고 싶은 작업이다. 또한, 우리 본부내에서 안드로이드 프로젝트에 참여하는 사람들이 설치에 관해 이것 저것 물어보고, 설치 메뉴얼을 제대로 따라하지 않아서 문제가 많이 발생하는 것을 보고, “아~ 이건 아니다~”라고 생각했다.

그때 나타난 우리의 구세주~~~ 우리 부서에서 관리하는 성능이 좀 괜찮은 우분투 서버가 있었는데, 창진형이 그 서버에 이클립스를 설치하고 x11을 통해 원격으로 접속하게 환경을 갖춰 놓은 것을 보고, “아!!! 이거다!!!” 라는 생각이 들었다. 그래서 그 우분투 서버에 안드로이드 플랫폼 개발 환경을 모두 정상적으로 갖춰두고, “자~ 여러분들 이제 힘들게 개발 환경 따로 구축하지 말고, 여기에서 개발하면 됩니다!”… 내 생각에는 좋은 환경이었으나, 생각외로 리눅스라는 환경이 좀 귀찮았나보다. 현재 우분투에 구축된 개발 환경을 통해 개발하는 사람은 창진형과 나밖에 없다…ㅋㅋ

이 글의 주제는 이것이 아니고, 최근 회사에서 스마트폰 프로젝트 열풍이 불어서 맥북을 하나 얻었는데, 이게 개발 환경으로는 더 없이 좋은 것 같다. 운영체제도 깔끔하고 내장되어 있는 소프트웨어도 깔끔하고… 그래서 안드로이드 프로젝트도 여기서 개발하려고 환경을 갖추다가, 어떻게 하면 가장 깔끔한 원격 개발 환경을 갖출 수 있을까 고민하다가 알아낸 정보들을 정리하고자 한다. 우분투 서버에 개발 환경 구축하는 방법은 필요하면 다음에 포스팅 하도록 하겠다.

Android app 개발을 위한 툴은 이클립스를 사용할 것이기 때문에, Mac에서 이클립스를 통해 개발하는 방법을 간단히 요약하면 다음과 같다. 그리고 이후부터 Android 개발 환경이 구축되어 있는 서버를 “ubuntu”라 부르고, 실제 개발을 위해 사용하려는 pc를 “mac”이라 부르겠다.

-
x11_1-

즉, ssh client를 통해 ubuntu에 접속하고 ubuntu에서 eclipse process를 구동시킨다. 그리고 eclipse app의 화면 출력은 x11 forwarding을 통해 mac의 x11 server로 전송하고, x11 server는 전송된 데이터를 이용해 ubuntu에서 실행된 eclipse 프로그램을 mac에 띄우게 된다.

물론 위의 방법은 x11 forwarding기능을 이용한 ssh tunneling을 통한 방법이다. 그 이외에 xhost나 xauth를 통해 실행하는 방법도 있다. 하지만 그 두가지 방법은 보안상으로 취약하다고 하기때문에 일단 ssh를 이용한 방법부터 설명하고 나머지 둘은 나중에 설명하도록 한다.

-

-
ssh를 이용한 x11이용 방법

1. ubuntu 측의 필요한 설정
우선 x11 forwarding 기능을 이용하려면 ubuntu측의 sshd설정 중, X11Forwarding 플래그의 값을 yes로 설정해야 한다. 일반적 설정의 ubuntu는 /etc/ssh/sshd_config 파일에 sshd 관련 설정들이 있다. X11Forwarding 플래그는 보안상의 이유로 주석 처리 되어있는데, 주석을 풀고 yes로 바꾸자. 물론 시스템의 보안이 최우선인 서버에서는 이것을 풀지 말아야 하고, 이 서버를 통한 개발도 하지 말아야 한다. 신중히 선택하자. 이 플래그를 바꾸면 x11을 다시 구동시켜야 한다.
ubuntu에 접속할 계정에 DISPLAY 환경 변수가 설정되어 있는지 확인한다. ssh 메뉴얼의 x11 forwarding 부분의 설명에도 나와있지만, DISPLAY 환경 변수는 설정되어 있지 말아야 x11 forwarding이 제대로 동작한다.

2. mac에서 x11을 구동시킨다.
eclipse를 실행시키는 입장은 ubuntu가 서버이고 mac이 클라이언트이지만, x11의 입장에서는 mac이 서버이고 ubuntu가 클라이언트이다. 즉, mac에서 실행된 x11 서버가, 각종 x11 클라이언트들로부터 전송된 x11 어플리케이션들을 화면에 띄우게 된다. 따라서 원격으로 eclipse를 실행시키기 전에, mac에서 x11 서버를 먼저 띄워야 한다.
x11은 dock > 응용프로그램 > 유틸리티에 있다.

3. ubuntu에 접속하고, eclipse를 실행
x11을 실행시키면 기본적으로 xterm이 뜬다. 다른 터미널로 접속해도 상관이 없지만, 이왕 뜬거니 그냥 여기에서 접속하자. xterm에서 다음을 실행한다. 참고로, 예전에는 ssh -X 옵션을 사용했었는데, 보안상의 문제로 -X 옵션이 참조하는 x11 forwarding은 표준에서 제외되었다. 대신 -Y옵션을 이용한다. -Y옵션은 trust x11 forwarding이다.

ssh -Y -l 접속할계정명 접속할서버
접속할계정명@접속할서버's password:
[$] eclipse &

x11_2
잘 실행된다.

-

-
xhost를 이용한 x11이용 방법

1. mac 측의 필요한 설정

x11에서 네트워크 연결을 가능하게 하기 위해서, mac에서 다음을 설정해줘야 한다고 한다.

defaults write com.apple.x11 nolisten_tcp -boolean false
defaults read com.apple.x11
{
"nolisten_tcp" = 0;
}

이는 mac의 x11 gui 환경설정 창에서도 설정할 수 있다.

x11_3
위에서 “네트워크 클라이언트에서의 연결을 허용”에 체크해 주면 된다.
하지만 실제로 테스트해보니, 환경설정 창에서의 설정은 먹혔는데, defaults를 이용한 세팅은 먹히지 않았다. 아마 개인적인 생각으로는 환경설정 창의 설정이 우선 시 되는 것 같다. 예전에 ubuntu 서버에서도 그런 현상이 있었던 것으로 봐서는…
설정을 바꾼 뒤에는 x11을 다시 실행시켜야 적용된다. x11을 종료시키고 다시 실행시키자.

2. ubuntu 측의 필요한 설정
x11 forwarding에서와는 달리, xhost를 이용한 방식은 eclipse 어플리케이션을 디스플레이 할 위치를 환경변수에 설정해 줘야 한다. 일반적으로 ubuntu에 접속할 계정의 홈디렉토리의 .bashrc에 다음을 입력해서 자동으로 설정되게 한다.

export DISPLAY=mac의주소:0

이제, 위의 해당 계정으로 접속해서 x11 어플리케이션을 실행시키면 항상 mac에 뜨게 된다.

3. eclipse를 실행시키자
xhost를 통한 접근이 가능하게 하려면, 접근하는 x11클라이언트(ubuntu)를 접근 가능하게 등록해 줘야 한다. mac에서 다음을 실행하자.

xhost +ubuntu주소

위에서 말했다시피, xhost는 보안에 아주 취약하다. 보내어지는 정보가 암호화 되지 않기때문에 mac에서 입력하는 정보들을 고스란히 캡춰당할 수 있다. 아주 안전한 접속에서만 이 방법을 사용하자.
이제 ubuntu에 접속하여, eclipse를 실행해 보자.

eclipse &

잘 실행된다.

xauth는… 알아봐야 할 것들이 많아서 다음에 설명해야 할 것 같다(사실은 포스팅하는데 시간이 너무 걸려, 눈치가 보여서…ㅋㅋ)
어쨌든 개발 환경을 하나로 구축해 놓으니 정말 환상이다. 처음에는 windows가 설치된 pc에서 개발하다가, 다른 환경에서 개발하려니, 개발 환경부터 소스 복사까지 귀찮은 일이 한 두가지가 아니었는데, 이제는 어떤 환경에서건 ubuntu 서버로 접속만 하면 끝이다. 또한 구글에서 Android sdk 및 ndk들을 자주 업그레이드 하는데, ubuntu 서버에서만 업그레이드 해주면 모든 사용자들이 업그레이드 된 sdk를 쓸 수 있다. 물론 eclipse Android plug-in 설정만 바꾸면 개인 sdk도 사용 가능하다. 역시 이래서 linux계열 운영체제와 gnu를 좋아한다…ㅋ

Leave a Reply

typename keyword와 template qualifier

Published by Nineye under template metaprogramming on June 29, 2010

nested template class의 inner class를 다루다보면 이상한 컴파일 오류에 직면하게 된다.
여기서 이상하다는 것은 몇번을 봐도 틀린 곳이 없는데 컴파일러가 오류를 리턴하는 경우다.
그것도 이상한 오류메세지로…

사실 이건 그다지 크지 않은 내용이라 해결할때마다 원인과 해결책에 대해 글을 쓰지 않았는데, 이렇게 글을 쓰는 이유는, 오류메세지가 실제 오류와는 무관한 내용이라, 이 문제에 부딪힐때마다 무슨 오류였는지 기억이 나지 않아서 반복적인 삽질을 하기때문에 시간 낭비를 하지 않기 위해서이다.

참고로 이러한 오류를 발생시키는 컴파일러는 gnu gcc 컴파일러 버전 4.x이다. visual studio 6.0컴파일러에서는 이러한 오류가 발생하지 않았는데, visual studio 2005부터는 어느정도 c++표준을 따르려고 노력했기때문에 이런 오류가 나타날 가능성은 있을 것 같다.

다음의 코드를 보자.

template<int __A, int __A2, int __B>
class Outer {
 public:
  template<int __InA>
  class Inner1 {
   public:
    Inner1() : _a(__InA) {}
    int print() { printf("val : %d\n", _a); }
    int _a;
  };

 public:
  Outer() : _b(__B) {}
  int print() { printf("val : %d\n", _b); }
 public:
  int _b;
};

template<int __AA, int __BB>
class Test {
 public:
  typedef Outer<__AA, __AA, __BB>::template Inner1<__AA> TestInner;
 public:
  Test() : _b(__AA) {}
 public:
  int print() { printf("val : %d\n", _b); }
  int _b;
};

보기에는 이상이 없어 보인다. 하지만 컴파일러는 다음의 오류를 리턴한다.

g++ -c duple.cpp
In file included from duple.cpp:1:
duple.h:27: error: type ‘Outer<__AA, __AA, __BB>’ is not derived from type ‘Test<__AA, __BB>’
duple.h:27: error: expected ‘;’ before ‘TestInner’

오류메세지를 보면, 이해가 잘 되지 않는다. “Outer가 Test로부터 파생되지 않았다?” Outer가 Test로부터 파생되도록 사용된 부분은 없는데… 그리고 “TestInner 앞에 ;가 빠졌다?” 보통 이런 오류는 정의되지 않은 뭔가가 있을때 나오는데, 위에서 정의되지 않은 부분은 없다. 왜 이런 오류를 리턴할까?

원인을 찾다보니 결국은 c++의 모호성때문이라는 결론을 냈다. 즉, 위에서 typedef문에 있는 Outer는 class Test의 template parameter들에 의해 type이 결정되고, Outer의 type이 결정되어야 Inner1에 대해 해석할 수 있는 것이다. 우리가 눈으로 보며 생각하기에는, class Test를 사용할 때 template parameter들이 주어질 것이고, 이 parameter들에 의해 Outer type이 정의될 것이며, Outer type이 정의되면 Inner1에 대한 해석도 가능해 보인다. 하지만 컴파일러는 이것을 잘 해석하지 못하는 것 같다.

따라서 이를 해결하려면 Inner1이 type이라는 것을 명시적으로 선언해 줄 필요가 있다. 이러한 명시적 선언은 typename keyword를 통해서 할 수 있다.
이 부분에 대해서 IBM홈페이지에 정리되어 있는 다음의 내용을 참고하자.

The typename Keyword

Use the keyword typename if you have a qualified name that refers to a type and depends on a template parameter. Only use the keyword typename in template declarations and definitions. The following example illustrates the use of the keyword typename:

template<class T> class A
{
T::x(y);
typedef char C;
A::C d;
}

The statement T::x(y) is ambiguous. It could be a call to function x() with a nonlocal argument y, or it could be a declaration of variable y with type T::x. C++ will interpret this statement as a function call. In order for the compiler to interpret this statement as a declaration, you would add the keyword typename to the beginning of it. The statement A::C d; is ill-formed. The class A also refers to A<T> and thus depends on a template parameter. You must add the keyword typename to the beginning of this declaration:

typename A::C d;

You can also use the keyword typename in place of the keyword class in template parameter declarations.

요약하면, template parameter에 의존적인 type으로 한정된 명칭에 대해 언급할때, typename keyword를 써야한다는 것이다.
위 상황과 비교하면, typedef문에서 Inner1 type에 대한 새로운 type 정의를 하는데, Outer와 Inner1이 template parameter에 의존적이기 때문에 정의앞에 typename keyword를 붙여야 한다는 것이다. 수정된 코드는 다음과 같다.

template<int __A, int __A2, int __B>
class Outer {
 public:
  template<int __InA>
  class Inner1 {
   public:
    Inner1() : _a(__InA) {}
    int print() { printf("val : %d\n", _a); }
    int _a;
  };

 public:
  Outer() : _b(__B) {}
  int print() { printf("val : %d\n", _b); }
 public:
  int _b;
};

template<int __AA, int __BB>
class Test {
 public:
  typedef typename Outer<__AA, __AA, __BB>::template Inner1<__AA> TestInner;
 public:
  Test() : _b(__AA) {}
 public:
  int print() { printf("val : %d\n", _b); }
  int _b;
};

이제 컴파일이 잘 된다.

참고로, 위의 코드 중, typedef문을 보면 약간 생소한 코드가 있다.

typedef typename Outer<__AA, __AA, __BB>::template Inner1<__AA> TestInner;

Inner1은 Outer의 inner class인데 그냥 ::뒤에 붙여쓰면 되지 않을까? 이렇게…

typedef typename Outer<__AA, __AA, __BB>::Inner1<__AA> TestInner;

하지만, 컴파일러는 다음의 오류를 리턴한다.

g++ -c duple.cpp
In file included from duple.cpp:1:
duple.h:27: error: non-template ‘Inner1′ used as template
duple.h:27: note: use ‘Outer<__AA, __AA, __BB>::template Inner1′ to indicate that it is a template
duple.h:27: error: declaration does not declare anything

이 부분은 컴파일러가 친절하게도 정확한 오류를 리턴해 준다. 즉, template이 아닌 Inner1을 tempalte처럼 사용했다는 것이다. 우리가 눈으로 보기에는 Inner1은 template이지만, 컴파일러는 Inner1이 template member인지 아니면 일반 member인지 알지 못하는 것 같다. 따라서 우리는 Inner1이 template이란걸 명시해 줄 필요가 있다.
이 부분에 있어서도 IBM홈페이지에 정리된 내용이 있는데, 다음과 같다.

The Keyword template as Qualifier

Use the keyword template as a qualifier to distinguish member templates from other names. The following example illustrates when you must use template as a qualifier:

class A
{
public:
template<class T> T function_m() { };
};

template<class U> void function_n(U argument)
{
char object_x = argument.function_m<char>();
}

The declaration char object_x = argument.function_m<char>(); is ill-formed. The compiler assumes that the < is a less-than operator. In order for the compiler to recognize the function template call, you must add the template quantifier:

char object_x = argument.template function_m<char>();

If the name of a member template specialization appears after a ., ->, or :: operator, and that name has explicitly qualified template parameters, prefix the member template name with the keyword template. The following example demonstrates this use of the keyword template:

#include <iostream>
using namespace std;

class X {
public:
template <int j> struct S {
void h() {
cout << “member template’s member function: ” << j << endl;
}
};
template <int i> void f() {
cout << “Primary: ” << i << endl;
}
};

template<> void X::f<20>() {
cout << “Specialized, non-type argument = 20″ << endl;
}

template<class T> void g(T* p) {
p->template f<100>();
p->template f<20>();
typename T::template S<40> s; // use of scope operator on a member template
s.h();
}

int main()
{
X temp;
g(&temp);
}

The following is the output of the above example:

Primary: 100
Specialized, non-type argument = 20
member template’s member function: 40

If you do not use the keyword template in these cases, the compiler will interpret the < as a less-than operator. For example, the following line of code is ill-formed:

p->f<100>();

The compiler interprets f as a non-template member, and the < as a less-than operator.

요약하면, member template과 일반 member를 구분하기 위해서 template 한정자를 사용하라는 것이다.

위, 내용을 보면 재미있는 부분이 있는데,

class A
{
public:
template<class T> T function_m() { };
};

template<class U> void function_n(U argument)
{
char object_x = argument.template function_m<char>();
}

이렇게 쓸 수 있다는 것은 나도 아직 몰랐다. 지금까지 리턴값에 대한 template parameter는 모호해서 불가능 할 것이라 생각했는데, 이런식으로 리턴값에 대한 모호성을 해결하는 방법이 있다는 것은 정말 재미있는 사실이다.
참고로, “.”뿐만 아니라 “->”, “::”에도 template 한정자를 사용할 수 있다. 즉, “Class1::template Class2<T>”와 “argument->template function_m<char>”가 가능하다는 것이다.

이러한 컴파일러 제약 사항은 c++0x 표준 제정 및 향상된 컴파일러로, 앞으로 개발자가 고민하지 않아도 될 내용일 수 있어서 삽질이라는 생각을 할 수 있지만, 지금의 컴파일러가 처리하지 못하는 부분에 대해 생각해 보는 것도 재미있는 것 같아서 의미가 있을 수 있을 것 같다는 생각이든다.

Leave a Reply

MediaWiki Extension – Wiki2LaTeX

Published by Nineye under weblog & wiki on November 24, 2009

 

위키의 활용 용도 중 하나가, 특정 규정집 작성이다 보니, 입력한 문서들을 책과 같은 문서로 만들어 주는 기능이 필요하게 되었다.

일반적으로 책이나, 논문들은 pdf파일로 주로 작성되고 공유되기 때문에, 위키 페이지를 pdf파일로 만들어 주는 Pdf Export, Pdf Book, Wiki2LaTeX 등의 extension들을 찾게 되었다.

Pdf Export, Pdf Book extension은 문서 변환에 htmldoc 명령어를 사용하는데, htmldoc은 한글을 지원해 주지 않는다. 따라서 LaTeX 기반의 pdflatex 명령어를 사용하는 Wiki2LaTeX extension을 사용하기로 하였고, Wiki2LaTeX extension을 설치하고 사용하면서 발생한 문제들에 대해 정리해 본다.

 

 

설명

위키내의 각 문서를 LaTeX 문서로 변환한 뒤, LaTeX 어플리케이션을 이용하여 pdf 문서로 만들 수 있게 해준다. linux 명령어는 pdflatex을 이용하며, 이는 w2lConfig.php에서 변경할 수 있다.

 

 

Extension 위치

 http://www.mediawiki.org/wiki/Extension:Wiki2LaTeX

 

 

준비작업

LaTeX의 설치

LaTeX을 이용하기 때문에 관련 어플리케이션을 설치해야 한다. 필자는 현재 계속적으로 유지되고 있는 texlive를 설치했다.


apt-get install texlive-full

or


apt-get install texlive

# 필요한 패키지들은 따로 설치

일단, 풀버전을 설치했는데, 풀버전의 사이즈는 거의 600mb정도되며, 사용하지 않는 패키지들도 많으니, texlive 패키지만 설치하고, 사용하면서 필요한 패키지만 따로 설치하는 것이 좋다.

 

 

설정하면서

 

한글(utf-8 인코딩)  문서의 적용

linux에서 개발 시, 가장 어려운 문제중의 하나가 바로 한글 문제이다. LaTeX에서는 다양한 한글 관련 패키지들이 존재한다. kotex, cjk 패키지들이 바로 그것들이다. 필자의 블로그에서는 kotex을 설치하여 한글을 이용했지만, w2l에서는 cjk 패키지에 대한 언급이 많아서 cjk를 설치하였다. cjk를 설치하며 utf-8 인코딩의 한글 문서 변환까지 많은 우여곡절이 있었는데, 내용이 너무 길어서 따로 포스팅하였다. http://nineye.net/blog/archives/1340 를 참고하자.

 

http protocol의 한글 파일명의 사용

w2l에서는 다운받는 pdf 문서의 파일명칭에 페이지 제목을 사용한다. 따라서 페이지 제목에 한글이 포함되는 경우도 고려했어야 했는데, w2l은 한글을 고려하지 않았다.  …/extension/w2l/w2lSendFile.php 파일을 열어서 다음 부분을


$title = addslashes( $_GET['title'] );

다음과 같이 수정하자.


$title = addslashes(iconv("UTF-8", "EUC-KR", $_GET['title']));

http protocol에서는 파일명을 euc-kr 인코딩으로 처리하는 것 같다. 따라서 utf-8 인코딩의 페이지 제목을 euc-kr 인코딩으로 변환하여 파일명칭으로 넘기니 정상적으로 동작한다.

 

가끔 pdf 파일을 전송 받지 못하는 문제

해당 페이지의 pdf 문서 변환 후, 다음 페이지에서 pdf문서 링크를 클릭하면 w2lSendFile.php 페이지가 동작하는데, 이때 가끔 pdf문서의 전송이 되지 않을 때가 있다. 시간이 없어서 원인은 아직 찾지 못하였으며, 일단 다음의 코드로 변경하니 실패 확률은 줄었다.


header("Content-Type: ".$mime_type);
// Es wird downloaded.pdf benannt
header('Content-Disposition: attachment; filename="'.$title.'.'.$fmt.'"');

위의 코드 앞에, 다음을 붙여 넣자.


## nineye edit
#
# add checking
    if (!is_file($file_loc)) {
        header('HTTP/1.0 404 Not Found');
    }
    if (!is_readable($file_loc)) {
        header('HTTP/1.0 403 Forbidden');
    }

    $stat = @stat($file_loc);
    $etag = sprintf('%x-%x-%x', $stat['ino'], $stat['size'], $stat['mtime'] * 1000000);

    header('Expires: ');
    header('Cache-Control: ');
    header('Pragma: ');

    if(isset($_SERVER['HTTP_IF_NONE_MATCH'])
        && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
        header('Etag: "' . $etag . '"');
        header('HTTP/1.0 304 Not Modified');
    } elseif(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
        && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])
            >= $stat['mtime']) {
        header('Last-Modified: ' . date('r', $stat['mtime']));
        header('HTTP/1.0 304 Not Modified');
    }

    header('Last-Modified: ' . date('r', $stat['mtime']));
    header('Etag: "' . $etag . '"');
    header('Accept-Ranges: bytes');
    header('Content-Length:' . $stat['size']);

# end edit

혹시 http protocol의 내용의 내용에 정보가 부족하여서 문제가 생길까.. 하여 더 많은 페이지 정보를 넣어봤다. 확실히 실패할 확률은 많이 줄어들긴 했지만 아직 문제가 있는 것으로 보아, 정답은 아닌 것 같다.

 

 

평가

 

안정성

한글 지원 문제와 파일 전송 문제를 제외하고는 문제가 없었다. 하지만 문제를 해결하기 위해 소스를 보다보니, 생각외로 오류 처리 코드가 많지 않아 잠재적인 위험도가 높은 것 같다.

 

기능

한 페이지의 위키 문서를 처리하기에는 알맞은 것 같지만, 다중 문서를 함께 처리하는 기능이 없다. 관련된 문서를 책처럼 만드는 기능이 필요할 것 같은데 없어서 아쉽다. 참고로 PdfBook, Book extension 들이 이를 지원하지만, htmldoc 명령어를 기반으로 하기때문에 한글이 지원되지 않는 문제가 있다.

html페이지의 변환은 약간 부족한 감이 있다. 단순한 페이지는 깨끗하게 변환하는 것 같지만, 약간 복잡한 페이지는 매끄럽게 변환하지 못한다. 전체적으로는 쓸만하게 변환하는 것 같다.

 

지원

현재 개발되고 있는 최근 버전이 2009년 7월 2일자인 것으로 봐서는 계속 개발 중인 것 같다. 다국어의 지원은 지켜봐야 할 일 같다.

3 Responses to “MediaWiki Extension – Wiki2LaTeX”

  • Marx Marx says:

    영구 여전히 열심이구나 보기좋다 ^^

    • Nineye Nineye says:

      열심이는요…ㅋ 몇달동안 바빠서 업데이트를 못해서… 쓸 건 많은데, 정리하기 귀찮네요…ㅋ 잘 지내고 계시죠? ㅋ

  • Leave a Reply

    Be lost in thought… is powered by Nineye in WordPress | Entries RSS and Comments RSS

    Copyright © 2009. All right reserved by Nineye.