diff --git a/tools/Makefile.host b/tools/Makefile.host index 297470dc6ee..c4fb6860d1f 100644 --- a/tools/Makefile.host +++ b/tools/Makefile.host @@ -1,7 +1,8 @@ ############################################################################ # Makefile.host # -# Copyright (C) 2007, 2008, 2011-2012, 2015, 2017 Gregory Nutt. All rights reserved. +# Copyright (C) 2007, 2008, 2011-2012, 2015, 2017-2018 Gregory Nutt. All +# rights reserved. # Author: Gregory Nutt # # Redistribution and use in source and binary forms, with or without @@ -70,13 +71,15 @@ endif all: b16$(HOSTEXEEXT) bdf-converter$(HOSTEXEEXT) cmpconfig$(HOSTEXEEXT) \ configure$(HOSTEXEEXT) mkconfig$(HOSTEXEEXT) mkdeps$(HOSTEXEEXT) \ mksymtab$(HOSTEXEEXT) mksyscall$(HOSTEXEEXT) mkversion$(HOSTEXEEXT) \ - cnvwindeps$(HOSTEXEEXT) nxstyle$(HOSTEXEEXT) initialconfig$(HOSTEXEEXT) + cnvwindeps$(HOSTEXEEXT) nxstyle$(HOSTEXEEXT) initialconfig$(HOSTEXEEXT) \ + logparser$(HOSTEXEEXT) default: mkconfig$(HOSTEXEEXT) mksyscall$(HOSTEXEEXT) mkdeps$(HOSTEXEEXT) \ cnvwindeps$(HOSTEXEEXT) ifdef HOSTEXEEXT .PHONY: b16 bdf-converter cmpconfig clean configure kconfig2html mkconfig \ - mkdeps cnvwindeps mksymtab mksyscall mkversion + mkdeps mksymtab mksyscall mkversion cnvwindeps nxstyle initialconfig \ + logparser else .PHONY: clean endif @@ -182,6 +185,15 @@ ifdef HOSTEXEEXT initialconfig: initialconfig$(HOSTEXEEXT) endif +# logparser - Convert a git log to ChangeLog format. + +logparser$(HOSTEXEEXT): logparser.c + $(Q) $(HOSTCC) $(HOSTCFLAGS) -o logparser$(HOSTEXEEXT) logparser.c + +ifdef HOSTEXEEXT +logparser: logparser$(HOSTEXEEXT) +endif + # cnvwindeps - Convert dependences generated by a Windows native toolchain # for use in a Cygwin/POSIX build environment diff --git a/tools/README.txt b/tools/README.txt index b332d495a3d..2f678745ba0 100644 --- a/tools/README.txt +++ b/tools/README.txt @@ -655,6 +655,11 @@ kconfig.bat - option env="APPSDIR" + default "../apps" +logparser.c +----------- + + Convert a git log to ChangeLog format. + mkimage.sh ---------- diff --git a/tools/logparser.c b/tools/logparser.c new file mode 100644 index 00000000000..7704bcbb4d0 --- /dev/null +++ b/tools/logparser.c @@ -0,0 +1,552 @@ +/**************************************************************************** + * tools/logparser.c + * + * Copyright (C) 2018 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define LINESIZE (PATH_MAX > 4096 ? PATH_MAX : 4096) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +enum parse_state_e +{ + STATE_IDLE = 0, /* Finished last, ready to start next */ + STATE_HEADER, /* Started next, parsing header */ + STATE_ENDHEADER, /* Finished the header looking for the start of the body */ + STATE_BODY, /* Parsing the body */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static char g_line[LINESIZE+1]; +static unsigned long g_lineno; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Skip over any spaces */ + +static char *skip_space(char *ptr) +{ + while (*ptr && isspace((int)*ptr)) ptr++; + return ptr; +} + +/* Find the end of a variable string */ + +static void trim_end(char *startptr, char *endptr) +{ + while (endptr != startptr && isspace((int)*endptr)) + { + *endptr = '\0'; + endptr--; + } +} + +/* check for a blank line */ + +static bool blank_line(void) +{ + char *ptr = g_line; + + while (*ptr != '\0') + { + if (!isspace((int)*ptr)) + { + return false; + } + + ptr++; + } + + return true; +} + +/* Convert git log date string */ + +static char *parse_date(char *ptr) +{ + char *next; + char *endptr; + char *alloc; + unsigned int mon; + unsigned long date; + unsigned long year; + + /* Form: DOW MON DD HH:MM:SS YYYY GMT + * Eg. Mon Jan 29 07:17:17 2018 -0600 + */ + + /* Skip the day of the week */ + + next = strchr(ptr, ' '); + if (next == NULL) + { + fprintf(stderr, "ERROR line %ld: Did not find day of the week\n", g_lineno); + exit(EXIT_FAILURE); + } + + ptr = skip_space(next); + + /* Get the month */ + + next = strchr(ptr, ' '); + if (next == NULL) + { + fprintf(stderr, "ERROR line %ld: Did not find month\n", g_lineno); + exit(EXIT_FAILURE); + } + + endptr = next; + next = skip_space(next); + trim_end(ptr, endptr); + + if (strncmp(ptr, "Jan", 3) == 0) + { + mon = 1; + } + else if (strncmp(ptr, "Feb", 3) == 0) + { + mon = 2; + } + else if (strncmp(ptr, "Mar", 3) == 0) + { + mon = 3; + } + else if (strncmp(ptr, "Apr", 3) == 0) + { + mon = 4; + } + else if (strncmp(ptr, "May", 3) == 0) + { + mon = 5; + } + else if (strncmp(ptr, "Jun", 3) == 0) + { + mon = 6; + } + else if (strncmp(ptr, "Jul", 3) == 0) + { + mon = 7; + } + else if (strncmp(ptr, "Aug", 3) == 0) + { + mon = 8; + } + else if (strncmp(ptr, "Sep", 3) == 0) + { + mon = 9; + } + else if (strncmp(ptr, "Oct", 3) == 0) + { + mon = 10; + } + else if (strncmp(ptr, "Nov", 3) == 0) + { + mon = 11; + } + else if (strncmp(ptr, "Dec", 3) == 0) + { + mon = 12; + } + else + { + fprintf(stderr, "ERROR line %ld: Unrecognized month %s\n", g_lineno, ptr); + exit(EXIT_FAILURE); + } + + /* Get the day of the month */ + + ptr = next; + next = strchr(ptr, ' '); + if (next == NULL) + { + fprintf(stderr, "ERROR line %ld: Did not find day of the month\n", g_lineno); + exit(EXIT_FAILURE); + } + + endptr = next; + next = skip_space(next); + trim_end(ptr, endptr); + + date = strtoul(ptr, &endptr, 10); + if (*endptr != '\0' || date > 31) + { + fprintf(stderr, "ERROR line %ld: Invalid date\n", g_lineno); + exit(EXIT_FAILURE); + } + + /* Skip over the time */ + + ptr = next; + next = strchr(ptr, ' '); + if (next == NULL) + { + fprintf(stderr, "ERROR line %ld: Did not find day of the month\n", g_lineno); + exit(EXIT_FAILURE); + } + + /* Get the year */ + + ptr = skip_space(next); + next = strchr(ptr, ' '); + if (next == NULL) + { + fprintf(stderr, "ERROR line %ld: Did not find year\n", g_lineno); + exit(EXIT_FAILURE); + } + + trim_end(ptr, next); + year = strtoul(ptr, &endptr, 10); + if (*endptr != '\0' || year < 2007 || year >= 3000) + { + fprintf(stderr, "ERROR line %ld: Invalid year: %s\n", g_lineno, ptr); + exit(EXIT_FAILURE); + } + + /* Then create the final date string */ + + asprintf(&alloc, "(%04lu-%02u-%02lu)", year, mon, date); + if (alloc == NULL) + { + fprintf(stderr, "ERROR line %ld: asprintf failed\n", g_lineno); + exit(EXIT_FAILURE); + } + + return alloc; +} + +/* Parse the entire file */ + +static void parse_file(FILE *stream) +{ + enum parse_state_e state; + bool lastblank; + bool firstline; + bool consumed; + bool merge; + char *name; + char *date; + char *ptr; + + /* Loop until the entire file has been parsed. */ + + g_lineno = 0; + state = STATE_IDLE; + name = NULL; + date = NULL; + consumed = true; + firstline = true; + merge = false; + + for (; ; ) + { + /* Read the next line from the file (unless it was not consumed on the + * previous pass through the loop. + */ + + if (consumed) + { + g_line[LINESIZE] = '\0'; + if (!fgets(g_line, LINESIZE, stream)) + { + /* Check if the body was terminated with the end of file */ + + if (state == STATE_BODY) + { + /* End of body. Add author, date, and final newline */ + + printf(" From %s %s.\n", name, date); + free(name); + free(date); + } + else if (state != STATE_IDLE) + { + fprintf(stderr, "ERROR line %ld: Unexpected EOF in state %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + + return; + } + + g_lineno++; + consumed = false; + } + + ptr = g_line; + + /* Process the line depending upon the state of the parser */ + + switch (state) + { + case STATE_IDLE: /* Finished last, ready to start next */ + if (blank_line()) + { + consumed = true; + break; + } + + if (isspace(g_line[0])) + { + fprintf(stderr, "ERROR line %lu: Unexpected whitespace in state %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + + /* Change state and fall through */ + + state = STATE_HEADER; + + case STATE_HEADER: /* Started next, parsing header */ + if (!isspace(g_line[0])) + { + if (strncmp(g_line, "commit ", 7) == 0) + { + /* Skip commit line */ + } + else if (strncmp(g_line, "Merge: ", 6) == 0) + { + /* Skip merges */ + + merge = true; + } + else if (strncmp(g_line, "Author: ", 8) == 0) + { + char *endptr; + + /* Extract the name */ + + ptr += 7; + ptr = skip_space(ptr); + endptr = strchr(ptr, '<'); + if (endptr == NULL) + { + fprintf(stderr, "ERROR line %lu: No email address after name, state %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + + if (name != NULL) + { + fprintf(stderr, "ERROR line %lu: Duplicate name, state %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + + /* Copy and save the name string */ + + trim_end(ptr, endptr - 1); + name = strdup(ptr); + if (name == NULL) + { + fprintf(stderr, "ERROR line %lu: Failed to duplicate name, state %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + } + else if (strncmp(g_line, "Date: ", 6) ==0) + { + if (date != NULL) + { + fprintf(stderr, "ERROR line %lu: Duplicate date, state %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + + /* Extract the date */ + + ptr += 6; + ptr = skip_space(ptr); + date = parse_date(ptr); + } + else + { + fprintf(stderr, "ERROR line %lu: Unrecognized header line in state %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + + consumed = true; + break; + } + + /* Change state and fall through */ + + if (name == NULL || date == NULL) + { + fprintf(stderr, "ERROR line %lu: name or date not found in header. State %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + + state = STATE_ENDHEADER; + + case STATE_ENDHEADER: /* Finished the header looking for the start of the body */ + if (blank_line()) + { + consumed = true; + break; + } + + if (!isspace(g_line[0])) + { + fprintf(stderr, "ERROR line %lu: Unexpected in state %d\n", + g_lineno, state); + exit(EXIT_FAILURE); + } + + /* Change state and fall through */ + + state = STATE_BODY; + lastblank = false; + + case STATE_BODY: /* Parsing the body */ + if (blank_line()) + { + lastblank = true; + consumed = true; + break; + } + + if (isspace(g_line[0])) + { + char *endptr; + + /* Remove the newline from the end */ + + ptr = skip_space(ptr); + endptr = &ptr[strlen(ptr) - 1]; + trim_end(ptr, endptr); + + /* Was this paragraph preceded by a blank line? */ + + if (!merge) + { + if (lastblank) + { + putchar('\n'); + } + + if (firstline) + { + printf("\t* %s", ptr); + } + else + { + printf("\n\t %s", ptr); + } + } + + consumed = true; + lastblank = false; + firstline = false; + break; + } + + /* End of body. Add author, date, and final newline */ + + if (!merge) + { + printf(" From %s %s.\n", name, date); + } + + /* Revert to IDLE state */ + + free(name); + name = NULL; + date = NULL; + free(date); + state = STATE_IDLE; + firstline = true; + merge = false; + break; + + default: + fprintf(stderr, "ERROR line %lu: Bad state %d\n", g_lineno, state); + exit(EXIT_FAILURE); + } + } +} + +static void show_usage(const char *progname) +{ + fprintf(stderr, "USAGE: %s \n", progname); + exit(EXIT_FAILURE); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, char **argv, char **envp) +{ + FILE *stream; + + if (argc != 2) + { + fprintf(stderr, "Unexpected number of arguments\n"); + show_usage(argv[0]); + } + + stream = fopen(argv[1], "r"); + if (!stream) + { + fprintf(stderr, "open %s failed: %s\n", argv[1], strerror(errno)); + return EXIT_FAILURE; + } + + parse_file(stream); + fclose(stream); + return EXIT_SUCCESS; +} \ No newline at end of file