งานที่ผมต้องทำต่อไปใน day job มันค่อนจะเกี่ยวกับ C++/Unix เยอะหน่อย อยากมีประสบการณ์กับพวกมันมากกว่านี้ก็เลยตั้งใจลองเขียนโปรแกรมเล่นๆขึ้นมาดู
โปรแกรมที่ต้องการจะเขียนจะมีหน้าที่คือดึง Update ของคนๆหนึ่งมาจาก Twitter แสดงบนหน้าจอ (ใน Console) แล้วแยกเฉพาะส่วนที่เป็นลิงค์ใน Tweet อันนั้นมาแสดงคู่กันด้วย
นั่งทำตั้งแต่บ่ายแก่ๆ มาเรื่อยๆ เพิ่งมาเสร็จเมื่อกี้ o__o!
สรุปปัญหาที่เจอและสิ่งที่เรียนรู้ดังนี้
เลือกใช้ VIM เป็น Editor อันนี้ลองใช้มาซักระยะแล้ว รู้สึกชอบมากกว่า Emacs นะครับ ;P รู้สึกว่าตัว VIM มัน Simple ดี แล้วแทบจะมีอยู่ทุกที่ หัดทีนึงคุ้มเลยทีเดียว มันก็มีพวก script มาให้ลงเพิ่มได้เยอะแยะนะครับ เช่นตัวช่วย browse โค้ด หรือ theme หรือตัวช่วยทำ auto-complete ที่ใน VIM จะมี feature ชื่อ omni-completion ให้ด้วย (เป็น auto-com แบบ context-sensitive พูดง่ายๆ คือฉลาด) แต่ก็ยังไม่รู้สึก “เหมือนอยู่บ้าน” เท่า Visual Studio
Build โดยใช้ Makefile อันนี้เล่นเอาปวดหัว ตอนแรกคิดว่าพวก automake autoconf จะช่วยทำ Makefile ให้ได้ง่ายๆแบบไม่เปลืองแรง แต่จริงๆพวกนั้นก็ต้องเขียนไฟล์ขึ้นมาเพิ่มเหมือนกัน สุดท้ายเลยลงมือหัดเขียน Makefile เอง (และก็ได้แบบพิการๆแต่ทำงานได้ออกมา)
เรียนรู้ option ของ g++ ปกติแล้วใช้ Visual Studio ก็เอาไฟล์ยัดเข้าโปรเจค แล้วก็กด F5 ก็เอาโปรแกรมมารันเล่นได้แล้ว แต่พอต้องใช้คำสั่ง cmd ในการ compile – link เล่นเอาเหนื่อยเหมือนกัน สรุปว่าใช้ดังนี้
g++ –L path/to/lib1 –L path/to/lib2 –ldynamclib –I path/to/include –o execname source1.o source2.o staticlib.a
หรือถ้าจะสร้าง object ไฟล์อย่างเดียว
g++ –c main.cpp main.o
การใช้ Libraries ภายนอก C++ มันแทบไม่มีอะไรมาให้ใช้เลยนอกจาก STL (Standard Template Library) อยากได้อะไรต้องหาเองหมด ในทีนี้ต้องหา
- Lib สำหรับการอ่าน XML เลือกใช้ TinyXML
- Lib สำหรับการทำ HTTP Request เลือกใช้ libcurl
- Lib สำหรับใช้ Regular Expression อันนี้จริงๆมัน overkill ไปหน่อย แต่อยากใช้ดู เอาส่วนหนึ่งของ Boost มาใช้
การเอาแต่ละอันมารวมกับโปรเจคก็เป็นเรื่องน่าปวดหัวมาก เนื่องจากด้อยประสบการณ์เรื่องพวกนี้เหลือเกิน กว่าจะรวมได้ก็แทบหัวแตก ปัญหาส่วนใหญ่เกิดตอน link
สรุปได้โปรแกรมออกมาสมใจ เสียเวลาไปมหาศาล ภูมิใจมาก T__T แทบจะเป็นโปรแกรม C++ โปรแกรมแรกบน Linux ที่ไม่ใช่ Hello World! (อ่อ ผมดึง update มาจากคุณ @tamir เพราะของตัวเองมีแต่อัพเดทภาษาไทย)
อันนี้เป็น Makefile .. คนเขียนเป็นมาเห็นคงดูออกว่านุ้บมาก 55+
CXXFLAGS= -I ~/boost/include/boost-1_38 -I lib/curl/include -lrt
SRCS=main.cpp
OBJS=main.o
TINYOBJS=lib/tinyxml/tinystr.o lib/tinyxml/tinyxml.o lib/tinyxml/tinyxmlerror.o lib/tinyxml/tinyxmlparser.o
LIBS=~/boost/lib/libboost_regex-gcc43-mt-1_38.a /usr/local/lib/libcurl.a
all: termtweet
termtweet: $(OBJS) $(TINYOBJS)
g++ $(CXXFLAGS) -o $@ $(OBJS) $(TINYOBJS) $(LIBS)
tinyxml: $(TINYOBJS)
depend: $(SRCS)
makedepend $(SRCS)
clean:
-rm termtweet *.o *.bak *.swp
อันนี้ Source Code ในภาษา C++
#include <cstdio>
#include <iostream>
#include "lib/tinyxml/tinyxml.h"
#include <curl/curl.h>
#include <string>
#include <boost/regex.hpp>
using namespace std;
static int writer(char *data, size_t size, size_t nmemb,
string *buffer)
{
int result = 0;
if(buffer == NULL) return result;
buffer->append(data, size * nmemb);
result = size * nmemb;
return result;
}
void extract_link(const char *s){
string line(s);
boost::regex pat(".* (http://.*) .*");
boost::smatch matches;
if(boost::regex_match(line, matches, pat))
cout << "link: " << matches[1] << endl;
}
int main(void){
cout << "TermTweet 0.1" << endl;
cout << "Fetching items ..." << endl;
CURL *curl;
CURLcode res;
string buffer;
curl = curl_easy_init();
if(curl){
curl_easy_setopt(curl, CURLOPT_URL, "http://twitter.com/statuses/user_timeline/6380022.rss");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writer);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
if(res != CURLE_OK)
{
cout << "Can't get the RSS feed" << endl;
exit(-1);
}
cout << "Page load completed. Parsing XML .." << endl;
TiXmlElement *root;
TiXmlDocument doc;
doc.Parse(buffer.c_str());
root = doc.FirstChildElement("rss");
if(root != NULL){
TiXmlElement *channel;
channel = root->FirstChildElement("channel");
if(channel != NULL){
TiXmlElement *item;
item = channel->FirstChildElement("item");
while(item != NULL){
TiXmlElement *title;
title = item->FirstChildElement("title");
if(title != NULL){
cout << title->GetText() << endl;
extract_link(title->GetText());
}
item = item->NextSiblingElement("item");
cout << "----------------------------------" << endl;
}
}else{
cout << "Cannot find 'channel'" << endl;
exit(-1);
}
}else{
cout << "Not an RSS" << endl;
exit(-1);
}
cout << "Parse OK" << endl;
return 0;
}
ได้อะไรมาเยอะเหมือนกัน จากการหัดทำโปรเจคแสนถึกอันนี้ (แต่ก็เสียไปเยอะเช่นกัน T_T) ต่อไป … ไฮไลท์ … โปรแกรมคล้ายๆกันใน C# >.< เพิ่งเขียนเมื่อกี้ ไม่ต้องลง Lib เพิ่ม ไม่ต้องทำเบื้อกใดๆทั้งสิ้น ! (เชื่อว่า Java ก็คงใกล้ๆกัน)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml;
using System.IO;
using System.Text.RegularExpressions;
namespace SharpTermTweet
{
class Program
{
static void Main(string[] args)
{
string address = "http://twitter.com/statuses/user_timeline/6380022.rss";
WebRequest req = WebRequest.Create(address);
WebResponse res = req.GetResponse();
StreamReader sr = new StreamReader(res.GetResponseStream());
string content = sr.ReadToEnd();
XmlDocument doc = new XmlDocument();
doc.LoadXml(content);
XmlNodeList list = doc.GetElementsByTagName("item");
foreach (XmlNode node in list)
{
Console.WriteLine(node["title"].InnerText);
ExtractLink(node["title"].InnerText);
Console.WriteLine("-----------------------------");
}
}
static void ExtractLink(string line)
{
Regex regex = new Regex(".* (http://[^\\s]*) .*");
Match match = regex.Match(line);
if(match.Groups.Count >= 2)
Console.WriteLine("link: {0}", match.Groups[1]);
}
}
}
สรุปว่า เลือกเครื่องมือให้ถูกงานครับ :)
ปล. เพิ่งจะรู้ว่า Put the Right man to the Right job เป็นสำนวนที่โคดไทยเลย ไม่เชื่อลองไปหาใน Google ดูสิ ส่วนประโยคต้นฉบับจริงๆน่าจะเป็น Right man for the job มากกว่าจากการวิจัยของผม ..
Tags: c++, c#