package shakuhachi

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file parser.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
(**********************************************************************************************)
(*                                                                                            *)
(* This file is part of shakuhachi: a music collection manager                                *)
(* Copyright (C) 2025 Yves Ndiaye                                                             *)
(*                                                                                            *)
(* shakuhachi is free software: you can redistribute it and/or modify it under the terms      *)
(* of the GNU General Public License as published by the Free Software Foundation,            *)
(* either version 3 of the License, or (at your option) any later version.                    *)
(*                                                                                            *)
(* shakuhachi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;    *)
(* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR           *)
(* PURPOSE.  See the GNU General Public License for more details.                             *)
(* You should have received a copy of the GNU General Public License along with shakuhachi.   *)
(* If not, see <http://www.gnu.org/licenses/>.                                                *)
(*                                                                                            *)
(**********************************************************************************************)

open Angstrom

let is_digit = function '0' .. '9' -> true | _ -> false
let number = map ~f:int_of_string (take_while1 is_digit)
let number64 = map ~f:Int64.of_string (take_while1 is_digit)
let number = choice [ number; map ~f:( ~- ) @@ (char '-' *> number) ]
let number64 = choice [ number64; map ~f:Int64.neg @@ (char '-' *> number64) ]
let whitespace next = skip_while (function ' ' -> true | _ -> false) *> next
let sexp_escape_char = Util.sexp_escape_char

let length =
  choice
    [
      map2 number (char ':' *> number) ~f:(fun min sec -> (min * 60) + sec);
      number;
    ]

let string_sexp f =
  let scanner_quoted state =
    let parser =
      scan_state (state, String.empty) (fun (state, buffer) c ->
          match state with
          | `Escaped ->
              (* Horrendous but can not use buffer. *)
              let buffer = Printf.sprintf "%s%c" buffer c in
              Some (`Unscaped, buffer)
          | `Unscaped -> (
              match c with
              | '\\' ->
                  Some (`Escaped, buffer)
              | '"' ->
                  None
              | _ ->
                  let buffer = Printf.sprintf "%s%c" buffer c in
                  Some (state, buffer)
            )
      )
    in
    map ~f:(fun (_, buffer) -> buffer) parser
  in
  let p =
    choice
      [
        char '"' *> scanner_quoted `Unscaped <* char '"';
        Angstrom.take_while (fun c -> not (List.mem c sexp_escape_char));
      ]
  in
  map p ~f

let string_all k = map (take_while (Fun.const true)) ~f:k

let sfield_choice ~pstring p i f =
  p *> char ':'
  *> choice
       [
         (* [true] for regex, i for case sensibility. *)
         char ':' *> pstring (fun s -> f true i s);
         pstring (fun s -> f false i s);
       ]

let sfield_cs ~pstring name f =
  let name = Printf.sprintf "%%%s" name in
  sfield_choice ~pstring (string name) false f

let sfield_ci ~pstring name f = sfield_choice ~pstring (string name) true f

let sfield ~pstring name f =
  choice [ sfield_cs ~pstring name f; sfield_ci ~pstring name f ]

let string_field ~pstring =
  choice
    [
      sfield ~pstring Keys.title Field.String.title;
      sfield ~pstring Keys.sort_name Field.String.sort_name;
      sfield ~pstring Keys.artist Field.String.artist;
      sfield ~pstring Keys.album Field.String.album;
      sfield ~pstring Keys.album_artist Field.String.album_artist;
      sfield ~pstring Keys.composer Field.String.composer;
      sfield ~pstring Keys.genre Field.String.genre;
      sfield ~pstring Keys.path Field.String.path;
      sfield ~pstring Keys.extension Field.String.extension;
    ]

(** Bool *)

let bfield name f =
  string name *> char ':'
  *> choice
       [
         map (string_ci "true") ~f:(fun _ -> f true);
         map (string_ci "false") ~f:(fun _ -> f false);
         map number ~f:(fun n -> f (n <> 0));
       ]

let flag = bfield Keys.flag Field.Bool.flag
let art = bfield Keys.art Field.Bool.art
let bool_field = choice [ art; flag ]

(* Int *)

let range parser =
  let oparser = option None (map ~f:Option.some parser) in
  map2 oparser (string ".." *> oparser) ~f:Range.range

let field_range name parser k =
  string name *> char ':'
  *> map ~f:k (choice [ range parser; map parser ~f:Range.value ])

let int_field =
  choice
    [
      field_range Keys.id number64 Field.Int.id;
      field_range Keys.size number64 Field.Int.size;
      field_range Keys.track number Field.Int.track;
      field_range Keys.year number Field.Int.year;
      field_range Keys.disc number Field.Int.disc;
      field_range Keys.plays number Field.Int.plays;
      field_range Keys.length length Field.Int.length;
      field_range Keys.rating number Field.Int.rating;
      field_range Keys.album_count number Field.Int.album_count;
    ]

(* Float *)

let field ~pstring =
  choice
    [
      map ~f:Field.string (string_field ~pstring);
      map ~f:Field.int int_field;
      map ~f:Field.bool bool_field;
    ]

let substring ~pstring = pstring Node.substring

let node ~pstring ~substr =
  choice [ map ~f:Node.field (field ~pstring); substring ~pstring:substr ]

let node_cli = node ~pstring:string_all ~substr:string_all
let node_sexp = node ~pstring:string_sexp ~substr:string_sexp