From ef6ff33de1ebf88a5436793cedac68cf77c6f81e Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Mon, 26 Feb 2024 10:56:50 +0000 Subject: [PATCH 1/9] Reorganize the code and expose get-activity-lib --- bin/dune | 5 ++++ main.ml => bin/main.ml | 2 ++ main.mli => bin/main.mli | 0 dune | 4 --- dune-project | 9 ++++++- get-activity-lib.opam | 31 ++++++++++++++++++++++ get-activity.opam | 12 ++++----- contributions.ml => lib/contributions.ml | 0 contributions.mli => lib/contributions.mli | 0 lib/dune | 4 +++ graphql.ml => lib/graphql.ml | 0 token.ml => lib/token.ml | 0 token.mli => lib/token.mli | 0 13 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 bin/dune rename main.ml => bin/main.ml (99%) rename main.mli => bin/main.mli (100%) delete mode 100644 dune create mode 100644 get-activity-lib.opam rename contributions.ml => lib/contributions.ml (100%) rename contributions.mli => lib/contributions.mli (100%) create mode 100644 lib/dune rename graphql.ml => lib/graphql.ml (100%) rename token.ml => lib/token.ml (100%) rename token.mli => lib/token.mli (100%) diff --git a/bin/dune b/bin/dune new file mode 100644 index 0000000..6f08743 --- /dev/null +++ b/bin/dune @@ -0,0 +1,5 @@ +(executable + (name main) + (public_name get-activity) + (package get-activity) + (libraries cmdliner get-activity-lib)) diff --git a/main.ml b/bin/main.ml similarity index 99% rename from main.ml rename to bin/main.ml index b8644ef..7ef8cab 100644 --- a/main.ml +++ b/bin/main.ml @@ -1,3 +1,5 @@ +open Get_activity + let ( / ) = Filename.concat let or_die = function diff --git a/main.mli b/bin/main.mli similarity index 100% rename from main.mli rename to bin/main.mli diff --git a/dune b/dune deleted file mode 100644 index b275bb5..0000000 --- a/dune +++ /dev/null @@ -1,4 +0,0 @@ -(executable - (name main) - (public_name get-activity) - (libraries cohttp cohttp-lwt cohttp-lwt-unix yojson cmdliner)) diff --git a/dune-project b/dune-project index 71f1bb5..e0482da 100644 --- a/dune-project +++ b/dune-project @@ -1,15 +1,22 @@ -(lang dune 2.3) +(lang dune 2.8) (name get-activity) (formatting disabled) (generate_opam_files true) (source (github tarides/get-activity)) (authors "talex5@gmail.com") (maintainers "Guillaume Petiot ") + (package (name get-activity) (synopsis "Collect activity as markdown") (depends (cmdliner (>= 1.1.1)) + get-activity (= :version))) + +(package + (name get-activity-lib) + (synopsis "Collect activity as markdown") + (depends cohttp cohttp-lwt cohttp-lwt-unix diff --git a/get-activity-lib.opam b/get-activity-lib.opam new file mode 100644 index 0000000..9f576d4 --- /dev/null +++ b/get-activity-lib.opam @@ -0,0 +1,31 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "Collect activity as markdown" +maintainer: ["Guillaume Petiot "] +authors: ["talex5@gmail.com"] +homepage: "https://github.com/tarides/get-activity" +bug-reports: "https://github.com/tarides/get-activity/issues" +depends: [ + "dune" {>= "2.8"} + "cohttp" + "cohttp-lwt" + "cohttp-lwt-unix" + "yojson" + "ocaml" {>= "4.08"} + "odoc" {with-doc} +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://github.com/tarides/get-activity.git" diff --git a/get-activity.opam b/get-activity.opam index 4869022..a06dd1d 100644 --- a/get-activity.opam +++ b/get-activity.opam @@ -6,16 +6,14 @@ authors: ["talex5@gmail.com"] homepage: "https://github.com/tarides/get-activity" bug-reports: "https://github.com/tarides/get-activity/issues" depends: [ - "dune" {>= "2.3"} + "dune" {>= "2.8"} "cmdliner" {>= "1.1.1"} - "cohttp" - "cohttp-lwt" - "cohttp-lwt-unix" - "yojson" - "ocaml" {>= "4.08"} + "get-activity" + "=" {version} + "odoc" {with-doc} ] build: [ - ["dune" "subst"] {pinned} + ["dune" "subst"] {dev} [ "dune" "build" diff --git a/contributions.ml b/lib/contributions.ml similarity index 100% rename from contributions.ml rename to lib/contributions.ml diff --git a/contributions.mli b/lib/contributions.mli similarity index 100% rename from contributions.mli rename to lib/contributions.mli diff --git a/lib/dune b/lib/dune new file mode 100644 index 0000000..cd7ce68 --- /dev/null +++ b/lib/dune @@ -0,0 +1,4 @@ +(library + (name get_activity) + (public_name get-activity-lib) + (libraries cohttp cohttp-lwt cohttp-lwt-unix yojson)) diff --git a/graphql.ml b/lib/graphql.ml similarity index 100% rename from graphql.ml rename to lib/graphql.ml diff --git a/token.ml b/lib/token.ml similarity index 100% rename from token.ml rename to lib/token.ml diff --git a/token.mli b/lib/token.mli similarity index 100% rename from token.mli rename to lib/token.mli From cfbebd8ffbb9144ea914d493c915d067ef14318c Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Mon, 26 Feb 2024 10:59:55 +0000 Subject: [PATCH 2/9] changelog --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 727632f..0258993 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +## unreleased + +### Added + +Expose the library `get-activity-lib` as an opam package (#, @gpetiot) + ## 0.1 (changes before Feb '24 not tracked) From b78d7db1d2dfda586cb8ac768396783e5c91ce96 Mon Sep 17 00:00:00 2001 From: "Guillaume \"Liam\" Petiot" Date: Mon, 26 Feb 2024 11:27:34 +0000 Subject: [PATCH 3/9] Update CHANGES.md Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0258993..c412fc4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ### Added -Expose the library `get-activity-lib` as an opam package (#, @gpetiot) +Expose the library `get-activity-lib` as an opam package (#4, @gpetiot) ## 0.1 From 71cc4f11c7d2058a55abd41251585b22cfdb079e Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Mon, 26 Feb 2024 11:36:53 +0000 Subject: [PATCH 4/9] Fix deps --- dune-project | 2 +- get-activity.opam | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dune-project b/dune-project index e0482da..f05dfb6 100644 --- a/dune-project +++ b/dune-project @@ -11,7 +11,7 @@ (synopsis "Collect activity as markdown") (depends (cmdliner (>= 1.1.1)) - get-activity (= :version))) + (get-activity (= :version)))) (package (name get-activity-lib) diff --git a/get-activity.opam b/get-activity.opam index a06dd1d..fe2dadc 100644 --- a/get-activity.opam +++ b/get-activity.opam @@ -8,8 +8,7 @@ bug-reports: "https://github.com/tarides/get-activity/issues" depends: [ "dune" {>= "2.8"} "cmdliner" {>= "1.1.1"} - "get-activity" - "=" {version} + "get-activity" {= version} "odoc" {with-doc} ] build: [ From 912b1acb0bd74f5c4e3688468e16d7218ca1803c Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Mon, 26 Feb 2024 11:49:57 +0000 Subject: [PATCH 5/9] expose more of Contributions --- lib/contributions.mli | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/contributions.mli b/lib/contributions.mli index aec24b5..8773e76 100644 --- a/lib/contributions.mli +++ b/lib/contributions.mli @@ -1,5 +1,20 @@ type t +module Datetime : sig + type t = string +end + +type item = { + repo : string; + kind : [`Issue | `PR | `Review of string | `New_repo ]; + date: Datetime.t; + url : string; + title : string; + body : string; +} + +module Repo_map : Map.S with type key = string + val fetch : period:(string * string) -> token:Token.t -> Yojson.Safe.t val of_json : from:string -> Yojson.Safe.t -> t From 95a76cda554221df7f8a65d315ffd12a617144c0 Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Mon, 26 Feb 2024 11:59:37 +0000 Subject: [PATCH 6/9] Create Period file --- bin/main.ml | 45 ++++----------------------------------------- lib/period.ml | 44 ++++++++++++++++++++++++++++++++++++++++++++ lib/period.mli | 12 ++++++++++++ 3 files changed, 60 insertions(+), 41 deletions(-) create mode 100644 lib/period.ml create mode 100644 lib/period.mli diff --git a/bin/main.ml b/bin/main.ml index 7ef8cab..0c7743b 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -8,8 +8,6 @@ let or_die = function Fmt.epr "%s@." m; exit 1 -let one_week = 60. *. 60. *. 24. *. 7. - let home = match Sys.getenv_opt "HOME" with | None -> Fmt.failwith "$HOME is not set!" @@ -32,44 +30,9 @@ let mtime path = | info -> Some info.Unix.st_mtime | exception Unix.Unix_error(Unix.ENOENT, _, _) -> None -let set_mtime path time = - if not (Sys.file_exists path) then - close_out @@ open_out_gen [Open_append; Open_creat] 0o600 path; - Unix.utimes path time time - let get_token () = Token.load (home / ".github" / "github-activity-token") -let to_8601 t = - let open Unix in - let t = gmtime t in - Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ" - (t.tm_year + 1900) - (t.tm_mon + 1) - (t.tm_mday) - (t.tm_hour) - (t.tm_min) - (t.tm_sec) - -(* Run [fn (start, finish)], where [(start, finish)] is the period specified by [period]. - If [period] is [`Since_last_fetch] or [`Last_week] then update the last-fetch timestamp on success. *) -let with_period period fn = - let now = Unix.time () in - let last_week = now -. one_week in - let range = - match period with - | `Since_last_fetch -> - let last_fetch = Option.value ~default:last_week (mtime last_fetch_file) in - (to_8601 last_fetch, to_8601 now) - | `Last_week -> - (to_8601 last_week, to_8601 now) - | `Range r -> r - in - fn range; - match period with - | `Since_last_fetch | `Last_week -> set_mtime last_fetch_file now - | `Range _ -> () - let show ~from json = let contribs = Contributions.of_json ~from json in if Contributions.is_empty contribs then @@ -96,7 +59,7 @@ let last_week = Arg.(value & flag doc) let period = - let f from to_ last_week = + let f from to_ last_week : Period.t = if last_week then `Last_week else match (from, to_) with @@ -111,20 +74,20 @@ let info = Cmd.info "get-activity" let run period : unit = match mode with | `Normal -> - with_period period (fun period -> + Period.with_period period ~last_fetch_file ~f:(fun period -> (* Fmt.pr "period: %a@." Fmt.(pair string string) period; *) let token = get_token () |> or_die in show ~from:(fst period) @@ Contributions.fetch ~period ~token ) | `Save -> - with_period period (fun period -> + Period.with_period period ~last_fetch_file ~f:(fun period -> let token = get_token () |> or_die in Contributions.fetch ~period ~token |> Yojson.Safe.to_file "activity.json" ) | `Load -> (* When testing formatting changes, it is quicker to fetch the data once and then load it again for each test: *) - let from = mtime last_fetch_file |> Option.value ~default:0.0 |> to_8601 in + let from = mtime last_fetch_file |> Option.value ~default:0.0 |> Period.to_8601 in show ~from @@ Yojson.Safe.from_file "activity.json" let term = Term.(const run $ period) diff --git a/lib/period.ml b/lib/period.ml new file mode 100644 index 0000000..3e4efb6 --- /dev/null +++ b/lib/period.ml @@ -0,0 +1,44 @@ +type t = + [ `Last_week + | `Range of string * string + | `Since_last_fetch ] + +let one_week = 60. *. 60. *. 24. *. 7. + +let to_8601 t = + let open Unix in + let t = gmtime t in + Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ" + (t.tm_year + 1900) + (t.tm_mon + 1) + (t.tm_mday) + (t.tm_hour) + (t.tm_min) + (t.tm_sec) + +let mtime path = + match Unix.stat path with + | info -> Some info.Unix.st_mtime + | exception Unix.Unix_error(Unix.ENOENT, _, _) -> None + +let set_mtime path time = + if not (Sys.file_exists path) then + close_out @@ open_out_gen [Open_append; Open_creat] 0o600 path; + Unix.utimes path time time + +let with_period period ~last_fetch_file ~f = + let now = Unix.time () in + let last_week = now -. one_week in + let range = + match period with + | `Since_last_fetch -> + let last_fetch = Option.value ~default:last_week (mtime last_fetch_file) in + (to_8601 last_fetch, to_8601 now) + | `Last_week -> + (to_8601 last_week, to_8601 now) + | `Range r -> r + in + f range; + match period with + | `Since_last_fetch | `Last_week -> set_mtime last_fetch_file now + | `Range _ -> () diff --git a/lib/period.mli b/lib/period.mli new file mode 100644 index 0000000..1c2d083 --- /dev/null +++ b/lib/period.mli @@ -0,0 +1,12 @@ +type t = + [ `Last_week + | `Range of string * string + | `Since_last_fetch ] + +val one_week : float + +val to_8601 : float -> string + +val with_period : t -> last_fetch_file:string -> f:(string * string -> unit) -> unit +(** Run [f (start, finish)], where [(start, finish)] is the period specified by [period]. + If [period] is [`Since_last_fetch] or [`Last_week] then update the last-fetch timestamp on success. *) From 7bf07fe4d2d535a44b45464cb8b5aa916b9c900b Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Mon, 26 Feb 2024 12:56:03 +0000 Subject: [PATCH 7/9] Add username to the activity --- lib/contributions.ml | 29 +++++++++++++++++------------ lib/contributions.mli | 4 ++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/contributions.ml b/lib/contributions.ml index 0ad1da1..c05df53 100644 --- a/lib/contributions.ml +++ b/lib/contributions.ml @@ -5,6 +5,7 @@ let ( / ) a b = Json.Util.member b a let query = {| query($from: DateTime!, $to: DateTime!) { viewer { + login contributionsCollection(from: $from, to: $to) { issueContributions(first: 100) { nodes { @@ -82,6 +83,8 @@ type item = { body : string; } +type t = { username : string; activity : item list Repo_map.t } + let read_issues json = Json.Util.to_list (json / "nodes") |> List.filter ((<>) `Null) |> List.map @@ fun node -> let date = Datetime.parse (node / "occurredAt") in @@ -123,6 +126,7 @@ let read_repos json = { kind = `New_repo; date; url; title = "Created new repository"; body = ""; repo } let of_json ~from json = + let username = json / "data" / "viewer" / "login" |> Json.Util.to_string in let contribs = json / "data" / "viewer" / "contributionsCollection" in let items = read_issues (contribs / "issueContributions") @ @@ -130,13 +134,16 @@ let of_json ~from json = read_reviews (contribs / "pullRequestReviewContributions") @ read_repos (contribs / "repositoryContributions") in - (* GitHub seems to ignore the time part, so do the filtering here. *) - items - |> List.filter (fun item -> item.date >= from) - |> List.fold_left (fun acc item -> - let items = Repo_map.find_opt item.repo acc |> Option.value ~default:[] in - Repo_map.add item.repo (item :: items) acc - ) Repo_map.empty + let activity = + (* GitHub seems to ignore the time part, so do the filtering here. *) + items + |> List.filter (fun item -> item.date >= from) + |> List.fold_left (fun acc item -> + let items = Repo_map.find_opt item.repo acc |> Option.value ~default:[] in + Repo_map.add item.repo (item :: items) acc + ) Repo_map.empty + in + { username; activity } let id url = match Astring.String.cut ~sep:"/" ~rev:true url with @@ -171,12 +178,10 @@ let pp_items = Fmt.(list ~sep:(cut ++ cut) pp_item) let pp_repo f (name, items) = Fmt.pf f "### %s@,@,%a" name pp_items items -type t = item list Repo_map.t - -let is_empty = Repo_map.is_empty +let is_empty { activity; _} = Repo_map.is_empty activity -let pp f t = - let by_repo = Repo_map.bindings t in +let pp f { activity; _ } = + let by_repo = Repo_map.bindings activity in match by_repo with | [] -> Fmt.string f "(no activity)" | [(_, items)] -> pp_items f items diff --git a/lib/contributions.mli b/lib/contributions.mli index 8773e76..e58aa3e 100644 --- a/lib/contributions.mli +++ b/lib/contributions.mli @@ -1,5 +1,3 @@ -type t - module Datetime : sig type t = string end @@ -15,6 +13,8 @@ type item = { module Repo_map : Map.S with type key = string +type t = { username : string; activity : item list Repo_map.t } + val fetch : period:(string * string) -> token:Token.t -> Yojson.Safe.t val of_json : from:string -> Yojson.Safe.t -> t From eee2841dd1141de7150247e044ff277e9a53dc9e Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Mon, 26 Feb 2024 13:13:49 +0000 Subject: [PATCH 8/9] Add .mli for graphql --- lib/contributions.ml | 2 +- lib/graphql.ml | 2 +- lib/graphql.mli | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 lib/graphql.mli diff --git a/lib/contributions.ml b/lib/contributions.ml index c05df53..5b7a9c6 100644 --- a/lib/contributions.ml +++ b/lib/contributions.ml @@ -61,7 +61,7 @@ let fetch ~period:(start, finish) ~token = "from", `String start; "to", `String finish; ] in - Graphql.exec token ~variables query + Graphql.exec ~token ~variables ~query () end module Datetime = struct diff --git a/lib/graphql.ml b/lib/graphql.ml index 0c8238d..36e4dec 100644 --- a/lib/graphql.ml +++ b/lib/graphql.ml @@ -4,7 +4,7 @@ let graphql_endpoint = Uri.of_string "https://api.github.com/graphql" let ( / ) a b = Yojson.Safe.Util.member b a -let exec ?variables token query = +let exec ?variables ~token ~query () = let body = `Assoc ( ("query", `String query) :: diff --git a/lib/graphql.mli b/lib/graphql.mli new file mode 100644 index 0000000..325aae8 --- /dev/null +++ b/lib/graphql.mli @@ -0,0 +1 @@ +val exec : ?variables:(string * Yojson.Safe.t) list -> token:string -> query:string -> unit -> Yojson.Safe.t Lwt.t From 539ad0e557df71e6bf126ee29d8eafdd811fa239 Mon Sep 17 00:00:00 2001 From: Guillaume Petiot Date: Mon, 26 Feb 2024 13:21:23 +0000 Subject: [PATCH 9/9] changelog details --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c412fc4..1cf099c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,12 @@ ### Added Expose the library `get-activity-lib` as an opam package (#4, @gpetiot) +- Expose `Get_ctivity.Period` +- Expose `Get_ativity.Contributions.Datetime` +- Expose `Get_activity.Contributions.Repo_map` +- Expose `Get_activity.Contributions.item` +- Add a `username` field to `Get_activity.Contributions.t` +- Label the parameters of `Get_activity.Graphql.exec` ## 0.1