Saturday, March 25, 2006
Cutaway illustration
ใครอยากรู้ว่า Cutaway Illustration ที่เขียนด้วย
Illustrator + Photoshop นั้นทำอย่างไรบ้าง
ลองเข้าไปดูที่นี่
'Empress of the Seas' Cruise Ship Cutaway Illustration
ขนาด Width: 45" Resolution: 350 dpi Format: CMYK Layered Photoshop File
File Size: 1.3gb
Illustration Time: 960 Hours
เวลา click ดูรูปขยายนี่ รู้สึกหนาวเลย
ขยันจริงๆ
Note: ให้ดีต้อง click ดู HowTo ด้วย
Illustrator + Photoshop นั้นทำอย่างไรบ้าง
ลองเข้าไปดูที่นี่
'Empress of the Seas' Cruise Ship Cutaway Illustration
ขนาด Width: 45" Resolution: 350 dpi Format: CMYK Layered Photoshop File
File Size: 1.3gb
Illustration Time: 960 Hours
เวลา click ดูรูปขยายนี่ รู้สึกหนาวเลย
ขยันจริงๆ
Note: ให้ดีต้อง click ดู HowTo ด้วย
Related link from Roti
สร้าง ruby class แบบ dynamic
ถ้าเราดูในเอกสารของ Ruby จะมีคำอธิบายถึง Class ว่า
ถ้าลองนำมาเขียนในแบบที่ไม่ได้ใช้ syntax ปกติดู
method
ในทีนี้ก็คือการ set global constant ที่ชื่อ "Person"
ที่นี้ถ้าต้องการ define method ก็สามารถใช้พวก class_eval ได้เลย
When a new class is created (typically using class Name ... end), an object of type Class is created and assigned to a global constant (Name in this case).
ถ้าลองนำมาเขียนในแบบที่ไม่ได้ใช้ syntax ปกติดู
Object.const_set("Person", Class.new)
pok = Person.new
method
const_set
คือการ set ค่า global constantในทีนี้ก็คือการ set global constant ที่ชื่อ "Person"
ที่นี้ถ้าต้องการ define method ก็สามารถใช้พวก class_eval ได้เลย
Person.class_eval do
attr_accessor :name, :gender
def to_s
"#{name}, #{gender}"
end
end
pok.name = "polawat"
pok.gender = "male"
puts pok # => polawat, male
Related link from Roti
Friday, March 24, 2006
SwingX
SwingX ผลงานของ Romain Guy
จับ swing มาแต่งหน้า
ดูเปรียบเทีียบกับตอน layout ใน netbeans
(ใช้ matisse ด้วย)
บริเวณ header ที่มีแสงสะท้อน (gloss) กับลายแถบ (strips)
เขาเขียน code แบบนี้
MattePainter คือ การลงสี
สังเกตว่ามีการเรียงลำดัับใน CompoundPainter ด้วย
ส่วน wood surface เขาลงสีด้วย BasicGradientPainter
เพื่อให้มีเงาของ menu ข้างบน
จากนั้นก็ใส่ Noise ด้วย NoiseFilter
ตบให้ noise กลายเป็นเส้นด้วย MotionBlurFilter
(พวก xxxFilter นี่เขาใช้ library ของ JH Labs (Java Image Processing)
ปัญหาของการใช้ effect นี้ ก็คือความเร็ว
เครื่องผม (g5) ใช้เวลา render ไป 3 วินาที
เห็นผู้เขียนเขาบอกว่าใน Mustang จะดีกว่านี้
ไม่มีปัญหาเรื่องกระตุก
ใครสนใจ code ลองไป checkout ออกมาเล่นดูได้
จับ swing มาแต่งหน้า
ดูเปรียบเทีียบกับตอน layout ใน netbeans
(ใช้ matisse ด้วย)
บริเวณ header ที่มีแสงสะท้อน (gloss) กับลายแถบ (strips)
เขาเขียน code แบบนี้
private org.jdesktop.swingx.JXPanel header;
private void setupHeaderPainters() {
GlossPainter gloss = new GlossPainter(new Color(1.0f, 1.0f, 1.0f, 0.2f),
GlossPainter.GlossPosition.TOP);
PinstripePainter stripes = new PinstripePainter();
stripes.setPaint(new Color(1.0f, 1.0f, 1.0f, 0.17f));
stripes.setSpacing(5.0);
MattePainter matte = new MattePainter(new Color(51, 51, 51));
header.setBackgroundPainter(new CompoundPainter(matte, stripes, gloss));
}
MattePainter คือ การลงสี
สังเกตว่ามีการเรียงลำดัับใน CompoundPainter ด้วย
ส่วน wood surface เขาลงสีด้วย BasicGradientPainter
เพื่อให้มีเงาของ menu ข้างบน
จากนั้นก็ใส่ Noise ด้วย NoiseFilter
ตบให้ noise กลายเป็นเส้นด้วย MotionBlurFilter
(พวก xxxFilter นี่เขาใช้ library ของ JH Labs (Java Image Processing)
BasicGradientPainter gradient = new BasicGradientPainter(
new GradientPaint(new Point2D.Double(0.0, 0.0), new Color(0xd67801),
new Point2D.Double(0.0, 1.0), new Color(0xb35b01)));
NoiseFilter noise = new NoiseFilter();
noise.setDistribution(NoiseFilter.GAUSSIAN);
noise.setMonochrome(true);
MotionBlurFilter blur = new MotionBlurFilter();
blur.setDistance(1.0f);
BrushedMetalFilter metal = new BrushedMetalFilter();
metal.setMonochrome(false);
gradient.setEffect(new CompoundEffect(new ImageEffect(noise),
new ImageEffect(blur)));
ปัญหาของการใช้ effect นี้ ก็คือความเร็ว
เครื่องผม (g5) ใช้เวลา render ไป 3 วินาที
เห็นผู้เขียนเขาบอกว่าใน Mustang จะดีกว่านี้
ไม่มีปัญหาเรื่องกระตุก
ใครสนใจ code ลองไป checkout ออกมาเล่นดูได้
Related link from Roti
สร้าง Maven2 Archetype Plugin
วันนี้ลองสร้าง archetype ดู
เป้าหมายก็เพื่อเอาไว้ทำ template สำหรับ setup project ใหม่ๆ
เนื่องจากช่วงนี้มีน้องใหม่เข้ามาทำงานหลายคน
ต้องมี tool เข้ามาช่วยหน่อย
(ไม่งั้น เวลา set project ใหม่ๆ เพื่อไว้ train
ก็ต้องมานั่ง copy, paste กันวุ่นวาย)
เริ่มด้วย
structure ของ Archetype Plugin ต้องมีหน้าตาดังนี้
เริ่มแรกสุดต้องมี file pom.xml
ซึ่ง file ที่เก็บ information ของ plugin เรา
Directory
คือ directory ที่เก็บ template file ของเรา
มีโครงสร้างและ files ตามที่เราอยากให้ archetype generate
file ที่สำคัญที่สุดก็คือ
เนื่องจากเป็น file ที่ใช้บอกว่า archetype ต้อง copy file อะไรบ้าง
เวลาที่ archetype สร้าง file ให้เรานั้น
archetype ไม่ได้แค่ copy file เฉยๆ
แต่มันจะ transform file ให้เราด้วย
โดยมันจะใช้ velocity เข้ามาช่วย
(file ที่เราเตรียมไว้ จะกลายเป็น input template ของ velocity)
ยกตัวอย่าง file web.xml
ถ้าเรากำหนดเนื้อหาแบบนี้
ตอนที่ velocity transform template แปลง
ก็จะมองหา System Property ที่ชื่อ
ถ้าหาได้ ก็จะนำมาแทนค่าให้
ยังมี case พิเศษอีกคือ
กรณีที่เป็น java code
(archetype รู้ได้จากการใช้ tag
ใน archetype.xml)
ตอนที่ archetype แปลงนั้น
มันจะทำการแปะ package (สร้้าง directory) ให้เราด้วย
โดยชื่อของ package จะตั้งตาม System Property ที่ชื่อ
ดังนั้นกรณีที่เราไม่อยากให้มันยุ่งกับ java file เรา
ก็กำหนดให้มันเป็น
หลังจากเตรียม file ทั้งหมดเสร็จ
ก็ให้ใช้คำสั่ง
เพื่อสั่งให้ maven pack plugin เรา และ deploy เข้าไปไว้ใน local repository
เมื่อต้องการใช้ ก็แค่สั่ง (ไม่ใช่ "แค่" เพราะคำสั่งโคตรยาวเลย)
อ่านเพิ่มเติม
http://maven.apache.org/guides/mini/guide-creating-archetypes.html
Note: เอกสารที่อ้างถึง อธิบายตกไปบางจุด
โชคดีที่มีคน submit project ตัวอย่างเข้ามาใน svn ของ apache
เมื่อ 2 วันก่อน ผมก็เลยรอดตายมาได้
ไม่งั้นคงไล่ไม่เจอ
เป้าหมายก็เพื่อเอาไว้ทำ template สำหรับ setup project ใหม่ๆ
เนื่องจากช่วงนี้มีน้องใหม่เข้ามาทำงานหลายคน
ต้องมี tool เข้ามาช่วยหน่อย
(ไม่งั้น เวลา set project ใหม่ๆ เพื่อไว้ train
ก็ต้องมานั่ง copy, paste กันวุ่นวาย)
เริ่มด้วย
structure ของ Archetype Plugin ต้องมีหน้าตาดังนี้
เริ่มแรกสุดต้องมี file pom.xml
ซึ่ง file ที่เก็บ information ของ plugin เรา
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>mx.plugin.arch.struts</groupId>
<artifactId>strutsApp</artifactId>
<version>0.1</version>
<packaging>maven-plugin</packaging>
</project>
Directory
archetype-resources
คือ directory ที่เก็บ template file ของเรา
มีโครงสร้างและ files ตามที่เราอยากให้ archetype generate
file ที่สำคัญที่สุดก็คือ
src/main/resources/META-INF/archetype.xml
เนื่องจากเป็น file ที่ใช้บอกว่า archetype ต้อง copy file อะไรบ้าง
<archetype>
<id>strutsApp</id>
<sources>
<source>src/main/java/Main.java</source>
</sources>
<testSources>
<source>src/test/java/Main.java</source>
</testSources>
<resources>
<resource>src/webapp/WEB-INF/web.xml</resource>
<resource>src/webapp/WEB-INF/struts-bean.tld</resource>
<resource>src/webapp/WEB-INF/struts-config.xml</resource>
<resource>src/webapp/WEB-INF/struts-html.tld</resource>
<resource>src/webapp/WEB-INF/struts-logic.tld</resource>
<resource>src/webapp/WEB-INF/struts-nested.tld</resource>
<resource>src/webapp/WEB-INF/tiles-defs.xml</resource>
<resource>src/webapp/WEB-INF/validation.xml</resource>
<resource>src/webapp/WEB-INF/validation-rules.xml</resource>
<resource>src/webapp/WEB-INF/classes/MessageResources.properties</resource>
</resources>
</archetype>
เวลาที่ archetype สร้าง file ให้เรานั้น
archetype ไม่ได้แค่ copy file เฉยๆ
แต่มันจะ transform file ให้เราด้วย
โดยมันจะใช้ velocity เข้ามาช่วย
(file ที่เราเตรียมไว้ จะกลายเป็น input template ของ velocity)
ยกตัวอย่าง file web.xml
ถ้าเรากำหนดเนื้อหาแบบนี้
<web-app>
<display-name>${artifactId}</display-name>
ตอนที่ velocity transform template แปลง
ก็จะมองหา System Property ที่ชื่อ
artifactId
ถ้าหาได้ ก็จะนำมาแทนค่าให้
ยังมี case พิเศษอีกคือ
กรณีที่เป็น java code
(archetype รู้ได้จากการใช้ tag
sources
และ testSources
ใน archetype.xml)
ตอนที่ archetype แปลงนั้น
มันจะทำการแปะ package (สร้้าง directory) ให้เราด้วย
โดยชื่อของ package จะตั้งตาม System Property ที่ชื่อ
groupId
ดังนั้นกรณีที่เราไม่อยากให้มันยุ่งกับ java file เรา
ก็กำหนดให้มันเป็น
resource
แทนที่จะเป็น source
หลังจากเตรียม file ทั้งหมดเสร็จ
ก็ให้ใช้คำสั่ง
mvn install
เพื่อสั่งให้ maven pack plugin เรา และ deploy เข้าไปไว้ใน local repository
เมื่อต้องการใช้ ก็แค่สั่ง (ไม่ใช่ "แค่" เพราะคำสั่งโคตรยาวเลย)
mvn archetype:create -DarchetypeArtifactId=strutsApp -DarchetypeGroupId=mx.plugin.arch.struts -DartifactId=pr1 -DgroupId=mx.pok.test -DarchetypeVersion=0.1
อ่านเพิ่มเติม
http://maven.apache.org/guides/mini/guide-creating-archetypes.html
Note: เอกสารที่อ้างถึง อธิบายตกไปบางจุด
โชคดีที่มีคน submit project ตัวอย่างเข้ามาใน svn ของ apache
เมื่อ 2 วันก่อน ผมก็เลยรอดตายมาได้
ไม่งั้นคงไล่ไม่เจอ
Related link from Roti
Thursday, March 23, 2006
Torta
Torta เป็นโปรแกรมที่เขียนด้วย clisp
ช่วยในการ present harddisk ของเรา ว่ามีอะไรอยู่บ้าง
โดยมันจะ scan directory ที่เรากำหนด
จากนั้นก็สร้าง flash file ที่แสดง output ให้เรา
ของเครื่องผม เบอร์หนึ่งคือ Movie ของเจ้าลูกชาย (ไม่ใช่หนังโป๊นะ)
ส่วน image ภาพศิลป์นั้น ไม่กินที่เยอะอย่างที่คิด
ถ้าดูพื้นที่ทำงาน, Java project ของผมกินที่ไป 400 MByte
ที่กินที่เยอะโดยไม่รู้ตัว ก็คือ cocoon
พี่แกเล่นกินที่ส่วน build ไป 336 MByte
(อันนี้คนละพื้นที่กับ java project)
rails ไม่ต้องพูดถึง code มันน้อยๆ
ไม่ติดฝุ่นเขาเลย
ช่วยในการ present harddisk ของเรา ว่ามีอะไรอยู่บ้าง
โดยมันจะ scan directory ที่เรากำหนด
จากนั้นก็สร้าง flash file ที่แสดง output ให้เรา
(torta:torta "/Users/pphetra" :output "/tmp/pok.swf")
ของเครื่องผม เบอร์หนึ่งคือ Movie ของเจ้าลูกชาย (ไม่ใช่หนังโป๊นะ)
ส่วน image ภาพศิลป์นั้น ไม่กินที่เยอะอย่างที่คิด
ถ้าดูพื้นที่ทำงาน, Java project ของผมกินที่ไป 400 MByte
ที่กินที่เยอะโดยไม่รู้ตัว ก็คือ cocoon
พี่แกเล่นกินที่ส่วน build ไป 336 MByte
(อันนี้คนละพื้นที่กับ java project)
rails ไม่ต้องพูดถึง code มันน้อยๆ
ไม่ติดฝุ่นเขาเลย
Related link from Roti
emacs+ruby, scheme
ผมเคยเขียน Customize Syntax Highlight
ด้วย Ruby ไปที
ตอนนั้นใช้ Ruby Syntax 1.0.0 ช่วยทำ
วันนี้มีเหตุให้ปัดฝุ่นกลับมาใช้อีกครั้ง
แต่คราวนี้มาในรูปแบบพิสดารกว่าเดิม
เป้าหมายในคราวนี้คือ
ตัวโปรแกรมจะ parse ruby code
output ที่ได้จะอยู่ในรูป scheme code
(syntax highlight ในรูปแบบ scheme code)
โดยโปรแกรมต้องสามารถเรียกจาก emacs ได้โดยตรง
ตัวโปรแกรม ruby เขียนไม่ยาก
แค่ extend
code ที่ได้หน้าตาแบบนี้
ซึ่งจะเอาไป output ออกเป็น image อีกที (ไม่ขอพูดถึงตอนนี้)
เวลาใช้ก็ต้อง integrate เข้ากับ emacs
โดยผมเลือกเอา el4r มาใช้
โดยต้องเขียน function ขึ้นมา 2 ตัว
ตัวแรกเป็น ruby function ที่อยู่ใน form ที่ emacs สามารถ call ได้
ส่วนอีกตัว ก็คือ elisp ที่ทำหน้าที่ดึงข้อมูลจาก buffer แล้วส่งต่อ
ให้ ruby function จากนั้นก็นำผลลัพท์ไปใส่ไว้ใน clipboard
(จริงๆเขียนฝั่ง ruby ตัวเดียวก็ได้ แต่ขึ้เกียจ debug หรือ หัดใช้ el4r มากกว่านี้
ก็เลยเอาแค่ทำงานได้)
ด้วย Ruby ไปที
ตอนนั้นใช้ Ruby Syntax 1.0.0 ช่วยทำ
วันนี้มีเหตุให้ปัดฝุ่นกลับมาใช้อีกครั้ง
แต่คราวนี้มาในรูปแบบพิสดารกว่าเดิม
เป้าหมายในคราวนี้คือ
ตัวโปรแกรมจะ parse ruby code
output ที่ได้จะอยู่ในรูป scheme code
(syntax highlight ในรูปแบบ scheme code)
โดยโปรแกรมต้องสามารถเรียกจาก emacs ได้โดยตรง
ตัวโปรแกรม ruby เขียนไม่ยาก
แค่ extend
Syntax::Convertors::Abstract
class Ruby2SS < Syntax::Convertors::Abstract
def convert(text)
str = "(page-para (code-block (code-line "
regions = []
@tokenizer.tokenize(text) do |tok|
str << to_scheme(tok)
end
str << ')))'
end
def to_scheme(tok)
case tok.group
when :punct :
if tok =~ /"|'/
"(code 'punct \"\\#{tok}\")\n"
else
"(code 'punct \"#{tok}\")\n"
end
when :normal :
if tok =~ /[^\n]*\n([^\n]*)/
")\n (code-line (code 'normal \"#{$1}\") \n"
else
"(code 'normal \"#{tok}\")\n"
end
else
"(code '#{tok.group} \"#{tok}\")\n"
end
end
end
code ที่ได้หน้าตาแบบนี้
(page-para (code-block
(code-line (code 'ident "order")
(code 'normal " ")
(code 'punct "=")
(code 'normal " ")
(code 'constant "Order")
(code 'punct ".")
(code 'ident "new")
)
(code-line (code 'normal "")
(code 'ident "order")
(code 'punct ".")
(code 'ident "name")
(code 'normal " ")
(code 'punct "=")
(code 'normal " ")
(code 'punct "\"")
(code 'string "")
(code 'string "pok")
(code 'string "")
(code 'punct "\"")
)))
ซึ่งจะเอาไป output ออกเป็น image อีกที (ไม่ขอพูดถึงตอนนี้)
เวลาใช้ก็ต้อง integrate เข้ากับ emacs
โดยผมเลือกเอา el4r มาใช้
โดยต้องเขียน function ขึ้นมา 2 ตัว
ตัวแรกเป็น ruby function ที่อยู่ใน form ที่ emacs สามารถ call ได้
defun(:ruby_syntax_to_scheme,
:interactive => false) { |code|
Ruby2SS.new(Syntax::load('ruby')).convert code
}
ส่วนอีกตัว ก็คือ elisp ที่ทำหน้าที่ดึงข้อมูลจาก buffer แล้วส่งต่อ
ให้ ruby function จากนั้นก็นำผลลัพท์ไปใส่ไว้ใน clipboard
(defun cv-ruby-syntax-to-scheme-slide (beg end)
(interactive "r")
(let ((str (filter-buffer-substring beg end))
(x-select-enable-clipboard t))
(kill-new (ruby-syntax-to-scheme str) 'replace)))
(define-key ruby-mode-map "\C-c\C-p" 'cv-ruby-syntax-to-scheme-slide)
(จริงๆเขียนฝั่ง ruby ตัวเดียวก็ได้ แต่ขึ้เกียจ debug หรือ หัดใช้ el4r มากกว่านี้
ก็เลยเอาแค่ทำงานได้)
Related link from Roti
Eclipse releng
วันนี้นั่งอ่าน presentation ของ Eclipse releng (Release Engineering)
From Developer to Download: A Tour of the Eclipse Platform Build Factory
น่าสนใจดี
process ของ releng เริ่มจาก
developer จะ submit เข้ามาว่า
stable version ล่าสุดของตนคืออะไร
ผ่านทาง map file
ที่มีหน้าตาประมาณนี้
ข้อมูลนี้จะไว้ใช้ build integration builds
ส่วน nightly build จะ fetch map file จาก cvs HEAD ตรงๆเลย
script และ platform (software) ที่จะใช้ในการ build
จะถูกเก็บไว้ใน local CVS ที่อยู่บนเครื่องอีกเครื่องหนึ่ง
เพื่อให้สามารถเปลี่ยน build machine ได้ง่ายๆ (กรณี machine failure)
เมื่อถึงรอบเวลาที่จะทำการ build
build machine (G5 + Yellow Linux)
ก็จะทำดังนี้
น่าสนใจนะว่า JDK ก็อยู่ใน repository ด้วย
เสร็จจาก build ก็เป็นเรื่อง test
test แบ่งหัวเรื่องที่ test ออกเป็น
ในส่วนของ JUnit กับ Performance Test
จะทำขนานกัน
(โดยใน Ant script จะใช้คำสั่ง ant parallel task )
JUnit test จะ test บน Windows, Linux, Mac
ส่วน Performance Test จะใช้ 5 เครื่อง
ประกอบด้วย 3 linux, 2 Window
เป็นเครื่องเร็วๆ 3 เครื่อง, เครื่อง cpu ช้าๆ 2 เครื่อง
ผลลัพท์ที่ได้ จะเก็บใน Derby Database Server
เครื่องที่ใช้ใน Performance Test
จะถูก Ghost ทุกๆ อาทิตย์ เพื่อที่จะได้ไม่มีตัวแปรเรื่อง
software configuration ที่จะไปรบกวน ผลการ test
เมื่อผ่านการ test ทั้งหมด
ก็เป็นขั้นการ publish ผลลัพท์
ขั้นตอนก็คือ ใช้ template สร้าง web page
ใน build machine
จากนั้นก็ใช้ rsync sync ข้อมูลกับ public site
ที่ชอบใจก็คือ เขา list ให้ดูเล่นๆว่า
failure ที่เกิดขึ้น มาจากอะไรได้บ้าง
(เขาใช้คำว่า "bloopers" -> "an embarrassing error" )
Usual Suspects
Extraordinary Build Bloopers
สุดท้ายก็เป็นขั้นตอน release
ที่ต้องจัดการเรื่อง Mirror Servers ทั้งหลาย
รวมทั้ง backup servers
ตัวเลขที่น่าสนใจ ก็คือ
jar file ที่ถูก download จาก eclipse.org โดยตรง
จะตกประมาณวันละ 1,351,152 ครั้ง
From Developer to Download: A Tour of the Eclipse Platform Build Factory
น่าสนใจดี
process ของ releng เริ่มจาก
developer จะ submit เข้ามาว่า
stable version ล่าสุดของตนคืออะไร
ผ่านทาง map file
ที่มีหน้าตาประมาณนี้
plugin@org.eclipse.ui=I20060117-0800,:pserver:anonymous@dev.eclipse.org:/cvsroot/eclipse
ข้อมูลนี้จะไว้ใช้ build integration builds
ส่วน nightly build จะ fetch map file จาก cvs HEAD ตรงๆเลย
script และ platform (software) ที่จะใช้ในการ build
จะถูกเก็บไว้ใน local CVS ที่อยู่บนเครื่องอีกเครื่องหนึ่ง
เพื่อให้สามารถเปลี่ยน build machine ได้ง่ายๆ (กรณี machine failure)
เมื่อถึงรอบเวลาที่จะทำการ build
build machine (G5 + Yellow Linux)
ก็จะทำดังนี้
- checkout target code จาก dev.eclipse.org
- checkout JDK
- เรียก Ant file เพื่อทำการ build
น่าสนใจนะว่า JDK ก็อยู่ใน repository ด้วย
เสร็จจาก build ก็เป็นเรื่อง test
test แบ่งหัวเรื่องที่ test ออกเป็น
- JUnit Tests
- Performance tests
- Verification for translation
- Javadoc
- API scanner
scan หา internal api ?ไม่แน่ใจว่าตรงนี้หาที่ไหน
ที่ javadoc หรือที่ไหน
ในส่วนของ JUnit กับ Performance Test
จะทำขนานกัน
(โดยใน Ant script จะใช้คำสั่ง ant parallel task )
JUnit test จะ test บน Windows, Linux, Mac
ส่วน Performance Test จะใช้ 5 เครื่อง
ประกอบด้วย 3 linux, 2 Window
เป็นเครื่องเร็วๆ 3 เครื่อง, เครื่อง cpu ช้าๆ 2 เครื่อง
ผลลัพท์ที่ได้ จะเก็บใน Derby Database Server
เครื่องที่ใช้ใน Performance Test
จะถูก Ghost ทุกๆ อาทิตย์ เพื่อที่จะได้ไม่มีตัวแปรเรื่อง
software configuration ที่จะไปรบกวน ผลการ test
เมื่อผ่านการ test ทั้งหมด
ก็เป็นขั้นการ publish ผลลัพท์
ขั้นตอนก็คือ ใช้ template สร้าง web page
ใน build machine
จากนั้นก็ใช้ rsync sync ข้อมูลกับ public site
ที่ชอบใจก็คือ เขา list ให้ดูเล่นๆว่า
failure ที่เกิดขึ้น มาจากอะไรได้บ้าง
(เขาใช้คำว่า "bloopers" -> "an embarrassing error" )
Usual Suspects
- สะกดผิด เช่น map file ระบุ version อะไรมาก็ไม่รู้
- disk space เต็ม
- Screen savers interupt macro tests
- Virus Scanner ตื่นขึ้นมาทำงาน ระหว่าง test
- X window crashes
- Network fail ระหว่าง checkout
Extraordinary Build Bloopers
- ฟ้าผ่า
- ไฟดับ
- overheated เพราะ แอร์ไม่เย็น
สุดท้ายก็เป็นขั้นตอน release
ที่ต้องจัดการเรื่อง Mirror Servers ทั้งหลาย
รวมทั้ง backup servers
ตัวเลขที่น่าสนใจ ก็คือ
jar file ที่ถูก download จาก eclipse.org โดยตรง
จะตกประมาณวันละ 1,351,152 ครั้ง
Related link from Roti
Tuesday, March 21, 2006
Rails Localization Plugin
Localization Plugin เป็น Plugin ที่ช่วยทำ localize อย่างง่ายๆ
ไม่ต้องไปยุ่งกับโปรแกรม gettext
หลักการ ก็คือเราต้องสร้าง ruby program ใต้
${RAILS_ROOT}/lang
จะแยกเป็น file ตาม language หรือ รวมทุก lang ไว้ใน file เดียวก็ได้
เนื้อหาใน file จะเป็นดังนี้
เวลาใช้ใน view ก็เรียกใช้ผ่าน method
การ switch ภาษา จะทำผ่าน Object Localization
feature ที่ดูน่าสนใจ อยู่ที่ตรงการ Localization.define
ที่เราสามารถทำอะไรแบบนี้ได้
ใน view จะเขียนแบบนี้
ไม่ต้องไปยุ่งกับโปรแกรม gettext
หลักการ ก็คือเราต้องสร้าง ruby program ใต้
${RAILS_ROOT}/lang
จะแยกเป็น file ตาม language หรือ รวมทุก lang ไว้ใน file เดียวก็ได้
เนื้อหาใน file จะเป็นดังนี้
Localization.define('th') do |l|
l.store 'yes', 'ตกลง'
l.store 'no', 'ไม่ตกลง'
l.store 'hello world', 'สวัสดีครับ'
end
เวลาใช้ใน view ก็เรียกใช้ผ่าน method
_
<body>
<%= _ 'hello world' %>
</body>
การ switch ภาษา จะทำผ่าน Object Localization
Localization.lang = params[:lang] == nil ? :default : params[:lang]
feature ที่ดูน่าสนใจ อยู่ที่ตรงการ Localization.define
ที่เราสามารถทำอะไรแบบนี้ได้
l.store '(time)', lambda { |t| t.strftime('%H:%M') }
ใน view จะเขียนแบบนี้
<%= _ '(time)', Time.now %>
Related link from Roti
Rails 's Date Widget Plugin
ปกติใน rails, ถ้าเราต้องการ entry ข้อมูลที่เป็น Date
rails จะมี helper ที่ชื่อ
ซึ่งได้หน้าตาของ UI ออกมาอย่างนี้
ดูแล้วไม่ถูกใจ user ชาวไทยอย่างแน่นอน
ผมก็เลยหาทางเปลี่ยนไปใช้ jsCalendar แทน
หน้าตาที่ได้ ก็จะเปลี่ยนไปเป็นแบบนี้
โดย jsCalendar มีวิธีการใช้ง่ายๆดังนี้
ที่นี้ ถ้าอยากนำมาใช้ใน rails
จะทำอย่างไรให้ดูเป็น rails-style
(เขียนน้อยๆ, default เยอะๆ)
ประเด็นของการทำ widget เอง คงแยกเป็น 2 เรื่อง คือ
กรณีของการ render เราจะทำ helper method ขึ้นมาใหม่ตัวหนึ่ง
ให้ชื่อว่า date_picker
มีลักษณะการใช้งานดังนี้
(จะเห็นว่ามีวิธีการใช้ที่เหมือนกับ date_select ของ Rails เลย)
ส่วนการ parse parameter นั้น
จะออกแบบให้ transparent กับ code เดิม
โดยต้องการให้ใช้กับ code ที่ gen จาก scaffold ได้เลย
(code ที่อยู่ใน controller class)
ของเดิม เวลา date_select submit ข้อมูลกลับมา
ข้อมูลที่กลับมาจะอยู่ในรูปแบบนี้
เราก็จะเลียนแบบวิธีการส่ง parameter แบบเดิมนี้ (rails เรียกว่า multiparameter_attributes)
โดยแทรก filter เข้าไป
เพื่อทำการดักแปลงข้อมูลที่ submit มาจาก form
การใช้งาน filter ออกแบบให้ใช้คำสั่งแบบนี้้
เมื่อตกลงใจเรื่อง design ได้แล้ว ก็มาถึงคำถามว่า
จะ implement เป็น plugin ได้อย่างไร
เริ่มด้วยการตั้งชื่อให้ plugin เราก่อน
โดยใช้ชื่อว่า
directory structure ของ plugin เป็นแบบนี้
file init.rb เป็น file ที่จะถูก rails เรียกใช้
มีเนื้อหาดังนี้
การทำงานก็คือ สั่งให้ Class ActionController::Base include
Module
ใน module
จะเริ่มด้วย method
method นี้เป็น callback method
ซึ่งจะถูกเรียกใช้เมื่อมีการเรียก include module ของเรา
logic ที่
ใน method
จะมีการ add filter โดยใช้คำสั่ง
และใช้ class ParamUpdator ในการ scan และแปลงวันที่ที่อยู่ใน params object
ประเด็นที่น่าสนใจในส่วนของการแปลง parameter ก็คือ
object
แต่เป็น instance ของ HashWithIndifferentAccess
ส่วน
ประเด็นที่น่าสนใจ ก็คือการ get value จาก Model instance
ที่ใช้ method
กับ assumption ที่ว่า model instance จะอยู่ในรูป instance variable
เหลือที่ยังไม่ได้ทำ ก็คือ ส่วนของการ validate
ที่จะ implement ด้วย javascript ที่ฝั่ง browser เลย
rails จะมี helper ที่ชื่อ
date_select
date_select("period", "start_date")
ซึ่งได้หน้าตาของ UI ออกมาอย่างนี้
ดูแล้วไม่ถูกใจ user ชาวไทยอย่างแน่นอน
ผมก็เลยหาทางเปลี่ยนไปใช้ jsCalendar แทน
หน้าตาที่ได้ ก็จะเปลี่ยนไปเป็นแบบนี้
โดย jsCalendar มีวิธีการใช้ง่ายๆดังนี้
<input type="text" id="data" name="period[dp_start_date]"/>
<button id="trigger">...</button>
<script type="text/javascript">
Calendar.setup(
{
inputField : "data",
ifFormat : "%d/%m/%y",
button : "trigger"
}
);
</script>
ที่นี้ ถ้าอยากนำมาใช้ใน rails
จะทำอย่างไรให้ดูเป็น rails-style
(เขียนน้อยๆ, default เยอะๆ)
ประเด็นของการทำ widget เอง คงแยกเป็น 2 เรื่อง คือ
- การ render
- การ parse request parameter
กรณีของการ render เราจะทำ helper method ขึ้นมาใหม่ตัวหนึ่ง
ให้ชื่อว่า date_picker
มีลักษณะการใช้งานดังนี้
<%= date_picker "period", "start_date" %>
(จะเห็นว่ามีวิธีการใช้ที่เหมือนกับ date_select ของ Rails เลย)
ส่วนการ parse parameter นั้น
จะออกแบบให้ transparent กับ code เดิม
โดยต้องการให้ใช้กับ code ที่ gen จาก scaffold ได้เลย
(code ที่อยู่ใน controller class)
ของเดิม เวลา date_select submit ข้อมูลกลับมา
ข้อมูลที่กลับมาจะอยู่ในรูปแบบนี้
param-name param-value
===========================
start_date(1i) 2006
start_date(2i) 03 (เดือน)
start_date(3i) 20 (วัน)
เราก็จะเลียนแบบวิธีการส่ง parameter แบบเดิมนี้ (rails เรียกว่า multiparameter_attributes)
โดยแทรก filter เข้าไป
เพื่อทำการดักแปลงข้อมูลที่ submit มาจาก form
การใช้งาน filter ออกแบบให้ใช้คำสั่งแบบนี้้
class ApplicationController < ActionController::Base
date_picker_filter
end
เมื่อตกลงใจเรื่อง design ได้แล้ว ก็มาถึงคำถามว่า
จะ implement เป็น plugin ได้อย่างไร
เริ่มด้วยการตั้งชื่อให้ plugin เราก่อน
โดยใช้ชื่อว่า
datepicker
directory structure ของ plugin เป็นแบบนี้
+project-name
+app
+...
+vendor
+plugins
+datepicker
+lib
datepicker.rb
init.rb
file init.rb เป็น file ที่จะถูก rails เรียกใช้
มีเนื้อหาดังนี้
require 'datepicker'
ActionController::Base.send :include, DatePicker
การทำงานก็คือ สั่งให้ Class ActionController::Base include
Module
Datepicker
ของเรา (Mixin)ใน module
Datepicker
จะเริ่มด้วย method
self.included
method นี้เป็น callback method
ซึ่งจะถูกเรียกใช้เมื่อมีการเรียก include module ของเรา
logic ที่
Datepicker
ทำ ก็คือ- สั่ง extend controller ด้วย module ClassMethods
add class methoddate_picker_filter
ให้เรียกใช้จาก controller ได้ - เพิ่ม helper
date_picker
ที่เรียกใช้จากใน view หรือใน controller ได้
module DatePicker
def self.included(controller)
controller.extend(ClassMethods)
controller.helper_method(:date_picker)
end
module ClassMethods
def date_picker_filter(options = {})
before_filter do |c|
ParamUpdator.new(options).update(c.params)
end
end
end
...
def date_picker(object, method, options = {})
...
end
ใน method
date_picker_filter
จะมีการ add filter โดยใช้คำสั่ง
before_filter
)และใช้ class ParamUpdator ในการ scan และแปลงวันที่ที่อยู่ใน params object
class ParamUpdator
def initialize(options={})
@prefix = options[:prefix] || 'dp_'
@be = options[:ad] || false # buddhist era
@delim = options[:delim] || '/'
@prefix_regexp = Regexp.new("^#{@prefix}")
@delim_regexp = Regexp.new(@delim);
end
def update(hash)
hash.each do |key, value|
if value.class.to_s =~ /^Hash/
update(value)
else
if @prefix_regexp =~ key
update_param(hash, key, value)
end
end
end
end
def update_param(hash, key, value)
darys = parseDate(value)
realName = key[(@prefix.length)..(key.length)]
hash["#{realName}(1i)"] = darys[0]
hash["#{realName}(2i)"] = darys[1]
hash["#{realName}(3i)"] = darys[2]
hash.delete(key)
end
def parseDate(value)
darys = value.split(@delim_regexp);
if darys[2].length <= 2
darys[2] = (darys[2].to_i + (@be ? 1957 : 2000)).to_s
end
darys.reverse
end
end #end ParamUpdator
ประเด็นที่น่าสนใจในส่วนของการแปลง parameter ก็คือ
object
params
ไม่ได้เป็น instance ของ Hash
แต่เป็น instance ของ HashWithIndifferentAccess
ส่วน
date_picker
ที่ใช้ render ก็มีหน้าตาแบบนี้
def date_picker(object, method, options = {})
prefix = options[:prefix] || "dp_"
format = options[:format] || "%d/%m/%y"
size = options[:size] || "10"
inputClazz = options[:input_class] || "date_input"
triggerClazz = options[:trigger_class] || "date_trigger"
be = options[:be] || false
maxsize = options[:maxsize] || format.length
id = "#{object}_#{method}"
name = "#{object}[#{prefix}#{method}]"
obj = self.instance_variable_get "@#{object}"
value = obj == nil ? "" : (date_to_string(format, obj.send(method), be))
<<EOS
<input type="text" size="#{size}" maxsize="#{maxsize}"
id="#{id}" name="#{name}"
class="#{inputClazz}" value="#{value}"/>
<button id="trigger_#{id}" class="#{triggerClazz}">...</button>
<script>
Calendar.setup(
{
inputField : "#{id}",
ifFormat : "#{format}",
button : "trigger_#{id}"
}
);
</script>
EOS
end
ประเด็นที่น่าสนใจ ก็คือการ get value จาก Model instance
ที่ใช้ method
instance_variable_get
กับ assumption ที่ว่า model instance จะอยู่ในรูป instance variable
เหลือที่ยังไม่ได้ทำ ก็คือ ส่วนของการ validate
ที่จะ implement ด้วย javascript ที่ฝั่ง browser เลย
Related link from Roti
Subscribe to:
Posts (Atom)