레이블이 Ruby인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Ruby인 게시물을 표시합니다. 모든 게시물 표시

2007년 7월 11일 수요일

[메모] 루비로 특정사이트를 스크랩하기에 대한 생각?

친구인 H군(사이트 운영자입니다.)이 '스팸 때문에 골치아퍼~'라고 투덜투덜 되길래 루비로 스크래퍼(?)를 만들면 어떨까 하고 고민을 하다가 조사를 좀 해봤습니다. 

기본적인 아이디어는 다음과 같습니다.
  1. 사이트의 주소를 인자로 받습니다.
  2. 사이트의 html을 읽어옵니다.
  3. html에서 http://로 시작되는 주소를 추출합니다.
  4. 해당 주소가 스팸주소인지를 비교합니다.
    1. 스팸주소인 경우 해당주소를 삭제 대상으로 기억해 놓습니다.
    2. 해당 주소가 운영 사이트의 하위 사이트면 해당 주소로 이동 후 3,4의 과정을 반복합니다.
아주~ 단순한 프로그램이라서 금방되겠지 생각을 했답니다만.. 쉽지가 않더군요. (세상일이 만만하겠습니까?)

1생략하고 2의 부분은 Net::HTTP.start(url) {|http| response=http.get('index.html')} 을 사용하여 페이지를 요청한 다음의 response를 얻어냅니다. 그리고, response.body를 통해서 페이지의 각 라인을 얻어옵니다.

여기에서 정규표현식을 써서 http://로 된 주소를 얻어옵니다. 이 주소를 이용해서 다시 탐색에 들어가거나 스팸여부를 비교하기 위한 수단으로 사용합니다.

주소를 얻는 부분은 쉽긴한데 html을 파싱해서 어떤 데이타를 얻어야할지 필요한데이타를 어떻게 가져와야할 지가 애매하더군요. 공식적인 html 파서는 없는 듯 했습니다. (검색 결과 몇개 나오는 것은 있더군요. RAA-html-parser2, JavaScript파서도 있더군요.)

결론은... 여기까지.. 역시 같이 뭔가를 같이할 사람이 필요한 시점인 것 같군요 ^^

관련자료

2007년 7월 10일 화요일

[메모]루비로 프로그램 짜면서 실수했던 것들

TinyMCE의 jstrim으로 js파일들을 통합하려고하니 다음의 문제가 있었습니다.
  • 합쳐야할 js파일들의 목록을 xml파일로 따로 관리해야한다.
  • 에러나는 경우 어디에서 에러가 발생하는 지 알수가 없다.
  • (그리고 개인적인 사정) C#을 봐야 소스를 볼 수 있다. (-_- 귀차니즘~)
이러한 이유로 루비로 뚝딱뚝딱 프로그램을 만들어 보았습니다. 프로그램의 동작은 단순합니다.
  1. php파일을 읽어들입니다.
  2. <? if($isDebug) { ?> 에서부터 <?} else {?>까지의 script태그를 가진 라인들에서 src의 주소를읽어 들입니다.
  3. <?} else {?> 에서부터 <?} ?>까지의 script태그를 읽어들입니다.
  4. 2의 파일목록들을 3의 파일에 합쳐서 배포합니다.
아주 단순한 프로그램입니다만 짜는 과정에서 다음과 같은 에러로 고생을 했습니다.
  • String 변수와 String 변수를 +연산자로 합치는 경우 (변수가 nil 이 될 때 에러가 나더군요.)
  • Ruby 프로그램은 Block없이 프로그램을 짤 수 없다.
  • File.open()으로 파일을 여는 경우 인자로 받는 경로가 절대 경로가 아니면 에러가 발생한다.(File.expand_path()로 절대 경로를 알 수 있다.
이런 노력(?)끝에 허접 프로그램이 나왔습니다. 돌기는 잘 도는 군요 - .-)//

허접이지만.. 오랜만에 포스팅을 위해서 올려봅니다.

more..

2007년 5월 7일 월요일

4장 컨테이너,블록,반복자

  배열(Array)
Array 클래스는 객체 참조를 콜렉션(Collection)으로 저장합니다. 배열의 기본 특징은 다음과 같습니다.
  • []를 사용하여 위치를 지정합니다.
  • 위치는 정수로 지정을 합니다.
  • 인덱스는 0부터 시작합니다.
  • 정수로 지정하면 해당 위치의 객체를 반환하고, 없는 경우에는 nil을 리턴합니다.
  • 음수로 지정하는 경우에는 뒤에서부터 찾습니다.
  • 배열 인덱스를 [start, count]를 사용하여 새로운 배열을 반환할 수 있습니다.
    a = [1,2,3,4,5]
    a[1,4]  -> [2,3,4,5]
  • 또한 범위(.. ...)를 사용하여서 새로운 배열을 반환할 수 있습니다.
    a = [1,2,3,4,5]
    a[1..3]  ->  [2,3,4]   # ..는 마지막 위치를 포함합니다.
    a[1...3]  ->  [2,3]    # ...는 마지막 위치를 포함하지 않습니다.
  해시(Hash)
배열과 흡사합니다. 배열의 인덱스는 정수이나, 해시의 인덱스는 어떠한 종류의 객체도 사용할 수 있습니다. 키 => 값의 쌍을 중괄호 사이에 써서 해시를 정의합니다. 해시에는 원소에 순서가 없기 때문에, 스택이나 큐로는 쓰기 어렵습니다.
 
   h = {'dong' =>'canine', 'cat' =>'filine', 'donkey'=>'asinine'}

  SongList 컨테이너 구현하기
class SongList
   def initialize
      @songs = Array.new
   end
   def [](index)
      @songs[index]
   end
end

  블록과 반복자
class SongList
   def with_title(title)
      @songs.find{|song| title == song.name}    # title이 song(songs의 element)의 name과 같은지 비교
   end
end
블록은 메소드 호출 바로 뒤에만 나타납니다.
블록 안의 코드는 나타나자마자 실행되지 않으며, 블록이 나타날 때의 문맥을 기억하고 메소드로 들어갑니다.
def three_times
   yield "A"
   yield "B"
   yield "C"
end
three_times {|name| puts name}
또한, 위의 코드처럼 매개변수를 넘기고 이를 넘겨 받을 수도 있습니다.
블록은 {}로도 정의할 수 있으나, do...end를 사용하여 정의할 수 있습니다. 이 경우에는 {}보다 우선순위가 더 낮습니다.

inject 메소드는 컬렉션의 원소를 하나씩 반복하며 모아서 새로운 값을 만듭니다.
   [1,3,5,7].inject(0) {|sum,element| sum + element}   -> 16

inject 메소드에서 sum의 값은 0이 됩니다.(매개변수가 됩니다.) element의 경우에는 [1,3,5,7] 배열의 첫 번째인자가 됩니다. 두 번째 실행될 때는 바로 전에 실행된 블록의 반환 값이 sum이 됩니다.
매개변수가 없는 경우에는 [1,3,5,7] 배열의 첫 번째 값이 인자가 됩니다.

  트랜잭션을 위한 블록
class File
   # *args: 실제로 메소드에서 넘겨받은 매개변수를 모아서 배열로 만들고 args로 이름을 붙인다.
   def File.open_and_process(*args)
      # ["test", "r"]을 배열로 받아서 File.open으로 넘긴 다음에 블록을 실행합니다.
      f = File.open(*args)
      yield f
      f.close()
   end
end

File.open_and_process("test","r") do|file|
   while line = file.gets
      puts line
   end
end

  블록은 클로저이기도 하다.
Button을 상속받아 새로운 Button을 만드는 경우, 새로운 버튼을 만들기 위해서 많은 수의 하위클래스가 필요해지고, 실제 버튼을 눌렀을 때 동작하는 부분은 버튼이 아니라 버튼에 의해서 실행되는 코드가 책임을 가지고 있다.

songlist = SongList.new
class JukeboxButton < Button
   # &를 붙일 경우, 코드 블록이 있는지 살펴본 다음에 결합된 코드는 Proc 클래스 인스턴스로 변환되어
   # 형식인수에 할당됩니다.
   def initialize(label, &action)
      super(label)
      @action = &action
   end
   def button_pressed
      @action.call(self)
   end
end
start_button = JukeboxButton.new("Start") { songList.start }
pause_button = JukeboxButton.new("Pause") { songList.pause }

Proc객체를 만들어 얻는 것은, Proc이 정의된 시점에서의 모든 문맥 정보를 함께 갖게 된다는 점입니다. self값과 범위내의 메소드, 변수, 상수를 포함하게 됩니다.(closure)

def n_times(thing)
   return lamda {|n| thing *n}   # 블록을 Proc객체로 변환시켜 줍니다.
end
p1 = n_imes(23)
p1.call(3)

2007년 5월 1일 화요일

3장 클래스,객체,변수

  클래스
클래스 정의는 다음의 양식을 띕니다. (Object에서 상속)
class ClassName
    # 생성자
        def initialize(param)
            @name = name     #인스턴스 변수 앞에는 @를 사용한다.
            ....
        end

    # 기본제공, 객체의 ID와 인스턴스 변수를 출력
    def inspect
       ...
    end

    # obj를 나타내는 string을 리턴
    def to_s
       ...
    end
end

  상속과 메시지
상속은 < 기호를 사용합니다.
class SubClassName < SuperClassName
    def initialize(param)
       # 부모 초기화
       super(param)
       ...
    end
   
    # super의 사용 예
    def to_s
      super + ""
    end
end

- SubClass에서 메소드가 없는 경우 부모를 따라가며 해당하는 메소드를 찾습니다.
- 메소드 내에서 super 키워드를 매개변수 없이 호출하면, 현재 객체의 부모의 메소드 중 동일한 이름의 메소드를 호출하도록 요청한다. 때로는 현재 메소드 호출에 전달된 매개변수를 호출하도록 요청합니다.
- 루비 객체의 최상위 부모는 Object이다. Object에는 to_s를 비롯한 몇 개의 메소드를 정의하고 있습니다.

 객체와 속성
외부에서 객체의 상태에 접근하고 이를 다루는 메소드를 속성(attribute)라고 부릅니다.
Song 클래스의 name 접근 메소드를 정의한다면 다음과 같이 정의할 수 있습니다.
class Song
   def name
      @name
   end
end

song = Song.new()
puts song.name

접근 메소드를 attr_reader를 사용하여 쉽게 정의할 수 있습니다. 사용법은 위의 접근 메소드와 동일합니다.
class Song
   attr_reader :name
end

  쓰기 가능한 속성
쓰기 가능한 속성의 정의는 다음과 같습니다.
class Song
   def duration=(new_duration)
      @duration = new_duration
   end
end

song = Song.new()
song.duration = 300

attr_reader처럼 attr_writer를 사용해서도 쉽게 정의할 수 있습니다.
class Song
   attr_writer :duration
end

  가상의 속성
속성이 꼭 인스턴스 변수로 존재할 필요는 없습니다. 가상으로 정의가 가능합니다.
class Song
   def duration_in_minutes
      @duration/60.0
   end
   def duration_in_minutes=(new_duration)
      @duration = (new_duration*60).to_i
   end
위의 코드 정의를 보면 song.duration_in_minutes라는 변수를 사용할 수 있지만, 실제로는 변수가 정의되어 있지는 않습니다.

  클래스 변수와 클래스 메서드
클래스 변수(@@name처럼 @두 개를 사용하여 정의)는 그 클래스의 모든 객체가 공유하며, 클래스 메서드에 의해서만 접근할 수 있습니다.

class MyLogger
   private_class_method : new

   # 클래스 변수는 사용하기 전에 반드시 초기화되어야 합니다.
   @@logger = nil

   # 클래스 메서드의 정의는 클래스이름.메소드이름으로 정의합니다.
   def MyLogger.create
      @@logger = new unless @@logger
      @@logger
   end
end
위의 코드대로 정의하는 경우 new를 사용하여 클래스를 생성할 수 없고, MyLogger.create를 사용해서만 클래스를 생성할 수 있게 됩니다.(싱글턴 패턴)

  접근제어
접근에 대해서 설정할 수 있는 속성은 세가지가 있습니다.
  • Public 메소드 - 아무런 접근 제어를 하지 않습니다.
  • Protected 메소드 - 그 객체를 정의한 클래스와 그 하위 클래스에서만 접근할 수 있습니다.
  • Private 메소드 - private 메소드는 오직 현재 객체에서만 접근 가능합니다. 같은 클래스의 다른 객체에서도 접근이 불가능합니다.
class MyClass
   def method1     #기본값은 public입니다.
   end
protected      #이하 정의되는 메소드는 protected 접근을 가집니다.
   def method2
   end
   ...
private     #이하 정의되는 메소드는 private 접근을 가집니다.
   ...
public
   ...
end

또는 다음과 같이 정의할 수 있습니다.
class MyClass
   ...
   public :method1, :method2
   protected :method3
   private :method4
end

변수는 값을 저장하는게 아니라 레퍼런스(참조)를 저장하고 있다. (Java와 동일)

2장 Ruby.new 정리

  루비 설치, 맥에서 빌드하기
맥에서 루비 설치할 때 사용했던 관련 포스트(Building and installing Ruby 1.8.6 on Mac OS X Tiger)
RDT에 RDoc과 ri명령어를 넣어주기 위해서는 따로 설치가 필요
./configure --enable-install-doc --with-readline-dir=/usr/local --enable-pthread
make
make test
sudo make install
make check

  루비 기초 문법

루비는 객체지향 언어다.
클래스/메서드/ 인스턴스/생성자 - new 메서드로 호출
객체는 고유하다. 객체 아이디(Object identifier), 인스턴스 변수(Instance variables), 인스턴스 메서드(Instance method, 클래스 내부적으로 호출되는 메소드)를 갖는다.
루비에서 메소드 호출 형태 : Receiver.methodName()
자바와 달리 루비에서는 숫자 객체 자체에 절대값을 구하는 기능을 포함하고 있다.
  Math.abs(number) <-> number.abs
루비는 한 줄에 하나의 표현식만 쓴다면 세미콜론을 넣을 필요가 없다.
주석은 #을 사용하면 줄 끝까지 적용된다.

메소드를 정의하려면 def키워드가 필요하다.
메소드  다음에는 괄호로 싸인 메서드 형식인수를 쓴다.  def method(param)
중괄호로 구분하지 않는다.
키워드 end를 써준다.

변수는 대입을 하려 할 때, 자동으로 만들어진다.

문자열을 만드는 방법 - 문자열 리터럴을 사용한다. ''""를 붙여서 문자열 리터럴을 만든다.
 - 작은 따옴표는 루비에서 사소한 작업시 사용하며
 - 큰 따옴표는 더많은 작업을 한다. 대표적인 것으로 \n에 대한 개행 처리를 수행한다.
표현식을 삽입한다. #{expression}과 같은 형태가 있으면 이는 expression의 값으로 표현된다.
전역변수($name)나 인스턴스 변수(@name)도 다음과 같이 "#$greeting #@name"형태로 사용이 가능하다.
메소드에서는 마지막으로 실행된 표현식의 결과값이 리턴된다.

  Naming Rule
지역변수, 메서드형식인수, 메서드이름은 모두 소문자나 밑줄(_)로 시작되어야 한다.
전역변수는 $
인스턴스 변수는 @
클래스 변수는 @@
클래스 이름, 모듈 이름, 상수는 대문자로 시작된다.
인스턴스 변수는 단어 사이에 밑줄을 넣어서 구분하고, 클래스 이름의 경우에는 대문자로 시작한다.

  배열과 해시
Array는 []로 정의 하며, 스트링으로 구성된 배열은 %w{}를 사용하여 쉽게 정의할 수 있다.
Hash는 {}로 정의하며, {key => value} 형태로 정의한다. 객체가 없는 경우는 nil을 리턴한다.

  제어문
if/elsif/else/end 와 같이 정의할 수 있다. 종료는 end로 끝난다.
while 조건문
 내용
end

내용이 한 줄인 경우, 구문 변경자(statement modifier)를 사용하여 한 줄로 표시할 수 있다.
if radiation >3000
  puts "위험합니다."
end
 => puts "위험합니다." if radiation >3000

  정규표현식
if line ~= /Perl|Python/
  puts "스크립트 언어에 대한 언급: #{line}"
end
~= 특정 문자열이 정규표현식과 매치되는지 검사하는데 사용할 수 있다.

  블록과 반복자
코드블록은 중괄호do...end로 묶인 코드다.
{puts "Hello"}
do
  puts "Hello"
end
블록을 메소드에서 다시 호출하기 위해서는 yield를 사용해서 호출할 수 있다.
블록에 매개변수를 전달하려면 |를 사용한다. call_block {|param| ...}

  읽기와 쓰기
puts는 개행문자를 문자열의 끝 부분에 더해서 출력을 해준다.
print는 개행문자 없이 문자열만을 출력해준다.
둘 다(puts/print) 어떤 I/O 객체에든 사용할 수 있다.
printf(C나 perl)과 동일하다.
입력, gets 메소드를 사용하면 스트림에서 문자열 한 줄을 읽어들인다.