aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md112
-rw-r--r--gn/data/hits.scm22
-rw-r--r--gn/runner/gemma.scm19
-rwxr-xr-xscripts/precompute/list-traits-to-compute.scm76
-rwxr-xr-xscripts/precompute/run-gemma.scm42
-rw-r--r--web/gn-uri.scm47
-rw-r--r--web/sxml.scm553
-rw-r--r--web/view/markdown.scm136
-rw-r--r--[-rwxr-xr-x]web/webserver.scm333
9 files changed, 853 insertions, 487 deletions
diff --git a/README.md b/README.md
index c7f367b..d60ee59 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,118 @@ Next fire up emacs with `emacs-geiser-guile` and connect to the running web serv
Some tooling and scripts that run independently are stored in `./scripts`.
+Here’s the entire markdown content combined into a single, copyable file:
+
+
+# Gn-Markdown
+
+Gn-Markdown is an API endpoint to edit, parse, and commit markdown files for gn-docs.
+
+## How to Test the APIs
+
+1. **Navigate to the Web Directory and Start the Server**
+
+```sh
+cd web
+export CURRENT_REPO_PATH=<path-to-git-repo-with-files>
+export CGIT_REPO_PATH=<path-to-git-bare-repo>
+. .guix-shell -- guile -L .. --listen=1970 -e main ./webserver.scm 8091
+```
+
+2. **Test Endpoints**
+
+The main endpoints provided are `/edit` and `/commit`. More endpoints may be added in the future.
+
+## Edit (GET)
+
+This is a GET request to retrieve a file's details. Make sure you pass a valid file_path as search_query (the path should be relative to the repo)
+
+**Request Example:**
+
+```bash
+
+curl -G -d "file_path=test.md" localhost:8091/edit
+
+```
+
+**Expected Success Response:**
+
+```json
+{
+"file_path": "test.md",
+"content": "Test for new user\n test 2 for line\n test 3 for new line\n ## real markdown two test\n",
+"hash": "ecd96f27c45301279150fbda411544687db1aa45"
+}
+```
+
+**Expected Error Response (Status 400):**
+
+```json
+{
+"error": <error_type>,
+"msg": <error_reason>
+}
+```
+
+## Commit (POST)
+
+This is a POST request to commit changes to a file.
+
+**Request URL:**
+
+```bash
+
+curl -X POST http://127.0.0.1:8091/commit \
+-H 'Content-Type: application/json' \
+-d '{
+"content": "make test commit",
+"filename": "test.md",
+"email": "test@gmail.com",
+"username": "test",
+"commit_message": "init commit",
+"prev_commit": "7cbfc40d98b49a64e98e7cd562f373053d0325bd"
+}'
+
+```
+
+
+
+**Expected Response for success:**
+
+```json
+{
+"status": "201",
+"message": "Committed file successfully",
+"content": "Test for new user\n test 2 for line\n test 3 for new line\n ## real markdown two test\n",
+"commit_sha": "47df3b7f13a935d50cc8b40e98ca9e513cba104c",
+"commit_message": "commit by genetics"
+}
+
+```
+
+**If No Changes to File:**
+
+```json
+{
+"status": "200",
+"message": "Nothing to commit, working tree clean",
+"commit_sha": "ecd96f27c45301279150fbda411544687db1aa45"
+}
+```
+
+**Expected Error Response:**
+
+```json
+{
+"error": "system-error",
+"msg": "Commits do not match. Please pull in the latest changes for the current commit *ecd96f27c45301279150fbda411544687db1aa45* and previous commits."
+}
+```
+
+## Notes
+
+This is meant to be used as api endpoint only to edit any local repo; Clients are expected to handle other service e.g User Interface, authentication
+
# Development
```
diff --git a/gn/data/hits.scm b/gn/data/hits.scm
index f7ce49e..85c4912 100644
--- a/gn/data/hits.scm
+++ b/gn/data/hits.scm
@@ -5,6 +5,7 @@
#:use-module (ice-9 iconv)
#:use-module (ice-9 receive)
#:use-module (ice-9 string-fun)
+ #:use-module (srfi srfi-9)
;; #:use-module (gn db sparql)
#:use-module (dbi dbi)
#:use-module (gn db mysql)
@@ -17,11 +18,26 @@
get-precompute-hit
set-precompute-hit-status!
update-precompute!
+ hit-data-id
+ hit-probeset-id
+ hit-probesetfreeze-id
))
-(define (get-precompute-hits db prev-id num)
- (dbi-query db (string-append "select Locus, DataId, ProbeSetId, ProbeSetFreezeId from ProbeSetXRef where DataId>" (int-to-string prev-id) " AND Locus_old is NULL ORDER BY DataId LIMIT " (format #f "~d" num)))
- (get-rows db '()))
+
+(define-record-type <hit>
+ (make-hit data-id probeset-id probesetfreeze-id)
+ hit?
+ (data-id hit-data-id)
+ (probeset-id hit-probeset-id)
+ (probesetfreeze-id hit-probesetfreeze-id)
+ )
+
+(define (get-precompute-hits db first-id num)
+ (dbi-query db (string-append "select Locus, DataId, ProbeSetId, ProbeSetFreezeId from ProbeSetXRef where DataId>" (int-to-string first-id) " AND Locus_old is NULL ORDER BY DataId LIMIT " (int-to-string num)))
+ (map (lambda (r)
+ (make-hit (assoc-ref r "DataId") (assoc-ref r "ProbeSetId") (assoc-ref r "ProbeSetFreezeId")))
+ (get-rows db '())
+ ))
(define (get-precompute-hit db prev-id)
(car (get-precompute-hits db prev-id 1)))
diff --git a/gn/runner/gemma.scm b/gn/runner/gemma.scm
index 69991dd..9a5c0fc 100644
--- a/gn/runner/gemma.scm
+++ b/gn/runner/gemma.scm
@@ -39,24 +39,27 @@
))
)
-(define (invoke-gemma-wrapper-loco name trait-name pheno-fn)
+(define (invoke-gemma-wrapper-loco name trait-name trait-fn pheno-fn geno-fn)
"Create a tmpdir and invoke gemma-wrapper using parallel LOCO. Note that at this point we use a number of defaults for BXD"
(let* [(population "BXD")
(sys-tmpdir (getenv "TMPDIR"))
(tmpdir (mkdtemp (string-append sys-tmpdir "/run-gemma-XXXXXX")))
(k-json-fn (string-append tmpdir "/K.json"))
- (gwa-json-fn (string-append tmpdir "/GWA.json"))]
+ (gwa-json-fn (string-append tmpdir "/GWA.json"))
+ (trait-json-fn (string-append tmpdir "/" trait-fn))]
+ (copy-file trait-fn trait-json-fn)
;; --- First we compute K - control output goes to K.json
- (let [(err (system (string-append "/gemma-wrapper/bin/gemma-wrapper --verbose --population \"" population "\" --name \"" name "\" --trait \"" trait-name "\" --verbose --loco --json --parallel -- -gk -g BXD.8_geno.txt.gz -p " pheno-fn " -a BXD.8_snps.txt > " k-json-fn )))]
+ (let [(err (system (string-append "/gemma-wrapper/bin/gemma-wrapper --verbose --population \"" population "\" --name \"" name "\" --trait \"" trait-name "\" --verbose --loco --json --parallel -- -gk -g " geno-fn " -p " pheno-fn " -a BXD.8_snps.txt > " k-json-fn )))]
(if (not (= err 0))
(exit err)))
- (let [(err (system (string-append "/gemma-wrapper/bin/gemma-wrapper --population \"" population "\" --name \"" name "\" --id \"" trait-name "\" --trait \"" trait-name "\" --verbose --loco --json --input " k-json-fn " -- -g BXD.8_geno.txt.gz -p " pheno-fn " -a BXD.8_snps.txt -lmm 9 -maf 0.1 > " gwa-json-fn)))]
+ (let [(err (system (string-append "/gemma-wrapper/bin/gemma-wrapper --meta \"" trait-json-fn "\" --population \"" population "\" --name \"" name "\" --id \"" trait-name "\" --trait \"" trait-name "\" --verbose --loco --json --lmdb --input " k-json-fn " -- -g " geno-fn " -p " pheno-fn " -a BXD.8_snps.txt -lmm 9 -maf 0.1 > " gwa-json-fn)))]
(if (not (= err 0))
(exit err)))
- ;; (delete-file pheno-fn)
- ;; (delete-file gwa-json-fn)
- ;; (delete-file k-json-fn)
- ;; (rmdir tmpdir)
+ (delete-file pheno-fn)
+ (delete-file gwa-json-fn)
+ (delete-file k-json-fn)
+ (delete-file trait-json-fn)
+ (rmdir tmpdir)
)
)
diff --git a/scripts/precompute/list-traits-to-compute.scm b/scripts/precompute/list-traits-to-compute.scm
index 2c48d83..9f900d1 100755
--- a/scripts/precompute/list-traits-to-compute.scm
+++ b/scripts/precompute/list-traits-to-compute.scm
@@ -78,19 +78,50 @@ When that is the case we might as well write the phenotype file because we have
(json)
(rnrs bytevectors)
(srfi srfi-1)
+ (srfi srfi-9)
(srfi srfi-19) ; time
)
+(define (get-trait db probeset-id)
+ (dbi-query db (string-append "select Id,Chr,Mb,Name,Symbol,description from ProbeSet where Id=" (int-to-string probeset-id) " limit 1"))
+ (get-row db)
+ )
+
+#!
+
+The following is produced by gemma-wrapper as metadata
+
+ "meta": {
+ "type": "gemma-wrapper",
+ "version": "0.99.7-pre1",
+ "population": "BXD",
+ "name": "HC_U_0304_R",
+ "trait": "101500_at",
+ "url": "https://genenetwork.org/show_trait?trait_id=101500_at&dataset=HC_U_0304_R",
+ "archive_GRM": "46bfba373fe8c19e68be6156cad3750120280e2e-gemma-cXX.tar.xz",
+ "archive_GWA": "779a54a59e4cd03608178db4068791db4ca44ab3-gemma-GWA.tar.xz",
+ "dataid": 75629,
+ "probesetid": 1097,
+ "probesetfreezeid": 7
+ }
-(define (write-json-ld id recs)
+!#
+
+
+(define (write-json-ld id name trait trait-name probesetfreeze-id probeset-id recs)
;; see also https://www.w3.org/2018/jsonld-cg-reports/json-ld/
(display id)
+ (display ":")
+ (display name)
+ (display ":")
+ (display trait-name)
(newline)
(let* [(traits (map (lambda (r)
(match r
[(strain-id . value) (cons (bxd-name strain-id) value)]
))
(reverse recs)))
+ (uri (format #f "https://genenetwork.org/show_trait?trait_id=~a&dataset=~a" trait-name name))
(sha256 (sha-256->string (sha-256 (string->utf8 (scm->json-string traits)))))
(json-data `(("@context" . "https://genenetwork.org/resource")
(type . traits)
@@ -98,8 +129,16 @@ When that is the case we might as well write the phenotype file because we have
(steps . ())
(sha256 . ((input-traits . ,sha256)))
(time . ,(date->string (time-utc->date (current-time))))))
- (traits .
- ((,id . ,traits)))))]
+ (data .
+ ((,id .
+ ((group . "BXD")
+ (probesetfreeze-id . ,probesetfreeze-id)
+ (probeset-id . ,probeset-id)
+ (name . ,name)
+ (trait-name . ,trait-name)
+ (uri . ,uri)
+ (traits . ,traits)
+ ))))))]
(call-with-output-file (string-append (number->string id) ".json")
(lambda (port)
(put-string port (scm->json-string json-data))))
@@ -111,22 +150,23 @@ When that is the case we might as well write the phenotype file because we have
(begin
;; (let [(bxd-strains (memo-bxd-strain-id-names #:used-for-mapping? #t))]
(define (run-list-traits-to-compute db num prev-id)
+ ;; ---- Build a query to collect num traits
(let* [(count (if (< batch-size num)
batch-size
num))
(rest (- num count))
(hits (get-precompute-hits db prev-id count))
- (data-ids (map (lambda (hit)
- (let* [(data-id (assoc-ref hit "DataId"))
- ; (data-id-str (int-to-string data-id))
- ]
- data-id))
+ (data-ids (map (lambda (h)
+ (hit-data-id h))
hits))
(data-str-ids (map (lambda (id) (string-append "Id=" (int-to-string id))) data-ids))
(data-ids-query (string-join data-str-ids " OR "))
(query (string-append "SELECT Id,StrainId,value FROM ProbeSetData WHERE " data-ids-query))
]
+ (format #t "writing-phenotypes from ~d to ~d batch ~d to ~d\n" first-id (+ first-id num) prev-id (+ prev-id count))
+ (display query)
(dbi-query db query)
+ ;; ---- Walk each resulting trait and build a hash of data-id and list of trait values
(let [(id-traits (get-rows db '()))
(nrecs '())]
(for-each (lambda (r)
@@ -140,12 +180,20 @@ When that is the case we might as well write the phenotype file because we have
)]
(set! nrecs (assoc-set! nrecs data-id lst))))
id-traits)
- (for-each (lambda (r)
- (match r
- ((id . recs) (if (has-bxd? recs)
- (write-json-ld id recs)
- ))
- )) nrecs)
+ ;; --- create the json output as a file by walking traits and hits
+ (for-each (lambda (h)
+ (let* [
+ (id (hit-data-id h))
+ (probeset-id (hit-probeset-id h))
+ (probesetfreeze-id (hit-probesetfreeze-id h))
+ (name (dataset-name db probesetfreeze-id))
+ (trait (get-trait db probeset-id))
+ (trait-name (assoc-ref trait "Name"))
+ (recs (assoc-ref nrecs id))
+ ]
+ (if (has-bxd? recs)
+ (write-json-ld id name trait trait-name probesetfreeze-id probeset-id recs)
+ ))) hits)
(if (> rest 0)
(run-list-traits-to-compute db rest (first (reverse data-ids)))) ;; start precompute
)))
diff --git a/scripts/precompute/run-gemma.scm b/scripts/precompute/run-gemma.scm
index e6a4e26..4952834 100755
--- a/scripts/precompute/run-gemma.scm
+++ b/scripts/precompute/run-gemma.scm
@@ -9,7 +9,7 @@ Run from base dir with
and with some extra paths (for gemma)
-~/opt/guix-pull/bin/guix shell -C -F xz tar time parallel coreutils-minimal guile guile-dbi guile-json ruby --expose=/home/wrk/iwrk/opensource/code/genetics/gemma-wrapper/=/gemma-wrapper --expose=/home/wrk/iwrk/opensource/code/genetics/gemma/=/gemma -- env TMPDIR=tmp GEMMA_COMMAND=/gemma/bin/gemma-0.98.5-linux-static-debug guile -L . -e main -s ./scripts/precompute/run-gemma.scm
+~/opt/guix-pull/bin/guix shell -C -F xz python python-lmdb tar time parallel coreutils-minimal guile guile-dbi guile-json ruby --expose=/home/wrk/iwrk/opensource/code/genetics/gemma-wrapper/=/gemma-wrapper --expose=/home/wrk/iwrk/opensource/code/genetics/gemma/=/gemma -- env TMPDIR=tmp GEMMA_COMMAND=/gemma/bin/gemma-0.98.5-linux-static-debug guile -L . -e main -s ./scripts/precompute/run-gemma.scm --id 21529
!#
@@ -30,24 +30,36 @@ and with some extra paths (for gemma)
;; (write args)
(let* [
(option-spec '( (version (single-char #\v) (value #f))
+ (id (value #t))
(help (single-char #\h) (value #f))))
(options (getopt-long args option-spec))
+ (show-version (option-ref options 'version #f))
(help-wanted (option-ref options 'help #f))]
- (display "RUNNING")
+ (if show-version
+ (begin
+ (display "run-gemma version 1.0\n")
+ (exit)))
(if help-wanted
- (format #t "list-traits-to-compute writes JSON traits files from the GN DB
-Usage: list-traits-to-compute [options...]
- -h, --help Display this help
-"))
- (let [(trait-name "115475")]
- (call-with-input-file "115475.json"
+ (format #t "run-gemma
+Usage: run-gemma [options...] filename(s)
+ --id Run on identifier
+ -v --version Display version
+ -h --help Display this help
+")
+ (let* [(trait-id (option-ref options 'id "0"))
+ (trait-fn (string-append trait-id ".json"))
+ ]
+
+ (call-with-input-file trait-fn
(lambda (port)
(let* [(json (json->scm port))
- (dataset (assoc-ref json "traits"))
- (dataset-name (car (car dataset)))
- (traits (assoc-ref dataset dataset-name))
+ (dataset (car (assoc-ref json "data")))
+ (data (cdr dataset))
+ (dataset-name (assoc-ref data "name"))
+ (trait-name (assoc-ref data "trait-name"))
+ (traits (assoc-ref data "traits"))
+ (pheno-fn (string-append trait-id "-pheno.txt"))
]
- (display dataset)
- (write-pheno-file "pheno.txt" traits)
- (invoke-gemma-wrapper-loco dataset-name trait-name "pheno.txt")
- ))))))
+ (write-pheno-file pheno-fn traits)
+ (invoke-gemma-wrapper-loco dataset-name trait-name trait-fn pheno-fn "BXD.8_geno.txt.gz")
+ )))))))
diff --git a/web/gn-uri.scm b/web/gn-uri.scm
index b849e95..3455d51 100644
--- a/web/gn-uri.scm
+++ b/web/gn-uri.scm
@@ -3,11 +3,10 @@
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
- #:export (
- get-version
- ; url-parse-id
- ; normalize-id
- ; strip-lang
+ #:export (get-version
+ ;; url-parse-id
+ ;; normalize-id
+ ;; strip-lang
mk-meta
mk-data
mk-doc
@@ -18,50 +17,38 @@
mk-predicate
prefix
url-parse-id
- normalize-id
- ))
-
+ normalize-id))
(define (normalize-id str)
;; (string-replace-substring (string-downcase str) " " "_")
(match str
(#f "unknown")
- (_ (string-replace-substring str " " "_"))
- ))
+ (_ (string-replace-substring str " " "_"))))
(define (url-parse-id uri)
(if uri
- (car (reverse (string-split uri #\057)))
- "unknown"
- ))
+ (car (reverse (string-split uri #\/))) "unknown"))
(define get-version
"4.0.0")
-; (define base-url
-; "https://luna.genenetwork.org")
-
-;(define (prefix)
-; "Build the API URL including version"
-; (string-append base-url "/api/v" get-version))
-
(define base-url
"http://localhost:8091")
-(define uri-base-url ; always points to genenetwork.org!
+(define uri-base-url
+ ;always points to genenetwork.org!
"http://genenetwork.org")
(define (prefix)
- "Build the API URL including version"
- base-url)
+ "Build the API URL including version" base-url)
-(define* (mk-url postfix #:optional (ext ""))
+(define* (mk-url postfix
+ #:optional (ext ""))
"Makes a fully qualified URL by adding the path (postfix+ext) to the API URL.
If there is an existing http+hostname no prefix is added"
(match (string-match "^http:" postfix)
- [ #f (string-append (prefix) "/" postfix ext)]
- [ _ (string-append postfix ext)]
- ))
+ (#f (string-append (prefix) "/" postfix ext))
+ (_ (string-append postfix ext))))
(define* (mk-uri postfix)
"Add the path to the GN URI. A URI always points to http://genenetwork.org/"
@@ -92,9 +79,11 @@
(define (mk-id postfix)
"Expand URL to make $api/id/identifier.
If postfix is a path it will only apply the last element"
- (mk-url (string-append "id" "/" (url-parse-id postfix))))
+ (mk-url (string-append "id" "/"
+ (url-parse-id postfix))))
(define (mk-gnid postfix)
"Expand URL to make http://genenetwork.org/id/identifier.
If postfix is a path it will only apply the last element"
- (mk-uri (string-append "id" "/" (url-parse-id postfix))))
+ (mk-uri (string-append "id" "/"
+ (url-parse-id postfix))))
diff --git a/web/sxml.scm b/web/sxml.scm
index de30b3f..29eeee3 100644
--- a/web/sxml.scm
+++ b/web/sxml.scm
@@ -30,277 +30,274 @@
#:export (sxml->html))
(define %self-closing-tags
- '(area
- base
- br
- col
- command
- embed
- hr
- img
- input
- keygen
- link
- meta
- param
- source
- track
- wbr))
+ '(area base
+ br
+ col
+ command
+ embed
+ hr
+ img
+ input
+ keygen
+ link
+ meta
+ param
+ source
+ track
+ wbr))
(define (self-closing-tag? tag)
"Return #t if TAG is self-closing."
(pair? (memq tag %self-closing-tags)))
(define %escape-chars
- (alist->hash-table
- '((#\" . "quot")
- (#\& . "amp")
- (#\' . "apos")
- (#\< . "lt")
- (#\> . "gt")
- (#\¡ . "iexcl")
- (#\¢ . "cent")
- (#\£ . "pound")
- (#\¤ . "curren")
- (#\¥ . "yen")
- (#\¦ . "brvbar")
- (#\§ . "sect")
- (#\¨ . "uml")
- (#\© . "copy")
- (#\ª . "ordf")
- (#\« . "laquo")
- (#\¬ . "not")
- (#\® . "reg")
- (#\¯ . "macr")
- (#\° . "deg")
- (#\± . "plusmn")
- (#\² . "sup2")
- (#\³ . "sup3")
- (#\´ . "acute")
- (#\µ . "micro")
- (#\¶ . "para")
- (#\· . "middot")
- (#\¸ . "cedil")
- (#\¹ . "sup1")
- (#\º . "ordm")
- (#\» . "raquo")
- (#\¼ . "frac14")
- (#\½ . "frac12")
- (#\¾ . "frac34")
- (#\¿ . "iquest")
- (#\À . "Agrave")
- (#\Á . "Aacute")
- (#\Â . "Acirc")
- (#\Ã . "Atilde")
- (#\Ä . "Auml")
- (#\Å . "Aring")
- (#\Æ . "AElig")
- (#\Ç . "Ccedil")
- (#\È . "Egrave")
- (#\É . "Eacute")
- (#\Ê . "Ecirc")
- (#\Ë . "Euml")
- (#\Ì . "Igrave")
- (#\Í . "Iacute")
- (#\Î . "Icirc")
- (#\Ï . "Iuml")
- (#\Ð . "ETH")
- (#\Ñ . "Ntilde")
- (#\Ò . "Ograve")
- (#\Ó . "Oacute")
- (#\Ô . "Ocirc")
- (#\Õ . "Otilde")
- (#\Ö . "Ouml")
- (#\× . "times")
- (#\Ø . "Oslash")
- (#\Ù . "Ugrave")
- (#\Ú . "Uacute")
- (#\Û . "Ucirc")
- (#\Ü . "Uuml")
- (#\Ý . "Yacute")
- (#\Þ . "THORN")
- (#\ß . "szlig")
- (#\à . "agrave")
- (#\á . "aacute")
- (#\â . "acirc")
- (#\ã . "atilde")
- (#\ä . "auml")
- (#\å . "aring")
- (#\æ . "aelig")
- (#\ç . "ccedil")
- (#\è . "egrave")
- (#\é . "eacute")
- (#\ê . "ecirc")
- (#\ë . "euml")
- (#\ì . "igrave")
- (#\í . "iacute")
- (#\î . "icirc")
- (#\ï . "iuml")
- (#\ð . "eth")
- (#\ñ . "ntilde")
- (#\ò . "ograve")
- (#\ó . "oacute")
- (#\ô . "ocirc")
- (#\õ . "otilde")
- (#\ö . "ouml")
- (#\÷ . "divide")
- (#\ø . "oslash")
- (#\ù . "ugrave")
- (#\ú . "uacute")
- (#\û . "ucirc")
- (#\ü . "uuml")
- (#\ý . "yacute")
- (#\þ . "thorn")
- (#\ÿ . "yuml")
- (#\Π. "OElig")
- (#\œ . "oelig")
- (#\Š . "Scaron")
- (#\š . "scaron")
- (#\Ÿ . "Yuml")
- (#\ƒ . "fnof")
- (#\ˆ . "circ")
- (#\˜ . "tilde")
- (#\Α . "Alpha")
- (#\Β . "Beta")
- (#\Γ . "Gamma")
- (#\Δ . "Delta")
- (#\Ε . "Epsilon")
- (#\Ζ . "Zeta")
- (#\Η . "Eta")
- (#\Θ . "Theta")
- (#\Ι . "Iota")
- (#\Κ . "Kappa")
- (#\Λ . "Lambda")
- (#\Μ . "Mu")
- (#\Ν . "Nu")
- (#\Ξ . "Xi")
- (#\Ο . "Omicron")
- (#\Π . "Pi")
- (#\Ρ . "Rho")
- (#\Σ . "Sigma")
- (#\Τ . "Tau")
- (#\Υ . "Upsilon")
- (#\Φ . "Phi")
- (#\Χ . "Chi")
- (#\Ψ . "Psi")
- (#\Ω . "Omega")
- (#\α . "alpha")
- (#\β . "beta")
- (#\γ . "gamma")
- (#\δ . "delta")
- (#\ε . "epsilon")
- (#\ζ . "zeta")
- (#\η . "eta")
- (#\θ . "theta")
- (#\ι . "iota")
- (#\κ . "kappa")
- (#\λ . "lambda")
- (#\μ . "mu")
- (#\ν . "nu")
- (#\ξ . "xi")
- (#\ο . "omicron")
- (#\π . "pi")
- (#\ρ . "rho")
- (#\ς . "sigmaf")
- (#\σ . "sigma")
- (#\τ . "tau")
- (#\υ . "upsilon")
- (#\φ . "phi")
- (#\χ . "chi")
- (#\ψ . "psi")
- (#\ω . "omega")
- (#\ϑ . "thetasym")
- (#\ϒ . "upsih")
- (#\ϖ . "piv")
- (#\  . "ensp")
- (#\  . "emsp")
- (#\  . "thinsp")
- (#\– . "ndash")
- (#\— . "mdash")
- (#\‘ . "lsquo")
- (#\’ . "rsquo")
- (#\‚ . "sbquo")
- (#\“ . "ldquo")
- (#\” . "rdquo")
- (#\„ . "bdquo")
- (#\† . "dagger")
- (#\‡ . "Dagger")
- (#\• . "bull")
- (#\… . "hellip")
- (#\‰ . "permil")
- (#\′ . "prime")
- (#\″ . "Prime")
- (#\‹ . "lsaquo")
- (#\› . "rsaquo")
- (#\‾ . "oline")
- (#\⁄ . "frasl")
- (#\€ . "euro")
- (#\ℑ . "image")
- (#\℘ . "weierp")
- (#\ℜ . "real")
- (#\™ . "trade")
- (#\ℵ . "alefsym")
- (#\← . "larr")
- (#\↑ . "uarr")
- (#\→ . "rarr")
- (#\↓ . "darr")
- (#\↔ . "harr")
- (#\↵ . "crarr")
- (#\⇐ . "lArr")
- (#\⇑ . "uArr")
- (#\⇒ . "rArr")
- (#\⇓ . "dArr")
- (#\⇔ . "hArr")
- (#\∀ . "forall")
- (#\∂ . "part")
- (#\∃ . "exist")
- (#\∅ . "empty")
- (#\∇ . "nabla")
- (#\∈ . "isin")
- (#\∉ . "notin")
- (#\∋ . "ni")
- (#\∏ . "prod")
- (#\∑ . "sum")
- (#\− . "minus")
- (#\∗ . "lowast")
- (#\√ . "radic")
- (#\∝ . "prop")
- (#\∞ . "infin")
- (#\∠ . "ang")
- (#\∧ . "and")
- (#\∨ . "or")
- (#\∩ . "cap")
- (#\∪ . "cup")
- (#\∫ . "int")
- (#\∴ . "there4")
- (#\∼ . "sim")
- (#\≅ . "cong")
- (#\≈ . "asymp")
- (#\≠ . "ne")
- (#\≡ . "equiv")
- (#\≤ . "le")
- (#\≥ . "ge")
- (#\⊂ . "sub")
- (#\⊃ . "sup")
- (#\⊄ . "nsub")
- (#\⊆ . "sube")
- (#\⊇ . "supe")
- (#\⊕ . "oplus")
- (#\⊗ . "otimes")
- (#\⊥ . "perp")
- (#\⋅ . "sdot")
- (#\⋮ . "vellip")
- (#\⌈ . "lceil")
- (#\⌉ . "rceil")
- (#\⌊ . "lfloor")
- (#\⌋ . "rfloor")
- (#\〈 . "lang")
- (#\〉 . "rang")
- (#\◊ . "loz")
- (#\♠ . "spades")
- (#\♣ . "clubs")
- (#\♥ . "hearts")
- (#\♦ . "diams"))))
+ (alist->hash-table '((#\" . "quot") (#\& . "amp")
+ (#\' . "apos")
+ (#\< . "lt")
+ (#\> . "gt")
+ (#\¡ . "iexcl")
+ (#\¢ . "cent")
+ (#\£ . "pound")
+ (#\¤ . "curren")
+ (#\¥ . "yen")
+ (#\¦ . "brvbar")
+ (#\§ . "sect")
+ (#\¨ . "uml")
+ (#\© . "copy")
+ (#\ª . "ordf")
+ (#\« . "laquo")
+ (#\¬ . "not")
+ (#\® . "reg")
+ (#\¯ . "macr")
+ (#\° . "deg")
+ (#\± . "plusmn")
+ (#\² . "sup2")
+ (#\³ . "sup3")
+ (#\´ . "acute")
+ (#\µ . "micro")
+ (#\¶ . "para")
+ (#\· . "middot")
+ (#\¸ . "cedil")
+ (#\¹ . "sup1")
+ (#\º . "ordm")
+ (#\» . "raquo")
+ (#\¼ . "frac14")
+ (#\½ . "frac12")
+ (#\¾ . "frac34")
+ (#\¿ . "iquest")
+ (#\À . "Agrave")
+ (#\Á . "Aacute")
+ (#\Â . "Acirc")
+ (#\Ã . "Atilde")
+ (#\Ä . "Auml")
+ (#\Å . "Aring")
+ (#\Æ . "AElig")
+ (#\Ç . "Ccedil")
+ (#\È . "Egrave")
+ (#\É . "Eacute")
+ (#\Ê . "Ecirc")
+ (#\Ë . "Euml")
+ (#\Ì . "Igrave")
+ (#\Í . "Iacute")
+ (#\Î . "Icirc")
+ (#\Ï . "Iuml")
+ (#\Ð . "ETH")
+ (#\Ñ . "Ntilde")
+ (#\Ò . "Ograve")
+ (#\Ó . "Oacute")
+ (#\Ô . "Ocirc")
+ (#\Õ . "Otilde")
+ (#\Ö . "Ouml")
+ (#\× . "times")
+ (#\Ø . "Oslash")
+ (#\Ù . "Ugrave")
+ (#\Ú . "Uacute")
+ (#\Û . "Ucirc")
+ (#\Ü . "Uuml")
+ (#\Ý . "Yacute")
+ (#\Þ . "THORN")
+ (#\ß . "szlig")
+ (#\à . "agrave")
+ (#\á . "aacute")
+ (#\â . "acirc")
+ (#\ã . "atilde")
+ (#\ä . "auml")
+ (#\å . "aring")
+ (#\æ . "aelig")
+ (#\ç . "ccedil")
+ (#\è . "egrave")
+ (#\é . "eacute")
+ (#\ê . "ecirc")
+ (#\ë . "euml")
+ (#\ì . "igrave")
+ (#\í . "iacute")
+ (#\î . "icirc")
+ (#\ï . "iuml")
+ (#\ð . "eth")
+ (#\ñ . "ntilde")
+ (#\ò . "ograve")
+ (#\ó . "oacute")
+ (#\ô . "ocirc")
+ (#\õ . "otilde")
+ (#\ö . "ouml")
+ (#\÷ . "divide")
+ (#\ø . "oslash")
+ (#\ù . "ugrave")
+ (#\ú . "uacute")
+ (#\û . "ucirc")
+ (#\ü . "uuml")
+ (#\ý . "yacute")
+ (#\þ . "thorn")
+ (#\ÿ . "yuml")
+ (#\Π. "OElig")
+ (#\œ . "oelig")
+ (#\Š . "Scaron")
+ (#\š . "scaron")
+ (#\Ÿ . "Yuml")
+ (#\ƒ . "fnof")
+ (#\ˆ . "circ")
+ (#\˜ . "tilde")
+ (#\Α . "Alpha")
+ (#\Β . "Beta")
+ (#\Γ . "Gamma")
+ (#\Δ . "Delta")
+ (#\Ε . "Epsilon")
+ (#\Ζ . "Zeta")
+ (#\Η . "Eta")
+ (#\Θ . "Theta")
+ (#\Ι . "Iota")
+ (#\Κ . "Kappa")
+ (#\Λ . "Lambda")
+ (#\Μ . "Mu")
+ (#\Ν . "Nu")
+ (#\Ξ . "Xi")
+ (#\Ο . "Omicron")
+ (#\Π . "Pi")
+ (#\Ρ . "Rho")
+ (#\Σ . "Sigma")
+ (#\Τ . "Tau")
+ (#\Υ . "Upsilon")
+ (#\Φ . "Phi")
+ (#\Χ . "Chi")
+ (#\Ψ . "Psi")
+ (#\Ω . "Omega")
+ (#\α . "alpha")
+ (#\β . "beta")
+ (#\γ . "gamma")
+ (#\δ . "delta")
+ (#\ε . "epsilon")
+ (#\ζ . "zeta")
+ (#\η . "eta")
+ (#\θ . "theta")
+ (#\ι . "iota")
+ (#\κ . "kappa")
+ (#\λ . "lambda")
+ (#\μ . "mu")
+ (#\ν . "nu")
+ (#\ξ . "xi")
+ (#\ο . "omicron")
+ (#\π . "pi")
+ (#\ρ . "rho")
+ (#\ς . "sigmaf")
+ (#\σ . "sigma")
+ (#\τ . "tau")
+ (#\υ . "upsilon")
+ (#\φ . "phi")
+ (#\χ . "chi")
+ (#\ψ . "psi")
+ (#\ω . "omega")
+ (#\ϑ . "thetasym")
+ (#\ϒ . "upsih")
+ (#\ϖ . "piv")
+ (#\20002 . "ensp")
+ (#\20003 . "emsp")
+ (#\20011 . "thinsp")
+ (#\– . "ndash")
+ (#\— . "mdash")
+ (#\‘ . "lsquo")
+ (#\’ . "rsquo")
+ (#\‚ . "sbquo")
+ (#\“ . "ldquo")
+ (#\” . "rdquo")
+ (#\„ . "bdquo")
+ (#\† . "dagger")
+ (#\‡ . "Dagger")
+ (#\• . "bull")
+ (#\… . "hellip")
+ (#\‰ . "permil")
+ (#\′ . "prime")
+ (#\″ . "Prime")
+ (#\‹ . "lsaquo")
+ (#\› . "rsaquo")
+ (#\‾ . "oline")
+ (#\⁄ . "frasl")
+ (#\€ . "euro")
+ (#\ℑ . "image")
+ (#\℘ . "weierp")
+ (#\ℜ . "real")
+ (#\™ . "trade")
+ (#\ℵ . "alefsym")
+ (#\← . "larr")
+ (#\↑ . "uarr")
+ (#\→ . "rarr")
+ (#\↓ . "darr")
+ (#\↔ . "harr")
+ (#\↵ . "crarr")
+ (#\⇐ . "lArr")
+ (#\⇑ . "uArr")
+ (#\⇒ . "rArr")
+ (#\⇓ . "dArr")
+ (#\⇔ . "hArr")
+ (#\∀ . "forall")
+ (#\∂ . "part")
+ (#\∃ . "exist")
+ (#\∅ . "empty")
+ (#\∇ . "nabla")
+ (#\∈ . "isin")
+ (#\∉ . "notin")
+ (#\∋ . "ni")
+ (#\∏ . "prod")
+ (#\∑ . "sum")
+ (#\− . "minus")
+ (#\∗ . "lowast")
+ (#\√ . "radic")
+ (#\∝ . "prop")
+ (#\∞ . "infin")
+ (#\∠ . "ang")
+ (#\∧ . "and")
+ (#\∨ . "or")
+ (#\∩ . "cap")
+ (#\∪ . "cup")
+ (#\∫ . "int")
+ (#\∴ . "there4")
+ (#\∼ . "sim")
+ (#\≅ . "cong")
+ (#\≈ . "asymp")
+ (#\≠ . "ne")
+ (#\≡ . "equiv")
+ (#\≤ . "le")
+ (#\≥ . "ge")
+ (#\⊂ . "sub")
+ (#\⊃ . "sup")
+ (#\⊄ . "nsub")
+ (#\⊆ . "sube")
+ (#\⊇ . "supe")
+ (#\⊕ . "oplus")
+ (#\⊗ . "otimes")
+ (#\⊥ . "perp")
+ (#\⋅ . "sdot")
+ (#\⋮ . "vellip")
+ (#\⌈ . "lceil")
+ (#\⌉ . "rceil")
+ (#\⌊ . "lfloor")
+ (#\⌋ . "rfloor")
+ (#\〈 . "lang")
+ (#\〉 . "rang")
+ (#\◊ . "loz")
+ (#\♠ . "spades")
+ (#\♣ . "clubs")
+ (#\♥ . "hearts")
+ (#\♦ . "diams"))))
(define (string->escaped-html s port)
"Write the HTML escaped form of S to PORT."
@@ -313,9 +310,7 @@
(define (object->escaped-html obj port)
"Write the HTML escaped form of OBJ to PORT."
- (string->escaped-html
- (call-with-output-string (cut display obj <>))
- port))
+ (string->escaped-html (call-with-output-string (cut display obj <>)) port))
(define (attribute-value->html value port)
"Write the HTML escaped form of VALUE to PORT."
@@ -334,11 +329,11 @@
list ATTRS and the child nodes in BODY."
(format port "<~a" tag)
(for-each (match-lambda
- ((attr value)
- (display #\space port)
- (attribute->html attr value port)))
- attrs)
- (if (and (null? body) (self-closing-tag? tag))
+ ((attr value)
+ (display #\space port)
+ (attribute->html attr value port))) attrs)
+ (if (and (null? body)
+ (self-closing-tag? tag))
(display " />" port)
(begin
(display #\> port)
@@ -348,7 +343,8 @@ list ATTRS and the child nodes in BODY."
(define (doctype->html doctype port)
(format port "<!DOCTYPE ~a>" doctype))
-(define* (sxml->html tree #:optional (port (current-output-port)))
+(define* (sxml->html tree
+ #:optional (port (current-output-port)))
"Write the serialized HTML form of TREE to PORT."
(match tree
(() *unspecified*)
@@ -357,10 +353,13 @@ list ATTRS and the child nodes in BODY."
;; Unescaped, raw HTML output
(('raw html)
(display html port))
- (((? symbol? tag) ('@ attrs ...) body ...)
+ (((? symbol? tag)
+ ('@ attrs ...) body ...)
(element->html tag attrs body port))
- (((? symbol? tag) body ...)
- (element->html tag '() body port))
+ (((? symbol? tag)
+ body ...)
+ (element->html tag
+ '() body port))
((nodes ...)
(for-each (cut sxml->html <> port) nodes))
((? string? text)
diff --git a/web/view/markdown.scm b/web/view/markdown.scm
index 2af2b26..6aa2935 100644
--- a/web/view/markdown.scm
+++ b/web/view/markdown.scm
@@ -6,50 +6,126 @@
#:use-module (ice-9 receive)
#:use-module (ice-9 string-fun)
#:use-module (ice-9 textual-ports)
+ #:use-module (ice-9 exceptions)
+ #:use-module (ice-9 popen)
+ #:use-module (ice-9 rdelim)
#:use-module (sxml simple)
#:use-module (web client)
#:use-module (web uri)
#:use-module (web request)
#:use-module (web sxml)
#:use-module (commonmark)
-
- #:export (markdown-file->sxml
- markdown-github->sxml
- fetch-raw-file)
- )
-
+ #:export (markdown-file->sxml markdown-github->sxml fetch-file
+ fetch-raw-file commit-file git-invoke))
(define (markdown-file->sxml fn)
"Parse a local file"
- (commonmark->sxml
- (call-with-input-file fn
- get-string-all)))
-
-;; --- fetch github style URLs
+ (commonmark->sxml (call-with-input-file fn
+ get-string-all)))
(define (fetch-raw-file url)
(receive (response-status response-body)
- (http-request url)
- response-body))
+ (http-request url) response-body))
-;; https://github.com/genenetwork/gn-docs/master/general/brand/aging/home.md
-;; https://raw.githubusercontent.com/genenetwork/gn-docs/master/general/brand/aging/home.md
-;; https://github.com/genenetwork/gn-docs/edit/master/general/brand/aging/home.md
+(define* (form-github-raw-url project repo page #:optional (branch "master"))
+ (string-append "https://raw.githubusercontent.com/"
+ project
+ "/"
+ repo
+ "/"
+ branch
+ "/"
+ (string-join page "/")))
-(define (form-github-raw-url project repo page)
- (string-append "https://raw.githubusercontent.com/" project "/" repo "/master/" (string-join page "/")))
-
-(define (form-github-edit-url project repo page)
- (string-append "https://github.com/" project "/" repo "/edit/master/" (string-join page "/")))
+(define* (form-github-edit-url project repo page #:optional (branch "master"))
+ (string-append "https://github.com/"
+ project
+ "/"
+ repo
+ "/edit/"
+ branch
+ "/"
+ (string-join page "/")))
(define (markdown-github->sxml path)
"Parse a github markdown file that is formed like genenetwork/gn-docs/general/brand/aging/home.md"
- (match-let (((project repo page ...) (string-split path #\/)))
- `(div (@ (class "markdown"))
- ,(commonmark->sxml
- (fetch-raw-file (pk (form-github-raw-url project repo (pk page)))))
- (p
- (div (@ (class "button-align-right"))
- (a (@ (href ,(form-github-edit-url project repo page)) (role "button")) "edit")))
- (br)
- (br))))
+ (match-let (((project repo page ...)
+ (string-split path #\/)))
+ `(div (@ (class "markdown"))
+ ,(commonmark->sxml (fetch-raw-file (pk (form-github-raw-url
+ project repo
+ (pk page)))))
+ (p (div (@ (class "button-align-right"))
+ (a (@ (href ,(form-github-edit-url project repo
+ page))
+ (role "button")) "edit")))
+ (br)
+ (br))))
+
+(define (fetch-file repo query-path)
+ (let* ((abs-path (format #f "~a/~a" repo query-path)))
+ (if (file-exists? abs-path)
+ (let* ((full-path (canonicalize-path abs-path))
+ (content (call-with-input-file full-path
+ get-string-all))
+ (commit-sha (get-latest-commit-sha1 repo)))
+ `(("file_path" unquote query-path)
+ ("content" unquote content)
+ ("hash" unquote commit-sha)))
+ (throw 'file-error
+ (format #f "~a does not exists" abs-path)))))
+
+(define (git-invoke repo-path . args)
+ (apply system* "git" "-C" repo-path args))
+
+(define (git-repository? repo-path)
+ (let ((data (git-invoke repo-path "rev-parse")))
+ (zero? data)))
+
+(define (get-latest-commit-sha1 repo-path)
+ (let* ((output-port (open-input-pipe (string-append "git -C " repo-path
+ " log -n 1 --pretty=format:%H HEAD")))
+ (commit-sha (read-line output-port)))
+ (close-port output-port) commit-sha))
+
+(define* (commit-file repo
+ file-path
+ content
+ commit-message
+ username
+ email
+ #:optional (prev-commit ""))
+ (unless (string=? prev-commit
+ (get-latest-commit-sha1 repo))
+ (throw 'system-error
+ (format #f
+ "Commits do no match.Please pull in latest changes for current * ~a * and prev * ~a * "
+ (get-latest-commit-sha1 repo) prev-commit)))
+ (if (git-repository? repo)
+ (match (file-exists? (format #f "~a/~a" repo file-path))
+ (#t (with-output-to-file (format #f "~a/~a" repo file-path)
+ (lambda ()
+ (display content)))
+ (let* ((git-add-file (git-invoke repo "add" file-path))
+ (git-commit-file (git-invoke repo
+ "commit"
+ "-m"
+ commit-message
+ "-m"
+ " * Commit made via the GN Markdown Editor"
+ "--author"
+ (format #f "~a <~a>" username email)))
+ (git-commit-sha (get-latest-commit-sha1 repo)))
+ (if (zero? git-commit-file)
+ `(("status" . "201")
+ ("message" . "committed file successfully")
+ ("content" . ,content)
+ ("commit_sha" . ,git-commit-sha)
+ ("commit_message" . ,commit-message))
+ `(("status" . "200")
+ ("message" . "Nothing to commit, working tree clean")
+ ("commit_sha" . ,git-commit-sha)))))
+ (#f (throw 'system-error
+ (format #f "~a File does not exist error" file-path))))
+ (throw 'system-error
+ (format #f "~a is no a git repo" repo))))
diff --git a/web/webserver.scm b/web/webserver.scm
index 05e842c..c0fb9a1 100755..100644
--- a/web/webserver.scm
+++ b/web/webserver.scm
@@ -1,70 +1,61 @@
-#!/usr/bin/env guile \
--e main -s
-!#
-;; Minimal web server can be started from command line. Current example routes:
-;;
-;; localhost:8080/
-;;
-
-(use-modules
- (json)
- (ice-9 match)
- (ice-9 format)
- (ice-9 iconv)
- (ice-9 receive)
- (ice-9 string-fun)
- ;; (ice-9 debugger)
- ;; (ice-9 breakpoints)
- ;; (ice-9 source)
- (srfi srfi-1)
- (srfi srfi-11) ; let-values
- (srfi srfi-19) ; time
- (srfi srfi-26)
- (rnrs io ports) ; bytevector-all
- (web http)
- (web client)
- (web request)
- (web response)
- (web uri)
- (fibers web server)
- (gn cache memoize)
- (web gn-uri)
- (gn db sparql)
- (gn data species)
- (gn data group)
- (web sxml)
- (web view view)
- (web view doc))
-
-
-(define info `(
- ("name" . "GeneNetwork REST API")
- ("version" . ,get-version)
- ("comment" . "This is the official REST API for the GeneNetwork service hosted at https://genenetwork.org/")
- ("license" . (("source code (unless otherwise specified)" . "Affero GNU Public License 3.0 (AGPL3)")
- ("data (unless otherwise specified)" . "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)")))
- ("note" . "This is work in progress (WIP). Note that the final base URL will change! The temporary prefix is:")
- ("prefix" . ,(prefix))
- ("links". (("species" . ,(mk-meta "species"))))))
-
-(define info-meta `(
- ("doc" . ,(mk-html "info"))
- ("API" .
- ((,(mk-url "species")."Get a list of all species")
- (,(mk-url "mouse")."Get information on mouse")
- (,(mk-url "datasets")."Get a list of datasets")))))
+(use-modules (json)
+ (ice-9 match)
+ (ice-9 format)
+ (ice-9 iconv)
+ (ice-9 receive)
+ (ice-9 string-fun)
+ (ice-9 exceptions)
+ (srfi srfi-1)
+ (srfi srfi-11)
+ (srfi srfi-19)
+ (srfi srfi-26)
+ (rnrs io ports)
+ (rnrs bytevectors)
+ (web http)
+ (web client)
+ (web request)
+ (web response)
+ (web uri)
+ (fibers web server)
+ (gn cache memoize)
+ (web gn-uri)
+ (gn db sparql)
+ (gn data species)
+ (gn data group)
+ (web sxml)
+ (web view view)
+ (web view doc)
+ (web view markdown))
+(define +current-repo-path+
+ (getenv "CURRENT_REPO_PATH"))
+
+(define +cgit-repo-path+
+ (getenv "CGIT_REPO_PATH"))
+
+(define +info+
+ `(("name" . "GeneNetwork REST API") ("version" ,get-version)
+ ("comment" . "This is the official REST API for the GeneNetwork service hosted at https://genenetwork.org/")
+ ("license"
+ ("source code (unless otherwise specified)" . "Affero GNU Public License 3.0 (AGPL3)")
+ ("data (unless otherwise specified)" . "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)"))
+ ("note" . "This is work in progress (WIP). Note that the final base URL will change! The temporary prefix is:")
+ ("prefix" ,(prefix))
+ ("links" ("species" ,(mk-meta "species")))))
+
+(define +info-meta+
+ `(("doc" ,(mk-html "info"))
+ ("API" ((,(mk-url "species")) . "Get a list of all species")
+ ((,(mk-url "mouse")) . "Get information on mouse")
+ ((,(mk-url "datasets")) . "Get a list of datasets"))))
(define (get-id-data id)
"Get data based on identifier id. If it is a taxon return the taxon data,
otherwise search for set/group data"
- (let ([taxoninfo (get-expanded-taxon-data id)])
- (if taxoninfo
- taxoninfo
+ (let ((taxoninfo (get-expanded-taxon-data id)))
+ (if taxoninfo taxoninfo
(cdr (get-group-data id)))))
-;; ---- REST API web server handler
-
(define (not-found2 request)
(values (build-response #:code 404)
(string-append "Resource X not found: "
@@ -72,85 +63,189 @@ otherwise search for set/group data"
(define (not-found uri)
(list (build-response #:code 404)
- (string-append "Resource not found: " (uri->string uri))))
-
+ (string-append "Resource not found: "
+ (uri->string uri))))
(define file-mime-types
- '(("css" . (text/css))
- ("js" . (text/javascript))
- ("svg" . (image/svg+xml))
- ("png" . (image/png))
- ("gif" . (image/gif))
- ("jpg" . (image/jpg))
- ("woff" . (application/font-woff))
- ("ttf" . (application/octet-stream))
- ("map" . (text/json))
- ("html" . (text/html))))
+ '(("css" text/css)
+ ("js" text/javascript)
+ ("svg" image/svg+xml)
+ ("png" image/png)
+ ("gif" image/gif)
+ ("jpg" image/jpg)
+ ("woff" application/font-woff)
+ ("ttf" application/octet-stream)
+ ("map" text/json)
+ ("html" text/html)))
(define (file-extension file-name)
(last (string-split file-name #\.)))
-(define* (render-static-image file-name #:key (extra-headers '()))
+(define* (render-static-image file-name
+ #:key (extra-headers '()))
(let* ((stat (stat file-name #f))
(modified (and stat
- (make-time time-utc 0 (stat:mtime stat)))))
- (list `((content-type . ,(assoc-ref file-mime-types
- (file-extension file-name)))
- (last-modified . ,(time-utc->date modified)))
- (call-with-input-file file-name get-bytevector-all))))
+ (make-time time-utc 0
+ (stat:mtime stat)))))
+ (list `((content-type ,(assoc-ref file-mime-types
+ (file-extension file-name)))
+ (last-modified ,(time-utc->date modified)))
+ (call-with-input-file file-name
+ get-bytevector-all))))
-(define* (render-static-file path #:optional rec #:key (extra-headers '()))
+(define* (render-static-file path
+ #:optional rec
+ #:key (extra-headers '()))
(let* ((stat (stat path #f))
(modified (and stat
- (make-time time-utc 0 (stat:mtime stat)))))
- (list `((content-type . ,(assoc-ref file-mime-types
- (file-extension path)))
- (last-modified . ,(time-utc->date modified)))
- (call-with-input-file path get-bytevector-all))))
+ (make-time time-utc 0
+ (stat:mtime stat)))))
+ (list `((content-type ,(assoc-ref file-mime-types
+ (file-extension path)))
+ (last-modified ,(time-utc->date modified)))
+ (call-with-input-file path
+ get-bytevector-all))))
-(define* (render-doc path page #:optional rec #:key (extra-headers '()))
+(define* (render-doc path
+ page
+ #:optional rec
+ #:key (extra-headers '()))
(list (append extra-headers
- '((content-type . (text/html))))
+ '((content-type text/html)))
(lambda (port)
(sxml->html (view-doc path page rec) port))))
-(define* (render-brand path #:optional rec #:key (extra-headers '()))
+(define* (render-brand path
+ #:optional rec
+ #:key (extra-headers '()))
(list (append extra-headers
- '((content-type . (text/html))))
+ '((content-type text/html)))
(lambda (port)
(sxml->html (view-brand path) port))))
(define (render-json json)
- (list '((content-type . (application/json)))
+ (list '((content-type application/json))
(lambda (port)
(scm->json json port))))
(define (render-json-string2 json)
- (list '((content-type . (text/plain)))
- (lambda (port)
- (format port "~a" "foo"))))
+ (list '((content-type text/plain))
+ (lambda (port)
+ (format port "~a" "foo"))))
+
+(define (build-json-response status-code json)
+ (list (build-response #:code status-code
+ #:headers `((content-type application/json)))
+ (lambda (port)
+ (scm->json json port))))
+
+(define (decode-request-json body)
+ (if (not body)
+ '()
+ (json-string->scm (utf8->string body))))
+
+(define (decode-query-component component)
+ (let* ((index (string-index component #\=))
+ (key (if index
+ (substring component 0 index) component))
+ (value (if index
+ (substring component
+ (1+ index)) "")))
+ (cons (string->symbol (uri-decode key))
+ (uri-decode value))))
+
+(define (edit-file-handler repo request)
+ (catch 'file-error
+ (lambda ()
+ (let* ((query (uri-query (request-uri request)))
+ (params (if (not query)
+ '()
+ (map decode-query-component
+ (string-split query #\&))))
+ (query-path (assoc-ref params
+ 'file_path)))
+ (if query-path
+ (build-json-response 200
+ (fetch-file repo query-path))
+ (throw 'file-error
+ "Please provide a valid file path in the query"))))
+ (lambda (key . args)
+ (let ((msg (car args)))
+ (build-json-response 400
+ `(("error" ,key)
+ ("msg" ,msg)))))))
+
+(define (invalid-data? data target)
+ (if (string? (assoc-ref data target))
+ (if (string-null? (assoc-ref data target))
+ (throw 'system-error
+ (format #f "Value for Key *** ~a *** Cannot be Empty" target))
+ (assoc-ref data target))
+ (throw 'system-error
+ (format #f "The Key *** ~a *** is missing in your Json Data"
+ target))))
+
+(define (commit-file-handler repo request body)
+ (catch 'system-error
+ (lambda ()
+ (let* ((post-data (decode-request-json body))
+ (_ (for-each (lambda (target)
+ (invalid-data? post-data target))
+ '("filename" "content" "username" "email"
+ "prev_commit")))
+ (file-name (assoc-ref post-data "filename"))
+ (content (assoc-ref post-data "content"))
+ (username (assoc-ref post-data "username"))
+ (email (assoc-ref post-data "email"))
+ (commit-message (assoc-ref post-data "commit_message"))
+ (prev-commit (assoc-ref post-data "prev_commit")))
+ (build-json-response 200
+ ((lambda ()
+ (let ((message
+ (commit-file +current-repo-path+
+ file-name
+ content
+ commit-message
+ username
+ email
+ prev-commit)))
+ (git-invoke +current-repo-path+ "push" +cgit-repo-path+)
+ message))))))
+ (lambda (key . args)
+ (let ((msg (car args)))
+ (build-json-response 400
+ `(("error" ,key)
+ ("msg" ,msg)))))))
(define (controller request body)
(match-lambda
(('GET)
- (render-json info))
+ (render-json +info+))
(('GET "version")
(render-json get-version))
- (('GET "css" fn )
+ (('GET "css" fn)
(render-static-file (string-append "css/" fn)))
- (('GET "map" fn )
+ (('GET "map" fn)
(render-static-file (string-append "css/" fn)))
(('GET "static" "images" fn)
(render-static-image (string-append "static/images/" fn)))
(('GET "home" path)
(render-brand path))
(('GET "doc" "species.html")
- (render-doc "doc" "species.html" (get-species-meta)))
+ (render-doc "doc" "species.html"
+ (get-species-meta)))
(('GET "doc" taxon)
(match (string->list taxon)
- [(name ... #\. #\h #\t #\m #\l)
- (render-doc "doc" taxon (get-expanded-taxon-meta (list->string name)))]))
- (('GET "doc" path ... page) ; serve documents from /doc/
+ ((name ...
+ #\.
+ #\h
+ #\t
+ #\m
+ #\l)
+ (render-doc "doc" taxon
+ (get-expanded-taxon-meta (list->string name))))))
+ (('GET "doc" path ... page)
+ ;; serve documents from /doc/
(render-doc path page))
(('GET "species.json")
(render-json (get-species-data)))
@@ -158,15 +253,34 @@ otherwise search for set/group data"
(render-json (get-species-meta)))
(('GET "species")
(render-json (get-species-meta)))
+ (('GET "edit")
+ (edit-file-handler +current-repo-path+ request))
+ (('POST "commit")
+ (commit-file-handler +current-repo-path+ request body))
(('GET id)
- (let ([names (get-species-shortnames (get-expanded-species))])
+ (let ((names (get-species-shortnames (get-expanded-species))))
(match (string->list id)
- [(name ... #\. #\m #\e #\t #\a #\. #\j #\s #\o #\n) (render-json (get-expanded-taxon-meta (list->string name)))]
- [(name ... #\. #\j #\s #\o #\n) (render-json
- (get-id-data (list->string name)))]
- [rest (render-json "NOP")])))
- (_ (not-found (request-uri request)))
- ))
+ ((name ...
+ #\.
+ #\m
+ #\e
+ #\t
+ #\a
+ #\.
+ #\j
+ #\s
+ #\o
+ #\n)
+ (render-json (get-expanded-taxon-meta (list->string name))))
+ ((name ...
+ #\.
+ #\j
+ #\s
+ #\o
+ #\n)
+ (render-json (get-id-data (list->string name))))
+ (rest (render-json "NOP")))))
+ (_ (not-found (request-uri request)))))
(define (request-path-components request)
(split-and-decode-uri-path (uri-path (request-uri request))))
@@ -182,8 +296,7 @@ otherwise search for set/group data"
(define (start-web-server address port)
(format (current-error-port)
- "GN REST API web server listening on http://~a:~a/~%"
- address port)
+ "GN REST API web server listening on http://~a:~a/~%" address port)
;; Wrap handler in another function to support live hacking via the
;; REPL. If handler is passed as is and is then redefined via the
;; REPL, the web server will still be using the old handler. The
@@ -199,6 +312,4 @@ otherwise search for set/group data"
(newline)
(let ((listen (inexact->exact (string->number (car (cdr args))))))
(display `("listening on" ,listen))
- ;; (write listen)
- ;; (run-server hello-world-handler 'http `(#:port ,listen))))
- (start-web-server "127.0.0.1" listen)))
+ (start-web-server "127.0.0.1" listen)))