package liquid_std

  1. Overview
  2. Docs

Source file liquid_list.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
open Base
open Liquid_syntax
open Syntax
open Tools
open Values
open Helpers

let compact ctx = function
  | List lst :: _ -> List.filter lst ~f:(Values.is_not_nil ctx) |> ok_list
  | other -> errc "compact accepts a list" other

let concat _ = function
  | List a :: List b :: _ -> a @ b |> ok_list
  | other -> errc "concat accepts 2 lists" other

let first _ = function
  | List (hd :: _) :: _ -> hd |> ok
  | other -> errc "first accepts a list" other

let join ctx params =
  let joiner lst delim =
    let vs = List.map (unwrap_all ctx lst) ~f:(string_from_value ctx) in
    String.concat ~sep:delim vs |> ok_str
  in

  match params with
  | List lst :: String delim :: _ -> joiner lst delim
  | List lst :: _ -> joiner lst " "
  | other -> errc "join accepts a list and a delimiter (string)" other

let last _ = function
  | List [] :: _ -> Nil |> ok
  | List lst :: _ -> lst |> List.rev |> List.hd_exn |> ok
  | other -> errc "last accepts a list" other

let map _ = function
  | List lst :: String key :: _ ->
      let mapped = extract_key_from_object_list lst key in
      mapped |> ok_list
  | other -> errc "map accepts a list and a key (string)" other

let reverse _ = function
  | List lst :: _ -> List.rev lst |> ok_list
  | other -> errc "reverse accepts a list" other

let size _ = function
  | List lst :: _ -> lst |> List.length |> Int.to_float |> ok_num
  | String s :: _ -> s |> String.length |> Int.to_float |> ok_num
  | other -> errc "size accepts a list or a string" other

let sort _ = function
  | List lst :: String key :: _ ->
      let compare_by_key a b =
        let extract = function
          | Object obj -> (
              match Object.find_opt key obj with Some v -> v | None -> Nil)
          | v -> v
        in
        Values.compare_value (extract a) (extract b)
      in
      let sorted = List.sort lst ~compare:compare_by_key in
      sorted |> ok_list
  | List lst :: _ ->
      let sorted = List.sort lst ~compare:Values.compare_value in
      sorted |> ok_list
  | other -> errc "sort accepts a list an optional object key" other

let sort_natural ctx params =
  let comp_key = function
    | String t ->
        let whitespace_exp = ~/"\\s|-|_" in
        Re2.rewrite_exn whitespace_exp ~template:"" t |> String.lowercase
    | v -> Values.string_from_value ctx v
  in

  let compare a b = String.compare (comp_key a) (comp_key b) in

  match params with
  | List lst :: String key :: _ ->
      let compare_by_key a b =
        let extract = function
          | Object obj -> (
              match Object.find_opt key obj with Some v -> v | None -> Nil)
          | v -> v
        in
        String.compare (comp_key (extract a)) (comp_key (extract b))
      in
      let sorted = List.sort lst ~compare:compare_by_key in
      sorted |> ok_list
  | List lst :: _ ->
      let sorted = List.sort lst ~compare in
      sorted |> ok_list
  | other -> errc "sort_natural accepts a list an optional object key" other

(* TODO: Implement negative indexs *)
let slice _ params =
  let do_slice slicer lengther lst fstart flength =
    let start, length = (fi fstart, fi flength) in
    if start >= 0 then slicer lst ~pos:start ~len:length
    else
      let cstart = lengther lst + start in
      slicer lst ~pos:cstart ~len:length
  in

  let do_slice_string = do_slice String.sub String.length in
  let do_slice_list = do_slice List.sub List.length in

  match params with
  | String s :: Number fstart :: Number flength :: _ ->
      do_slice_string s fstart flength |> ok_str
  | String s :: Number findex :: _ -> do_slice_string s findex 1. |> ok_str
  | List lst :: Number fstart :: Number flength :: _ ->
      do_slice_list lst fstart flength |> ok_list
  | List lst :: Number findex :: _ -> do_slice_list lst findex 1. |> ok_list
  | other ->
      errc
        "slice accepts a string or list as well as a start index and optional \
         length"
        other

let uniq _ = function
  | List lst :: _ ->
      let folder acc curr =
        if Tools.contains acc curr then acc else acc @ [ curr ]
      in
      let rl = List.fold_left lst ~init:[] ~f:folder in
      rl |> ok_list
  | other -> errc "uniq accepts a list" other

let where ctx params =
  let do_where lst test_key check =
    let filterer = function
      | Object obj -> (
          match Object.find_opt test_key obj with
          | Some value -> check value
          | _ -> false)
      | _ -> false
    in

    let filtered_lst = List.filter lst ~f:filterer in
    filtered_lst |> ok_list
  in

  match params with
  | List lst :: String key :: test_value :: _ ->
      do_where lst key (Values.eq ctx test_value)
  | List lst :: String key :: _ -> do_where lst key (Values.is_truthy ctx)
  | other ->
      errc
        "where accepts a list, a key (string) and an optional test value (any)"
        other

let function_from_id = function
  | "compact" -> Some compact
  | "concat" -> Some concat
  | "first" -> Some first
  | "join" -> Some join
  | "last" -> Some last
  | "map" -> Some map
  | "reverse" -> Some reverse
  | "size" -> Some size
  | "slice" -> Some slice
  | "sort" -> Some sort
  | "sort_natural" -> Some sort_natural
  | "uniq" -> Some uniq
  | "where" -> Some where
  | _ -> None