home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 8 Other / 08-Other.zip / nosaa107.zip / cvsgenlog.cmd < prev    next >
OS/2 REXX Batch file  |  1999-11-11  |  16KB  |  582 lines

  1. /*
  2.  *      CVSLOGGEN.CMD - V1.07 - NOSA Administrator - C.Langanke@TeamOS2.DE - 1999
  3.  *
  4.  *     Syntax: CVSLOGGEN.CMD [outputfile]
  5.  *
  6.  *     An up-to-date working directory is required before using this program !
  7.  *     Please call this program via: cvsenv [archivename] $GENLOG
  8.  */
  9. /* First comment is used as help text */
  10.  
  11.  SIGNAL ON HALT
  12.  
  13.  TitleLine = STRIP(SUBSTR(SourceLine(2), 3));
  14.  PARSE VAR TitleLine CmdName'.CMD 'Info
  15.  Title     = CmdName Info
  16.  
  17.  env          = 'OS2ENVIRONMENT';
  18.  TRUE         = (1 = 1);
  19.  FALSE        = (0 = 1);
  20.  Redirection  = '> NUL 2>&1';
  21.  CrLf         = "0d0a"x;
  22.  '@ECHO OFF'
  23.  
  24.  /* OS/2 errorcodes */
  25.  ERROR.NO_ERROR           =  0;
  26.  ERROR.INVALID_FUNCTION   =  1;
  27.  ERROR.FILE_NOT_FOUND     =  2;
  28.  ERROR.PATH_NOT_FOUND     =  3;
  29.  ERROR.ACCESS_DENIED      =  5;
  30.  ERROR.NOT_ENOUGH_MEMORY  =  8;
  31.  ERROR.INVALID_FORMAT     = 11;
  32.  ERROR.INVALID_DATA       = 13;
  33.  ERROR.NO_MORE_FILES      = 18;
  34.  ERROR.WRITE_FAULT        = 29;
  35.  ERROR.READ_FAULT         = 30;
  36.  ERROR.GEN_FAILURE        = 31;
  37.  ERROR.INVALID_PARAMETER  = 87;
  38.  ERROR.ENVVAR_NOT_FOUND   = 203;
  39.  
  40.  GlobalVars = 'Title CmdName env TRUE FALSE Redirection ERROR. CrLf';
  41.  SAY;
  42.  
  43.  /* show help */
  44.  ARG Parm .
  45.  IF (POS('?', Parm) > 0) THEN
  46.  DO
  47.     rc = ShowHelp();
  48.     EXIT(ERROR.INVALID_PARAMETER);
  49.  END;
  50.  
  51.  /* load RexxUtil */
  52.  CALL RxFuncAdd    'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs';
  53.  CALL SysLoadFuncs;
  54.  
  55.  /* Defaults */
  56.  GlobalVars = GlobalVars 'fCleanUp LineDelimiter TmpDir LogHeader Fullname.';
  57.  
  58.  rc            = ERROR.NO_ERROR;
  59.  fDebug        = FALSE;
  60.  fCleanup      = TRUE;
  61.  
  62.  
  63.  fNodeleteLog  = FALSE;
  64.  LineDelimiter = "01"x
  65.  LogHeader     = COPIES( '-', 77);
  66.  
  67.  TmpDir = VALUE('TMP',,env);
  68.  User   = VALUE('USER',,env);
  69.  CvsRoot = VALUE('CVSROOT',,env);
  70.  
  71.  DO UNTIL (TRUE)
  72.  
  73.     /* determine output file */
  74.     PARSE ARG ChangelogFile LogFile .;
  75.     IF (STRIP( ChangelogFile) = '') THEN
  76.        ChangelogFile = 'Changelog.txt';
  77.  
  78.     /* create temporary outfile */
  79.     TmpFile = SysTempFileName( TmpDir'\cvslog.???');
  80.     rc = LINEOUT( TmpFile);
  81.  
  82.     /* check parms */
  83.     IF (User = '') THEN
  84.     DO
  85.        SAY CmdName': error: environment variable USER not set.';
  86.        rc = ERROR.ENVVAR_NOT_FOUND;
  87.        LEAVE;
  88.     END;
  89.     IF (CvsRoot = '') THEN
  90.     DO
  91.        SAY CmdName': error: environment variable CVSROOT not set.';
  92.        rc = ERROR.ENVVAR_NOT_FOUND;
  93.        LEAVE;
  94.     END;
  95.  
  96.     /* determine root dir of archive */
  97.     SELECT
  98.        WHEN ( POS(':pserver:', CvsRoot) = 1) THEN
  99.           PARSE VAR CvsRoot 'pserver:'account':'CvsRoot;
  100.  
  101.        WHEN ( POS(':local:', CvsRoot) = 1) THEN
  102.           PARSE VAR CvsRoot ':local:'CvsRoot;
  103.  
  104.        OTHERWISE NOP;
  105.  
  106.     END;
  107.  
  108.     /* determine fullname list file */
  109.     /* and store it to a stem */
  110.     Fullname.  = '';
  111.     FullnamesList = GetCallDir()'\fullnames.lst';
  112.     IF (FileExist( FullnamesList)) THEN
  113.     DO
  114.        CALL CHAROUT, 'Reading mail address list ... ';
  115.        DO WHILE (LINES( FullnamesList) > 0)
  116.           ThisLine = LINEIN( FullnamesList);
  117.           IF (ThisLine = '') THEN ITERATE;
  118.           PARSE VAR ThisLine UserId UserFullName
  119.           Fullname.Userid = STRIP(UserFullName);
  120.        END;
  121.        rc = STREAM( FullnamesList, 'C', 'CLOSE');
  122.        SAY 'Ok.';
  123.     END;
  124.  
  125.     /* current datetime */
  126.     NowStamp = TRANSLATE( 'abcd-ef-gh', DATE('S'), 'abcdefgh')' 'TIME();
  127.     LogRange = 'from start to' NowStamp;
  128.  
  129.     /* log file given ? */
  130.     LogFile = STRIP( LogFile);
  131.     IF (LogFile \= '') THEN
  132.        fNodeleteLog = TRUE;
  133.     ELSE
  134.     DO
  135.        /* read last log date and time from existing changelog */
  136.        LogStamp = ReadLastChangeLogStamp( ChangeLogFile);
  137.        IF (LogStamp = '') THEN
  138.        DO
  139.           CvsRange  = '<' NowStamp;
  140.        END;
  141.        ELSE
  142.        DO
  143.           CvsRange = LogStamp '<' NowStamp;
  144.           LogRange = 'from' LogStamp 'to' NowStamp;
  145.        END;
  146.  
  147.        /* generate log */
  148.        CALL CHAROUT, 'Generating log output ... ';
  149.        LogFile = SysTempFileName( TmpDir'\cvslog.???');
  150.        'cvs -q log -d "'CvsRange'" .  >' LogFile
  151.        SAY 'Ok.';
  152.     END;
  153.  
  154.     /* read in log entries */
  155.     rc = ReadCvsLogEntries( LogFile, TmpFile);
  156.     IF (rc \= ERROR.NO_ERROR) THEN
  157.        LEAVE;
  158.  
  159.     /* write change log */
  160.     rc = WriteChangeLog( ChangeLogFile, TmpFile, User, LogRange, CvsRoot);
  161.     IF (rc \= ERROR.NO_ERROR) THEN
  162.        LEAVE;
  163.  
  164.  END;
  165.  
  166.  /* cleanup */
  167.  IF (fCleanup) THEN
  168.  DO
  169.     IF (\fNodeleteLog) THEN
  170.        rcx = SysFileDelete( LogFile);
  171.  END;
  172.  
  173.  
  174.  /* exit */
  175.  EXIT( rc);
  176.  
  177. /* ------------------------------------------------------------------------- */
  178. HALT:
  179.  SAY;
  180.  SAY 'Interrupted by user.';
  181.  EXIT(ERROR.GEN_FAILURE);
  182.  
  183. /* ------------------------------------------------------------------------- */
  184. ShowHelp: PROCEDURE EXPOSE (GlobalVars)
  185.  
  186.  SAY Title;
  187.  SAY;
  188.  
  189.  PARSE SOURCE . . ThisFile
  190.  
  191.  DO i = 1 TO 3
  192.     rc = LINEIN(ThisFile);
  193.  END;
  194.  
  195.  ThisLine = LINEIN(Thisfile);
  196.  DO WHILE (ThisLine \= ' */')
  197.     SAY SUBSTR(ThisLine, 7);
  198.     ThisLine = LINEIN(Thisfile);
  199.  END;
  200.  
  201.  rc = LINEOUT(Thisfile);
  202.  
  203.  RETURN('');
  204.  
  205. /* ------------------------------------------------------------------------- */
  206. FileExist: PROCEDURE
  207.  PARSE ARG FileName
  208.  
  209.  RETURN(STREAM(Filename, 'C', 'QUERY EXISTS') > '');
  210.  
  211. /* ------------------------------------------------------------------------- */
  212. LOWER: PROCEDURE
  213.  
  214.  Lower = 'abcdefghijklmnopqrstuvwxyzäöü';
  215.  Upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ';
  216.  
  217.  PARSE ARG String
  218.  RETURN(TRANSLATE(String, Lower, Upper));
  219.  
  220. /* ========================================================================= */
  221. ReadIniValue: PROCEDURE
  222. PARSE ARG IniFile, IniAppname, IniKeyName
  223.  
  224.  IniValue = SysIni(IniFile, IniAppname, IniKeyName);
  225.  IF (IniValue = 'ERROR:') THEN
  226.     IniValue = '';
  227.  
  228.  IF ((IniValue \= '') & (RIGHT(IniValue, 1) = "00"x)) THEN
  229.     IniValue = LEFT( IniValue, LENGTH( IniValue) - 1);
  230.  
  231.  RETURN( IniValue);
  232.  
  233. /* ------------------------------------------------------------------------- */
  234. GetCalldir: PROCEDURE
  235. PARSE SOURCE . . CallName
  236.  CallDir = FILESPEC('Drive', CallName)||FILESPEC('Path', CallName);
  237.  RETURN(LEFT(CallDir, LENGTH(CallDir) - 1));
  238.  
  239. /* ------------------------------------------------------------------------- */
  240. DirExist: PROCEDURE
  241.  PARSE ARG Dirname
  242.  
  243.  IF (Dirname = '') THEN
  244.     RETURN(0);
  245.  
  246.  /* use 'QUERY EXISTS' with root dirs */
  247.  IF (RIGHT(DirName, 2) = ':\') THEN
  248.    RETURN(STREAM(Dirname, 'C', 'QUERY EXISTS') \= '');
  249.  
  250.  /* query all others */
  251.  IF ((STREAM(Dirname, 'C', 'QUERY EXISTS') = '') &,
  252.      (STREAM(Dirname, 'C', 'QUERY DATETIME') \= '')) THEN
  253.     RETURN(1);
  254.  ELSE
  255.     RETURN(0);
  256.  
  257.  
  258. /* ========================================================================= */
  259. GetFullName:  PROCEDURE EXPOSE (GlobalVars)
  260.  PARSE ARG UserId;
  261.  
  262.  IF (Fullname.UserId \= '') THEN
  263.     RETURN Fullname.UserId;
  264.  ELSE
  265.     RETURN(UserId);
  266.  
  267. /* ========================================================================= */
  268. ReadLastChangeLogStamp:  PROCEDURE EXPOSE (GlobalVars)
  269.  PARSE ARG ChangeLogFile;
  270.  
  271.  LogStamp      = '';
  272.  RangeTitle    = 'log range: ';
  273.  RangeTitleLen = LENGTH( RangeTitle);
  274.  
  275.  DO UNTIL (TRUE)
  276.     /* Check file */
  277.     IF (\FileExist( ChangeLogFile)) THEN
  278.        LEAVE;
  279.  
  280.     ThisLine  = LINEIN( ChangeLogFile);
  281.     IF (LEFT( ThisLine, 20) \= COPIES('-', 20)) THEN
  282.        LEAVE;
  283.  
  284.     ThisLine  = LINEIN( ChangeLogFile);
  285.     IF (LEFT( ThisLine, RangeTitleLen) \= RangeTitle) THEN
  286.        LEAVE;
  287.  
  288.     PARSE VAR ThisLine . 'to' LogStamp;
  289.  
  290.  END;
  291.  
  292.  rc = STREAM( ChangeLogFile, 'C', 'CLOSE');
  293.  RETURN( LogStamp);
  294.  
  295.  
  296. /* ========================================================================= */
  297. ReadCvsLogEntries: PROCEDURE EXPOSE (GlobalVars)
  298.  PARSE ARG Logfile, TmpFile;
  299.  
  300.  rc        = ERROR.NO_ERROR;
  301.  LineCount = 0;
  302.  
  303.  FileFooter     = COPIES( '=', 77);
  304.  RevisionHeader = COPIES( '-', 28);
  305.  
  306.  FilenameTitle    = 'RCS file: ';
  307.  FilenameTitleLen = LENGTH( FilenameTitle);
  308.  
  309.  fLogFound         = FALSE;
  310.  fReadRevisions    = FALSE;
  311.  fSkipFile         = FALSE;
  312.  
  313.  DO UNTIL (TRUE)
  314.  
  315.     /* Check file */
  316.     IF (\FileExist( Logfile)) THEN
  317.     DO
  318.        SAY CmdName': error: cvs log output could not be found.';
  319.        rc = ERROR.FILE_NOT_FOUND;
  320.        LEAVE;
  321.     END;
  322.  
  323.     CALL CHAROUT, 'Reading log output ... ';
  324.     /* read entries */
  325.     DO WHILE (LINES( LogFile) > 0)
  326.  
  327.        /* read line */
  328.        ThisLine = LINEIN( LogFile);
  329.        IF (ThisLine = '') THEN ITERATE;
  330.  
  331.        fLogFound = TRUE;
  332.  
  333.        /* is it end of file section ? */
  334.        IF (ThisLine = FileFooter) THEN
  335.        DO
  336.           /* write out current info to tmp file */
  337.           IF ((\fSkipFile) & (fReadRevisions)) THEN
  338.              rcx = LINEOUT( TmpFile, '');
  339.           fReadRevisions = FALSE;
  340.           fSkipFile      = FALSE;
  341.           ITERATE;
  342.        END;
  343.  
  344.        /* file to be skipped ? */
  345.        IF (fSkipFile) THEN ITERATE;
  346.  
  347.        /* get working filename and initialize revision list */
  348.        IF ( LEFT( ThisLine, FilenameTitleLen) = FilenameTitle)THEN
  349.        DO
  350.           Filename = DELSTR( ThisLine, 1, FilenameTitleLen);
  351.           Filename = DELSTR( FileName, LENGTH( Filename) - 1);
  352.           IF ( POS( '/CVSROOT/', Filename) > 0) THEN
  353.              fSkipFile  = TRUE;
  354.           ITERATE;
  355.        END;
  356.  
  357.        /* read revision entries */
  358.        IF (ThisLine = RevisionHeader) THEN
  359.        DO
  360.           IF (fReadRevisions) THEN
  361.              rcx = LINEOUT( TmpFile, '');
  362.  
  363.           fReadRevisions         = TRUE;
  364.  
  365.           ThisRevision = LINEIN( LogFile);
  366.           PARSE VAR ThisRevision 'revision' ThisRevision;
  367.  
  368.           ThisDate =  LINEIN( LogFile);
  369.           PARSE VAR ThisDate 'date:' ThisDate ThisTime';' 'author:' ThisAuthor';'
  370.  
  371.           rcx = CHAROUT( TmpFile, Filename STRIP(ThisRevision) STRIP( ThisDate) STRIP(ThisTime) STRIP( ThisAuthor)' ');
  372.           ITERATE;
  373.        END;
  374.  
  375.        /* read revision log text */
  376.        IF (fReadRevisions) THEN
  377.        DO
  378.           LineCount = LineCount + 1;
  379.           rcx = CHAROUT( TmpFile, LineDelimiter''ThisLine);
  380.        END;
  381.  
  382.     END;
  383.     rcx = STREAM( TmpFile, 'C', 'CLOSE');
  384.     rcx = STREAM( LogFile, 'C', 'CLOSE');
  385.     SAY LineCount 'entries -  Ok.';
  386.  
  387.  END;
  388.  
  389.  IF (\fLogFound) THEN
  390.     rc = ERROR.FILE_NOT_FOUND;
  391.  
  392.  RETURN( rc);
  393.  
  394. /* ========================================================================= */
  395. WriteChangeLog: PROCEDURE EXPOSE (GlobalVars)
  396.  PARSE ARG ChangeLogfile, TmpFile, User, LogRange, CvsRoot;
  397.  
  398.  rc = ERROR.NO_ERROR;
  399.  fRevisionsFound = FALSE;
  400.  WrapCol         = 70;
  401.  
  402.  Indent = ' ';
  403.  NewFile  = SysTempFileName( TmpDir'\cvsnew.???');
  404.  SortFile = SysTempFileName( TmpDir'\cvssort.???');
  405.  CopyFile = SysTempFileName( TmpDir'\cvscopy.???');
  406.  
  407.  /* sort the temporary file */
  408.  'gsort.bin +2 -4r +4 +0 <' TmpFile '>' SortFile;
  409.  
  410.  CALL CHAROUT, 'Generating changelog ... ';
  411.  
  412.  /* write new log entries: header */
  413.  rcx = SysFileDelete( NewFile);
  414.  rcx = LINEOUT( NewFile, LogHeader);
  415.  IF (LogRange \= '') THEN
  416.     rcx = LINEOUT( NewFile, 'log range:' LogRange);
  417.  rcx = LINEOUT( NewFile, 'generated by:' GetFullName( User));
  418.  rcx = LINEOUT( NewFile, '');
  419.  rcx = LINEOUT( NewFile, '');
  420.  
  421.  /* preread one line */
  422.  ThisRevision = ''
  423.  DO WHILE ((LINES(SortFile) > 0) & (ThisRevision = ''))
  424.     ThisLine = LINEIN( SortFile);
  425.     PARSE VAR ThisLine ThisFile ThisRevision ThisDate ThisTime ThisInfo
  426.     ThisFile = DELSTR( ThisFile, 1, LENGTH( CvsRoot) + 1);
  427.  END;
  428.  
  429.  DO WHILE (LINES(SortFile) > 0)
  430.  
  431.     /* reset file list */
  432.     FileList     = '';
  433.     EndTime    = ThisTime;
  434.     NextRevision = '';
  435.     NetxtDate    = '          ';
  436.     /* preread following line */
  437.     DO WHILE ((LINES(SortFile) > 0) & (NextRevision = ''))
  438.        NextLine = LINEIN( SortFile);
  439.        PARSE VAR NextLine NextFile NextRevision NextDate NextTime NextInfo
  440.     END;
  441.     /* read all lines of same log info */
  442.     DO WHILE ((NextRevision \= '') & (ThisInfo = NextInfo))
  443.        FileList = FileList ThisFile;
  444.        ThisFile = DELSTR( NextFile, 1, LENGTH( CvsRoot) + 1);
  445.  
  446.        /* preread as much as needed */
  447.        IF (LINES(SortFile) = 0) THEN LEAVE;
  448.        NextLine = LINEIN( SortFile);
  449.        StartTime  = NextTime;
  450.        PARSE VAR NextLine NextFile NextRevision NextDate NextTime NextInfo
  451.     END;
  452.  
  453.     PARSE VAR ThisInfo ThisAuthor ThisLog;
  454.     ThisInfo   = NextInfo;
  455.     ThisTime   = NextTime;
  456.     ThisAuthor = GetFullName( ThisAuthor);
  457.     IF (WORDPOS( ThisFile, FileList) = 0) THEN
  458.        FileList   = ThisFile FileList;
  459.  
  460.     /* check out start and end time */
  461. /*
  462.     IF (StartTime = EndTime) THEN
  463.        TimeRange =  StartTime;
  464.     ELSE
  465.        TimeRange =  StartTime '-' EndTime;
  466. */
  467.     TimeRange =  StartTime;
  468.  
  469.     /* write out revision information */
  470.     fRevisionsFound = TRUE;
  471.     rcx = LINEOUT( NewFile, '' NextDate TimeRange',' ThisAuthor);
  472.  
  473.     rcx = WriteFileList( NewFile, WrapCol, '          ', ' file(s):' STRIP(Filelist));
  474.     rcx = LINEOUT( NewFile, COPIES( ' - ', 23));
  475.     rcx = WriteLogComment( NewFile, WrapCol, ' ', ThisLog);
  476.  
  477.     rcx = LINEOUT( NewFile, '');
  478.     rcx = LINEOUT( NewFile, '');
  479.  END;
  480.  
  481.  rc = STREAM( SortFile, 'C', 'CLOSE');
  482.  rc = STREAM( NewFile, 'C', 'CLOSE');
  483.  
  484.  IF (fRevisionsFound) THEN
  485.  DO
  486.     IF (FileExist( ChangeLogfile)) THEN
  487.     DO
  488.        /* append the old file to the new one */
  489.        'copy' ChangeLogfile CopyFile Redirection;
  490.        'copy' NewFile '+' CopyFile ChangeLogfile Redirection;
  491.        rcx = SysFileDelete( CopyFile);
  492.     END;
  493.     ELSE
  494.        /* create the output file */
  495.        'copy' NewFile ChangeLogfile Redirection;
  496.  END;
  497.  
  498.  /* cleanup */
  499.  IF (fCleanup) THEN
  500.  DO
  501.     rcx = SysFileDelete( NewFile);
  502.     rcx = SysFileDelete( SortFile);
  503.     rcx = SysFileDelete( TmpFile);
  504.  END;
  505.  
  506.  SAY 'Ok.';
  507.  
  508.  IF (\fRevisionsFound) THEN
  509.     SAY 'No revisions found in log range.' ChangeLogfile 'unchanged.';
  510.  
  511.  RETURN(rc);
  512.  
  513. /* ========================================================================= */
  514. WriteFileList: PROCEDURE EXPOSE (GlobalVars)
  515.  PARSE ARG File, WrapCol, Indent, Filelist;
  516.  
  517.  DO UNTIL (TRUE)
  518.  
  519.     /* output of one row only */
  520.     DO WHILE (LENGTH( FileList) >= WrapCol)
  521.        /* check where to split the line */
  522.        SplitPos = LASTPOS( ' ', FileList, WrapCol);
  523.        IF (SplitPos = 0) THEN
  524.           SplitPos = POS( ' ', FileList, WrapCol);
  525.        IF (SplitPos = 0) THEN
  526.           LEAVE;
  527.  
  528.        /* split it and write the line */
  529.        ThisLine  = LEFT( FileList, SplitPos - 1);
  530.        FileList  = Indent''STRIP(SUBSTR( FileList, SplitPos + 1));
  531.        rcx = LINEOUT( File, ThisLine);
  532.     END;
  533.  
  534.     /* write only/last line */
  535.     IF (STRIP( FileList) \= '') THEN
  536.        rcx = LINEOUT( File, FileList);
  537.  
  538.  END;
  539.  
  540.  RETURN(0);
  541.  
  542. /* ========================================================================= */
  543. WriteLogComment: PROCEDURE EXPOSE (GlobalVars)
  544.  PARSE ARG File, WrapCol, Indent, Log;
  545.  
  546.  /* write out formatted change comment */
  547.  DO WHILE (Log \= '')
  548.     DelimiterPos = POS( LineDelimiter, Log);
  549.     IF (DelimiterPos > 0) THEN
  550.     DO
  551.        ThisPart = SUBSTR( Log, 1, DelimiterPos - 1);
  552.        Log = SUBSTR( Log, DelimiterPos + 1);
  553.     END;
  554.     ELSE
  555.     DO
  556.        ThisPart = Log;
  557.        Log  = '';
  558.     END;
  559.     IF (ThisPart \= '') THEN
  560.     DO
  561.        DO WHILE (LENGTH( ThisPart) >= WrapCol)
  562.           /* check where to split the line */
  563.           SplitPos = LASTPOS( ' ', ThisPart, WrapCol);
  564.           IF (SplitPos = 0) THEN
  565.              SplitPos = POS( ' ', ThisPart, WrapCol);
  566.           IF (SplitPos = 0) THEN
  567.              LEAVE;
  568.  
  569.           /* split it and write the line */
  570.           ThisLine  = LEFT( ThisPart, SplitPos - 1);
  571.           ThisPart  = STRIP(SUBSTR( ThisPart, SplitPos + 1));
  572.           rcx = LINEOUT( File, Indent Indent ThisLine);
  573.        END;
  574.  
  575.        /* write only/last line */
  576.        IF (STRIP( ThisPart) \= '') THEN
  577.           rcx = LINEOUT( File, Indent Indent ThisPart);
  578.     END;
  579.  END;
  580.  RETURN(0);
  581.  
  582.