package cascade

  1. Overview
  2. Docs
CSS generation and manipulation library for OCaml

Install

dune-project
 Dependency

Authors

Maintainers

Sources

cascade-1.2.1.tbz
sha256=1cf5079b3797cd6e70fbced8dc2631074f357d36fcdea8597c7ba17ac2903f6e
sha512=43f5e6a3986f8f43ce948b8f1d59f6f00e1047f5707c5449b6323b683d2ecd51a0fb34793aad9c48904a67b0f7268d7379ab9baf29917c25a962064bfa3ab694

doc/src/cascade/source_index.ml.html

Source file source_index.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
(** See {!Source_index}. *)

type block = { body : string; next : int }
(** A [{ ... }] or [( ... )] body, and the offset just past its close. *)

type header = { prelude : string; brace : int; block : block }
(** An [@name PRELUDE { ... }] header: what stands between the at-keyword and
    the [{], where that [{] is, and the block it opens. *)

type statement = { prelude : string; next : int }
(** A blockless at-rule's prelude and the offset after its semicolon. When a
    closing brace terminates it, [next] points at that brace. *)

type t = {
  call : (int, string * block) Hashtbl.t;
      (** function-token offset -> the name it calls and its arguments *)
  at : (int, string * header) Hashtbl.t;
      (** at-keyword offset -> its at-name and its header *)
  statement : (int, string * statement) Hashtbl.t;
      (** at-keyword offset -> its at-name and blockless statement *)
}

(* Where the contents of a group end. One the source left open ends with the
   source, which is where the parser ends it too. *)
let inner_end ~closed (loc : Loc.t) =
  if closed then loc.end_pos - 1 else loc.end_pos

let group css ~from ~upto ~next =
  { body = String.sub css from (upto - from); next }

let block_of css (b : Component.block Component.node) =
  let upto = inner_end ~closed:b.node.closed b.loc in
  group css ~from:(b.loc.start_pos + 1) ~upto ~next:b.loc.end_pos

(* A call's arguments start where its first one does; one that takes none has an
   empty body against its own closer. *)
let call_of css (f : Component.func Component.node) =
  let upto = inner_end ~closed:f.node.terminated f.loc in
  let from =
    match f.node.arguments with
    | argument :: _ -> (Component.source_loc argument).start_pos
    | [] -> upto
  in
  group css ~from ~upto ~next:f.loc.end_pos

(* The [{] an at-rule's prelude leads to. A group in the prelude is a single
   component, so a [;] inside one does not end the at-rule. *)
let rec brace_of = function
  | [] -> None
  | Component.Block ({ node = { opening = Token.Curly; _ }; _ } as b) :: _ ->
      Some b
  | Component.Preserved { kind = Token.Semicolon | Token.Close _; _ } :: _ ->
      None
  | _ :: rest -> brace_of rest

(* The semicolon or enclosing closer that terminates a blockless at-rule. *)
let rec statement_end ~closer = function
  | [] -> (closer, closer)
  | Component.Preserved { kind = Token.Semicolon; loc; _ } :: _ ->
      (loc.start_pos, loc.end_pos)
  | Component.Preserved { kind = Token.Close Token.Curly; loc; _ } :: _ ->
      (loc.start_pos, loc.start_pos)
  | _ :: rest -> statement_end ~closer rest

let v css =
  let t =
    {
      call = Hashtbl.create 16;
      at = Hashtbl.create 16;
      statement = Hashtbl.create 16;
    }
  in
  let header name (at : Loc.t) rest =
    match brace_of rest with
    | None -> ()
    | Some b ->
        let brace = b.Component.loc.start_pos in
        let prelude =
          String.trim (String.sub css at.end_pos (brace - at.end_pos))
        in
        Hashtbl.replace t.at at.start_pos
          (name, { prelude; brace; block = block_of css b })
  in
  let statement ~closer name (at : Loc.t) rest =
    let upto, next = statement_end ~closer rest in
    let prelude = String.sub css at.end_pos (upto - at.end_pos) in
    Hashtbl.replace t.statement at.start_pos (name, { prelude; next })
  in
  let rec walk ~closer = function
    | [] -> ()
    | item :: rest ->
        (match item with
        | Component.Preserved { kind = Token.At_keyword name; loc; _ } -> (
            (* An at-rule is one or the other. [brace_of] stops at the
               terminator, so finding no brace is exactly "blockless", and
               registering both would answer [at_statement] for a blockful rule
               with a prelude running to the end of the source. *)
            match brace_of rest with
            | Some _ -> header name loc rest
            | None -> statement ~closer name loc rest)
        | Component.Preserved _ -> ()
        | Component.Block b ->
            walk ~closer:(inner_end ~closed:b.node.closed b.loc) b.node.value
        | Component.Func f ->
            Hashtbl.replace t.call f.loc.start_pos (f.node.name, call_of css f);
            walk
              ~closer:(inner_end ~closed:f.node.terminated f.loc)
              f.node.arguments);
        walk ~closer rest
  in
  let parsed = Parser.list_of_component_values (Reader.of_string css) in
  walk ~closer:(String.length css) parsed.value;
  t

(* [name( ... )] starting at [i]. *)
let call t ~name i =
  match Hashtbl.find_opt t.call i with
  | Some (n, block) when n = name -> Some block
  | _ -> None

(* [@name PRELUDE { ... }] starting at [i]. An at-rule with no block of its own
   is not one of these. *)
let at_rule t ~name i =
  match Hashtbl.find_opt t.at i with
  | Some (n, header) when "@" ^ n = name -> Some header
  | _ -> None

(* A blockless [@name PRELUDE;] starting at [i]. *)
let at_statement t ~name i =
  match Hashtbl.find_opt t.statement i with
  | Some (n, statement) when "@" ^ n = name -> Some statement
  | _ -> None

(* Every [name( ... )] in the source, with the offset each starts at. *)
let calls t ~name =
  Hashtbl.fold
    (fun i (n, block) acc -> if n = name then (i, block) :: acc else acc)
    t.call []

(* Every blockless [@name PRELUDE;], the same way. *)
let at_statements t ~name =
  Hashtbl.fold
    (fun i (n, s) acc -> if "@" ^ n = name then (i, s) :: acc else acc)
    t.statement []