open Http_client.Convenience open OUnit (* A data structure to hold forecast information. *) type forecast = { date : string; skyCond : string; highTemp : string; lowTemp : string; pop : string; } (* A variable used to store the URL where the forecast is found. * * string *) let forecastURL = "http://www.wrh.noaa.gov/Saltlake/forecast/SFT.shtml" (* Get the raw text from the given url. * * string -> string *) let getPage url = http_get url (* Get the test data and put it into a string. * * string *) let getTestPage = let fileName = "testFcst.txt" in let fin = open_in fileName in let buf = Buffer.create 4096 in let rec loop() = let line = input_line fin in Buffer.add_string buf line; Buffer.add_char buf '\n'; loop() in try loop() with End_of_file -> Buffer.contents buf (* A helper function that takes a string and prints the string * followed by a space. * string -> unit *) let printL s = Printf.printf "[%s]" s (* Takes a string s and gets the nth line beginning from position p. * * int -> int -> string -> string *) let rec findLine n p s = let j = Str.search_forward (Str.regexp ".*") s p in match n with 1 -> Str.matched_string s | _ -> findLine (n-1) (Str.match_end()+1) s (* A function that takes the forecast web page and parses out the * useful information. * * string -> string *) let getPreData data = let startPrePos = Str.search_forward (Str.regexp "
") data 0 in
  let firstPre = Str.string_after data startPrePos in
  let endPrePos = Str.search_forward (Str.regexp "
") firstPre 0 in let preTagData = Str.string_before firstPre endPrePos in let startFcst = Str.search_forward (Str.regexp "FCST.*") preTagData 0 in let dayStr = Str.string_after preTagData (Str.match_end() + 1) in let endSLC = Str.search_forward (Str.regexp "POP [0-9]+.*") dayStr 0 in Str.string_before dayStr (Str.match_end()) (* Take the list of l/h temperatures and extract the high temps * * string list -> string list *) let getHighTempL tL = List.flatten (List.map (Str.split (Str.regexp "[0-9]*/")) tL) (* Take the list of l/h temperatures and extract the low temps * * string list -> string list *) let getLowTempL tL = let ftL = List.flatten (List.map (Str.split (Str.regexp "/[0-9]+")) tL) in if (List.length ftL) = 7 then ftL else "-" :: ftL (* Take the list of POP # and extract the #s * * string list -> string list *) let getPrecipNum pL = List.flatten (List.map (Str.split (Str.regexp "POP ")) pL) (* Given lists of the date, sky condition, high temp, low temp, and * chance of precipitation, a forecast list, and build a forecast list * * int -> string list -> string list -> string list -> string list -> * string list -> forecast list -> forecast list *) let rec createFcstL n dL cL htL ltL pL fL = match n with 6 -> let fcst = { date = List.nth dL 6; skyCond = List.nth cL 6; highTemp = List.nth htL 6; lowTemp = List.nth ltL 6; pop = List.nth pL 6 } in List.append fL (fcst :: []) | i -> let fcst = { date = List.nth dL i; skyCond = List.nth cL i; highTemp = List.nth htL i; lowTemp = List.nth ltL i; pop = List.nth pL i } in createFcstL (n+1) dL cL htL ltL pL (List.append fL (fcst :: [])) (* Parse the data generated by getPreData. * * string -> forecast list *) let parsePreData s = let dates = findLine 2 0 s in let datesL = Str.split (Str.regexp " +") dates in let cond = findLine 6 0 s in let condL = Str.split (Str.regexp " +") cond in let temp = findLine 7 0 s in let tempL = Str.split (Str.regexp " +") temp in let hTempL = getHighTempL tempL in let lTempL = getLowTempL tempL in let precip = findLine 8 0 s in let precipL = Str.split (Str.regexp " +") precip in let precipNumL = getPrecipNum precipL in createFcstL 0 datesL condL hTempL lTempL precipNumL [] (* Test the parsePreData function. * * 'a -> unit *) let testParsePreData _ = let p = getTestPage in let s = getPreData p in let dates = findLine 2 0 s in let datesL = Str.split (Str.regexp " +") dates in let cond = findLine 6 0 s in let condL = Str.split (Str.regexp " +") cond in let temp = findLine 7 0 s in let tempL = Str.split (Str.regexp " +") temp in let hTempL = getHighTempL tempL in let lTempL = getLowTempL tempL in let precip = findLine 8 0 s in let precipL = Str.split (Str.regexp " +") precip in let precipNumL = getPrecipNum precipL in assert_equal 7 (List.length datesL); assert_equal datesL ["FEB 11"; "FEB 12"; "FEB 13"; "FEB 14"; "FEB 15"; "FEB 16"; "FEB 17"]; assert_equal 7 (List.length condL); assert_equal condL ["FLRRYS"; "SUNNY"; "SUNNY"; "PTCLDY"; "PTCLDY"; "PTCLDY"; "SUNNY"]; assert_equal 7 (List.length tempL); assert_equal tempL ["/25"; "7/28"; "11/28"; "18/35"; "21/35"; "18/34"; "18/33"]; assert_equal 7 (List.length hTempL); assert_equal hTempL ["25"; "28"; "28"; "35"; "35"; "34"; "33"]; assert_equal 7 (List.length lTempL); assert_equal lTempL ["-"; "7"; "11"; "18"; "21"; "18"; "18"]; assert_equal 7 (List.length precipL); assert_equal precipL ["POP 10"; "POP 0"; "POP 0"; "POP 10"; "POP 0"; "POP 0"; "POP 0"]; assert_equal 7 (List.length precipNumL); assert_equal precipNumL ["10"; "0"; "0"; "10"; "0"; "0"; "0"] (* Output the forecast to a string. * * forecast -> string *) let printFcstOut f = Printf.sprintf "%s\t" f.date ^ Printf.sprintf "%s\t" f.skyCond ^ Printf.sprintf "%s\t" f.highTemp ^ Printf.sprintf "%s\t" f.lowTemp ^ Printf.sprintf "%s\n" f.pop (* Output the forecastList data structure to the given channel. * * forecast list -> out_channel -> unit *) let outputFcstL l chan = let time = Unix.localtime (Unix.time ()) in let { Unix.tm_mon = mon; Unix.tm_mday = mday; Unix.tm_year = year; Unix.tm_yday = yday; Unix.tm_hour = hour; Unix.tm_min = min} = time in Printf.fprintf chan "Collected: %02d-%02d-%d @ %d:%d\n" (mon+1) mday (year+1900) hour min; Printf.fprintf chan "Date\tWeather\tHigh\tLow\tProb. of Precip.\n"; Printf.fprintf chan "%s" (String.concat "" (List.map printFcstOut l)) (* Write the forecastList data structure to a file * * forecast list -> unit *) let fOutputFcstL l = let time = Unix.localtime (Unix.time ()) in let { Unix.tm_year = year; Unix.tm_yday = yday; } = time in let fileName = Printf.sprintf "%d-%d.fcst" yday (year+1900) in let fout = open_out fileName in Printf.printf "fileName: %s\n" fileName; outputFcstL l fout; close_out fout (* The main function for grabbing the forecast * * unit *) let genFcst = let p = getPage forecastURL in let p1 = getTestPage in let s = getPreData p1 in let preStr = getPreData p in let res = parsePreData preStr in Printf.printf ":%s:" s; outputFcstL res stdout; fOutputFcstL res (* Test the top level function. * * 'a -> unit *) let testGenFcst _ = let p = getTestPage in let preStr = getPreData p in let res = parsePreData preStr in assert_equal 7 (List.length res); assert_equal res [{date = "FEB 11"; skyCond = "FLRRYS"; highTemp = "25"; lowTemp = "-"; pop = "10"}; {date = "FEB 12"; skyCond = "SUNNY"; highTemp = "28"; lowTemp = "7"; pop = "0"}; {date = "FEB 13"; skyCond = "SUNNY"; highTemp = "28"; lowTemp = "11"; pop = "0"}; {date = "FEB 14"; skyCond = "PTCLDY"; highTemp = "35"; lowTemp = "18"; pop = "10"}; {date = "FEB 15"; skyCond = "PTCLDY"; highTemp = "35"; lowTemp = "21"; pop = "0"}; {date = "FEB 16"; skyCond = "PTCLDY"; highTemp = "34"; lowTemp = "18"; pop = "0"}; {date = "FEB 17"; skyCond = "SUNNY"; highTemp = "33"; lowTemp = "18"; pop = "0"} ] let suite = "Testing genFcst" >::: ["testGenFcst" >:: testGenFcst; "testParsePreData" >:: testParsePreData ] let _ = let verbose = ref false in let set_verbose _ = verbose := true in Arg.parse [("-verbose", Arg.Unit set_verbose, "Run the test in verbose mode.");] (fun x -> raise (Arg.Bad ("Bad argument : " ^ x))) ("usage: " ^ Sys.argv.(0) ^ " [-verbose]"); if not (was_successful (run_test_tt ~verbose:!verbose suite)) then exit 1