/
search_with_timeout_no_leak_simple.ml
85 lines (81 loc) · 2.78 KB
/
search_with_timeout_no_leak_simple.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
open Core.Std
open Async.Std
(* Generate a DuckDuckGo search URI from a query string *)
let query_uri ~server query =
let base_uri =
Uri.of_string (String.concat ["http://";server;"/?format=json"])
in
Uri.add_query_param base_uri ("q", [query])
(* Extract the "Definition" or "Abstract" field from the DuckDuckGo results *)
let get_definition_from_json json =
match Yojson.Safe.from_string json with
| `Assoc kv_list ->
let find key =
begin match List.Assoc.find kv_list key with
| None | Some (`String "") -> None
| Some s -> Some (Yojson.Safe.to_string s)
end
in
begin match find "Abstract" with
| Some _ as x -> x
| None -> find "Definition"
end
| _ -> None
(* part 1 *)
(* Execute the DuckDuckGo search *)
let get_definition ~server ~interrupt word =
try_with (fun () ->
Cohttp_async.Client.get ~interrupt (query_uri ~server word)
>>= fun (_, body) ->
Pipe.to_list body
>>| fun strings ->
(word, get_definition_from_json (String.concat strings)))
>>| function
| Ok (word,result) -> (word, Ok result)
| Error exn -> (word, Error exn)
(* part 2 *)
let get_definition_with_timeout ~server ~timeout word =
get_definition ~server ~interrupt:(after timeout) word
>>| fun (word,result) ->
let result' = match result with
| Ok _ as x -> x
| Error _ -> Error "Unexpected failure"
in
(word,result')
(* part 3 *)
(* Print out a word/definition pair *)
let print_result (word,definition) =
printf "%s\n%s\n\n%s\n\n"
word
(String.init (String.length word) ~f:(fun _ -> '-'))
(match definition with
| Error _ -> "DuckDuckGo query failed unexpectedly"
| Ok None -> "No definition found"
| Ok (Some def) ->
String.concat ~sep:"\n"
(Wrapper.wrap (Wrapper.make 70) def))
(* Run many searches in parallel, printing out the results after they're all
done. *)
let search_and_print ~servers ~timeout words =
let servers = Array.of_list servers in
Deferred.all (List.mapi words ~f:(fun i word ->
let server = servers.(i mod Array.length servers) in
get_definition_with_timeout ~server ~timeout word))
>>| fun results ->
List.iter results ~f:print_result
let () =
Command.async_basic
~summary:"Retrieve definitions from duckduckgo search engine"
Command.Spec.(
let string_list = Arg_type.create (String.split ~on:',') in
empty
+> anon (sequence ("word" %: string))
+> flag "-servers"
(optional_with_default ["api.duckduckgo.com"] string_list)
~doc:" Specify server to connect to"
+> flag "-timeout" (optional_with_default (sec 5.) time_span)
~doc:" Abandon queries that take longer than this time"
)
(fun words servers timeout () ->
search_and_print ~servers ~timeout words)
|> Command.run