Lightpd & FastCGI vs. Apache & mod_php,mod_python...

Opublikowane przez Jarosław Zabiełło Mon, 24 Apr 2006 17:40:00 GMT

Serwer Apache jest bardzo popularnym i solidnym serwerem www. Nie dziwi więc powszechne używanie jego modułow do obsługi Perla, PHP czy Pythona. Uzywanie mod_php jest wręcz nagminne (cały serwis sourceforge.net stoi na mod_php). Podobnie twórcy pythonowego frameworku Django zalecają używanie mod_pythona. W dokumentacji piszą, że:

“jeśli chcesz używać Django na serwerze produkcyjnym, uzywaj Apache’a z mod_pythonem (...) Django wymaga Apache 2.x oraz mod_python 3.x.”

Zupełnie inne podejście zalecają twórcy frameworka Ruby on Rails. Nie dość, że zalecają użycie modułu FastCGI to na dodatek, zalecają (o grozo:) używanie innego serwera www: Ligttpd. Które podejście jest lepsze?

Problem z uniwersalnością

Korzystanie z modułów Apache’a wiąże nas na dobre i złe z tym serwerem. korzystanie z fastcgi daje nam większe pole manewru. Możemy korzystać z Apache, Lighttpd, IIS i innych serwerów www.

Problem stabilności serwera

Jakikolwiek błąd w skryptach pracujących w trybie mod_php czy mod_python odbija się bardzo niebezpiecznie na stabilności całego serwera. Jeden błędnie działający skrypt może zawiesić cały serwer! Dotyczy to nie tylko Apache, ale także windowsowego IIS i modułów ISAPI. Co ciekawe, moduł ISAPI dla języka PHP jest wiecznie w fazie niestabilnej i testowej. Co z tego, że jest szybszy niż klasyczny CGI, skoro jeden drobny błąd w skrypcie PHP potrafi zawiesić cały serwer i unieruchomić cała sieć intranetową na nim opartą? To jeden z powodów dla których PHP nie nadaje się najlepiej do pracy pod Windowsami (wiem, że istnieje Apache/win32 ale z różnych powodów, których tu nie będę rozwijał, korporacyjne sieci intranetowe oparte na Windows Server wolą używać IIS)

Procesy FastCGI są izolowane od serwera. Jak któryś się zawiesi, to serwerowi nic się nie stanie. Proces można ubić, zrestartowac. Daje to znacznie większą stabilność pracy serwera.

Problem bezpieczeństwa

Każdy skrypt odpalany w trybie mod_php na Apache’u jest odpalany w kontekście tego samego uzytkownika. Tworzy to dosyć poważną dziurę bezpieczeństwa na serwerach hostingowych. Każdy użytkownik na serwerze za pomocą prostego kodu PHP może przeglądać źródła dowolnego pliku innych użytkowników. Wszyscy mają dostęp do wszystkich! Napisałem nawet swego czasu prosty skrypt wedrujący po całym sourceforge.net i przeglądający źródła wszystkich dostępnych tam projektów. :) Każdy, średnio zaawansowany programista z łatwością napisze sobie taki kod. Nie ma znaczenia, czy to będzie Perl (mod_perl), Python (mod_python) czy PHP (mod_php).

W FastCGI tego problemu nie ma. Każdy użytkownik może pracować na swoich prawach w pełnej izolacji. Wystarczy każdemu przydzielić po procesie FastCGI na prawach danego użytkownika.

Marnowanie możliwości Apache’a 2.x

To może nie jest najważniejszy problem. Apache 2.x potrafi pracować w trybie wielowątkowym (worker) lub wieloprocesowym (prefork) który jest zgodny ze starym Apache 1.x . Ten pierwszy jest nowocześniejszy, szybszy i pożera mniej pamięci. Problem jednak w tym, że najbardziej popularny język skryptowy nie posiada poprawnie zaimplementowanej wielowątkowości. Jeśli więc chcesz używać PHP, to zapomnij o trybie workera. Musisz pracować w starym trybie prefork. Zajmuje to więcej pamięci, ale za to nie będzie problemów z różnymi bibliotekami PHP.

Problem zasobożerności

Gdy uruchamiamy Apache 1.x lub Apache 2.x w trybie prefork, uruchamiamy szereg podprocesów. Każdy z nich zajmuje określoną ilość pamięci. Każdy z zainstalowanych modułów… również. A to przecież nie ma sensu! W praktyce nie chcemy wykorzystywać wszystkich procesów do przetwarzania mod_pythona. Większość procesów jest zajęta podawaniem statycznej wartości (obrazki, pliki ze stylami kaskadowymi, skryptami JavaScript itp).

FastCGI pozwala na ograniczenie ustalenie ilości swoich procesów w sposób niezależny od wszystkich procesów serwera www. Np. możemy sobie zadecydować, aby 100 procesów zajmowało się wartością statyczną, a tylko 10 wartością dynamiczną. Takie coś jest niemożliwe wypadku korzystania z mod_php czy mod_pythona. A oszczędności na zużyciu pamięci będą kolosalne!

Np. można załozyć, że pojedyńczy proces Apache podczas serwowania statycznych stron zajmie nie więcej niż ok 5 MB pamięci. Natomiast w wypadku użycia mod_ruby może wzrosnąć nawet do ok. 20-30MB na proces. Użycie zatem 100 procesów Apache’a z tylko 10 procesami FastCGI zużyje ok. 800MB pamięci. Zaś użycie mod_ruby bez problemu pożre nawet i 3GB!

Dlaczego Lighttpd a nie Apache?

Na końcu chciałbym jeszcze zastanowić się czy twórcy Railsów nie mają racji i czy w ogóle nie warto zrezygnować z Apache na rzecz Lighttpd? Są generalnie 3 powody za i jeden przeciw.

Za: lepiej dopracowany moduł FastCGI

Moduł FastCGI do Apache’a ma opinię nie do końca stabilnego i dopracowanego. To powoduje obawę większości użytkowników od używania go dla serwera Apache. Z kolei serwer Lighttpd od samego początku kładł nacisk na dobrą implementację swego modułu FastCGI. Jest on stabilniejszy i lepiej dopracowany niż pod Apachem.

Za: Wbudowana możliwość rozkładania obciążenia

Lighttpd ma wbudowany mechanizm rozkładania ruchu na wiele serwerów. Jeśli więc nawet ktoś upiera się przy Apache’u, to w wypadku kiedy jeden serwer nie jest wystarczający do obsługi ruchu, może warto rozważyć aby postawić na froncie jeden serwer Lighttpd który by rozkładał ruch pomiędzy kilka serwerów?

(Najnowszy Apache 2.2 ma posiadać podobny mechanizm. Jednak jest to jeszcze bardzo świeża wersja, która (w momencie pisania tego tekstu) nie ma nawet prekompilowanej instalacji dostępnej dla Windowsów oraz nie wiadomo jak jest ze stabilnością wszystkich modułów.)

Za: Lighttpd jest szybszy

O ile najnowszy Apache 2.2 nie zmieni sytuacji, to wszystko wskazuje na to, ze Lighttpd jest po prostu szybszy. Nie tylko zdecydowanie szybciej podaje statyczne dane ale także szybciej działa PHP pod Lighttpd niż mod_php pod Apachem. To nie są drobne różnice. To są różnice rzędu 2-3 razy!

Przeciw: Apache ma więcej modułów

Właściwie jedynym powodem aby używać Apache zamiast Lighttpd jest to, że do Apache napisano znacznie więcej różnych modułów. Ale szczerze mówiąc, w praktyce to rzadko ma znaczenie. Większość osób uzywa głównie kilku modułów. Zaś Lighttpd posiada praktycznie wszystko, co potrzeba: mod_fastcgi, mod_cgi, mod_redirect, mod_access, mod_auth, mod_compress, mod_webdav, mod_ssl, mod_alias, mod_proxy, mod_rewrite, itp. itd.

Lighttpd + FastCGI. To działa!

Na szczęście, dzięki standardowi WSGI, użytkownicy Pythona nie muszą słuchać zaleceń developerów Django. Teraz praktycznie wszystkie frameworki Pythona pozwalają dzięki WSGI korzystać z dobrodziejstw FastCGI. Ja również na swoim serwerze wyrzuciłem Apache’a i zastapiłem go Lighttpd. Podaje zarówno strony statyczne jak i obsługuje ten blog, a nawet PHP 5.1. Nawet Plone mi udało mi się uruchomić z Lighttpd na przodzie.

Posted in , ,  | Tagi , , ,  | 11 comments

Modularyzacja RoR - komponenty

Opublikowane przez Jarosław Zabiełło Wed, 19 Apr 2006 21:11:00 GMT

RoR pozwala na modularyzację projektu za pomocą wtyczek, zagnieżdżonych miniaplikacji (rails engines) oraz komponentów.

Używany tutaj blog jest oparty na Typo, aplikacji napisanej w RoR. Nie znalazłem w niej jednak gotowego, wbudowanego mechanizmu aby dodać galerię fotek. Są co prawda linki do zewnętrznego serwisu flickr.com, ale w wersji darmowej można umieścić stosunkowo niewiele fotek.

Aplikacja bloga chodzi na lighttpd i fastcgi. Wszelkie modyfikacje kodu wymagają restartu serwera. Mało to raczej wygodne. Postanowiłem więc stworzyć sobie oddzielny projekt RoR. A zatem standardowo: rails fotki; cd fotki. Potem modyfikacja config/database.yml (ustawilem polaczenie do dowolnej bazy, i tak nie bede uzywal). Do pracy roboczej wystarczy mi webrick. A zatem ruby scripts/serwer i pod adresem http://localhost:3000 dziala nowy projekt.

Starając się trzymać zasady DRY (czyli unikać powtarzania kodu). Działający kod zamieniłem następnie na komponent. Czyli procedura była taka: wpierw tworzę sobie aplikację w izolowanym projekcie RoR. Na stępnie przenoszę ją do komponentu (nadal w tym samym projekcie). Testuję. Jak działa, to łączę komponent z aplikacją docelową. Dzięki użyciu komponentu, unikam zaśmiecania kodu aplikacji docelowej, ograniczając do minimum ilość zmian jakie muszę dodać.

Komponenty

Komponenty w RoR tworzy się (podobnie jak inne rzeczy) stosunkowo łatwo i przyjemnie. Kazda świeża instalacja RoR tworzy domyślnie pusty folder components. Należy tam przejść i stworzyć swój podfolder, np. fotki.

Następnie przenosimy tam plik z kontrolerem. W tym wypadku było to plik miniaturki_controlle.rb. Wiadomo gdzie był: apps/controllers/. Przenosimy go zatem do components/fotki/. Musimy także dokonać w nim paru zmian. Przede wszystkim klasa kontrolera nie może dziedziczyć bezpośrednio po ApplicationController. Musimy uwzględnić to, że kontroler leży w innym miejscu.

Trzeba także zmienić definicje klasy i dodać informację, że szablony będą leżeć w innym miejscu niż domyślnie app/views/. Kontroler więc wyglądać może tak:

class Fotki::MiniaturkiController < ApplicationController
  uses_component_template_root

  def mysio_lista
    filenames(File.dirname(__FILE__)+'/../../public/fotki/mysio', '*.jpg')
    @subfolder = 'mysio/'
    render :template => 'fotki/miniaturki/lista'
  end

  def mysio_fotka
    filenames(File.dirname(__FILE__)+'/../../public/fotki/mysio', '*.jpg')
    @subfolder = 'mysio/'
    render :template => 'fotki/miniaturki/fotka'
  end

  protected

  def filenames(path, filter)
    @filenames = []
    Dir.glob(path+File::SEPARATOR+filter).each do |f|
      @filenames << File.basename(f).sub(/.*^\D+(\d+).+$/, '\1')
    end
    @filenames.sort!
  end
end

Metoda filenames wciaga liste plików i przypisuje do zmiennej instancji @filenames (zmienne tego typu są widziane w szablonach).

Przenieśliśmy kontroler, pora na szablony. Tworzymy zatem kolejny podfolder components/fotki/miniaturki/ i przenosimy tam wszystkie nasze szablony z app/views/miniaturki/. Szablon lista.rhtml służy do wyrzucenia na ekran miniaturek z obrazkami:

<% for f in @filenames %>
  <% if @subfolder%>
    <%= link_to(image_tag("[email protected]}thumbnails/prev_pict#{f}.jpg",:border => "1"), "[email protected]}#{f}") %>
  <% else %>
    <%= link_to(image_tag("[email protected]}thumbnails/prev_pict#{f}.jpg",:border => "1"), "/fotka/#{f}") %>
  <% end %>
<% end %>

Natomiast szablon fotka.rhtml wyświetla jedno zdjęcie w pełnym formacie:

<div align="center">
  <div> 
    <%= render :partial => 'menu' %>
    <%= image_tag "[email protected]}pict#{params[:id]}.jpg" %>
    <%= render :partial => 'menu' %>
</div>

Dobrze byłoby także, aby można było nawigować między obrazkami. Dodałem więc proste menu u góry i na dole. Aby się nie powtarzać, szablon z treścią menu wyniosłem do szablonu cząstkowego (partial). Plik z takim szablonem musi mieć obowiązkowo znak podkreślnika na początku nazywy. Szablon _menu.rhtml wygląda tak:

  <div style="margin-top:10px; margin-bottom: 10px"> 
    [ 
      <% if @filenames.first != params[:id] %>
        <% if @subfolder %>
          <a href="/<%= @subfolder %><%= @filenames[@filenames.index(@params[:id]) - 1] %>">&lt;&lt;</a>
        <% else %>
          <a href="/fotka/<%= @filenames[@filenames.index(@params[:id]) - 1] %>">&lt;&lt;</a>
        <% end %>
        |
      <% end %>
      <% if @subfolder %>
        <a href="/<%= @subfolder %>">miniaturki</a> 
      <% else %>
        <a href="/fotki">miniaturki</a> 
      <% end %>
      <% if @filenames.last != params[:id] %>
        |
        <% if @subfolder %>
          <a href="/<%= @subfolder %><%= @filenames[@filenames.index(@params[:id]) + 1] %>">&gt;&gt;</a>
        <% else %>
          <a href="/fotka/<%= @filenames[@filenames.index(@params[:id]) + 1] %>">&gt;&gt;</a>
        <% end %>
      <% end %>
    ]
  </div>

Jest troszkę rozbudowany z dwóch powodów. Chciałem aby przejścia do następnej lub wcześniejszej fotki były dostępne tylko wtedy kiedy to ma sens. Poza tym, kod uwzględnia tworzenie podfolderów ze zdjęciami. Stąd we wszystkich szablonach dodatkowa zmienna @subfolder.

Gdybym chciał umieścić wywołanie komponentu w szablonie aplikacji, to musiałbym użyć funkcji render_component. Jednak, w tym wypadku nie chciałem grzebać w szablonach Typo. Zatem ostatnią rzeczą jaka została, to zdefiniwanie odpowiedniej reguły dla resolvera adresów. Chcę aby lista pokazała się pod adresem /mysio a konkretne fotki pod adresem /mysio/nrfotki. Reguły rozwiązywania adresów URL znajdują się w pliku config/routes.rb:

ActionController::Routing::Routes.draw do |map|
  # reszta kodu
  map.connect 'mysio', 
              :controller => 'fotki/miniaturki',
              :action     => 'mysio_lista'              
  map.connect 'mysio/:id',
              :controller => 'fotki/miniaturki',
              :action     => 'mysio_fotka',
              :id         => /\d+/
  # blok kończy domyślna reguła:
  map.connect ':controller/:action/:id'
end

To prawie wszystko. Prawie, bo musiałem także napisać kod do przeskalowania obrazków (domyślnie były w rozdzielczości 1280×1024) oraz wygenerowania miniaturek. W związku z tym, że chciałem wszystko już napisac w Ruby, zabrałem się za pisanie skryptu w tym języku. Przeskalowanie fotek do rozdzielczości 1024×768:

#!/usr/bin/ruby
require 'RMagick'
default_path = '/home/rubyonrails/typo/public/fotki'
path = $1 || default_path
Dir.chdir(path)
Dir.glob('*.jpg').each do |f|    
  img = Magick::Image::read(f).first
  if img.columns > 1024
    print f
    img.change_geometry("1024x768") { |cols, rows, im| im.resize!(cols, rows) }
    img.write f
    puts " przeskalowane"
  end
end

Oraz generowanie miniaturek:

#!/usr/bin/ruby
require 'RMagick'
default_path = '/home/rubyonrails/typo/public/fotki'
path = $1 || default_path
outpath = path + File::SEPARATOR + 'thumbnails'
Dir.mkdir(outpath) if not File.exists?(outpath)
Dir.chdir(path)
Dir.glob('*.JPG').each do |f|  
  if f != f.downcase
    File.rename(f, f.downcase)
    f.downcase!
  end
  img = Magick::Image::read(f).first
  img.change_geometry("128x128") { |cols, rows, im| im.resize!(cols, rows) }
  img.write "#{outpath}#{File::SEPARATOR}prev_#{f}"
  p f
end

Niestety, tu spotkała mnie przykra niespodzianka. Biblioteka RMagic wywalała się z komunikatem o braku pamięci. Kod jest poprawny. To jest błąd w tej bibliotece. Chcąc, nie chcąc, przepisałem kod do Pythona:

#!/usr/bin/env python2.4
'''
Skaluje obrazki do 1020x768 oraz generuje miniatiurki
Operuje na plikach JPEG.
'''
import glob, Image, os, sys
scale = (1024,768)

try:
    path, pattern, prefix = sys.argv[1], sys.argv[2], sys.argv[3]
except:
    print "Skladnia: sciezka wzorzec prefix"
    sys.exit(1)
os.chdir(path)
thumbnails_path = path + os.sep + 'thumbnails'
if not os.path.exists(thumbnails_path):
    os.makedirs(thumbnails_path)
for f in glob.glob(pattern):
  print f,
  img = Image.open(f)
  size = img.size
  if max(size) > 1024:
    print "%s (%sx%s)" % (f, size[0], size[1]),
    img = img.resize(scale, Image.ANTIALIAS)
    img.save(f, 'JPEG')
    size = img.size
    print ' przeskalowane do %sx%s' % (size[0], size[1]),
  print " przeskalowywanie do 128x128",
  img.thumbnail((128, 128), Image.ANTIALIAS)
  img.save('thumbnails/%s_%s' % (prefix, f), 'JPEG')
  print

Tym razem wszystko przeszło gładko i szybko. Kod Pythona wykorzystujący biblioteke PIL był poza tym jakieś 2x szybszy. Sprawdza się zatem to, że współcześni programiści nie mogą ograniczać się tylko do jednego języka. Czasami warto mieć w zapasie drugi. Python jest doskonałym uzupełnieniem dla Rubiego. Ruby ma lepszy framework, ale Python ma dojrzalszą bibliotekę graficzna.

Jedyną rzeczą którą nie udało mi się rozwiązać, to umieszczenie treści komponentu w ramach aplikacji Typo (tak jak widać np,. ten artykuł). Typo jest dosyć skomplikowaną aplikacją i nie mogłem się połapac w jego plikach z layoutem. Niektórzy żartują że Typo to przykład kodowania sztucznej inteligencji. :) Przykład działających fotek z moim ulubionym futrzakiem można zobaczyć: tutaj.

Posted in  | Tagi  | 3 comments

RadRails 0.6.2

Opublikowane przez Jarosław Zabiełło Thu, 13 Apr 2006 20:15:00 GMT

Wyszła najnowsza wersja edytora RadRails 0.6.2. Dodano m.in. kompatybilność z formatami RJS i RXML. Kod zbudowano na Eclipse 3.2 M6. Niestety wersja pod Windows jest dosyć wadliwa. Lepiej pozostać na razie przy wersji 0.6.1.

Tagi ,  | 1 comment

Ruby, rozszerzanie klas i docstringi

Opublikowane przez Jarosław Zabiełło Thu, 13 Apr 2006 19:22:00 GMT

W jednym z wcześniejszych tekstów opisywałem zalety docstringów – wbudowanego mechanizmu dokumentacji w Pythonie.

W języku Ruby nie ma takiego mechanizmu. Jednak Ruby posiada unikalną cechę która pozwala go rozszerzać bez konieczności sięgania do języka C. Tą cechą są otwarte klasy.

class X
  def hello
    "Witaj"
  end
end

x = X.new
puts x.hello # => Witaj

class X
  def bye
    "Żegnaj"
  end
end

x = X.new
puts x.hello # => Witaj
puts x.bye # => Żegnaj

Coś takiego jest w ogóle nie do pomyślenia w językach tak mało dynamicznych jak Java czy C++ gdzie definicje klas są ustalane na poziomie kompilacji. W Ruby klasy są obiektami i są tworzone w momencie uruchomienia programu co daje nam w efekcie znacznie większy dynamizm i siłę ekspresji kodu.

Z kolei próba napisania czegoś podobnego w innych językach dynamicznych prowadzi także do zgoła kompletnie odmiennych zachowań. W przypadku PHP 5.1.2 poniższy kod wygeneruje błąd składni:

<?php
class X {
    function hello() {
        return "Witaj";
    }
}

$x = new X();
print $x->hello() # => Witaj

class X { # => Parse error: syntax error, unexpected T_CLASS
    function bye() {
        return "Żegnaj";
    }
}
?>

Zaś w Pythonie poznikają nam definicje poprzednich metod (ale wcześniej zdefiniowane instancje klasy nie ulegną zmianie).

class X(object):
    def hello(self):
        return "Witaj"

x = X()
print x.hello() # => Witaj

class X(object):
    def bye(self):
        return "Żegnaj"

print x.hello() # => Witaj

x = X()
print x.bye() # => Żegnaj
print x.hello() # => AttributeError: X instance has no attribute 'hello'

No dobrze, wróćmy do próby zaimplementowanie w Rubym czegoś podobnego do pythonowych docstringów. Załóżmy, że chcielibyśmy korzystać z następującej składni (poniższy kod jest oparty na PixAxe2, s.389):

class Przyklad
  doc "To jest przykład doctringu"
  # reszta kodu 
end

W związku z tym, że chcielibyśmy aby taka składnia działała dla każdego modułu i klasy, kod implementujący taki mechanizm powinniśmy zdefiniować w klasie Module.

class Module
  @@docs = {}
  def doc(s)
    @@docs[self.name] = s.gsub(/^\s+/, '')
  end
  def Module::doc(aClass)
    aClass = aClass.name if aClass.class <= Module
    @@docs[aClass] || 'Nie ma dokumentacji'
  end
end

Oraz przykład użycia:

class A
end

class B
  doc "Przykład dokumentacji"
end

module C
  doc <<-edoc
    Dokumentacja
    wielowierszowa
  edoc
end

puts Module::doc(A) # => Nie ma dokumentacji
puts Module::doc(B) # => Przykład dokumentacji
puts Module::doc(C) #=> Dokumentacja\nwielowierszowa

Posted in  | Tagi  | 4 comments

Ruby vs. Python - operacje na plikach

Opublikowane przez Jarosław Zabiełło Tue, 11 Apr 2006 19:47:00 GMT

Ostatnio miałem potrzebę napisania skryptu kopiującego całe pliki z całego drzewa folderów. Problem w tym, że chciałem kopiować tylko pliki, które nie miały ustawionych atrybutów tylko do odczytu. I to musiało być zrobione pod Windowsami. Zwykle jestem przyzwyczajony że tego typu zadania to piszę w Pythonie w kilka minut. Nie spodziewałem się że tu będzie inaczej….

Okazało się że Python (2.4.3) nie ma porządnie zaimplementowanych metod sprawdzania atrybutów pliku. Na próżno szukałem w manualu, Python Cookbook , internecie.

Zgodnie z prawami Murphego rozwiązanie znalazłem jak już było niepotrzebne (w międzyczasie zdążyłem napisać to w Ruby).

>>> os.stat(r'c:\tmp\test1')[stat.ST_MODE] & stat.S_IWRITE
128
>>> os.stat(r'c:\tmp\test2')[stat.ST_MODE] & stat.S_IWRITE
0

Wygląda to raczej paskudnie na tle analogicznego kodu Rubiego:

>>> File.writable?('c:\tmp\test1')
true
>>> File.writable?('c:\tmp\test2')
false

Może to szczególny przypadek, ale jakoś nie sprawdziła się w tym wypadku prostota Pythona. Metody Rubiego w tym temacie są znacznie bardziej intuicyjne. Zresztą zobaczmy poniższy przykład z manuala Pythona:

import os, sys
from stat import *
def walktree(top, callback):
    for f in os.listdir(top):
        pathname = os.path.join(top, f)
        mode = os.stat(pathname)[ST_MODE]
        if S_ISDIR(mode):
            walktree(pathname, callback)
        elif S_ISREG(mode):
            callback(pathname)
        else:
            print 'Skipping %s' % pathname
def visitfile(file):
    print 'visiting', file
if __name__ == '__main__':
    walktree(sys.argv[1], visitfile)

Ten sam kod w języku Ruby wygląda tak:

require 'find'
if $0 == __FILE__
  Find.find(ARGV[0]) do |f|
    if File.file?(f)
      puts 'visiting ' + f 
    elsif not File.directory?(f)
      puts 'skipping ' + f
    end
  end
end

I kto tu powie, że Ruby jest mniej czytelny od Pythona?

Posted in ,  | Tagi ,  | 8 comments

Arachno IDE 0.6.9

Opublikowane przez Jarosław Zabiełło Thu, 06 Apr 2006 21:21:00 GMT

Została udostępniona kolejna wersja beta edytora Arachno IDE. Z najważniejszych zmian jest pełna obsługa UTF-8! Program wraz z plikiem zawierającym klucz autoryzującym na 3 miesiące można pobrać z forum . Zachęcam użytkowników Rubiego i Rails do testów.

Stworzono też odzielne forum dla beta testerów. Można na nim pobrać wersję beta (aktualnie jest już wersja 0.6.10)

Tagi , ,  | brak comments

RoR 1.1 i problemy z Typo

Opublikowane przez Jarosław Zabiełło Wed, 05 Apr 2006 07:18:00 GMT

Niektóre aplikacje nie są jeszcze w pełni dostosowane do pracy z najnowszą wersją Railsów. Np. taką aplikacją jest Typo (obsługująca zresztą ten blog) Wpierw należy sprawdzić jaką wersją Railsów dysponujemy:
rails -v
Jeśli jest nowsza od wersji 1.0, to należy się cofnąć:
gem install -r rails -v "=1.0.0"
Następnie wszystkie podejrzane aplikacje RoR “zamrażamy”. Wystarczy przejść do folderu gdzie mamy aplikację w Rails i z poziomu konsoli wykonać następującą komendę:
rake freeze_edge REVISION=3303

Dopiero teraz możemy zrobić upgrade Railsów:

gem install -r rails --include-dependencies

Powinno wszystko działać (w Railsach 1.1.1 wprowadzono dodatkowe nowości zamrażania). Poza tym dobrze wiedzieć, że trwają prace nad Typo aby działał z RoR 1.1

Posted in  | Tagi ,  | brak comments