package dune-release

  1. Overview
  2. Docs

Source file pkg.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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
(*---------------------------------------------------------------------------
   Copyright (c) 2016 Daniel C. Bünzli. All rights reserved.
   Distributed under the ISC license, see terms at the end of the file.
   dune-release 0.3.0
  ---------------------------------------------------------------------------*)

open Bos_setup

(* Misc *)

let uri_sld uri = match Text.split_uri uri with
| None -> None
| Some (_, host, _) ->
    match List.rev (String.cuts ~sep:"." host) with
    | _ :: snd :: _ -> Some snd
    | _ -> None

let uri_append u s = match String.head ~rev:true u with
| None -> s
| Some '/' -> strf "%s%s" u s
| Some _ -> strf "%s/%s" u s

let chop_ext u = match String.cut ~rev:true ~sep:"." u with
| None -> u
| Some (u, _) -> u

let chop_git_prefix u = match String.cut ~sep:"git+" u with
| Some ("", uri) -> uri
| _ -> u

(* Package *)

type t =
  { name : string;
    version : string option;
    delegate: Cmd.t option;
    build_dir : Fpath.t option;
    drop_v: bool;
    opam : Fpath.t option;
    opam_descr : Fpath.t option;
    opam_fields : (string list String.map, R.msg) result Lazy.t;
    readmes : Fpath.t list option;
    change_logs : Fpath.t list option;
    licenses : Fpath.t list option;
    distrib: Distrib.t;
    distrib_uri : string option;
    distrib_file : Fpath.t option;
    lint_files: Fpath.t list option;
    publish_msg : string option;
    publish_artefacts : [`Distrib | `Doc | `Alt of string] list option }

let opam_fields p = Lazy.force p.opam_fields
let opam_field p f = opam_fields p >>| fun fields -> String.Map.find f fields
let opam_field_hd p f = opam_field p f >>| function
  | None | Some [] -> None
  | Some (v :: _) -> Some v

let opam_homepage p = opam_field_hd p "homepage"
let opam_doc p = opam_field_hd p "doc"
let opam_homepage_sld p = opam_homepage p >>| function
  | None -> None
  | Some uri -> match uri_sld uri with None -> None | Some sld -> Some (uri, sld)
let opam_doc_sld p = opam_doc p >>| function
  | None -> None
  | Some uri -> match uri_sld uri with None -> None | Some sld -> Some (uri, sld)

let name p = Ok p.name

let version p = match p.version with
| Some v -> Ok v
| None -> Vcs.get () >>= fun r -> Vcs.describe ~dirty:false r

let delegate p =
  let not_found = function
  | None ->
    R.error_msg
      "Package delegate command cannot be found (no homepage or doc field). \
       Try `dune-release help delegate` for more information."
  | Some cmd ->
    R.error_msgf
      "%a: package delegate cannot be found. \
       Try `dune-release help delegate` for more information."
      Cmd.pp cmd
  in
  match p.delegate with
  | Some cmd -> Ok (Some cmd)
  | None ->
      let delegate =
        match OS.Env.(value "DUNE_RELEASE_DELEGATE" (some string) ~absent:None) with
        | Some cmd -> Some cmd
        | None     -> None
      in
      let guess_delegate () =
        match delegate with
        | Some d -> Ok d
        | None   ->
            let cmd sld = strf "%s-dune-release-delegate" sld in
            (* first look at `doc:` then `homepage:` *)
            opam_doc_sld p >>= function
            | Some (_, sld) -> Ok (cmd sld)
            | None -> opam_homepage_sld p >>= function
              | Some (_, sld) -> Ok (cmd sld)
              | None -> not_found None
      in
      guess_delegate () >>= fun cmd ->
      let x = Cmd.v cmd in
      OS.Cmd.exists x >>= function
      | true -> Ok (Some x)
      | false ->
          if cmd <> "github-dune-release-delegate"
          then not_found (Some x)
          else Ok None

let build_dir p = match p.build_dir with
| Some b -> Ok b
| None   -> Ok (Fpath.v "_build")

let readmes p = match p.readmes with
| Some f -> Ok f
| None  ->  Ok [Fpath.v "README.md"]

let readme p = readmes p >>= function
  | [] -> R.error_msgf "No readme file specified in the package description"
  | r :: _ -> Ok r

let opam p = match p.opam with
| Some f -> Ok f
| None   -> name p >>| fun name -> Fpath.v (name ^ ".opam")

let opam_descr p =
  let descr_file_for_opam opam =
    if Fpath.has_ext ".opam" opam then Fpath.(rem_ext opam + ".descr") else
    Fpath.(parent opam / "descr")
  in
  let read f = OS.File.read f >>= fun c -> Opam.Descr.of_string c in
  match p.opam_descr with
  | Some f -> read f
  | None ->
      opam p
      >>= fun opam -> Ok (descr_file_for_opam opam)
      >>= fun descr_file -> OS.File.exists descr_file
      >>= function
      | true ->
          Logs.info (fun m -> m "Found opam descr file %a" Fpath.pp descr_file);
          read descr_file
      | false ->
          readme p
          >>= fun readme ->
          Logs.info
            (fun m -> m "Extracting opam descr from %a" Fpath.pp readme);
          Opam.Descr.of_readme_file readme

let change_logs p = match p.change_logs with
| Some f -> Ok f
| None   -> Ok [Fpath.v "CHANGES.md"]

let change_log p = change_logs p >>= function
  | [] -> R.error_msgf "No change log specified in the package description."
  | l :: _ -> Ok l

let licenses p = match p.licenses with
| Some f -> Ok f
| None   -> Ok [Fpath.v "LICENSE.md"]

let drop_initial_v version = match String.head version with
| Some ('v' | 'V') -> String.with_index_range ~first:1 version
| None | Some _ -> version

let dev_repo p =
  opam_field_hd p "dev-repo" >>= function
  | None   -> Ok None
  | Some r ->
      let uri = chop_git_prefix r in
      match String.cut ~sep:"https://github.com/" uri with
      | Some ("", path) -> Ok (Some ("git@github.com:" ^ path))
      | _ -> Ok (Some uri)

let distrib_uri ?(raw = false) p =
  let subst_uri p uri =
    uri
    >>= fun uri -> name p
    >>= fun name -> version p
    >>= fun vers -> (if p.drop_v then Ok (drop_initial_v vers) else Ok vers)
    >>= fun version_num ->
    let defs = String.Map.(empty
                           |> add "NAME" name |> add "VERSION" vers
                           |> add "VERSION_NUM" version_num)
    in
    Pat.of_string uri >>| fun pat -> Pat.format defs pat
  in
  let not_found () =
    R.error_msg "no distribution URI found, see dune-release's API documentation."
  in
  let uri = match p.distrib_uri with
  | Some u -> Ok u
  | None ->
      opam_homepage_sld p >>= function
      | None -> not_found ()
      | Some (uri, sld) ->
          if sld <> "github"
          then (Ok (uri_append uri "releases/$(NAME)-$(VERSION_NUM).tbz"))
          else
          opam_field_hd p "dev-repo">>= function
          | None -> not_found ()
          | Some dev_repo ->
              Ok (uri_append (chop_git_prefix (chop_ext dev_repo))
                    "releases/download/$(VERSION)/$(NAME)-$(VERSION_NUM).tbz")
  in
  if raw then uri else subst_uri p uri

let distrib_filename ?(opam = false) p =
  let sep = if opam then '.' else '-' in
  name p
  >>= fun name -> version p
  >>= fun vers -> (if p.drop_v then Ok (drop_initial_v vers) else Ok vers)
  >>= fun version_num -> Fpath.of_string (strf "%s%c%s" name sep version_num)

let distrib_archive_path p =
  build_dir p
  >>= fun build_dir -> distrib_filename ~opam:false p
  >>| fun b -> Fpath.(build_dir // b + ".tbz")

let distrib_file ~dry_run p = match p.distrib_file with
| Some f -> Ok f
| None ->
    (distrib_archive_path p
     >>= fun f -> Sos.file_must_exist ~dry_run f)
    |> R.reword_error_msg
      (fun _ -> R.msgf "Did you forget to call 'dune-release distrib' ?")


let distrib_user_and_repo p =
  distrib_uri p >>= fun uri ->
  let uri_error uri =
    R.msgf "Could not derive user and repo from opam dev-repo \
            field value %a; expected the pattern \
            $SCHEME://$HOST/$USER/$REPO[.$EXT][/$DIR]" String.dump uri
  in
  match Text.split_uri ~rel:true uri with
  | None -> Error (uri_error uri)
  | Some (_, _, path) ->
      if path = "" then Error (uri_error uri) else
      match String.cut ~sep:"/" path with
      | None -> Error (uri_error uri)
      | Some (user, path) ->
          let repo = match String.cut ~sep:"/" path with
          | None -> path
          | Some (repo, _) -> repo
          in
          begin
            Fpath.of_string repo
            >>= fun repo -> Ok (user, Fpath.(to_string @@ rem_ext repo))
          end
          |> R.reword_error_msg (fun _ -> uri_error uri)

let doc_uri p = opam_field_hd p "doc" >>| function
  | None     -> ""
  | Some uri -> uri

let doc_user_repo_and_path p =
  doc_uri p >>= fun uri ->
  (* Parses the $PATH of $SCHEME://$HOST/$REPO/$PATH *)
  let uri_error uri =
    R.msgf "Could not derive publication directory $PATH from opam doc \
            field value %a; expected the pattern \
            $SCHEME://$USER.github.io/$REPO/$PATH" String.dump uri
  in
  match Text.split_uri ~rel:true uri with
  | None -> Error (uri_error uri)
  | Some (_, host, path) ->
      if path = "" then Error (uri_error uri) else
      (match String.cut ~sep:"." host with
      | Some (user, g) when String.equal g "github.io" -> Ok user
      | _ -> Error (uri_error uri))
      >>= fun user ->
      match String.cut ~sep:"/" path with
      | None -> Error (uri_error uri)
      | Some (repo, "") -> Ok (user, repo, Fpath.v ".")
      | Some (repo, path) ->
          (Fpath.of_string path >>| fun p -> user, repo, Fpath.rem_empty_seg p)
          |> R.reword_error_msg (fun _ -> uri_error uri)

let publish_msg p = match p.publish_msg with
| Some msg -> Ok msg
| None ->
    change_log p
    >>= fun change_log -> Text.change_log_file_last_entry change_log
    >>= fun (_, (_, txt)) -> Ok (strf "CHANGES:\n\n%s\n" (String.trim txt))

let publish_artefacts p = match p.publish_artefacts with
| Some arts -> Ok arts
| None -> Ok [`Doc; `Distrib]

let infer_name () =
  let opam_files =
    Sys.readdir "."
    |> Array.to_list
    |> List.filter (String.is_suffix ~affix:".opam")
  in
  if opam_files = [] then begin
    Logs.err (fun m -> m "no <package>.opam files found.");
    exit 1
  end;
  let package_names =
    let suffix_len = String.length ".opam" in
    List.map (fun s ->
        String.with_range s ~len:(String.length s - suffix_len)
      ) opam_files
  in
  let name =
    let shortest =
      match package_names with
      | [] -> assert false
      | first :: rest ->
          List.fold_left (fun acc s ->
              if String.length s < String.length acc
              then s
              else acc
            ) first rest
    in
    if List.for_all (String.is_prefix ~affix:shortest) package_names
    then shortest
    else begin
      let err () =
        Logs.err (fun m ->
            m "cannot determine name automatically (names are %a).\n\
               Use `-p <name>`"
              Fmt.(list ~sep:(unit ",@ ") string) package_names);
        exit 1
      in
      (* look at the README title to infer the name ... *)
      if Sys.file_exists "README.md" then begin
        let ic = open_in "README.md" in
        let title = input_line ic in
        close_in ic;
        let title =
          String.trim ~drop:(function '#'|' ' -> true | _ -> false) title
        in
        match
          List.filter (fun affix -> String.is_prefix ~affix title) package_names
        with
        | [name] -> name
        | _ -> err ()

      end else err ()
    end
  in
  name

let v ~dry_run
    ?name ?version ?delegate ?(drop_v=true) ?build_dir ?opam:opam_file ?opam_descr
    ?readme ?change_log ?license ?distrib_uri ?distrib_file ?publish_msg
    ?publish_artefacts ?(distrib=Distrib.v ()) ?(lint_files = Some []) ()
  =
  let name = match name with None -> infer_name () | Some v -> v in
  let readmes = match readme with Some r -> Some [r] | None -> None in
  let change_logs = match change_log with Some c -> Some [c] | None -> None in
  let licenses = match license with Some l -> Some [l] | None -> None in
  let rec opam_fields = lazy (opam p >>= fun o -> Opam.File.fields ~dry_run o)
  and p =
    { name; version; delegate; drop_v; build_dir; opam = opam_file; opam_descr;
      opam_fields; readmes; change_logs; licenses; distrib_uri; distrib_file;
      publish_msg; publish_artefacts; distrib; lint_files }
  in
  p

(* Distrib *)

let distrib_version_opam_files ~dry_run p ~version =
  let version = if p.drop_v then drop_initial_v version else version in
  opam p
  >>= fun file -> OS.File.read file
  >>= fun o -> Ok (Fmt.strf "version: \"%s\"\n%s" version o)
  >>= fun o -> Sos.write_file ~dry_run file o

let distrib_prepare ~dry_run p ~dist_build_dir ~name ~version ~opam =
  let d = p.distrib in
  let ws = Distrib.watermarks d in
  let ws_defs = Distrib.define_watermarks ws ~dry_run ~name ~version ~opam in
  Sos.with_dir ~dry_run dist_build_dir (fun () ->
      Distrib.files_to_watermark d ()
      >>= fun files -> Distrib.watermark_files ws_defs files
      >>= fun () -> distrib_version_opam_files ~dry_run p ~version
      >>= fun () -> Distrib.massage d ()
      >>= fun () -> Distrib.exclude_paths d ()
    ) ()
  |> R.join

let distrib_archive ~dry_run p ~keep_dir =
  Archive.ensure_bzip2 ()
  >>= fun () -> name p
  >>= fun name -> build_dir p
  >>= fun build_dir -> version p
  >>= fun version -> opam p
  >>= fun opam -> distrib_filename p
  >>= fun root -> Ok Fpath.(build_dir // root + ".build")
  >>= fun dist_build_dir -> Sos.delete_dir ~dry_run ~force:true dist_build_dir
  >>= fun () -> Vcs.get ()
  >>= fun repo -> Vcs.commit_id repo ~dirty:false
  >>= fun head -> Vcs.commit_ptime_s repo ~commit_ish:head
  >>= fun mtime -> Vcs.clone ~dry_run ~force:true repo ~dir:dist_build_dir
  >>= fun () -> Vcs.get ~dir:dist_build_dir ()
  >>= fun clone -> Ok (Fmt.strf "dune-release-dist-%s" head)
  >>= fun branch -> Vcs.checkout ~dry_run clone ~branch ~commit_ish:head
  >>= fun () -> distrib_prepare ~dry_run p ~dist_build_dir ~name ~version ~opam
  >>= fun exclude_paths ->
  let exclude_paths = Fpath.Set.of_list exclude_paths in
  Archive.tar dist_build_dir ~exclude_paths ~root ~mtime
  >>= fun tar -> distrib_archive_path p
  >>= fun archive -> Archive.bzip2 ~dry_run ~force:true ~dst:archive tar
  >>= fun () ->
  (if keep_dir then Ok () else Sos.delete_dir ~dry_run dist_build_dir)
  >>= fun () -> Ok archive

(* Test & build *)

type f =
  dry_run:bool ->
  dir:Fpath.t ->
  args:Cmd.t ->
  out:(OS.Cmd.run_out -> (string * OS.Cmd.run_status, Sos.error) result) ->
  t -> (string * OS.Cmd.run_status, Sos.error) result

let run ~dry_run ~dir ~args ~out ~default p cmd =
  let name = p.name in
  let cmd = Cmd.(v "jbuilder" % cmd % "-p" % name %% args) in
  let run () = Sos.run_out ~dry_run cmd ~default out in
  R.join @@ Sos.with_dir ~dry_run dir run ()

let test ~dry_run ~dir ~args ~out p =
  run ~dry_run ~dir ~args ~out ~default:(Sos.out "") p "runtest"

let build ~dry_run ~dir ~args ~out p =
  run ~dry_run ~dir ~args ~out ~default:(Sos.out "") p "build"

let clean ~dry_run ~dir ~args ~out p =
  run ~dry_run ~dir ~args ~out ~default:(Sos.out "") p "clean"

(* Lint *)

let pp_path = Text.Pp.path
let pp_status = Text.Pp.status

let lint_disabled test =
  Logs.info (fun m -> m ~header:"LINT" "Package@ disabled@ %a." Fmt.text test);
  0

let std_files p =
  let v = function Some x -> x | None -> [] in
  v p.readmes @ v p.licenses @
  v p.change_logs @ match p.opam with Some v -> [v] | None -> []

let lint_files p = match p.lint_files with
| None (* disabled *) -> None
| Some fs -> Some (List.rev_append (std_files p) fs)

let lint_std_files ~dry_run p =
  let lint_exists file errs =
    let report exists =
      let status, errs = if exists then `Ok, errs else `Fail, errs + 1 in
      Logs.app (fun m ->
          m "%a @[File %a@ is@ present.@]" pp_status status pp_path file);
      errs
    in
    (Sos.file_exists ~dry_run file >>= fun exists -> Ok (report exists))
    |> Logs.on_error_msg ~use:(fun () -> errs + 1)
  in
  begin
    match lint_files p with
    | None -> Ok (lint_disabled "standard files linting")
    | Some files ->
        let files = Fpath.Set.of_list files in
        Ok (Fpath.Set.fold lint_exists files 0)
  end
  |> Logs.on_error_msg ~use:(fun () -> 1)

let lint_file_with_cmd ~dry_run file_kind ~cmd file errs handle_exit =
  let run_linter cmd file ~exists =
    if not (exists || dry_run) then
      Ok (`Fail (strf "%a: No such file" Fpath.pp file))
    else
    Sos.run_out ~dry_run ~err:OS.Cmd.err_run_out Cmd.(cmd % p file)
      ~default:(Sos.out "") OS.Cmd.out_string
    >>| fun (out, status) -> handle_exit (snd status) out
  in
  begin
    OS.File.exists file
    >>= fun exists -> run_linter cmd file ~exists
    >>| function
    | `Ok ->
        Logs.app
          (fun m -> m "%a @[lint@ %s %a.@]"
              pp_status `Ok file_kind pp_path file);
        errs
    | `Fail msgs ->
        Logs.app
          (fun m -> m "%a @[<v>@[lint@ %s %a:@]@,@[%a messages:@]@,%a@]"
              pp_status `Fail file_kind pp_path file Cmd.pp cmd Fmt.lines msgs);
        errs + 1
  end
  |> Logs.on_error_msg ~use:(fun () -> errs + 1)

let lint_opams ~dry_run p =
  let tool_version = Lazy.force Opam.version in
  let lint opam_version =
    let args = match opam_version, Lazy.force Opam.version with
    | Some ["1.2"], `v2 -> Cmd.v "--warn=-21-32-48"
    | _ -> Cmd.empty
    in
    opam p >>= fun opam ->
    (* We first run opam lint with -s and if there's something beyond 5
       we rerun it without it for the error messages. It's ugly since 5
       will still but opam lint's cli is broken. *)
    let cmd = Cmd.(Opam.cmd % "lint" %% args) in
    let handle_exit file status out = match status, out with
    | `Exited 0,
      ("" | "5" (* dirname version vs opam file version *)) -> `Ok
    | _ ->
        let err = OS.Cmd.err_run_out in
        let cmd = Cmd.(cmd % p file)  in
        let default = Sos.out "" in
        match Sos.run_out ~dry_run ~err cmd ~default OS.Cmd.out_string with
        | Ok (out, _     ) -> `Fail out
        | Error (`Msg out) -> `Fail out
    in
    let cmd = Cmd.(cmd % "-s") in
    let d =
      lint_file_with_cmd ~dry_run "opam file" ~cmd opam 0 (handle_exit opam)
    in
    (* lint fields *)
    if dry_run then Ok 0
    else (
      doc_user_repo_and_path p >>= fun _ ->
      distrib_user_and_repo p >>| fun _ ->
      d
    )
  in
  Logs.on_error_msg ~use:(fun () -> 1) (
    (* remove opam.1.2-related warnings *)
    opam_field p "opam-version" >>= fun opam_version ->
    match opam_version, tool_version with
    | Some ["2.0"], `v1_2_2 ->
        Logs.app (fun m ->
            m "Skipping opam lint as `opam-version` field is \"2.0\" \
               while `opam --version` is 1.2.2");
        Ok 0
    | _ -> lint opam_version)

type lint = [ `Std_files | `Opam ]

let lints =
  [`Std_files, lint_std_files;
   `Opam, lint_opams ]

let lint_all = List.map fst lints

let lint ~dry_run ~dir p todo =
  let lint pkg =
    let do_lint acc (l, f) = acc + if List.mem l todo then f ~dry_run pkg else 0 in
    match List.fold_left do_lint 0 lints with
    | 0 ->
        Logs.app (fun m -> m "%a lint@ %a %a"
                     pp_status `Ok pp_path dir
                     (Fmt.styled_unit `Green "success") ()); 0
    | n ->
        Logs.app (fun m -> m "%a lint@ %a@ %a:@ %d@ errors."
                     pp_status `Fail pp_path dir
                     (Fmt.styled_unit `Red "failure") () n); 1
  in
  Sos.with_dir ~dry_run dir lint p

(* tags *)

let extract_version change_log =
  Text.change_log_file_last_entry change_log
  >>= fun (version, _) -> Ok version

let tag pkg = change_log pkg >>= fun cl -> extract_version cl

(*---------------------------------------------------------------------------
   Copyright (c) 2016 Daniel C. Bünzli

   Permission to use, copy, modify, and/or distribute this software for any
   purpose with or without fee is hereby granted, provided that the above
   copyright notice and this permission notice appear in all copies.

   THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ---------------------------------------------------------------------------*)