src/Pure/General/bytes.ML
changeset 75577 c51e1cef1eae
parent 75576 8c5eedb6c983
child 75581 29654a8e9374
--- a/src/Pure/General/bytes.ML	Tue Jun 21 16:03:00 2022 +0200
+++ b/src/Pure/General/bytes.ML	Tue Jun 21 22:17:11 2022 +0200
@@ -14,17 +14,24 @@
   type T
   val length: T -> int
   val contents: T -> string list
+  val contents_blob: T -> XML.body
   val content: T -> string
   val is_empty: T -> bool
   val empty: T
+  val build: (T -> T) -> T
   val add_substring: substring -> T -> T
   val add: string -> T -> T
+  val beginning: int -> T -> string
+  val exists_string: (string -> bool) -> T -> bool
+  val forall_string: (string -> bool) -> T -> bool
+  val last_string: T -> string option
+  val trim_line: T -> T
   val append: T -> T -> T
-  val build: (T -> T) -> T
   val string: string -> T
+  val newline: T
   val buffer: Buffer.T -> T
-  val read_chunk: BinIO.instream -> string
-  val read_stream: BinIO.instream -> T
+  val read_block: int -> BinIO.instream -> string
+  val read_stream: int -> BinIO.instream -> T
   val write_stream: BinIO.outstream -> T -> unit
   val read: Path.T -> T
   val write: Path.T -> T -> unit
@@ -48,12 +55,16 @@
 fun contents (Bytes {buffer, chunks, ...}) =
   rev (chunks |> not (null buffer) ? cons (compact buffer));
 
+val contents_blob = contents #> XML.blob;
+
 val content = implode o contents;
 
 fun is_empty bytes = length bytes = 0;
 
 val empty = Bytes {buffer = [], chunks = [], m = 0, n = 0};
 
+fun build (f: T -> T) = f empty;
+
 fun add_substring s (bytes as Bytes {buffer, chunks, m, n}) =
   if Substring.isEmpty s then bytes
   else
@@ -71,35 +82,76 @@
 
 val add = add_substring o Substring.full;
 
+fun exists_string pred (Bytes {buffer, chunks, ...}) =
+  let val ex = (exists o Library.exists_string) pred
+  in ex buffer orelse ex chunks end;
+
+fun forall_string pred = not o exists_string (not o pred);
+
+fun last_string (Bytes {buffer, chunks, ...}) =
+  (case buffer of
+    s :: _ => Library.last_string s
+  | [] =>
+      (case chunks of
+        s :: _ => Library.last_string s
+      | [] => NONE));
+
+fun trim_line (bytes as Bytes {buffer, chunks, ...}) =
+  let
+    val is_line =
+      (case last_string bytes of
+        SOME s => Symbol.is_ascii_line_terminator s
+      | NONE => false);
+  in
+    if is_line then
+      let
+        val (last_chunk, chunks') =
+          (case chunks of
+            [] => ("", [])
+          | c :: cs => (c, cs));
+        val trimed = Library.trim_line (last_chunk ^ compact buffer);
+      in build (fold_rev add chunks' #> add trimed) end
+    else bytes
+  end;
+
 end;
 
 
 (* derived operations *)
 
+fun beginning n bytes =
+  let
+    val dots = " ...";
+    val m = (String.maxSize - size dots) div chunk_size;
+    val a = implode (take m (contents bytes));
+    val b = String.substring (a, 0, Int.min (n, size a));
+  in if size b < length bytes then b ^ dots else b end;
+
 fun append bytes1 bytes2 =  (*left-associative*)
   if is_empty bytes1 then bytes2
   else if is_empty bytes2 then bytes1
   else bytes1 |> fold add (contents bytes2);
 
-fun build (f: T -> T) = f empty;
+val string = build o add;
 
-val string = build o add;
+val newline = string "\n";
 
 val buffer = build o fold add o Buffer.contents;
 
-val read_chunk = File.input_size chunk_size;
+fun read_block limit =
+  File.input_size (if limit < 0 then chunk_size else Int.min (chunk_size, limit));
 
-fun read_stream stream =
+fun read_stream limit stream =
   let
-    fun read_bytes bytes =
-      (case read_chunk stream of
+    fun read bytes =
+      (case read_block (limit - length bytes) stream of
         "" => bytes
-      | s => read_bytes (add s bytes))
-  in read_bytes empty end;
+      | s => read (add s bytes))
+  in read empty end;
 
 fun write_stream stream = File.outputs stream o contents;
 
-val read = File.open_input read_stream;
+val read = File.open_input (read_stream ~1);
 val write = File.open_output write_stream;