home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 18 REXX / 18-REXX.zip / CRONRF.ZIP / cronrgf.cmd next >
OS/2 REXX Batch file  |  1992-06-10  |  25KB  |  788 lines

  1. /*
  2. program: cronrgf.cmd
  3. type:    REXXSAA-OS/2, OS/2 2.0
  4. purpose: Unix-like cron; allow to repeatedly execute commands at given date/time
  5.          defined in a control file
  6. version: 1.0
  7. date:    1992-06-10
  8.  
  9. author:  Rony G. Flatscher
  10.          RONY@AWIWUW11.BITNET
  11.          rony@wu-wien.ac.at
  12.  
  13. needs:   DATERGF.CMD, some RxFuncts (loaded automatically)
  14.  
  15. usage:   CRONRGF [/Test] cronfile
  16.  
  17.     
  18.     Reads file 'cronfile' and executes command[s] repeatedly according to it.
  19.     'cronfile' has to be formatted like in Unix; unlike the Unix-version a '%' is
  20.     treated like any other character; empty lines and ones starting with
  21.     a semi-colon (;) are ignored.
  22.    
  23.     If switch '/Test' is given, the file is read and the user is presented
  24.     with the date/times and commands to be executed upon them.
  25.    
  26.     example:  CRONRGF /TEST testcron
  27.               execute statements in file 'testcron' in testmode
  28.  
  29.     example for a control-file:
  30.  
  31.         ; Sample file for CRONRGF.CMD
  32.         ;
  33.         ; This is a comment (starts with a semicolumn)
  34.         ; empty lines are ignored too...
  35.         
  36.         
  37.         ; LAYOUT OF THE CRON-FILE:
  38.         ;    * * * * * command
  39.         ; or
  40.         ;    minute hour day month weekday command
  41.         ; where minute  ranges from 0-59,
  42.         ;       hour    ranges from 0-23,
  43.         ;       day     ranges from 1-31,
  44.         ;       month   ranges from 1-12,
  45.         ;       weekday ranges from 1-7 (1 = Monday, 2 = Tuesday, ..., 7 = Sunday)
  46.         ;
  47.         ; you can give a list of values, separated by a comma (,), e.g. "1,3,7"
  48.         ; you can give a range of values, separated by a dash (-), e.g. "1-5"
  49.         ; you can give a star (*) instead of a value, meaning entire range of all valid values
  50.         ;
  51.         ; the given command is only executed when all criteriae are fullfilled !
  52.         ;
  53.         ; restriction: unlike to Unix, the percent-sign (%) is treated like any other character
  54.         ;
  55.         
  56.         ; the following command "@ECHO HI, I am Nr. 1 to be echoed every minute" would be
  57.         ; executed every minute
  58.         *  *  *  *  *  @ECHO Hi, I am Nr. 1 to be echoed every minute & pause
  59.         
  60.         59 23 31 12 5 command, one minute before year's end, and only if the last day is a Friday
  61.         
  62.         ; comment: every year at 17:45 on June 7th:
  63.         45 17  7  6  *  dir c:\*.exe
  64.         
  65.         ; comment: on every full quarter of an hour 
  66.         ;          at midnight, 6 in the morning, noon, 6 in the evening
  67.         ;          on the 1st, 15th and 31st of 
  68.         ;          every month on
  69.         ;          weekdays only
  70.         0,15,30,45   0,6,12,18   1,15,31   *   *   backup c:\*.* d:\ /s
  71.         
  72.         ; at noon on every day, if it is a weekday:
  73.         0 12 * * 1-5 XCOPY Q:\* D:\ /s
  74.         
  75.         ; every minute in January, March, May, July, September and November:
  76.         *  *  *  1,3,5,7,9,11  *  dir c:\*.cmd
  77.         
  78.         ; at the last day of the year at 23:01, 23:02, 23:03, 23:05, 23:20, 23:21,
  79.         ; 23:22, 23:23, 23:24, 23:25, 23:30, 23:31, 23:32, 23:33, 23:34, 23:35,
  80.         ; 23:59
  81.         1,2,3,5,20-25,30-35,59   23   31   12   *   COPY D:\*.log E:\backup
  82.         
  83.         ; make backups of OS2.INI and OS2SYS.INI on every first monday of a month,
  84.         ; at 9 o'clock in the morning
  85.         0 9 1-7 * 1 showini /bt d:\os2\os2.ini
  86.         0 9 1-7 * 1 showini /bt d:\os2\os2sys.ini
  87.         
  88.         ; at midnight on every month:
  89.         0 0 1 * * tapebackup /all
  90.         
  91.         ; execute every minute, no restrictions:
  92.         *  *  *  *  *  @ECHO Hi, I am Nr. 2 to be echoed every minute & pause
  93.         
  94.         ; execute every minute in January, February, March only !
  95.         * *  *  1,2,3  *  any-command any-arguments
  96.         
  97.         ; execute every day at midnight
  98.         0 0 * * * any-command any-arguments
  99.         
  100.         ; execute every wednesday at midnigth !
  101.         0 0 * * 3 any-command any-arguments
  102.         
  103.         ; this is a comment which concludes the sample file ===========================
  104.  
  105. All rights reserved, copyrighted 1992, no guarantee that it works without
  106. errors, etc. etc.
  107.  
  108. donated to the public domain granted that you are not charging anything (money
  109. etc.) for it and derivates based upon it, as you did not write it,
  110. etc. if that holds you may bundle it with commercial programs too
  111.  
  112. you may freely distribute this program, granted that no changes are made
  113. to it
  114.  
  115. Please, if you find an error, post me a message describing it, I will
  116. try to fix and rerelease it to the net.
  117.  
  118. */
  119. SIGNAL ON HALT
  120.  
  121. global. = ""            /* default for global */
  122. global.eTestmode = "0"  /* default: no testmode */
  123.  
  124. stemSchedule. = ""      /* default for empty array elements */
  125. iSchedule = 0           /* schedule counter */
  126.  
  127. IF ARG() = 0 THEN SIGNAL usage
  128.  
  129. PARSE ARG "/"switch filein      /* get filename */
  130.  
  131. IF switch <> "" THEN
  132. DO
  133.    IF TRANSLATE(SUBSTR(switch, 1, 1)) = "T" THEN global.eTestmode = "1"
  134.    ELSE 
  135.    DO
  136.       SAY "CRONRGF: unknown switch: [/" || switch ||"]."
  137.       SAY
  138.       CALL BEEP 2500, 100
  139.       SIGNAL usage              /* wrong switch */
  140.    END
  141. END
  142. ELSE                            /* no switch given */
  143.    PARSE ARG filein
  144.  
  145. filein = STRIP(filein)          /* get rid of leading and trailing spaces */
  146. global.eFilein = STREAM(filein, "C", "QUERY EXISTS")
  147.  
  148. IF global.eFilein = "" THEN
  149. DO
  150.    SAY "CRONRGF: file [" || filein || "] does not exist."
  151.    EXIT
  152. END
  153.  
  154. /* check whether RxFuncs are loaded, if not, load them */
  155. IF RxFuncQuery('SysLoadFuncs') THEN
  156. DO
  157.     /* load the load-function */
  158.     CALL RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'       
  159.  
  160.     /* load the Sys* utilities */
  161.     CALL SysLoadFuncs                                                 
  162. END
  163.  
  164.  
  165. line_no = 1             /* line-number */
  166. DO WHILE LINES(filein) > 0
  167.    line = LINEIN(filein)
  168.  
  169.    /* no empty lines and no comments */
  170.    IF line = "" | SUBSTR(STRIP(line), 1, 1) = ";" THEN ITERATE
  171.  
  172.    SAY "parsing [" || line || "]"
  173.  
  174.    IF check_it_out(line, line_no) > 0 THEN
  175.    DO
  176.       iSchedule = iSchedule + 1
  177.       global.original.line_no = line
  178.       stemSchedule.iSchedule =  DATE("S") TIME() "NOK" line_no  /* NOK = not o.k., get next date/time */
  179.       line_no = line_no + 1
  180.    END
  181. END
  182. SAY
  183.  
  184. CALL STREAM filein, "C", "CLOSE"        /* make sure file is closed */
  185.  
  186. global.0 = iSchedule                    /* set number of array elements */
  187. stemSchedule.0 = iSchedule              /* set number of array elements */
  188.  
  189. IF iSchedule > 0 THEN
  190.    CALL dispatch                                /* start dispatching */
  191.  
  192. SAY "CRONRGF: Nothing to schedule. Program ended."
  193.  
  194. EXIT
  195.  
  196.  
  197.  
  198.  
  199. /*
  200.         FOREVER-LOOP
  201. */
  202. DISPATCH: PROCEDURE EXPOSE global. stemSchedule.
  203.    DO FOREVER
  204.      SAY RIGHT("", 79, "=")
  205.      SAY
  206.      SAY "Processing commands given in:"                /* show user which file is being used */
  207.      SAY
  208.      SAY "  [" || global.eFilein || "]"
  209.      SAY
  210.  
  211.      CALL schedule_next
  212.      /* show user which command(s) will be executed when */
  213.      PARSE VAR stemSchedule.1 1 next_date_time 18       /* get next date/time */
  214.  
  215.      SAY "command[s] being scheduled on:" next_date_time
  216.      SAY
  217.  
  218.      DO i = 1 TO stemSchedule.0
  219.         PARSE VAR stemSchedule.i 1 tmp_date_time 18 status index
  220.  
  221.         IF next_date_time <> tmp_date_time THEN LEAVE
  222.         IF status = "OK" THEN
  223.         DO
  224.            SAY "  [" || VALUE("global." || index || ".eCommand.eValues") || "]"
  225.         END
  226.      END
  227.      SAY
  228.  
  229.      /* get actual DATE/TIME */
  230.      act_date_time = DATE("S") TIME()
  231.      difference = DATERGF(next_date_time, "-S", act_date_time)
  232.  
  233.      IF difference > 0 THEN seconds_to_sleep = DATERGF(difference, "SEC") % 1
  234.                        ELSE seconds_to_sleep = 0
  235.  
  236.       IF global.eTestmode THEN
  237.       DO
  238.          SAY RIGHT("", 79, "=")
  239.          SAY "Testmode (dispatch):    next_invocation =" next_date_time
  240.          SAY "Testmode (dispatch):   actual date/time =" act_date_time
  241.          SAY "Testmode (dispatch): difference in days =" difference "=" seconds_to_sleep "seconds"
  242.          SAY RIGHT("", 79, "=")
  243.  
  244.          SAY "Testmode (dispatch):"
  245.          SAY "   input:"
  246.          DO i = 1 TO global.0
  247.             SAY "     [" || RIGHT(i, LENGTH(global.0)) ||"]" global.original.i
  248.          END
  249.          SAY RIGHT("", 79, "=")
  250.  
  251.          SAY "   schedule list:"
  252.          DO i = 1 TO stemSchedule.0
  253.             PARSE VAR stemSchedule.i . . . gIndex 
  254.             SAY "    " stemSchedule.i "[" || VALUE("global." || gIndex || ".eCommand.eValues") || "]"
  255.          END 
  256.          SAY RIGHT("", 79, "=")
  257.  
  258.          SAY 'Press any key to continue, "q" to quit.'
  259.          CALL BEEP 500, 100
  260.          IF TRANSLATE(SysGetKey("NOECHO")) = "Q" THEN SIGNAL halt
  261.       END
  262.       ELSE
  263.       DO
  264.          /* 
  265.             take care of DosSleep()-unsigned long; to be safe just sleep a maximum 
  266.             of 31 days (= 31 * 24 * 60 * 60  == 2.678.400 seconds) at a time
  267.          */
  268.          thirtyone = 2678400
  269.          DO WHILE seconds_to_sleep > 0
  270.             IF seconds_to_sleep > thirtyone THEN        /* more than 31 days to sleep */
  271.             DO
  272.                sleeping         = thirtyone             /* sleep for 31 days */
  273.                seconds_to_sleep = seconds_to_sleep - thirtyone
  274.             END
  275.             ELSE
  276.             DO
  277.                sleeping         = seconds_to_sleep
  278.                seconds_to_sleep = 0
  279.             END
  280.  
  281.             CALL SysSleep sleeping                      /* sleep */
  282.          END
  283.    
  284.          DO i = 1 TO stemSchedule.0
  285.            PARSE VAR stemSchedule.i 1 date_time 18 status index
  286.    
  287.            IF date_time > next_date_time THEN
  288.               LEAVE
  289.  
  290.            IF status = "OK" THEN
  291.            DO
  292.               /* 
  293.                  start an own minimized session which closes automatically after the
  294.                  command was executed:
  295.               */
  296.               commandString = VALUE('global.' || index || '.eCommand.eValues')
  297.               title = '"CRONRGF:' date_time STRIP(commandString) '"'
  298.               ADDRESS CMD "@START" title '/C /WIN /MIN /B "' || commandString || '"'
  299.            END
  300.          END
  301.       END
  302.  
  303.       /* change the status of the executed programs to "NOK" */
  304.       DO i = 1 TO stemSchedule.0
  305.          PARSE VAR stemSchedule.i 1 date_time 18 status index
  306.  
  307.          IF date_time > next_date_time THEN
  308.             LEAVE
  309.  
  310.          stemSchedule.i = date_time "NOK" index
  311.       END
  312.    END
  313.  
  314.    RETURN
  315.  
  316.  
  317.  
  318. /*
  319.     calculate the schedule times, sort them in ascending order
  320. */
  321. SCHEDULE_NEXT: PROCEDURE EXPOSE global. stemSchedule.
  322.    /* 
  323.       as long as no viable date/time to schedule was found, iterate
  324.    */
  325.    main_run = 0
  326.    DO WHILE WORD(stemSchedule.1, 3) <> "OK"
  327.       main_run = main_run + 1                           /* count loops until a valid day was found for any of the commands */
  328.  
  329.       IF global.eTestmode THEN
  330.          SAY "Testmode (scheduling): main loop =" main_run
  331.  
  332.       IF main_run > 50 THEN
  333.       DO
  334.          SAY "CRONRGF: aborting after 2500 attempts to produce a valid date!"
  335.          EXIT -1
  336.       END
  337.  
  338.       DO i = 1 TO stemSchedule.0
  339.          PARSE VAR stemSchedule.i 1 year 5 month 7 day 9 10 hour 12 13 minute 15 18 disp_status glob_index
  340.    
  341.          IF disp_status = "OK" THEN ITERATE             /* not yed scheduled */
  342.          old_year  = year
  343.          old_month = month
  344.          old_day   = day
  345.          old_hour  = hour
  346.    
  347.          /* defaults */
  348.          first_minute = WORD(global.glob_index.eMinute.eValues, 1)
  349.          first_hour   = WORD(global.glob_index.eHOur.eValues, 1)
  350.          first_day    = WORD(global.glob_index.eDay.eValues, 1)
  351.          first_month  = WORD(global.glob_index.eMonth.eValues, 1)
  352.  
  353.  
  354.          /* minute */
  355.          DO j = 1 TO global.glob_index.eMinute.0
  356.             tmp = WORD(global.glob_index.eMinute.eValues, j)
  357.             IF tmp > minute THEN LEAVE
  358.          END
  359.    
  360.          IF j > global.glob_index.eMinute.0 THEN        /* minutes to wrap around */
  361.             hour   = hour + 1
  362.          ELSE                                           /* minutes within same hour */
  363.             minute = tmp
  364.    
  365.          /* hour */
  366.          DO j = 1 TO global.glob_index.eHour.0
  367.             tmp = WORD(global.glob_index.eHour.eValues, j)
  368.             IF tmp >= hour THEN LEAVE
  369.          END
  370.    
  371.          IF j > global.glob_index.eHour.0 THEN          /* hours to wrap around */
  372.             day    = day + 1
  373.          ELSE                                           /* hours within same day */
  374.             hour   = tmp
  375.     
  376.          ok = "NOK"                                     /* default: no date found yet */
  377.          run = 0
  378.          DO 50                                          /* try 50 times to produce a valid date */
  379.             run = run + 1
  380.             /* day */
  381.             DO j = 1 TO global.glob_index.eDay.0
  382.                tmp = WORD(global.glob_index.eDay.eValues, j)
  383.                IF tmp >= day THEN LEAVE
  384.             END
  385.       
  386.             IF j > global.glob_index.eDay.0 THEN        /* days to wrap around */
  387.             DO
  388.                day    = first_day
  389.                month  = month + 1
  390.             END
  391.             ELSE                                        /* days within same month */
  392.                day    = tmp
  393.       
  394.       
  395.             /* month */
  396.             DO j = 1 TO global.glob_index.eMonth.0
  397.                tmp = WORD(global.glob_index.eMonth.eValues, j)
  398.                IF tmp >= month THEN LEAVE
  399.             END
  400.       
  401.             IF j > global.glob_index.eMonth.0 THEN      /* months to wrap around */
  402.             DO
  403.                day    = first_day
  404.                month  = first_month
  405.                year   = year + 1
  406.             END
  407.             ELSE                                        /* months within same year */
  408.             DO
  409.                IF month <> tmp THEN                     /* did the month change ? */
  410.                   day = first_day
  411.  
  412.                month  = tmp
  413.             END
  414.       
  415.  
  416.             SELECT
  417.                WHEN old_year < year | old_month < month | old_day < day THEN 
  418.                     next_invocation = year || month || day first_hour || ":" || first_minute || ":00"
  419.                WHEN old_hour < hour THEN
  420.                     next_invocation = year || month || day       hour || ":" || first_minute || ":00"
  421.                OTHERWISE
  422.                     next_invocation = year || month || day       hour || ":" ||       minute || ":00"
  423.             END  
  424.  
  425.             IF global.eTestmode THEN
  426.                SAY "Testmode (scheduling): next_invocation =" next_invocation DATERGF(next_invocation, "DN")
  427.  
  428.             /* check whether day-of-week is o.k. */
  429.             IF DATERGF(next_invocation, "M") = "" THEN  /* illegal date produced, e.g. 19950231 ? */
  430.             DO
  431.                day   = first_day
  432.                month = month + 1
  433.                IF month > 12 THEN
  434.                DO
  435.                  month = first_month
  436.                  year  = year + 1
  437.                END
  438.                ITERATE
  439.             END
  440.  
  441.  
  442.             next_Weekday = DATERGF(next_invocation, "DI")  /* get weekday */
  443.       
  444.             /* using POS because weekdays are in the form of 01, 02, ..., 07 */
  445.             IF POS(next_Weekday, global.glob_index.eWeekday.eValues) = 0 THEN      /* invalid weekday ? */
  446.             DO
  447.                next_invocation = DATERGF(next_invocation, "+", "1")     /* add one day to present date */
  448.                PARSE VAR next_invocation 1 year 5 month 7 day 9
  449.  
  450.                ITERATE
  451.             END
  452.  
  453.             ok = " OK"                                  /* o.k. to invoke, because valid date */
  454.             LEAVE
  455.          END 
  456.  
  457.          IF global.eTestmode THEN
  458.          DO
  459.             SAY "Testmode (scheduling): date/time-loop =" run "time[s]"
  460.             SAY
  461.          END
  462.  
  463.    
  464.          /*
  465.             format in schedule list:
  466.             DATE TIME STATUS INDEX-INTO-GLOBAL-ARRAY
  467.          */
  468.          stemSchedule.i = next_invocation ok glob_index
  469.       END
  470.    
  471.       CALL sort_schedule_list
  472.    END
  473.  
  474.    RETURN
  475.  
  476. /*
  477.     sort the schedule list in ascending order
  478. */
  479. SORT_SCHEDULE_LIST: PROCEDURE EXPOSE stemSchedule.
  480.    length = 21          /* length of SUBSTR to compare, includes status */
  481.    /* define M for passes */
  482.    M = 1
  483.    DO WHILE (9 * M + 4) < stemSchedule.0
  484.       M = M * 3 + 1
  485.    END
  486.  
  487.    /* sort stem */
  488.    DO WHILE M > 0
  489.       K = stemSchedule.0 - M
  490.       DO J = 1 TO K
  491.          Q = J
  492.          DO WHILE Q > 0
  493.             L = Q + M
  494.             IF SUBSTR(stemSchedule.Q, 1, length) <= SUBSTR(stemSchedule.L, 1, length) THEN LEAVE
  495.             /* switch elements */
  496.             tmp            = stemSchedule.Q
  497.             stemSchedule.Q = stemSchedule.L
  498.             stemSchedule.L = tmp
  499.             Q = Q - M
  500.          END
  501.       END
  502.       M = M % 3
  503.    END
  504.  
  505.    RETURN
  506.  
  507.  
  508.  
  509.  
  510.  
  511. /*
  512.    analyze
  513.  
  514. */
  515.  
  516. CHECK_IT_OUT: PROCEDURE EXPOSE global.
  517.  
  518. to_parse = ARG(1)
  519. line_no  = ARG(2)
  520.  
  521. PARSE VAR to_parse sMinute sHour sDay sMonth sWeekday sCommand
  522.  
  523. line_no = setup_minutes(sMinute, line_no)       /* setup minute-values */
  524.  
  525. IF line_no <> 0 THEN                            /* setup hour-values */
  526.    line_no = setup_hours(sHour, line_no)
  527.  
  528. IF line_no <> 0 THEN                            /* setup day-values */
  529.    line_no = setup_days(sDay, line_no)
  530.  
  531. IF line_no <> 0 THEN                            /* setup month-values */
  532.    line_no = setup_months(sMonth, line_no)
  533.  
  534. IF line_no <> 0 THEN                            /* setup weekday-values */
  535.    line_no = setup_weekdays(sWeekday, line_no)
  536.  
  537. IF line_no <> 0 THEN                            /* setup command-values */
  538. DO
  539.    global.line_no.eCommand.0       = 1
  540.    global.line_no.eCommand.eValues = sCommand
  541. END
  542.  
  543. RETURN line_no
  544.  
  545. /*
  546.         parse and setup minutes
  547.         ARG(1) - minute string
  548.         ARG(2) - index into global array
  549. */
  550. SETUP_MINUTES: PROCEDURE EXPOSE global.
  551.    sMinute = ARG(1)
  552.    iIndex = ARG(2)
  553.    default.0 = 60
  554.    default.values = "00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19",
  555.                     "20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39",
  556.                     "40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59"
  557.  
  558.    string = parse_it("eMinute", sMinute, iIndex, 0, 59, default.0, default.values)
  559.  
  560.    IF string <> "" THEN
  561.    DO
  562.       SAY "CRONRGF: error in minute-format [" || string || "]"
  563.       SAY
  564.       RETURN 0
  565.    END
  566.  
  567.    RETURN iIndex
  568.  
  569.  
  570.  
  571. /*
  572.         parse and setup hours
  573.         ARG(1) - hour string
  574.         ARG(2) - index into global array
  575. */
  576. SETUP_HOURS: PROCEDURE EXPOSE global.
  577.    sHour = ARG(1)
  578.    iIndex = ARG(2)
  579.    default.0 = 23
  580.    default.values = "00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19",
  581.                     "20 21 22 23"
  582.  
  583.    string = parse_it("eHour", sHour, iIndex, 0, 23, default.0, default.values)
  584.  
  585.    IF string <> "" THEN
  586.    DO
  587.       SAY "CRONRGF: error in hour-format [" || string || "]"
  588.       SAY
  589.       RETURN 0
  590.    END
  591.  
  592.    RETURN iIndex
  593.  
  594.  
  595.  
  596.  
  597. /*
  598.         parse and setup days
  599.         ARG(1) - day string
  600.         ARG(2) - index into global array
  601. */
  602. SETUP_DAYS: PROCEDURE EXPOSE global.
  603.    sDay = ARG(1)
  604.    iIndex = ARG(2)
  605.    default.0 = 31
  606.    default.values = "   01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19",
  607.                     "20 21 22 23 24 25 26 27 28 29 30 31"
  608.  
  609.    string = parse_it("eDay", sDay, iIndex, 1, 31, default.0, default.values)
  610.  
  611.    IF string <> "" THEN
  612.    DO
  613.       SAY "CRONRGF: error in day-format [" || string || "]"
  614.       SAY
  615.       RETURN 0
  616.    END
  617.  
  618.    RETURN iIndex
  619.  
  620.  
  621.  
  622.  
  623. /*
  624.         parse and setup months
  625.         ARG(1) - month string
  626.         ARG(2) - index into global array
  627. */
  628. SETUP_MONTHS: PROCEDURE EXPOSE global.
  629.    sMonth = ARG(1)
  630.    iIndex = ARG(2)
  631.    default.0 = 12
  632.    default.values = "01 02 03 04 05 06 07 08 09 10 11 12"
  633.  
  634.    string = parse_it("eMonth", sMonth, iIndex, 1, 12, default.0, default.values)
  635.  
  636.    IF string <> "" THEN
  637.    DO
  638.       SAY "CRONRGF: error in month-format [" || string || "]"
  639.       SAY
  640.       RETURN 0
  641.    END
  642.  
  643.    RETURN iIndex
  644.  
  645.  
  646.  
  647.  
  648. /*
  649.         parse and setup weekdays
  650.         ARG(1) - weekday string
  651.         ARG(2) - index into global array
  652. */
  653. SETUP_WEEKDAYS: PROCEDURE EXPOSE global.
  654.    sWeekday = ARG(1)
  655.    iIndex = ARG(2)
  656.    default.0 = 7
  657.    default.values = "01 02 03 04 05 06 07"
  658.  
  659.    string = parse_it("eWeekday", sWeekday, iIndex, 1, 7, default.0, default.values)
  660.  
  661.    IF string <> "" THEN
  662.    DO
  663.       SAY "CRONRGF: error in weekday-format [" || string || "]"
  664.       SAY
  665.       RETURN 0
  666.    END
  667.  
  668.    RETURN iIndex
  669.  
  670.  
  671.  
  672.  
  673.  
  674.  
  675.  
  676. /*
  677.         parse values, list, setup array
  678.         ARG(1) = eName ("element name" in array)
  679.         ARG(2) = string containing numbers
  680.         ARG(3) = index into global array
  681.         ARG(4) = lower bound (inclusive)
  682.         ARG(5) = upper bound (inclusive)
  683.         ARG(6) = default number of elements
  684.         ARG(7) = default values
  685. */
  686.  
  687. PARSE_IT: PROCEDURE EXPOSE global.
  688.    eName       = ARG(1)
  689.    sValues        = ARG(2)
  690.    iIndex         = ARG(3)
  691.    lower          = ARG(4)
  692.    upper          = ARG(5)
  693.    default.0      = ARG(6)
  694.    default.values = ARG(7)
  695.  
  696.  
  697.    tmp = "global.iIndex." || eName || "."            /* build string of array-e-Name */
  698.    lastValue = 0
  699.  
  700.    IF sValues = "*" THEN                /* build all legal values */
  701.    DO
  702.       INTERPRET(tmp || "0 =" default.0)
  703.       INTERPRET(tmp || "eValues =" default.values)
  704.       RETURN ""
  705.    END
  706.  
  707.    INTERPRET(tmp || "0 = 0")                            /* set number of elements to 0 */
  708.    INTERPRET(tmp || 'eValues = ""')                     /* delete values */
  709.  
  710.    DO WHILE sValues <> ""
  711.       IF POS(",", sValues) > 0 THEN                     /* list of values ? */
  712.         PARSE VAR sValues tmpValue "," sValues
  713.       ELSE
  714.       DO
  715.         tmpValue = sValues                              
  716.         sValues = ""
  717.       END
  718.  
  719.       IF POS("-", tmpValue) > 0 THEN                    /* range of values ? */
  720.       DO
  721.          PARSE VAR tmpValue start "-" end
  722.       END
  723.       ELSE                                              /* single value */
  724.       DO
  725.          start = tmpValue
  726.          end   = tmpValue
  727.       END
  728.  
  729.  
  730.  
  731.       /* error in values ? */
  732.       IF start < lastValue | start < lower | start > end | ,
  733.          end > upper | ,
  734.          \DATATYPE(start, "N") | \DATATYPE(end, "N") THEN
  735.       DO
  736.          INTERPRET(tmp || '0  = ""')                    /* delete number of array elements */
  737.          INTERPRET(tmp || 'eValues = ""')               /* delete values */
  738.          SELECT
  739.             WHEN \DATATYPE(start, "N") THEN err_msg = '"' || start || '"' "is not numeric" '(part in error: "' || tmpValue || '")' 
  740.             WHEN \DATATYPE(end,   "N") THEN err_msg = '"' || end   || '"' "is not numeric" '(part in error: "' || tmpValue || '")' 
  741.             WHEN start < lastValue     THEN err_msg = start "<" lastValue "= lower bound"  '(part in error: "' || tmpValue || '")'
  742.             WHEN start < lower         THEN err_msg = start "<" lower "= lower bound"      '(part in error: "' || tmpValue || '")'
  743.             WHEN start > end           THEN err_msg = start ">" end                        '(part in error: "' || tmpValue || '")'
  744.             WHEN end   > upper         THEN err_msg = end   ">" upper "= upper bound"      '(part in error: "' || tmpValue || '")'
  745.             OTHERWISE NOP
  746.          END  
  747.  
  748.          RETURN err_msg
  749.       END
  750.  
  751.       /* build values */
  752.       DO i = start TO end
  753.          INTERPRET(tmp || "0 = " || tmp || "0 + 1")     /* increase counter for number of elements */
  754.          INTERPRET(tmp || "eValues = " || tmp || "eValues" RIGHT(i, 2, "0"))   /* add the next value */
  755.       END
  756.  
  757.       lastValue = i
  758.    END 
  759.  
  760.    RETURN ""
  761.  
  762.  
  763.  
  764.  
  765. USAGE:
  766.    SAY "CRONRGF.CMD - Unix-like cron; executes commands in file repeatedly."
  767.    SAY
  768.    SAY "usage:"
  769.    SAY
  770.    SAY "      CRONRGF [/Test] cronfile"
  771.    SAY 
  772.    SAY "Reads file 'cronfile' and executes command[s] repeatedly according to it."
  773.    SAY "'cronfile' has to be formatted like in Unix; unlike the Unix-version a '%' is"
  774.    SAY "treated like any other character; empty lines and ones starting with"
  775.    SAY "a semi-colon (;) are ignored."
  776.    SAY
  777.    SAY "If switch '/Test' is given, the file is read and the user is presented"
  778.    SAY "with the date/times and commands to be executed upon them."
  779.    SAY
  780.    SAY "example:  CRONRGF /TEST crontest"
  781.    SAY "          execute statements in file 'crontest' in testmode"
  782.    EXIT
  783.  
  784. HALT:
  785.    CALL STREAM filein, "C", "CLOSE"             /* make sure, file is closed */
  786.    SAY "CRONRGF: User interrupted program."
  787.    EXIT -1
  788.