Universidad de Costa Rica
Escuela de Ciencias de la
Computación e Informática
Profesor Adolfo Di Mare
CI-1201
II Semestre 2012
[<=] [home] [<>] [\/] [=>]
CI-1201 Programación II

Tarea #2 [solución]

Clase config para archivos de configuración de programas

; bt0pp.cfg
; =========
ext=txt pdf doc docx odt htm html url xls ods jpg png gif ppt odp
general=curriculum horario|horas declaracion
gsigla=??#### ;??-### ;???### ;etc.
gid=g##
guno=nota|notas asistencia evaluacion carta|descripcion
gsec=##-tarea|##-tpg examen.ex# (enunciado mejor peor)
                 ;proyecto-# ;lab|laboratorio##

      La clase config permite almacenar los valores de configuración para un programa. Estos valores se toman de un archivo de texto que contiente parejas de la forma llave=valor en donde la palabra llave está compuesta de letras, números y los caracteres '- ' y '_'. Ejemplos de llaves válidas son los siguientes: { "SIN" "CON_12" "R-LOCO" }. Los valores son un poco más estructurados, porque permite usar patrones como "###" para representas todos los números desde "000" hasta "999" o "??" para todas las combinaciones de 2 caracteres en los también que se incluye desde "aa" hasta "zz" y desde "AA" hasta "ZZ", pues nunca se considera diferente una letra mayúsculas de su corresponiente minúscula. Además, los valores pueden tener alternativas, como "nota|notas|n" y también pueden contener repetidores entre paréntesis, que aparecen separados por un espacio en blanco como en "(txt pdf htm html)". El caracter ';' sirve para marcar el comienzo de un comentario, que termina hasta el final del renglón. Otro ejemplode un archivo de configuración cuyos valores pueden ser almacenados en una instancia de la clase config:

; TODO esto es un comentario
llave=valor     ; valor puede ser simple
SIN=??####      ; CI0099 y con --0700 calzan en este patrón
CON_12=#12      ; 112 y 812 sí calzan pero 0012 no calza
otro=alfa|beta  ; patrón que solo calza con alfa y con beta
;
R-LOCO=???(txt pdf htm html) ; calza con aaa(txt txt pdf pdf)

; NO es posible usar ninguno de { '?' '|' ';' '#' '(' ')' } en "llave" en "valor"

      Para implementar su clase haga un programa de prueba que permita leer un archivo de texto que contiene el valor que será almacenado en su clase config. Talvez le sirva usar la versión de la función getline() que retornar una hilera estándar, de tipo std::string<>, pero no olvide incluir ejemplos BUnit en las especificaciones.

Di Mare, Adolfo:
BUnit.h: Un módulo simple para aprender prueba unitaria de programas en C++, X Simposio Internacional de Informática Educativa (SIIE'08) realizado del 1 al 3 de octubre 2008, Salamanca, España, I.S.B.N.: 978-84-7800-312-9, pp425-430, octubre 2008.
      http://www.di-mare.com/adolfo/p/BUnit.htm

      En la documentación de su clase incluya una comparación con el formato .ini que se ha usado en Windows para almacenar la configuración de los programas.

Consulta:
Profe: Al ponerle los métodos a la clase config necesito redefinir muchos de los métodos del diccionario. Me parece que eso da mucha duplicación y quisiera saber si puedo poner el Rep público para no tener que reprogramar todo, hasta los iteradores. ¿Puedo hacerlo solo esta vez?
Respuesta:
No hace falta hacer tanto enredo, pues para eso se puede utilizar herencia, de esta manera:
class valor_t { /* ... */ };

class config : public std::map< std::string, std::list<valor_t> > {
public:
    typedef std::string llave_t;
    typedef std::list< valor_t > lista_de_valores_t;
    typedef std::pair< llave_t , lista_de_valores_t > renglon_t;
};
Sí hay una diferencia importante pues, en lugar de que el tipo de valor_t esté definido dentro de la clase config, ahora es necesario dejarlo fuera de esa clase.

Consulta:
Profe: ¿Hay que detectar errores en el config? Es que me parece que hay algunos renglones que vienen con errores, como por ejemplo los siguientes:
;config con montones de repeticiones de "gsec"
gsec= ; llave sin palabras
= w   ; palabras sin llave
gsec= ) algo ; paréntesis abierto de primero
gsec= | algo ; barrita de primera
gsec= (solo) ; solo una palabra en el repetidor
gsec=  solo| ; barrita sin segunda palabra
gsec= ((si o no)); doble paréntesis
Respuesta:
Al usar std::map<> esa clase se encarga de nunca almacenar 2 renglones con la misma llave, por lo que solo el primero de todos los renglones con llave "gsec" quedaría almacenado en el dicciconario: en este caso lo mejor es agregar estos parámetros nuevos a la llave que ya está en el diccionario. Para los demás errores basta retornar un código de error al analizar el renglón independientemente de los otros renglones (es más simple solo detectar el primero error). Además, no se vale poner paréntesis o barritas anidadadas (para eso es mejor almacenar la configuración en formato XML).

Consulta:
Profe: Como no hay que hacer nada con el archivo del config, ¿le parece si lo imprimmos con las 3 catergorías? Por ejemplo los siguientes:
; CONFIG.cfg
guno  = nota|notas asistencia evaluacion examen (texto mejor peor)
gotro = nota notas asistencia evaluacion examen  texto mejor peor
Al ejecutar el programa de ejemplo produce algo parecido a lo siguiente:
X:\DIR\SubDir> use_config.exe CONFIG.cfg

use_config.exe ==> Leyendo archivo CONFIG.cfg...

Estas son las llaves y sus valores

Llave [guno]
- Palabras: asistencia evaluacion examen
- Alternativos: | nota notas |
- Repetidores: ( texto mejor peor )

Llave [gotro]
- Palabras: nota notas asistencia evaluacion examen texto mejor peor
- Alternativos:
- Repeditores:
Respuesta:
Es bonito lo que el programa use_config.cpp genera, pero me gustaría que ese programa verifique que un repetidor aparece únicamente después de una palabra. Por ejemplo, en la llave "guno" los repetidores "(texto mejor peor)" se aplican a la palabra "examen" que es la que les antecede y, por eso, el programa debiera destacare este hecho al producir algo similar a lo siguiente:
X:\DIR\SubDir> use_config.exe CONFIG.cfg

use_config.exe ==> Leyendo archivo CONFIG.cfg...

Estas son las llaves y sus valores

Llave [guno]
- Palabras: asistencia evaluacion
- Alternativos: | nota notas |
- Repetidores: examen ( texto mejor peor )

Llave [gotro]
- Palabras: nota notas asistencia evaluacion examen texto mejor peor
- Alternativos:
- Repeditores:
Para este ejemplo el archivo de configuración es el mismo:
; CONFIG.cfg
guno  = nota|notas asistencia evaluacion examen (texto mejor peor)
gotro = nota notas asistencia evaluacion examen  texto mejor peor

Consulta:
Profe: Me parece que usar la clase valor_t me complica todo porque creo que es más fácil solo usar una lista de hileras en el diccionario. Yo lo que quisiera es meter las hileras poniendo entre barritas los valores alternativos y entre paréntesis los repeditores. ¿Tengo que usar su clase config o puedo usar la mía? Por ejemplo, el renglón:
   guno = nota|notas asistencia evaluacion examen (texto mejor peor)
resulta en que la llave [guno] queda asociada a:
   [|] [nota] [notas] [|] [asistencia] [evaluacion] [examen] [(] [texto] [mejor] [peor] [)]
Respuesta:
Si te queda más cómodo usar la lista de hileras, por supuesto tenés todo el derecho de usarla:
class valor_t { /* ... */ };

class config : public std::map< std::string, std::list< std::string > > {
public:
    typedef std::string llave_t;
    typedef std::list< std::string > lista_de_valores_t;
    typedef std::pair< llave_t , lista_de_valores_t > renglon_t;
};

Consulta:
Profe: No me parece que haga falta al función bool check_ok(const valor_t& r); porque una hilera puede tener cualquier valor y no por eso es válido o inválido ese valor. Además, si la hilera contiene una palabra no es posible saber si después viene un paréntesis para que sea repetidora.
Respuesta:
Sí resulta más cómodo que valor_t sea una clase derivada de la hilera estándar, pero tenés razón en cuanto a que la noción de posición se pierde en la hilera. Una forma de remediar esto es trabajar con los iteradores de la lista de hileras que está asociada a cada llave:
class valor_t : public std::string {
public:
    // etc...
};

class config : public std::map< std::string, std::list< valor_t > > {
public:
    // etc...
    bool esBarrita( lista_de_valores_t::const_iterator it ) const {
        if ( this->end() == it ) { return false; }
        return (*it=="|");
    }
    bool esRepetidor( lista_de_valores_t::const_iterator it ) const {
        if ( esBarrita(it) } { return false; }
        lista_de_valores_t::const_iterator itNext = it;
        itNext++;
        if ( this->end() == itNext ) { return false; }
        return (*it=="(");
    }
    typedef std::string llave_t;
    typedef std::list< valor_t > lista_de_valores_t;
    typedef std::pair< llave_t , lista_de_valores_t > renglon_t;
    // etc...
    friend bool check_ok( const config& c );
    friend bool check_ok( const renglon_t& r );
    bool check_ok( const lista_de_valores_t::const_iterator& it );
};

      Entregue su tarea por correo electrónico, como lo hizo anteriormente.

[mailto:] Entrega de Tareas

Tiempo de entrega: 7 días
Entregue su documentación en la primera fecha, y luego entregue el programa completo en la segunda fecha.
Segunda etapa: 3 días
Modalidad: En parejas

Soluciones

[mailto:] Adolfo Di Mare <adolfo@di-mare.com>.
Copyright © 2012
Derechos de autor reservados © 2012
[home] <> [/\]