GPS Exchange Format library/CLI in OCaml

refactor: clean up platform modules and fix Eio print_stats

- Remove all remaining duplicate functions from platform modules
- Clean up documentation and module structure
- Fix print_stats in Gpx_eio to use sink capability instead of stdout
- Ensure platform modules focus solely on their I/O concerns

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+26 -95
lib
+2 -4
README.md
··· 14 14 dune build @install 15 15 dune install 16 16 17 - # Or use opam (when published) 17 + # Or use opam 18 18 opam install mlgpx 19 19 ``` 20 20 ··· 63 63 The library is split into four main components: 64 64 65 65 ### Core Library (`gpx`) 66 - - **Portable**: No Unix dependencies, works with js_of_ocaml 66 + - **Portable**: No Unix dependencies, works with `js_of_ocaml` 67 67 - **Streaming**: Uses xmlm for memory-efficient XML processing 68 68 - **Type-safe**: Strong typing with validation for coordinates and GPS data 69 69 - **Pure functional**: No side effects in the core parsing/writing logic ··· 283 283 284 284 let () = create_simple_gpx () 285 285 ``` 286 - 287 -
+2 -2
lib/gpx/gpx.mli
··· 499 499 This core module provides the foundation. For complete applications, consider: 500 500 501 501 - {!Gpx_unix}: File I/O operations using standard Unix libraries 502 - - [Gpx_eio]: Concurrent file I/O using the Eio effects library 502 + - {!Gpx_eio}: Concurrent file I/O using the Eio effects library 503 503 - {{:https://erratique.ch/software/xmlm}Xmlm}: Underlying XML processing library 504 504 - {{:https://erratique.ch/software/ptime}Ptime}: Time representation used for timestamps 505 505 ··· 508 508 - {{:https://www.topografix.com/gpx.asp}Official GPX specification} 509 509 - {{:https://www.topografix.com/GPX/1/1/gpx.xsd}GPX 1.1 XML Schema} 510 510 - {{:https://en.wikipedia.org/wiki/GPS_Exchange_Format}GPX Format on Wikipedia} 511 - - {{:https://en.wikipedia.org/wiki/World_Geodetic_System}WGS84 Coordinate System} *) 511 + - {{:https://en.wikipedia.org/wiki/World_Geodetic_System}WGS84 Coordinate System} *)
+6 -14
lib/gpx_eio/gpx_eio.ml
··· 1 - (** High-level Eio API for GPX operations *) 1 + (** Eio API for GPX operations *) 2 2 3 - (* I/O module *) 4 3 module IO = Gpx_io 5 - 6 - (** Convenience functions for common operations *) 7 4 8 5 (** Read and parse GPX file *) 9 6 let read ?(validate=false) ~fs path = IO.read_file ~validate ~fs path ··· 20 17 (** Write GPX to Eio sink *) 21 18 let to_sink ?(validate=false) sink gpx = IO.write_sink ~validate sink gpx 22 19 23 - (** Create simple waypoint *) 24 - let make_waypoint ~fs:_ ~lat ~lon ?name ?desc () = 25 - match (Gpx.Coordinate.latitude lat, Gpx.Coordinate.longitude lon) with 26 - | (Ok lat, Ok lon) -> 27 - let wpt = Gpx.Waypoint.make lat lon in 28 - { wpt with name; desc } 29 - | (Error e, _) | (_, Error e) -> failwith ("Invalid coordinate: " ^ e) 30 - 31 20 (** Pretty print GPX statistics *) 32 - let print_stats gpx = 33 - Format.printf "%a@." Gpx.Doc.pp_stats gpx 21 + let print_stats sink gpx = 22 + let buf = Buffer.create 256 in 23 + let fmt = Format.formatter_of_buffer buf in 24 + Format.fprintf fmt "%a@?" Gpx.Doc.pp_stats gpx; 25 + Eio.Flow.copy_string (Buffer.contents buf) sink
+11 -23
lib/gpx_eio/gpx_eio.mli
··· 1 - (** {1 GPX Eio - High-level Eio API for GPX operations} 1 + (** {1 Eio API for GPX operations} 2 2 3 - This module provides a high-level API for GPX operations using Eio's 3 + This module provides a direct-style API for GPX operations using Eio's 4 4 effects-based concurrent I/O system. It offers convenient functions 5 5 for common GPX operations while maintaining structured concurrency. 6 6 ··· 13 13 let fs = Eio.Stdenv.fs env in 14 14 15 15 (* Create a GPX document *) 16 - let lat = Gpx.latitude 37.7749 |> Result.get_ok in 17 - let lon = Gpx.longitude (-122.4194) |> Result.get_ok in 18 - let wpt = make_waypoint fs ~lat:(Gpx.latitude_to_float lat) ~lon:(Gpx.longitude_to_float lon) ~name:"San Francisco" () in 19 - let gpx = Gpx.make_gpx ~creator:"eio-example" in 20 - let gpx = { gpx with waypoints = [wpt] } in 16 + let lat = Gpx.Coordinate.latitude 37.7749 |> Result.get_ok in 17 + let lon = Gpx.Coordinate.longitude (-122.4194) |> Result.get_ok in 18 + let wpt = Gpx.Waypoint.make lat lon |> Gpx.Waypoint.with_name "San Francisco" in 19 + let gpx = Gpx.make_gpx ~creator:"eio-example" |> Gpx.Doc.add_waypoint wpt in 21 20 22 21 (* Write with validation *) 23 22 write ~validate:true fs "output.gpx" gpx; 24 23 25 24 (* Read it back *) 26 25 let gpx2 = read ~validate:true fs "output.gpx" in 27 - Printf.printf "Read %d waypoints\n" (List.length gpx2.waypoints) 26 + Printf.printf "Read %d waypoints\n" (List.length (Gpx.Doc.waypoints gpx2)) 28 27 29 28 let () = Eio_main.run main 30 29 ]} ··· 36 35 37 36 (** {2 Convenience File Operations} 38 37 39 - These functions provide simple file I/O with the filesystem from [Eio.Stdenv.fs]. *) 38 + These functions provide simple file I/O with the filesystem from {!Eio.Stdenv.fs}. *) 40 39 41 40 (** Read and parse GPX file. 42 41 @param fs Filesystem capability ··· 81 80 @raises Gpx.Gpx_error on write failure *) 82 81 val to_sink : ?validate:bool -> [> Eio.Flow.sink_ty ] Eio.Resource.t -> Gpx.t -> unit 83 82 84 - (** {2 Utility Functions} *) 85 - 86 - (** Create simple waypoint with coordinates. 87 - @param fs Filesystem capability (unused, for API consistency) 88 - @param lat Latitude in degrees 89 - @param lon Longitude in degrees 90 - @param ?name Optional waypoint name 91 - @param ?desc Optional waypoint description 92 - @return Waypoint data 93 - @raises Gpx.Gpx_error on invalid coordinates *) 94 - val make_waypoint : fs:[> Eio.Fs.dir_ty ] Eio.Path.t -> lat:float -> lon:float -> ?name:string -> ?desc:string -> unit -> Gpx.Waypoint.t 95 - 96 - (** Print GPX statistics to stdout. 83 + (** Print GPX statistics to sink. 84 + @param sink Output sink 97 85 @param gpx GPX document *) 98 - val print_stats : Gpx.t -> unit 86 + val print_stats : [> Eio.Flow.sink_ty ] Eio.Resource.t -> Gpx.t -> unit
+1 -3
lib/gpx_eio/gpx_io.ml
··· 1 1 (** GPX Eio I/O operations *) 2 2 3 - (* Real Eio-based I/O operations *) 4 - 5 3 (** Read GPX from file path *) 6 4 let read_file ?(validate=false) ~fs path = 7 5 let content = Eio.Path.load Eio.Path.(fs / path) in ··· 71 69 Eio.Path.save ~create:(`Or_truncate 0o644) Eio.Path.(fs / path) backup_content 72 70 with _ -> () (* Ignore restore errors *) 73 71 ); 74 - raise err 72 + raise err
+2 -26
lib/gpx_unix/gpx_unix.ml
··· 1 - (** High-level Unix API for GPX operations *) 1 + (** Unix API for GPX operations *) 2 2 3 - (* Re-export IO module *) 4 3 module IO = Gpx_io 5 - 6 - (* Re-export common types *) 7 4 open Gpx 8 5 9 6 (** Convenience functions for common operations *) ··· 17 14 (** Write GPX to file with backup *) 18 15 let write_with_backup = IO.write_file_with_backup 19 16 20 - (** Convert GPX to string *) 21 - let to_string = write_string 22 - 23 - (** Parse GPX from string *) 24 - let from_string = parse_string 25 - 26 - (** Quick validation check *) 27 - let is_valid = is_valid 28 - 29 - (** Get validation issues *) 30 - let validate = validate_gpx 31 - 32 - (** Create simple waypoint *) 33 - let make_waypoint ~lat ~lon ?name ?desc () = 34 - match (Coordinate.latitude lat, Coordinate.longitude lon) with 35 - | (Ok lat, Ok lon) -> 36 - let wpt = Waypoint.make lat lon in 37 - let wpt = { wpt with name; desc } in 38 - Ok wpt 39 - | (Error e, _) | (_, Error e) -> Error (Gpx.Error.invalid_coordinate e) 40 - 41 17 (** Pretty print GPX statistics *) 42 18 let print_stats gpx = 43 - Format.printf "%a@." Doc.pp_stats gpx 19 + Format.printf "%a@." Doc.pp_stats gpx
+2 -23
lib/gpx_unix/gpx_unix.mli
··· 1 - (** High-level Unix API for GPX operations *) 1 + (** Unix API for GPX operations *) 2 2 3 - (* Re-export IO module *) 4 - module IO = Gpx_io 5 - 6 - (* Re-export common types *) 7 3 open Gpx 8 4 9 - (** Convenience functions for common operations *) 10 - 11 5 (** Read and parse GPX file *) 12 6 val read : ?validate:bool -> string -> (t, error) result 13 7 ··· 17 11 (** Write GPX to file with backup *) 18 12 val write_with_backup : ?validate:bool -> string -> t -> (string, error) result 19 13 20 - (** Convert GPX to string *) 21 - val to_string : ?validate:bool -> t -> (string, error) result 22 - 23 - (** Parse GPX from string *) 24 - val from_string : ?validate:bool -> string -> (t, error) result 25 - 26 - (** Quick validation check *) 27 - val is_valid : t -> bool 28 - 29 - (** Get validation issues *) 30 - val validate : t -> validation_result 31 - 32 - (** Create simple waypoint *) 33 - val make_waypoint : lat:float -> lon:float -> ?name:string -> ?desc:string -> unit -> (Waypoint.t, error) result 34 - 35 14 (** Pretty print GPX statistics *) 36 - val print_stats : t -> unit 15 + val print_stats : t -> unit