Comando bash util del día
sudo !!
Ejecuta el último comando, pero con sudo delante. Muy útil para todas las veces que se teclea un “churro” enorme y el sistema responde: “You don’t have permissions…”
sudo !!
Ejecuta el último comando, pero con sudo delante. Muy útil para todas las veces que se teclea un “churro” enorme y el sistema responde: “You don’t have permissions…”
Desde hace ya casi un mes he unido fuerzas con BeCodeMyFriend, el colectivo Ágil de Valencia que desafía todas las definiciones. En “la madriguera” de BeCode comparten techo programadores ninjas, maestros ágiles, constructores de bicis y otros elementos variados. De hecho, ni siquiera compartimos techo ya que dos BeCodianos habituales trabajan en remoto desde Madrid.
La lógica, si es que hay alguna, detrás de todo esto es crear un sitio donde se venga a trabajar y a jugar, a aprender y a enseñar y en definitiva a construir cosas. Para un soldado de fortuna como yo, es una manera de tener acceso a un equipo de expertos que me apoye en cualquier proyecto. Para el colectivo, se suma un colaborador en el que apoyarse para multiplicar fuerzas.
Hoy inauguramos “la cueva” de BeCode, el espacio que nos une. Si tenéis una oportunidad, no dudéis en pasar a saludar y tomar algo con nosotros.
Esta mañana los BeCodianos me han llamado “el rey del if-then-else” por mi solución al Coding kata propuesto por CSD la semana pasada. ¿Ah si?
La solución, refactorizada (en pastebin, para mayor legibilidad), y los tests.
La mejoras:
La semana pasada me pasé por el segundo coding dojo en CSD, para conocer a otros desarrolladores y estirar los músculos de programar.
La kata en si no era muy compleja (enunciado en PDF), pero el truco estaba en que había que programarla usando técnicas de TDD. Interesante, puesto que una de las desventajas del TDD es que la velocidad inicial de desarrollo es bastante baja.
Para desarrollar la kata nos emparejaron e hicimos dos pomodoros de desarrollo con cinco minutos de descanso para discutir en grupo por dónde íbamos.
Resumen de la kata
La kata en si consistía en desarrollar un intérprete de líneas de comando al que se le pasan flags de tres tipos:
El primer paso es desarrollar un esquema en el que se indiquen los flags que acepta el programa, de qué tipo son y el valor por defecto si se omite el flag. Una vez tenemos el esquema, el programa tiene que ser capaz de interpretar una cadena de entrada, analizar si es sintácticamente correcta, analizar si es semánticamente correcta y lanzar excepciones si la entrada no es correcta.
Finalmente… antes de enseñaos la solución
Desde aquí, gracias a la gente de CSD por organizar el Dojo en las mazmorras de su oficina y por echarnos de comer y beber (free beer!) mientras programábamos.
Nuestra solución
Aunque no conseguimos resolver la kata en dos pomodoros, aquí estos son los tests y la solución en Ruby que creamos mi compañero Yago y yo.
Los tests:
require_relative '../lib/parseator.rb'
describe Parseator do
before :each do
@example_in = {"l" => ["bool", false],
"p" => ["number", 8080],
"d" => ["string", "/usr/local"] }
@parser = Parseator.new(@example_in)
end
describe ", Sintactic Tests" do
it "instantiates a Parseator object" do
@parser.should be_an_instance_of Parseator
end
it "parses the empty string as valid" do
@parser.valid?("").should == true
end
it "fails if first flag does not start with -" do
@parser.valid?("poop").should == false
end
it "parses a single boolean flag" do
@parser.valid?("-t").should == true
end
it "parses N boolean flags" do
@parser.valid?("-p -m").should == true
end
it "parses a single flag with args" do
@parser.valid?("-p 8080").should == true
end
it "parses a mix of boolean and arg flags without negative numbers" do
@parser.valid?("-p 8080 -m -l /hola/yo").should == true
end
it "parses a mix of boolean and arg flags without negative numbers with traling and leading spaces" do
@parser.valid?("-p 8080 -m -l /hola/yo").should == true
end
it "fails with an incorrect mix of boolean and arg flags with negative numbers" do
@parser.valid?("-p 8080 -m -500 p -l /hola/yo").should == false
end
it "parses a mix of boolean and arg flags with negative numbers" do
@parser.valid?("-p 8080 -m -500 -l /hola/yo").should == true
end
it "parses a mix of boolean and arg flags with negative numbers without spaces before argument" do
@parser.valid?("-p8080 -m -500 -l /hola/yo").should == true
end
end
describe ", Semantic Tests, " do
it "raises exception when passing a flag that is not defined" do
lambda { @parser.parse("-t") }.should raise_error UndefinedParam
end
it "returns a hash with default value when passing a single boolean flag" do
test_string = "-l"
@parser.parse(test_string).should have_key("l")
@parser.parse(test_string)["l"].should == true
end
it "returns a hash with false when passing an empty string, for the boolean flag" do
@parser.parse("").should have_key("l")
@parser.parse("")["l"].should == false
end
it "returns a hash with value when passing a single string" do
test_string = "-d /usr/bin"
@parser.parse(test_string).should have_key("d")
@parser.parse(test_string)["d"].should == "/usr/bin"
end
it "returns a hash with value when passing a single number" do
test_string = "-p 8080"
@parser.parse(test_string).should have_key("p")
@parser.parse(test_string)["p"].should == 8080
end
it "returns a hash with value when passing a single negative number" do
test_string = "-p -8080"
@parser.parse(test_string).should have_key("p")
@parser.parse(test_string)["p"].should == -8080
end
it "returns correctly when passing mix of strings, booleans and numbers" do
test_string = "-p -8080 -l -d /usr/test"
@parser.parse(test_string).should have_key("p")
@parser.parse(test_string)["p"].should == -8080
@parser.parse(test_string).should have_key("d")
@parser.parse(test_string)["d"].should == "/usr/test"
@parser.parse(test_string).should have_key("l")
@parser.parse(test_string)["l"].should == true
end
it "defaults correctly with some params defined" do
test_string = "-p -8080 -d /usr/test"
@parser.parse(test_string).should have_key("l")
@parser.parse("")["l"].should == false
end
it "defaults correctly with no params defined, aka empty string test" do
test_string = "-p -8080 -d /usr/test"
@parser.parse(test_string).should have_key("l")
@parser.parse("")["l"].should == false
end
it "fails when passing valid and invalid flags" do
test_string = "-p -8080 -m /usr/test"
lambda { @parser.parse(test_string) }.should raise_error UndefinedParam
end
it "raises exception, with explanation test when passing a flag that is not defined" do
lambda { @parser.parse("-t") }.should raise_error UndefinedParam, "Syntax error. Correct format is UNIMPLEMENTED"
end
it "raises exception, with explanation, passing a defined flag with wrong param" do
lambda { @parser.parse("-l /usr/test") }.should raise_error IncorrectParam, "Incorrect parameter -l /usr/test"
end
it "raises exception, with explanation, passing a number flag to a string flag" do
lambda { @parser.parse("-d 8080") }.should raise_error IncorrectParam, "Incorrect parameter -d 8080"
end
it "raises exception, with explanation, passing a number flag with wrong param" do
lambda { @parser.parse("-p /usr/test") }.should raise_error IncorrectParam, "Incorrect parameter -p /usr/test"
end
end
end
El código:
class UndefinedParam < StandardError; end
class IncorrectSyntax < StandardError; end
class IncorrectParam < StandardError; end
# Extend class string to check if params are numbers (floats included)
class String
def is_numeric?
begin Float(self) ; true end rescue false
end
end
class Parseator
# Constructor takes as parameter a hash with the valid flags, type and default value
def initialize(model_hash)
@model = model_hash
end
# valid? returns true if params is syntactically correct
def valid?(params)
if params == ""
return true
else
if params =~ /^\s*(\-[a-zA-Z]{1}\s*(\-?[0-9]+|[^-\s]+)*\s*)*\s*$/
return true
else
return false
end
end
end
# returns a Hash with the parameters and the set values
# or raises an exception if there are Syntax or Semantic errors
def parse(params)
params_out = Hash.new
if valid?(params) # First, check there are no syntax errors
# first go through the input string and add that to the results
# match[0] is the flag
# match[1] is the value
params.scan(/\-([a-zA-Z]{1})\s*(\-?[0-9]+|[^-\s]+)*/).each do |match|
if @model.has_key?(match[0])
# bool params
if @model[match[0]][0] == "bool"
unless match[1].nil? # bool types do not have a value
raise IncorrectParam, "Incorrect parameter -#{match[0]} #{match[1]}"
end
params_out[match[0]] = true
# string params
elsif @model[match[0]][0] == "string"
if match[1].nil? || match[1].is_numeric?
raise IncorrectParam, "Incorrect parameter -#{match[0]} #{match[1]}"
end
params_out[match[0]] = match[1]
# number params
elsif @model[match[0]][0] == "number"
if match[1].nil? || !match[1].is_numeric?
raise IncorrectParam, "Incorrect parameter -#{match[0]} #{match[1]}"
end
params_out[match[0]] = match[1].to_i
end
else
raise UndefinedParam, "Syntax error. Correct format is UNIMPLEMENTED"
end
end
# second, go through the model and see what params are missing, to add the defaults
@model.keys.each do |key|
unless params_out.has_key?(key)
params_out[key] = @model[key][1] # default value
end
end
return params_out
else # Raise syntax error if not valid?
# This method can be refined to analyze the string for the location of the syntax error(s)
# Left as an exercise for the reader
raise IncorrectSyntax
end
end
end
Ahora que lo miro… @model huele a refactoring…