Skip to content

Commit 1a7c8a5

Browse files
Get a file from a repo, refactor iterating pages (#7)
* Get a file from a repo, refactor iterating pages * Documentation. * Don't use concat in recursive function, flatten pages instead. * Use the first iteration function (better than mine). Refactor. * Use and test the IReduceInit interface * Documentation * Avoid horizontal scrollbar in readme. * Again * Bump version.
1 parent acd5f47 commit 1a7c8a5

File tree

13 files changed

+433
-154
lines changed

13 files changed

+433
-154
lines changed

.circleci/config.yml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ commands:
2323
- save_cache:
2424
paths:
2525
- ~/.m2
26+
- .cpcache
2627
key: v1-dependencies-{{ checksum "deps.edn" }}
2728

2829
acceptance-tests:

README.md

+40
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,46 @@ The EDN returned will contain basic information about the repos found. For examp
103103
]
104104
```
105105

106+
### Getting files in repos
107+
108+
Retrieve information about a file in a repository, on a particular branch. You can use "HEAD" for the branch to retrieve a file from the default branch. The information returns includes `:byteSize` and `:text`.
109+
110+
```clojure
111+
(require '[eamonnsullivan.github-api-lib.files :as files])
112+
(files/get-file token "eamonnsullivan" "github-api-lib" "HEAD" "README.md")
113+
114+
{:commitResourcePath
115+
"/eamonnsullivan/github-api-lib/commit/0805f4b95f5e01275e5962e0f8ed23def5129419",
116+
:byteSize 4296,
117+
:filepath "README.md",
118+
:abbreviatedOid "0805f4b",
119+
:isBinary false,
120+
:oid "0805f4b95f5e01275e5962e0f8ed23def5129419",
121+
:commitUrl
122+
"https://github.com/eamonnsullivan/github-api-lib/commit/...",
123+
:isTruncated false,
124+
:text
125+
"# github-api-lib\n\nA small, very simple..."}
126+
```
127+
128+
You can also try several files and the first one found is returned.
129+
```clojure
130+
(files/get-first-file token "eamonnsullivan" "github-api-lib" "HEAD"
131+
["build.sbt" ".nvmrc" "deps.edn" "project.edn"])
132+
133+
{:commitResourcePath
134+
"/eamonnsullivan/github-api-lib/commit/74c3092ef552681a7fa5c1a96b3a11479b4f0a28",
135+
:byteSize 1257,
136+
:filepath "deps.edn",
137+
:abbreviatedOid "74c3092",
138+
:isBinary false,
139+
:oid "74c3092ef552681a7fa5c1a96b3a11479b4f0a28",
140+
:commitUrl
141+
"https://github.com/eamonnsullivan/github-api-lib/commit/...",
142+
:isTruncated false,
143+
:text
144+
"{:paths [\"src\" \"resources\"]\n :deps ..."}
145+
```
106146
## Development Notes
107147

108148
To run the project's tests:

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>eamonnsullivan</groupId>
55
<artifactId>github-api-lib</artifactId>
6-
<version>0.1.16-SNAPSHOT</version>
6+
<version>0.1.16</version>
77
<name>github-api-lib</name>
88
<description>Library of Github API calls that I happen to need.</description>
99
<url>https://github.com/eamonnsullivan/github-api-lib</url>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
query getRepoFile($owner: String!, $name: String!, $file: String!) {
2+
repository(owner: $owner, name: $name) {
3+
object(expression: $file) {
4+
... on Blob {
5+
abbreviatedOid
6+
byteSize
7+
commitResourcePath
8+
commitUrl
9+
isBinary
10+
isTruncated
11+
oid
12+
text
13+
}
14+
}
15+
}
16+
}

src/eamonnsullivan/github_api_lib/core.clj

+71-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(ns eamonnsullivan.github-api-lib.core
22
(:require [clj-http.client :as client]
3-
[clojure.data.json :as json]))
3+
[clojure.data.json :as json]
4+
[clojure.java.io :as io]))
45

56
(def github-url "https://api.github.com/graphql")
67

@@ -19,6 +20,11 @@
1920
[access-token url opts]
2021
(client/get url (merge {:username access-token} opts)))
2122

23+
(defn get-graphql
24+
"Retrieve the GraphQL as a text blob"
25+
[name]
26+
(slurp (io/resource (format "graphql/%s.graphql" name))))
27+
2228
(defn make-graphql-post
2329
"Make a GraphQL request to Github using the provided query/mutation
2430
and variables. If there are any errors, throw a RuntimeException,
@@ -64,3 +70,67 @@
6470
{:pullRequestUrl (format "https://github.com/%s/%s/pull/%s" owner name number)
6571
:issueComment comment}
6672
(throw (ex-info (format "Could not parse comment from url: %s" comment-url) {})))))
73+
74+
(defn iteration
75+
"Taken from https://clojure.atlassian.net/browse/CLJ-2555.
76+
This function can just be removed when we start using 1.11 of Clojure.
77+
78+
creates a seqable/reducible given step!,
79+
a function of some (opaque continuation data) k
80+
81+
step! - fn of k/nil to (opaque) 'ret'
82+
83+
:some? - fn of ret -> truthy, indicating there is a value
84+
will not call vf/kf nor continue when false
85+
:vf - fn of ret -> v, the values produced by the iteration
86+
:kf - fn of ret -> next-k or nil (will not continue)
87+
:initk - the first value passed to step!
88+
89+
vf, kf default to identity, some? defaults to some?, initk defaults to nil
90+
91+
it is presumed that step! with non-initk is unreproducible/non-idempotent
92+
if step! with initk is unreproducible, it is on the consumer to not consume twice"
93+
[step! & {:keys [vf kf some? initk]
94+
:or {vf identity
95+
kf identity
96+
some? some?
97+
initk nil}}]
98+
(reify
99+
clojure.lang.Seqable
100+
(seq [_]
101+
((fn next [ret]
102+
(when (some? ret)
103+
(cons (vf ret)
104+
(when-some [k (kf ret)]
105+
(lazy-seq (next (step! k)))))))
106+
(step! initk)))
107+
clojure.lang.IReduceInit
108+
(reduce [_ rf init]
109+
(loop [acc init
110+
ret (step! initk)]
111+
(if (some? ret)
112+
(let [acc (rf acc (vf ret))]
113+
(if (reduced? acc)
114+
@acc
115+
(if-some [k (kf ret)]
116+
(recur acc (step! k))
117+
acc)))
118+
acc)))))
119+
120+
(defn get-all-pages
121+
"Convenience function for getting all of the results from a paged search.
122+
123+
getter: function that returns a single page, given a cursor string.
124+
results?: function that returns a boolean indicating whether the current page contains values.
125+
valuesfn: function to extract the values from a page.
126+
127+
Returns a flattened, realised sequence with all of the result. Don't
128+
use this on an infinite or very big sequence."
129+
[getter results? valuesfn]
130+
(let [get-next (fn [ret] (if (-> ret :data :search :pageInfo :hasNextPage)
131+
(-> ret :data :search :pageInfo :endCursor)
132+
nil))]
133+
(vec (reduce
134+
(fn [acc page] (concat acc page))
135+
[]
136+
(iteration getter :vf valuesfn :kf get-next :some? results?)))))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
(ns eamonnsullivan.github-api-lib.files
2+
(:require [eamonnsullivan.github-api-lib.core :as core]))
3+
4+
5+
(defn get-file
6+
"Get information and properties on a file in a repo, or nil if the
7+
file doesn't exist.
8+
9+
You can use \"HEAD\" if you want a file on the default branch, but
10+
you aren't sure of its name (e.g. \"main\" or \"master\")."
11+
[access-token owner repo branch filepath]
12+
(let [variables {:owner owner :name repo :file (format "%s:%s" branch filepath)}
13+
response (core/make-graphql-post
14+
access-token
15+
(core/get-graphql "get-file-text-query")
16+
variables)
17+
object (-> response :data :repository :object)]
18+
(when object
19+
(merge {:filepath filepath} object))))
20+
21+
(defn get-first-file
22+
"Get the first matching file in a repo. We try each of the files specified
23+
and return the first one that exists or nil if none of them do."
24+
[access-token owner repo branch files]
25+
(loop [files files
26+
result nil]
27+
(if-not (seq files)
28+
result
29+
(let [result (get-file access-token owner repo branch (first files))]
30+
(if (:oid result)
31+
result
32+
(recur (rest files)
33+
nil))))))

0 commit comments

Comments
 (0)