Cracker une clé MD5 …

… rien de plus simple avec BozoCarck qui utilise une recherche sur Google. Et encore mieux, il y a Md5This, si vous ne voulez/pouvez utiliser BozoCrack.

 

Publié dans Non classé | Commentaires fermés sur Cracker une clé MD5 …

Boilerplate : HTML5 et bonnes pratiques

Voici un framework CSS qui vaut le coup d’oeil, surtout pour ceux comme moi qui n’ont pas encore bien stabilisé les bonnes pratiques avec HTML5 : Boilerplate.

Bien que je ne sois pas fan des frameworks CSS, celui ci à l’avantage de se présenter sous différente forme et surtout assez didactique. J’avoue je n’ai pas passé au crible tous les frameworks CSS, mais au premier coup d’oeil celui ci me paraît valoir le coup.

Publié dans Non classé | Commentaires fermés sur Boilerplate : HTML5 et bonnes pratiques

Intégration HTML avec nanoc

Encore un petit outil sympathique fait en Ruby : nanoc.
Nanoc est un outil qui permet de réaliser un site web statique. D’ailleurs leur site est réalisé avec nanoc et vous pouvez télécharger les sources

Mais pour ma part je préfère l’utiliser dans le cas d’intégration HTML et que je dois livrer à des clients une version statique. Comme les frameworks MVC évolués, il gère un layout, des partial.

pour l’installation de nanoc, un petit coup de gem :

$ gem install nanoc

pour commencer le site, avec toute l’architecture :

$ nanoc create_site monsite
      create  config.yaml
      create  Rakefile
      create  Rules
      create  content/index.html
      create  content/stylesheet.css
      create  layouts/default.html
Created a blank nanoc site at 'monsite'. Enjoy!

$ cd monsite
$ ls -l
-rw-r--r-- 1 1000 1000 2090 mai 27 10:18 config.yaml
drwxr-xr-x 2 1000 1000 4096 mai 27 10:18 content
drwxr-xr-x 2 1000 1000 4096 mai 27 10:18 layouts
drwxr-xr-x 2 1000 1000 4096 mai 27 10:18 lib
drwxr-xr-x 2 1000 1000 4096 mai 27 10:18 output
-rw-r--r-- 1 1000 1000   22 mai 27 10:18 Rakefile
-rw-r--r-- 1 1000 1000  692 mai 27 10:18 Rules

Ensuite soit vous voulez une version statique de votre site :

$ nanoc compile
Loading site data...
Compiling site...
      create  [0.00s]  output/style.css
      create  [0.04s]  output/index.html

Site compiled in 0.21s.

ok mais moi, je veux développer, intégrer, modifier sans avoir à recompiler .. bah il y a le autocompile.
L’option autocompile lance un serveur web (comme webrick avec rails) et à chaque requête vérifie les timestamps des fichiers, recompile si nécéssaire et envoie la version statique via le serveur web.

$ nanoc autocompile
Running on http://0.0.0.0:3000/

Il suffit de regarder dans le navigateur à l’adresse http://0.0.0.0:3000/ et comme ça ldéveloppeur Rails n’est pas dépaysé !

Si vous ne souhaitez pas utiliser l’autocompile et encore moins le compile, mais juste voir le site via un serveur web rapidement il y la commande view :

$ nanoc view
Running on http://0.0.0.0:3000/

Bon ça c’est le cas simple du tutoriel. Pour réaliser l’intégration, j’ai du apporter mon lot de configuration tiré des sources du site de nanoc lui-même :

Il faut faut savoir que tout ce qui est dans content et layout est compilé par nanoc, et donc peut modifier les feuilles de style, déplacer les images.

Personnellement, je veux juste qu’il me génère mes pages HTML. J’ai donc mis mes feuilles de style et toutes les images de style et autre fichiers JS dans un dossier static

$ cd monsite
$ mkdir static

Editer le fichier config.yaml et ajouter le dossier static comme étant de type ‘static’

  ....
    layouts_root: /
  -
    type: static
    items_root: /static/

le type static n’existe pas dans nanoc. Il faut le déclarer soit-même et créer un data_source. Dans le dossier lib/data_sources, il faut créer le fichier static.rb :

require 'digest'

module Nanoc3::DataSources

  class Static < Nanoc3::DataSource

    identifier :static

    def items
      # Get prefix
      prefix = config[:prefix] || 'static'

      # Get all files under prefix dir
      filenames = Dir[prefix + '/**/*'].select { |f| File.file?(f) }

      # Convert filenames to items
      filenames.map do |filename|
        attributes = {
          :extension => File.extname(filename)[1..-1],
          :filename  => filename,
        }
        identifier = filename[(prefix.length+1)..-1] + '/'

        mtime      = File.mtime(filename)
        checksum   = checksum_for(filename)

        Nanoc3::Item.new(
          filename,
          attributes,
          identifier,
          :binary => true, :mtime => mtime, :checksum => checksum
        )
      end
    end

  private

    # Returns a checksum of the given filenames
    # TODO un-duplicate this somewhere
    def checksum_for(*filenames)
      filenames.flatten.map do |filename|
        digest = Digest::SHA1.new
        File.open(filename, 'r') do |io|
          until io.eof
            data = io.readpartial(2**10)
            digest.update(data)
          end
        end
        digest.hexdigest
      end.join('-')
    end

  end

end

Ce qui aura pour effet que de copier les fichiers dans le dossier output sans traitement particulier.
Note : Ce fichier est contenu dans les sources du site nanoc.

Il faut également modifier le fichier Rules, qui sert traiter les routes et à indiquer quels dossiers et fichiers doivent être compilé (vous pouvez compiler également des feuilles de style) :

#!/usr/bin/env ruby

route '/static/*' do
  # /static/foo.html/ → /foo.html
  item.identifier[7..-2]
end

compile '/static/*' do
end

compile '/stylesheet/' do
  # don’t filter or layout
end

compile '*' do
  filter :erb
  layout 'default'
end

route '/stylesheet/' do
  '/style.css'
end

route '*' do
  item.identifier + 'index.html'
end

layout '*', :erb

Maintenant vous pouvez travailler sur le layout :layouts/default.html, et sur le contenu d’une page : content/index.html.

Pour réaliser un partial style Rails :

  1. dans layouts, crééer le fichier : monpartial.html.erb
  2. dans la page de content :
    <%= render 'monpartial' %>
    

Voilà au moins le minimum pour pouvoir intégrer sereinement, et en plus vous êtes plusieurs sur l’intégration (ce qui est rare) SVN ou Git sont vos amis.

Publié dans Web Dev | Commentaires fermés sur Intégration HTML avec nanoc

Web spider (ou aspiration de site) avec Anemone …

J’ai besoin une version de quelques sites réalisés avec Rails en version statique pour une présentation offline.
J’aurai pu utiliser wget, mais franchement j’avais envie et besoin d’un outil plus personnalisable. Ou bien étendre les controller pour récupérer la sortie mais trop de problème et pas assez de souplesse. C’est là qu’intervient Anemone.

La mise en place est simple (via gem bien sûr), et la prise en main l’est tout autant, comme le montre l’exmple de leur site :

require 'anemone'

Anemone.crawl("http://www.example.com/") do |anemone|
  anemone.on_every_page do |page|
      puts page.url
  end
end

C’est assez rudimentaire, mais en couplant avec des outils puissant comme Hpricot, et une surcharge de la classe URI, on en sort un outil vraiment personnalisé et puissant.

J’ai pu réécrire les chemins statiques de mon code en chemin relatif pour une visualisation offline en surchargeant la classe URI


require 'uri'
require 'rubygems'
require 'anemone'
require 'hpricot'

OUTPUT_DIR = File.join(File.dirname(__FILE__), 'export', 'sites')

module UriExt

  def last_item_of_path_is_used_for_filename
    self.query || (File.basename(self.path) =~ /^(.*)\..*$/)
  end

  def to_static_filename
    filename = ''
    if self.query
      if (File.basename(self.path) =~ /^(.*)\..*$/)
        filename = $1
      else
        filename = File.basename(self.path)
      end
      filename += '-' + self.query_to_filename_ext + '.html'
    else
      if (File.basename(self.path) =~ /^(.*)\..*$/)
        filename = File.basename(self.path)
      else
        filename = 'index.html'
      end
    end
    filename
  end

  def query_to_filename_ext
    self.query ? self.query.gsub(/[=;&]/, '-') : nil
  end

  def build_relative_path(with_host=true)
    if self.path !~ /^\/$/
      items = with_host ? [self.host] : []
      original_path = (self.path =~ /^\/(.*)$/ ? $1 : self.path)
      items_original_path =  original_path.split('/')
      items_original_path.shift if items_original_path.first == '/'
      items_original_path.pop if self.last_item_of_path_is_used_for_filename
      items += items_original_path
      File.join(items)
    end
  end

  def complete_path_filename(with_host=true)
    items = []
    relative_path = self.build_relative_path(with_host)
    items << relative_path if relative_path
    items << self.to_static_filename
    File.join(items)
  end

  def create_dir
    relative_path = self.build_relative_path
    if relative_path
      FileUtils.mkdir_p(File.join(OUTPUT_DIR, relative_path))
    end
  end

  def create_file(content)
    self.create_dir
    File.open(File.join(OUTPUT_DIR, self.complete_path_filename), 'w') do |file|
      file.write(content)
    end
  end

  def build_relative_path_for_html
    if self.path !~ /^\/$/
      (self.path =~ /^\/(.*)$/ ? $1 : self.path)
    else
      'index.html'
    end
  end

  def root_back_relative_path(an_absloute_path)
    if self.path !~ /^\/$/
      return File.join((['..'] * ((self.path.split('/')).length - 1)).join('/'), (an_absloute_path =~ /^\/(.*)$/ ? $1 : an_absloute_path))
    else
      (an_absloute_path =~ /^\/(.*)$/ ? $1 : an_absloute_path)
    end
  end
end

URI::HTTP.send(:include, UriExt)
URI::Generic.send(:include, UriExt)

Anemone.crawl("http://www.example.com") do |anemone|
  # cache pages
  anemone.storage = Anemone::Storage.PStore('arnaudkozlinski.pstore')

  # avoid JPG crawl (just a particular case for my website)
  # it's here only for th example
  anemone.focus_crawl { |page| page.links.select{ |uri| uri.path !~ /\.(jpg|JPG)$/ } }

  anemone.on_every_page do |page|
      puts page.url
      # if craw a html page parse and modify it with Hpricot
      if page.headers['content-type'].join('') =~ /^text\/html/
        doc = Hpricot(page.body)

        # image source rewriting
        doc.search('img').each do |img|
          if img.attributes['src']
            uri_img = URI.parse(img.attributes['src'])
            img['src'] =  page.url.root_back_relative_path(uri_img.path)
          end
        end
        ....

        # stylesheet source rewriting
        doc.search('link').each do |script|
          if script.attributes['href']
            uri_script = URI.parse(script.attributes['href'])
            script['href'] =  page.url.root_back_relative_path(uri_script.path)
          end
        end
        ....

     end
  end

Petit bémol, reste le parsing des feuilles de style CSS pour la réécriture des url() vers les images de style. Mais comme les url des feuilles de style sont déjà en relatif (c’est ma règle en tout cas), je n’ai pas eu besoin de parser, juste copier les images au bon endroit. D’autant que je n’ai pas trouvé le Hpricot du CSS.

Ça peut paraître long à première vue, mais quand on maîtrise Ruby, c’est largement moins prise de tête que d’utiliser un web spider automatique, car il y a toujours des cas particuliers et par expérience aucun ne permet d’obtenir un résultat parfait.

Publié dans Web Dev | Commentaires fermés sur Web spider (ou aspiration de site) avec Anemone …

Rails cache_key et Array

c’est bien cache_key pour les ActiveRecord, mais c’est encore mieux pour les collections d’ActiveRecord.

ActiveRecord::Base.class_eval do
  def cache_key_with_extra_keys(*args)
    extra =  args.empty? ? '' :  '-' + args.collect{|a| a.to_s }.join('-')
    cache_key_without_extra_keys + extra
  end
  alias_method_chain :cache_key, :extra_keys
end

Array.class_eval do
  def cache_key(*args)
    keys = []
    self.each do |item|
      keys << (item.respond_to?(:updated_at) && item.respond_to?(:id) ? "#{item.id}-#{item.updated_at.to_s(:number)}" : item.object_id.to_s)     
    end     
    extra =  args.empty? ? '' :  '-' + args.collect{|a| a.to_s }.join('-')     
    return keys.join('-') + extra   
  end 
end 

comme ça, vous pouvez faire une cache key pour un ensemble :

 
last_articles = Article.find(:all,:limit => 5)
last_articles.cache_key
Publié dans Rails, Web Dev | Commentaires fermés sur Rails cache_key et Array

La magie de ruby …

Il y a des jours où vraiment Ruby me simplifie la vie. Ce qui suit n’est pas révolutionnaire mais je suis tellement content de gagner des heures de codes et de tests grâce à la souplesse de Ruby que je ne peux m’empêcher de vous en faire part.

Voici le problème :

Je possède un plugin contenant plusieurs modèles « métiers » que j’utilise dans plusieurs sites/applications.

Et dernièrement, je souhaite ajouter un attribut commun à tous ces modèles. Jusque là rien de bien compliqué, mais juste pour une seule application, c’est à dire sans avoir d’impact sur les autres applications.

Ex :
foo.rb :

class Foo < ActiveRecord::Base
  has_many :bars
end

bar.rb :

class Bar < ActiveRecord::Base
  belongs_to :foo
end

Ensuite vous avez une classe qui gère des tas de Foo et de Bar :

class MyScript
 
  def initialize(foo_name, bar_name)
    @foo_name = foo_name
    @bar_name = bar_name
  end

  def do_a_lot_things
     ...
     # a lot of things before
     if foo = Foo.find_by_name(@foo_name)
     ...
     end

     if bar = Bar.find_by_name(@bar_name)
      ...
     end
     # a lot of things after
     ...
  end
end

ok. maintenant j’ajoute un champ commun : domain une sorte d’enum contenant par exemple : [‘Sport’, ‘Economy’, ‘Culture’]

>> Foo.new.domain = 'Sport'
>> a_bar =Bar.find(:first)
>> a_bar.domain
'Culture'

bref simple … mais je veux cloisonner mon script en fonction du domaine. il faudrait que je fasse :

class MyScript
  ...
  def do_a_lot_things
     ...
     # a lot of things before
     if foo = Foo.find_by_name_and_domain(@foo_name, @domain)
     ...
     end

     if bar = Bar.find_by_name_and_domaine(@bar_name, @domain)
      ...
     end
     # a lot of things after
     ...
  end
end

Ok j’ai bien fait mes tests, la refactorisation ne devrait pas être compliquée … ah mais non ! mon code doit être compatible pour les applications qui utilise Foo, Bar et MyScript sans utilser le domaine.

On y arrive .. Heureusement ruby et rails mettent à disposition plusieurs outils qui permettent détendre sans tout casser.

Première piste : alias_method_chain

            @@domaine = 'Sport'
            ....
             def find_with_domaine(*args)
                find_options = {}
                if @domaine
                  find_options[:conditions] = [ 'domaine = ? ', @@domaine ]
                end
                with_scope(:find => find_options) do
                  find_without_domaine(*args)
                end
              end
              alias_method_chain :find, :domaine

mouais ok mais bon comment fournir le bon domaine pour tous les Foo et Bar utilisés dans MyScript ?

Deuxième piste : une variable de classe mais attention il faut être sûr qu’elle soit valable uniquement pour le thread, voulant utiliser le hash Thread.current, j’ai quand même pris conseil chez coderr pour éviter d’abuser du Thread.current

class Class
  def thread_local_accessor name, options = {}
    m = Module.new
    m.module_eval do
      class_variable_set :"@@#{name}", Hash.new {|h,k| h[k] = options[:default] }
    end
    m.module_eval %{
      FINALIZER = lambda {|id| @@#{name}.delete id }

      def #{name}
        @@#{name}[Thread.current.object_id]
      end

      def #{name}=(val)
        ObjectSpace.define_finalizer Thread.current, FINALIZER  unless @@#{name}.has_key? Thread.current.object_id
        @@#{name}[Thread.current.object_id] = val
      end
    }

    class_eval do
      include m
      extend m
    end
  end
end

et j’étends la classe active_record :

ActiveRecord::Base.class_eval do
  thread_local_accessor :domain_store, :default => nil

  class << self
    def do_actions_with_domain(domain, &block)
      ActiveRecord::Base.domain_store = domain
      yield 
      ActiveRecord::Base.domain_store = nil
    end     
  end     
end     

Ensuite j’appelle mon script encadré par do_actions_with_domain

ActiveRecord::Base.do_actions_with_domain('Economy') do
  MyScript.new('foo_name', 'bar_name').do_a_lot_things
end

et voilà pas besoin de modifier ma class MyScript et ça marche au poil, merci Ruby 🙂

Publié dans Rails, Web Dev | Commentaires fermés sur La magie de ruby …

TinyMCE 2 et Firefox 3.6.9, import d’image

Pour ceux qui n’ont pas migré le tinymce de leur site vers la version 3, l’import d’image ne fonctionne plus pour Firefox 3.6.9. Il suffit de modifier tiny_mce.js, dans la fonction fixGeckoBaseHREFBug :

if (m == 1) {
//      h = h.replace(/\ssrc=/gi, " mce_tsrc=");
//      h = h.replace(/\shref=/gi, " mce_thref=");

        return h;
}
Publié dans Web Dev | Commentaires fermés sur TinyMCE 2 et Firefox 3.6.9, import d’image

Iconv et Ruby (le retour)

Dans un précédent article, je vous parlais de ma découverte sur les problèmes avec iconv influencé par les variables d’environnements.

En fait avec Ruby (1.8.6) ça se corse, car Ruby est aussi buggé.

Alors on recommence les explications.

L’influence de la variable d’environnement sur iconv :

$ echo éà | LANG=fr_FR.UTF-8 iconv -f UTF-8 -t ascii//translit
ea
$ echo éà | LANG=C iconv -f UTF-8 -t ascii//translit
??

Maintenant avec Ruby et Rails, ça se corse …

Dans un premier temps, on vérifie que tout est ok avec irb.

Avec irb, avec ‘C’

$ LANG=C irb
irb(main):001:0> require 'iconv'
=> true
irb(main):002:0> Iconv.iconv('ASCII//TRANSLIT', 'UTF-8', 'éèà')
=> ["???"]
irb(main):003:0>

maintenant irb et fr_FR

$ LANG=fr_FR.UTF-8 irb
irb(main):001:0> require 'iconv'
=> true
irb(main):002:0>  Iconv.iconv('ASCII//TRANSLIT', 'UTF-8', 'éèà')
=> ["eea"]
irb(main):003:0>

Ok cool tout va bien 🙂

Maintenant le runner de Ruby On Rails

$ LANG=fr_FR.UTF-8 script/runner "puts Iconv.iconv('ASCII//TRANSLIT', 'UTF-8', 'ééà').inspect; puts ENV['LANG'].inspect"
["???"]
"fr_FR.UTF-8"

Et là on s’arrache les cheveux … qu’on change ou pas dans config/environement.rb, le résultat est toujours le même

Et pour vérifier que le problème ne vient pas de Rails, mais vraiment de Ruby :

$ ruby -e "require 'iconv'; puts Iconv.iconv('ASCII//TRANSLIT', 'UTF-8', 'éèà').inspect; puts ENV['LANG'].inspect"
["???"]
"fr_FR.UTF-8"

Donc on cherche un peu sur le net et la réponse est sur ruby-forum : en gros un problème d’initialisation de la variable d’environnement LC_TYPE.

Youpi … En plus Nobuyoshi Nakada nous explique comment faire son patch ..

Pour plus de simplicité et de pérennité, j’ai fait mon package. D’abord télécharger les sources de locale-bug.

Ensuite en tant que root :

$ tar -xvzf locale-bug.tgz
$ cd locale-bug
$ ruby extconf.rb
$ make
$ make install

Et dans votre application Rails (config/environment.rb) :

...
require 'locale_bug'
LocaleBug.ctype = 'fr_FR.UTF-8'
require 'iconv'
...
Publié dans Non classé | Commentaires fermés sur Iconv et Ruby (le retour)

Solimap

Juste pour faire un peu de pub à un développeur RoR, et pour son bébé qui est joliment fait pour un principe aussi vieux que le Web : Solimap un site d’annonces diverses. Du mashable, du RubyOnRails mais ça on ne le voit pas, de la transparence, simple et propre.

Publié dans Non classé | Commentaires fermés sur Solimap

Iconv et résultats variables

Arrg, il faut lire les pages de manuel en entier … iconv et toutes les wrapper dérivés sont influencé par les variables d’environnement LANG, LC_ALL, LC_CTYPE, LC_MESSAGES.

Par exemple :

$ echo éà | LANG=fr_FR.UTF-8 iconv -f UTF-8 -t ascii//translit
ea
$ echo éà | LANG=C iconv -f UTF-8 -t ascii//translit
??

Et comme bien sûr iconv est très bien documenté, il va sans dire que ça été facile de trouver cette bizzarie.

Donc comme on dit dans ces cas là, ce n’est pas un bug mais une fonctionnalité, à voir chez debian : ”Libc6: //translit fails with cyrillic and others

Et bien sûr quand vous lancez votre application rails avec Webrick dans votre environnement où LANG=fr_FR.UTF-8

$ locale
LANG=fr_FR.UTF-8
LC_CTYPE="fr_FR.UTF-8"
LC_NUMERIC="fr_FR.UTF-8"
LC_TIME="fr_FR.UTF-8"
LC_COLLATE="fr_FR.UTF-8"
LC_MONETARY="fr_FR.UTF-8"
LC_MESSAGES="fr_FR.UTF-8"
LC_PAPER="fr_FR.UTF-8"
LC_NAME="fr_FR.UTF-8"
LC_ADDRESS="fr_FR.UTF-8"
LC_TELEPHONE="fr_FR.UTF-8"
LC_MEASUREMENT="fr_FR.UTF-8"
LC_IDENTIFICATION="fr_FR.UTF-8"
LC_ALL=

$ script/server

Et d’un autre en lançant via passenger et que LANG est initialisé à “C”, alors on perd son temps à chercher le problème dans rails ou passenger.
RTFM !

J’aurais bien aimé utiliser iconv avec la locale fr_FR, mais comme j’utilise iconv pour mes permaliens (enregistré en BD ce qui n’est pas une bonne idée au passage) qui ont été généré avec sans locale (LANG=C), alors pour éviter les problèmes, je force l’appli à utiliser LANG=C dans mon config/envirronment.rb

...
ENV['LANG'] = 'C'
...

jusqu’au prochain problème …

Publié dans Configuration, Web Dev | Marqué avec , | Commentaires fermés sur Iconv et résultats variables