diff --git a/.gitignore b/.gitignore index e35d885..83659d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ _build +inputs/ diff --git a/bin/dune b/bin/dune index 6ed8299..2548b78 100644 --- a/bin/dune +++ b/bin/dune @@ -1,2 +1,3 @@ (executable - (name inspector)) + (name main) + (libraries inspector)) diff --git a/bin/inspector.ml b/bin/inspector.ml deleted file mode 100644 index f11b17e..0000000 --- a/bin/inspector.ml +++ /dev/null @@ -1,40 +0,0 @@ -module State = struct - type t = { lines : string array; cursor : int; size : int } - - let create (fname : string) : t = - let lines = - In_channel.with_open_text fname In_channel.input_lines |> Array.of_list - in - { lines; cursor = 0; size = Array.length lines } - - let show_line s : string = s.lines.(s.cursor) - - let rec repl s = - print_string "inspector> "; - flush stdout; - try - match read_line () |> String.lowercase_ascii with - | "exit" | "quit" -> print_endline "bye" - | "next" -> - if s.cursor < s.size - 1 then repl { s with cursor = s.cursor + 1 } - else repl s - | "prev" -> - if s.cursor > 0 then repl { s with cursor = s.cursor - 1 } else repl s - | "show" -> - Printf.printf "[%d]: %s\n" s.cursor (show_line s); - repl s - | str -> - print_endline str; - repl s - with End_of_file -> print_endline "bye" -end - -let () = - if Array.length Sys.argv < 2 then ( - Printf.eprintf "Usage: %s \n" (Filename.basename Sys.argv.(0)); - exit 1); - - let s = State.create Sys.argv.(1) in - Printf.printf "Loaded %d lines\n" s.size; - - State.repl s diff --git a/bin/main.ml b/bin/main.ml new file mode 100644 index 0000000..a6c3791 --- /dev/null +++ b/bin/main.ml @@ -0,0 +1,10 @@ +let () = + let open Inspector in + if Array.length Sys.argv < 2 then ( + Printf.eprintf "Usage: %s \n" (Filename.basename Sys.argv.(0)); + exit 1); + + let s = State.create Sys.argv.(1) in + Printf.printf "Loaded %d lines\n" (State.size s); + + Repl.loop s diff --git a/lib/dune b/lib/dune new file mode 100644 index 0000000..7c8a4d0 --- /dev/null +++ b/lib/dune @@ -0,0 +1,2 @@ +(library + (name inspector)) diff --git a/lib/repl.ml b/lib/repl.ml new file mode 100644 index 0000000..597ee68 --- /dev/null +++ b/lib/repl.ml @@ -0,0 +1,15 @@ +let rec loop state = + print_string "inspector> "; + flush stdout; + try + match read_line () |> String.lowercase_ascii with + | "exit" | "quit" -> print_endline "bye" + | "next" -> loop (State.next state) + | "prev" -> loop (State.prev state) + | "show" -> + Printf.printf "[%d]: %s\n" (State.cursor state) (State.show_line state); + loop state + | str -> + print_endline str; + loop state + with End_of_file -> print_endline "bye" diff --git a/lib/repl.mli b/lib/repl.mli new file mode 100644 index 0000000..cc32f76 --- /dev/null +++ b/lib/repl.mli @@ -0,0 +1,21 @@ +(** Interactive command loop for the inspector. + + The REPL (Read–Eval–Print Loop) repeatedly reads commands from standard + input and applies them to the current navigation state. + + Each command may update the state (for example moving the cursor) or display + information about the current log line. + + The loop continues until the user enters ["quit"] or ["exit"], or until an + end-of-file condition is encountered on standard input (for example when + pressing Ctrl-D). *) + +val loop : State.t -> unit +(** [loop state] starts the interactive command loop. + + The given [state] represents the current navigation state of the loaded log + file. Commands executed by the user may derive new states (for example via + {!State.next} or {!State.prev}) which are then used in the next iteration of + the loop. + + The function runs until the user exits the program. *) diff --git a/lib/state.ml b/lib/state.ml new file mode 100644 index 0000000..649cde3 --- /dev/null +++ b/lib/state.ml @@ -0,0 +1,18 @@ +type t = { lines : string array; cursor : int } +(** Invariant: cursor >= 0 cursor < Array.length lines *) + +let size s = Array.length s.lines +let cursor s = s.cursor + +let create (fname : string) : t = + let lines = + In_channel.with_open_text fname In_channel.input_lines |> Array.of_list + in + { lines; cursor = 0 } + +let show_line s : string = s.lines.(s.cursor) + +let next s = + if s.cursor < size s - 1 then { s with cursor = s.cursor + 1 } else s + +let prev s = if s.cursor > 0 then { s with cursor = s.cursor - 1 } else s diff --git a/lib/state.mli b/lib/state.mli new file mode 100644 index 0000000..5e9f96a --- /dev/null +++ b/lib/state.mli @@ -0,0 +1,24 @@ +type t +(** Log navigation state. + + A [t] represents a loaded log file and a cursor pointing to the currently + active line. *) + +val create : string -> t +(** [create file] loads the log file into memory and initializes the cursor at + the first line. *) + +val show_line : t -> string +(** [show_line s] returns the line currently pointed to by the cursor. *) + +val next : t -> t +(** [next s] moves the cursor to the next line if possible. *) + +val prev : t -> t +(** [prev s] moves the cursor to the previous line if possible. *) + +val cursor : t -> int +(** Current cursor position. *) + +val size : t -> int +(** Number of lines in the log. *)