Image

После работы, катания на велосипеде, булки из подорожника и литра Кока-Колы я воодушевленно сел писать код. После часов двух экспериментов и чтения манов я добавил в свою программу, для учета финансов возможность обрабатывать опции коммандной строки. Использовал только getopts(), потому что getopts_long() не поддерживается POSIX'ом. Возможно, позже и прикручу это через #ifdef'ы, но пока такой надобности нет.

Собственно, чего я хотел-то?! Хотел показать свои наработки. Моя программа уже достигла символичного размера — 255 строк :) У меня много ещё идей, которые лишь предстоит реализовать, но то что есть сейчас уже работает и успешно мной используется.

Я не призываю вас использовать мою программу, (хотя это и не воспрещается ;) ) Я хотел бы услышать советы, аргументированную критику и просто ваши мнения. Представьте, что эта программа должна соответствовать всем стандартам, что она должна соответствовать идеальным стандартам по оформлению кода, что она должна работать всегда и везде, быть готовой к любым ситуациям. И теперь сравните это с моим кодом. Чего не хватает? Что можно улучшить?

Your welcome! ;-)



/*
 * Open Finance Manager (OpenFM)
 *
 * Author:  Semushin Slava <php-coder at altlinux.ru>
 * Created: 23.04.2006
 * Updated: 24.06.2006
 * License: GPL
 *
 * */


/* for printf()
 *     fprintf()
 *     snprintf()
 *     sscanf()
 *     fopen()
 *     fgets()
 *     fclose()
 *     perror()
 *     FILE and NULL constants
 * */
#include <stdio.h>

/* for exit()
 *     malloc()
 *     free()
 *     getenv()
 *     EXIT_* constants
 * */
#include <stdlib.h>

/* for strlen()
 * */
#include <string.h>

/* for getopt()
 * */
#include <unistd.h>


/* Name of file with data */
#define DATA_FILE "finance.db"

/* Maximum size of symbols in string without '\n' and '\0' */
#define MAX_STRING_LENGTH 70


/* Prototypes */
static void parse_cmd_line(int argc, char **argv);
static void print_help(void);
static void print_version(void);


/* Variables */
static int verbose = 0;
static char *__progname; /* initial below */
static char *__version = "0.5";

/*
 * TODO:
 * - separate code to functions
 * - better English grammar
 * - gettextize
 *
 * */

int main(int argc, char **argv) {
 FILE *fd;
 int   ret;

 char   *dbfile;
 char   *homedir;
 size_t  filename_size;

 char  buffer[MAX_STRING_LENGTH + 2];
 char  sign;
 float plus, minus, curr;
 long  lineno;

 __progname = argv[0];

 /* look at command line options */
 parse_cmd_line(argc, argv);

 homedir = getenv("HOME");
 if (homedir == NULL) {
    fprintf(stderr, "getenv: cannot get value for $HOME variable\n");
    exit(EXIT_FAILURE);
 }

 if (verbose >= 1)
     printf("-> Your home directory is '%s'\n", homedir);

 /* Allocated memory for path to file with data:
  * strlen("/home/coder" + "/" + "finance.db" + "\0")
  * */
 filename_size = strlen(homedir) + strlen(DATA_FILE) + 2;
 dbfile = malloc(filename_size);
 if (dbfile == NULL) {
    fprintf(stderr, "malloc: cannot allocate memory\n");
    exit(EXIT_FAILURE);
 }


 ret = snprintf(dbfile, filename_size, "%s/%s", homedir, DATA_FILE);
 if ( (ret > 0) && ((size_t)ret >= filename_size) ) {
    /*
     * FIXME:
     * - May be we should print more information ?
     * - May occurs error when we cast ret to size_t type ?
     *
     * TODO:
     * - NOTES on man page say then some version of glibc can
     *   return -1 when output was truncated. We should correct
     *   handle that case too.
     *
     *   (BTW, is exist way to determine what glibc uses?)
     *
     * */
    fprintf(stderr, "Writing data was truncated.\n"
                    "Please notify author about this incident!\n"
                    "snprintf: return value is %d\n", ret);
    exit(EXIT_FAILURE);
 }
 if (ret < 0) {
    fprintf(stderr, "Failed to write to the character string\n"
                    "snprintf: return value is %d\n", ret);
    exit(EXIT_FAILURE);
 }

 /* open data file */
 if (verbose >= 1)
     printf("-> Open data file (%s)\n", dbfile);

 fd = fopen(dbfile, "r");
 if (fd == NULL) {
    fprintf(stderr, "Failed to open file: %s\n", dbfile);
    perror("fopen");
    exit(EXIT_FAILURE);
 }

 /* free memory for path to data file */
 free(dbfile);

 if (verbose >= 1)
     printf("-> Reading data...\n");

 plus = minus = 0.0;
 lineno = 0L;

 /* read and parse data file */
 /*
  * FIXME:
  * fgets() return NULL also when error occurs. We should correct
  * handle this situation.
  *
  * */
 while(fgets(buffer, MAX_STRING_LENGTH + 2, fd) != NULL){
   lineno++;

   if (verbose >= 3) {
       /*
        * FIXME:
        * Just think what happens if now newline in buffer
        *
        * */
       buffer[strlen(buffer) - 1] = '\0';
       printf("---> %ld: '%s'\n", lineno, buffer);
   }

   /*
    * TODO:
    * - correctly handle return value from sscanf()
    * - skip empty lines and lines which beginig with '#' (?)
    * - add verification function
    *
    * */
   (void)sscanf(buffer, "%c|%*2u.%*2u.%*4u|%f|%*s\n", &sign, &curr);
   if (sign == '-') {
      minus += curr;
      continue;
   }

   if (sign == '+') {
       plus += curr;
       continue;
   }

   /* this code never happens */
 }
 
 if (verbose >= 1)
     printf("-> Reads %ld strings from data file\n", lineno);

 /* print short statistic */
 printf("Finance statistic:\n"
        "plus:    %7.1f\n"
        "minus:   %7.1f\n"  /* seven because point belongs to digital */
        "balance: %7.1f\n",
        plus, minus, plus - minus);

 /* close data file */
 ret = fclose(fd);
 if (ret != 0) {
    perror("fclose");
    exit(EXIT_FAILURE);
 }

 return EXIT_SUCCESS;
}


static void print_help(void) {
  printf("%s: Your private finance manager\n\n"
         "Usage: %s [option]\n"
         "  -v\tenable verbose mode\n"
         "  -V\tprint version and exit\n"
         "  -h\tprint this help and exit\n",
         __progname, __progname);

  exit(EXIT_SUCCESS);
}

static void print_version(void) {
  printf("%s: version %s\n"
         "Copyright (C) 2006 Slava Semushin <[email protected]>\n",
         __progname, __version);

  exit(EXIT_SUCCESS);
}

static void parse_cmd_line(int argc, char **argv) {
  int option;

  while ((option = getopt(argc, argv, "vVh")) != -1) {
    switch (option) {
      case 'v': /* enable verbose mode */
        verbose++;
        break;
      case 'V': /* print version of program and exit */
        print_version();
        break;
      case 'h': /* print help and exit */
        print_help();
        break;
      case '?': /* for unknown options */
        exit(EXIT_FAILURE);
      default:  /* that case never happens */
        fprintf(stderr, "getopt: return %c\n", option);
        break;
    }
  }

}

_Winnie C++ Colorizer


Updated(20060625): added tag ofm
Updated(20060625): changed tag ofm to openfm