Witam w ostatniej części tego miniporadnika nt. przygotowania kodu dla mniej fajnych klientów ;)

W tej części zajmiemy się wyczyszczeniem plików rb z oryginalnych komentarzy oraz "zamieszaniem" w tychże plikach. Uruchomimy także całą resztę kodu, tak aby wszystko ładnie funkcjonowało.

A więc do dzieła!

Zacznijmy od metody która usunie nam wszystkie linijki komentarzy, ze wszystkich możliwych plików.

Metoda jest całkiem prosta. Otwieramy plik rb i usuwamy wszystkie linijki zaczynające się na "#", za wyjątkiem magicznego # coding: utf-8. Najpierw otwieramy plik do odczytu, kopiujemy linijka po linijce. Następnie otwieramy plik do zapisu (co automatycznie go wyczyści) a następnie zapisujemy tylko te linijki które nie są komentarzami. Przy okazji uważamy na #{ które nie jest komentarzem ale mogło zostać za takowy uznane.

Usuwamy także puste linijki i pozbywamy się wcięć.

    # Usuwa komentarze (ale nie maskuje - tylko komentarze i białe znaki) z kodu
    def self.clean(path = './deploy/')
      Find.find(path) { |f|
        # Jeśli to nie plik rb to idz do nastepnego pliku
        next unless f[f.size-3, f.size-1] == '.rb'
        filename =  f
        # Wrzuć info na konsole co sie dzieje
        puts "Cleaning #{filename}"

        lines = []
        File.open(filename, "r").each_line {|l| lines << l }

        File.open(filename, 'w') {|f|
          lines.each { |l|
            a = l.strip

            # Jeśli to komentarz to przeskocz dalej
            next if (a[0] == "\#" && a != '# coding: utf-8' && a[0,2] != "\#\{") || a.size == 0

            f.write(a+"\n")
          }
        }
      }
    end

Po przepuszczeniu naszego "deploy" przez ten kod, otrzymamy pliki które nie mają ani komentarzy ani też wcięć.

Pozostało nam jeszcze losowo "nabałaganić" w plikach i spiąć deploy.rb w jedną sensowną całość. Może najpierw pobałaganimy ;)

Po pierwsze, maskowanie powinno dotyczyć tylko i wyłącznie plików .rb, stąd poniższa linijka, która powoduje że kod nie zostanie wykonany dla plików o innych rozszerzeniach:

next unless f[f.size-3, f.size-1] == '.rb'

Dalej jest kawałek kodu, analogiczny do tego z metody czyszczącej komentarze. Ładujemy zawartość pliku do pamięci, po czym czyścimy go i otwieramy do zapisu. Bałaganimy generując losowo od 0 do 64 linijek z losowymi ciągami lub losowymi fragmentami kodu oraz z losową ilością spacji na początku każdej linijki.

Jedyna rzecz na jaką musimy uwazać to polecenia które składają się z wielu linijek, a w których nie można umieszczać komentarzy (np. wielolinijkowe stringi). Rozwiązałem to stosunkowo prosto. Zliczam po prostu liczbę otwarć cudzysłowów i jeśli nie jest parzysta, tzn że kod jest wielolinijkowy. Do czasu aż znajdę następną linijkę z ilością nieparzystą (co oznaczać będzie domknięcie), nie wstawiam śmieci. Śmieci też wstawiam na początku i końcu każdego pliku.

    def self.mask(path)
      rand = Randomer.new
      Find.find(path) { |f|
        # Jeśli to nie plik rb to idz do nastepnego pliku
        next unless f[f.size-3, f.size-1] == '.rb'
        filename =  f
        # Wrzuć info na konsole co sie dzieje
        puts "Masking: #{filename}"

        lines = []
        File.open(filename, "r").each_line {|l| lines << l }

        File.open(filename, 'w') {|f|
          # Czy wyrazenie jest wieloliniowe (nie wsadza w takie komentów)
          multiline = false
      	  i = 0
          lines.each { |l|
            a = l.strip

            # Jeśli to komentarz to przeskocz dalej
            next if (a[0] == "\#" && a != '# coding: utf-8' && a[0,2] != "\#\{") || a.size == 0

            if a == '# coding: utf-8'
              f.write(a+"\n")
              rand(64).times { f.write(rand.class.spaces+'# '+rand.line+"\n") }
            else
              rand(64).times { f.write(rand.class.spaces+'# '+rand.line+"\n") } if i == 0
              line = (multiline ? '' : rand.class.spaces) + a + "\n"
              multiline = !multiline if line.count('"') % 2 == 1
              multiline = !multiline if line.count("'") % 2 == 1
              f.write(line)
            end
            (rand(64).times { f.write(rand.class.spaces+'# '+rand.line+"\n") }) unless multiline
            i+=1
          }
          rand(64).times { f.write(rand.class.spaces+'# '+rand.line+"\n") }
        }
      }
    end

Całość opakowujemy w klasę:

  # Maskuje pliki rb z podanego katalogu
  class Masker
    def self.mask(path)
      rand = Randomer.new
      Find.find(path) { |f|
        # Jeśli to nie plik rb to idz do nastepnego pliku
        next unless f[f.size-3, f.size-1] == '.rb'
        filename =  f
        # Wrzuć info na konsole co sie dzieje
        puts "Masking: #{filename}"

        lines = []
        File.open(filename, "r").each_line {|l| lines << l }

        File.open(filename, 'w') {|f|
          # Czy wyrazenie jest wieloliniowe (nie wsadza w takie komentów)
          multiline = false
      	  i = 0
          lines.each { |l|
            a = l.strip

            # Jeśli to komentarz to przeskocz dalej
            next if (a[0] == "\#" && a != '# coding: utf-8' && a[0,2] != "\#\{") || a.size == 0

            if a == '# coding: utf-8'
              f.write(a+"\n")
              rand(64).times { f.write(rand.class.spaces+'# '+rand.line+"\n") }
            else
              rand(64).times { f.write(rand.class.spaces+'# '+rand.line+"\n") } if i == 0
              line = (multiline ? '' : rand.class.spaces) + a + "\n"
              multiline = !multiline if line.count('"') % 2 == 1
              multiline = !multiline if line.count("'") % 2 == 1
              f.write(line)
            end
            (rand(64).times { f.write(rand.class.spaces+'# '+rand.line+"\n") }) unless multiline
            i+=1
          }
          rand(64).times { f.write(rand.class.spaces+'# '+rand.line+"\n") }
        }
      }
    end

    # Usuwa komentarze (ale nie maskuje - tylko komentarze i białe znaki) z kodu
    def self.clean(path = './deploy/')
      Find.find(path) { |f|
        # Jeśli to nie plik rb to idz do nastepnego pliku
        next unless f[f.size-3, f.size-1] == '.rb'
        filename =  f
        # Wrzuć info na konsole co sie dzieje
        puts "Cleaning #{filename}"

        lines = []
        File.open(filename, "r").each_line {|l| lines << l }

        File.open(filename, 'w') {|f|
          lines.each { |l|
            a = l.strip

            # Jeśli to komentarz to przeskocz dalej
            next if (a[0] == "\#" && a != '# coding: utf-8' && a[0,2] != "\#\{") || a.size == 0

            f.write(a+"\n")
          }
        }
      }
    end
  end

Po czym odpalamy całość kodu napisanego przez 3 części tutoriala:

  # Zrób kopię zapasową do której robimy deploy
  Backuper.run
  # Usuń co niepotrzebne w deployu
  Cleaner.run

  # Wyczysc komentarze z calosci plikow
  Masker.clean
  # I zamaskuj reszte kodu
  MASK_DIRS.each {|d| Masker.mask(d)}

Po tych operacjach otrzymujemy kod który:

  • Nie zawiera elementów niepotrzebnych klientowi
  • Nie zawiera plików tymczasowych
  • Jest "niemiły" dla użyszkodników

To by było na tyle. Używajcie tego ilekroć macie klientów których niekoniecznie darzycie zaufaniem.

Sprawdź także pozostałe dwie części tutoriala: