Skip to content

Commit

Permalink
feat: java and typescript generation draft
Browse files Browse the repository at this point in the history
  • Loading branch information
krvital committed Aug 31, 2024
1 parent a45a45b commit d0200e7
Show file tree
Hide file tree
Showing 5 changed files with 449 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/aidbox_sdk/cli.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
["-h" "--help"]])

(def supported-commands #{"generate"})
(def supported-languages #{"dotnet" "python"})
(def supported-languages #{"dotnet" "python" "typescript" "java"})

(defn validate-args [args]
(let [[command target-language input] args]
Expand Down
7 changes: 4 additions & 3 deletions src/aidbox_sdk/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
[aidbox-sdk.converter :as converter]
[aidbox-sdk.fhir :as fhir]
[aidbox-sdk.generator :as generator]
[aidbox-sdk.generator.java :as java]
[aidbox-sdk.generator.dotnet :as dotnet]
[aidbox-sdk.generator.helpers :refer [vector->map]]
[aidbox-sdk.generator.python :as python]
[aidbox-sdk.generator.typescript :as typescript]
[aidbox-sdk.schema :as importer]
[clojure.java.io :as io]))

Expand Down Expand Up @@ -49,8 +50,8 @@
(case lang
:dotnet dotnet/generator
:python python/generator
#_#_#_#_:typescript typescript/generator
:java java/generator))
:typescript typescript/generator
:java java/generator))

(defn generate! [target-language input options]
(let [output-dir (io/file (:output-dir options))
Expand Down
165 changes: 165 additions & 0 deletions src/aidbox_sdk/generator/java.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
(ns aidbox-sdk.generator.java
(:require
[aidbox-sdk.generator.helpers :refer [->pascal-case uppercase-first-letter]]
[aidbox-sdk.generator.utils :as u]
[clojure.java.io :as io]
[clojure.string :as str])
(:import
[aidbox_sdk.generator CodeGenerator]))

(defn datatypes-file-path []
(io/file "datatypes.java"))

(defn package->directory
"Generates directory name from package name.
Example:
hl7.fhir.r4.core -> hl7-fhir-r4-core"
[x]
(str/replace x #"[\.#]" "-"))

(defn url->resource-name [reference]
(last (str/split (str reference) #"/")))

(defn class-name
"Generate class name from schema url."
[url]
(uppercase-first-letter (url->resource-name url)))

(defn resource-file-path [ir-schema]
(io/file (package->directory (:package ir-schema))
(str (->pascal-case (:name ir-schema)) ".java")))

(defn ->lang-type [fhir-type]
(case fhir-type
;; Primitive Types
"boolean" "boolean"
"instant" "String"
"time" "String"
"date" "String"
"dateTime" "String"
"decimal" "float"

"integer" "int"
"unsignedInt" "int"
"positiveInt" "int"

"integer64" "int"
"base64Binary" "String"

"uri" "String"
"url" "String"
"canonical" "String"
"oid" "String"
"uuid" "String"

"string" "String"
"code" "String"
"markdown" "String"
"id" "String"

;; hardcoded just in case
"Meta" "Meta"
;; else
fhir-type))

(defn generate-accessor [{:keys [name array required type base]}]
(let [lang-type (->lang-type type)]
(str
(str "public " lang-type " get" (uppercase-first-letter name) "() {\n"
u/indent "return " name ";"
"\n}")
"\n\n"
(str "public void set" (uppercase-first-letter name) "(" lang-type " " name ") {\n"
"this." name " = " name ";"
"\n}"))))

(defn generate-property [{:keys [name array required type base]}]
(let [lang-type (->lang-type type)
type (if array
(format "List<%s>" lang-type)
lang-type)]
(str "private " type " " name ";")))

(defn generate-class [ir-schema & [inner-classes]]
(let [base-class (url->resource-name (:base ir-schema))
schema-name (or (:url ir-schema) (:name ir-schema))
class-name' (class-name schema-name)
properties (->> (:elements ir-schema)
(map generate-property)
(remove nil?)
(map u/add-indent)
(str/join "\n"))

accessors (->> (:elements ir-schema)
(map generate-accessor)
(remove nil?)
(map u/add-indent)
(str/join "\n"))]
(str "public class " class-name' " extends " base-class " {\n"
(when (and inner-classes
(seq inner-classes))
"\n")
(str/join "\n\n" (map #(->> % str/split-lines (map u/add-indent) (str/join "\n")) inner-classes))
(when (and inner-classes
(seq inner-classes))
"\n")

properties
"\n"
accessors
"\n}")))

(defn generate-deps [deps]
(->> deps
(map (fn [{:keys [module members]}]
(if (seq members)
(str "import { " (str/join ", " members) " } from '" module "';")
(str "import " module ";"))))
(str/join "\n")))

(defn generate-module
[& {name' :name
:keys [deps classes interfaces structs enums delegates]
:or {classes []
interfaces []
structs []
enums []
delegates []}}]
(->> (conj []
(str "package " name' ";")
(generate-deps deps)
classes)
(flatten)
(str/join "\n\n")))

(defn generate-backbone-classes
"Generates classes from schema's backbone elements."
[ir-schema]
(->> (ir-schema :backbone-elements)
(map #(assoc % :base "BackboneElement"))
(map generate-class)))

(defrecord JavaCodeGenerator []
CodeGenerator
(generate-datatypes [_ ir-schemas]
[{:path (datatypes-file-path)
:content (generate-module
:deps []
:classes (map (fn [ir-schema]
(generate-class ir-schema
(generate-backbone-classes ir-schema)))
ir-schemas))}])
(generate-resource-module [_ ir-schema]
{:path (resource-file-path ir-schema)
:content (generate-module
{:name "aidbox.fhir.r4"
:deps [{:module "aidbox.fhir.datatypes.*" :members []}
{:module "java.util.List" :members []}]
:classes [(generate-class ir-schema
(generate-backbone-classes ir-schema))]})})
(generate-search-params [_ ir-schemas] [])
(generate-constraints [_ ir-schemas] [])
(generate-sdk-files [this] []))

(def generator (->JavaCodeGenerator))
141 changes: 141 additions & 0 deletions src/aidbox_sdk/generator/typescript.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
(ns aidbox-sdk.generator.typescript
(:require
[aidbox-sdk.generator.helpers :refer [uppercase-first-letter ->pascal-case]]
[aidbox-sdk.generator.utils :as u]
[clojure.java.io :as io]
[clojure.string :as str])
(:import
[aidbox_sdk.generator CodeGenerator]))

(defn package->directory
"Generates directory name from package name.
Example:
hl7.fhir.r4.core -> hl7-fhir-r4-core"
[x]
(str/replace x #"[\.#]" "-"))

(defn url->resource-name [reference]
(last (str/split (str reference) #"/")))

(defn datatypes-file-path []
(io/file "datatypes.ts"))

(defn resource-file-path [ir-schema]
(io/file (package->directory (:package ir-schema))
(str (->pascal-case (:name ir-schema)) ".ts")))

(defn constraint-file-path [ir-schema name]
(io/file (package->directory (:package ir-schema))
(str (->pascal-case (url->resource-name name)) ".ts")))

(defn search-param-filepath [ir-schema]
(io/file "search" (str (:name ir-schema) "SearchParameters.ts")))

(defn ->lang-type [fhir-type]
(case fhir-type
;; Primitive Types
"boolean" "boolean"
"instant" "string"
"time" "string"
"date" "string"
"dateTime" "string"
"decimal" "number"

"integer" "number"
"unsignedInt" "number"
"positiveInt" "number"

"integer64" "number"
"base64Binary" "string"

"uri" "string"
"url" "string"
"canonical" "string"
"oid" "string"
"uuid" "string"

"string" "string"
"code" "string"
"markdown" "string"
"id" "string"

;; hardcoded just in case
"Meta" "Meta"
;; else
fhir-type))

(defn class-name
"Generate class name from schema url."
[url]
(uppercase-first-letter (url->resource-name url)))

(defn generate-property [{:keys [name array required type base]}]
(let [lang-type (->lang-type type)]
(str name (when-not required "?") ": " lang-type (when array "[]") ";")))

(defn generate-class
"Generates Python class from IR (intermediate representation) schema."
[ir-schema & [inner-classes]]
(let [base-class (url->resource-name (:base ir-schema))
schema-name (or (:url ir-schema) (:name ir-schema))
class-name' (class-name schema-name)
properties (->> (:elements ir-schema)
(map generate-property)
(remove nil?)
(map u/add-indent)
(str/join "\n"))]
(str
(when (seq inner-classes)
(str (str/join "\n\n" inner-classes) "\n\n"))

"export class " class-name' " extends " base-class " {\n"
properties
"\n}")))

(defn generate-deps [deps]
(->> deps
(map (fn [{:keys [module members]}]
(if (seq members)
(str "import { " (str/join ", " members) " } from '" module "';")
(str "import " module ";"))))
(str/join "\n")))

(defn generate-module
[& {:keys [deps classes]
:or {classes []}}]
(->> (conj []
(generate-deps deps)
classes)
(flatten)
(str/join "\n\n")))

(defn generate-backbone-classes
"Generates classes from schema's backbone elements."
[ir-schema]
(->> (ir-schema :backbone-elements)
(map #(assoc % :base "BackboneElement"))
(map generate-class)))

(defrecord TypeScriptCodeGenerator []
CodeGenerator
(generate-datatypes [_ ir-schemas]
[{:path (datatypes-file-path)
:content (generate-module
:deps []
:classes (map (fn [ir-schema]
(generate-class ir-schema
(generate-backbone-classes ir-schema)))
ir-schemas))}])
(generate-resource-module [_ ir-schema]
{:path (resource-file-path ir-schema)
:content (generate-module
{:deps [{:module "../datatypes" :members ["Address" "Attachment" "BackboneElement" "CodeableConcept" "ContactPoint" "HumanName" "Identifier" "Period" "Reference"]}]
:classes [(generate-class ir-schema
(generate-backbone-classes ir-schema))]})})

(generate-search-params [_ ir-schemas] [])
(generate-constraints [_ ir-schemas] [])
(generate-sdk-files [this] []))

(def generator (->TypeScriptCodeGenerator))
Loading

0 comments on commit d0200e7

Please sign in to comment.