From 1696367e8ca7c094b7c1216ecfa9e29fd2b78a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20T=C3=B3th?= <tothmiklostibor@gmail.com> Date: Sat, 27 Feb 2021 23:44:27 +0100 Subject: [PATCH] class diagrams are fun --- .gitlab-ci.yml | 9 --- .gitmodules | 3 - Dockerfile | 3 +- Makefile | 9 ++- plab/classdiag.go | 197 ++++++++++++++++++++++++++++++++++++++++++++++ plab/javadoc.go | 76 +++++++++++++----- plab/main.go | 2 +- 7 files changed, 262 insertions(+), 37 deletions(-) delete mode 100644 .gitmodules create mode 100644 plab/classdiag.go diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e0605a..0df48d5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,15 +18,6 @@ go: paths: - plab/out/plab -#java: -# image: openjdk:11 -# stage: build -# script: -# - cd robinbird -# - ./gradlew build -# artifacts: -# paths: -# - robinbird/build/distributions/robinbird.tar Build: stage: docker diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 434f625..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "robinbird"] - path = robinbird - url = https://github.com/SeokhyunKim/robinbird.git diff --git a/Dockerfile b/Dockerfile index 9390111..e517f80 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,8 @@ FROM texlive/texlive -RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get -y upgrade && apt-get -y install openjdk-11-jdk-headless hunspell hunspell-hu hunspell-en-gb hunspell-en-us python3-pip && pip3 install javalang +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get -y upgrade && apt-get -y install inkscape plantuml openjdk-11-jdk-headless hunspell hunspell-hu hunspell-en-gb hunspell-en-us python3-pip && pip3 install javalang COPY tikz-uml.sty /usr/local/texlive/2020/texmf-dist/tex/latex/tikz-uml/tikz-uml.sty COPY jsonDoclet.jar /root/jsonDoclet.jar COPY ./plab/out/plab /usr/bin/plab -#ADD robinbird/build/distributions/robinbird.tar / RUN mktexlsr COPY gen_seq_diag.py / RUN chmod +x /gen_seq_diag.py \ No newline at end of file diff --git a/Makefile b/Makefile index f8f2279..ed4db04 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ -all: docker +all: podman -docker: - docker build -t projlab/projlab . +go_plab: + bash -c "cd plab; make podman" + +podman: go_plab + podman build -t projlab/projlab . diff --git a/plab/classdiag.go b/plab/classdiag.go new file mode 100644 index 0000000..19720c7 --- /dev/null +++ b/plab/classdiag.go @@ -0,0 +1,197 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "regexp" + "strings" +) + +var Classdiag = Subcommand{ + Name: "classdiag", + Command: func(args []string) { + if len(args) > 1 { + fmt.Println("usage: classdiag [source location]") + return + } + + if len(args) != 0 { + e := genJavadoc(args[0]) + if e != nil { + panic(e) + } + } + + classes, e := readJavadoc() + if e != nil { + panic(e) + } + + classes = linkInheritance(classes) + classes = resolveDocsFromInheritance(classes) + + makePlantUML(classes) + + fakePipeline() + }, + Help: "create and parse javadoc from code", +} + +func makePlantUML(classes map[string]*Class) { + genPlantUMLStr(classes) +} + +var arrowRegex = regexp.MustCompile(`(<|<\|)?[^0-9](--|\.\.)(>|\|>)?`) + +const classFile = "/tmp/projlabclasses.txt" + +func genPlantUMLStr(classes map[string]*Class) { + var output bytes.Buffer + output.WriteString(fmt.Sprintln("@startuml")) + output.WriteString(fmt.Sprintln("skinparam classAttributeIconSize 0")) + output.WriteString(fmt.Sprintln("set namespaceSeparator none")) + + for _, c := range classes { + after := "" + if c.Superclass.realClass != nil { + output.WriteString(fmt.Sprintf("%s --|> %s\n", c.Name, c.Superclass.realClass.Name)) + } + + for _, i := range c.Interfaces { + output.WriteString(fmt.Sprintf("%s ..|> %s\n", c.Name, i.Name)) + } + + t := "class" + if strings.Contains(c.Modifiers, "interface") { + t = "interface" + } + if strings.Contains(c.Modifiers, "abstract") { + t = "abstract " + t + } + output.WriteString(fmt.Sprintf("%s %s {\n", t, c.Name)) + for _, f := range c.Fields { + uml := false + for _, a := range f.Annotations { + if a.TypeName == "Docs" { + for _, e := range a.Elements { + switch e.QualifiedName { + case "projlab.Docs.uml": + e.Value = strings.ReplaceAll(e.Value, "\\\"", "🍆") + e.Value = strings.ReplaceAll(e.Value, "\"", "") + e.Value = strings.ReplaceAll(e.Value, "🍆", "\"") + uml = true + if e.Value != "" { + after += fmt.Sprintf("\n%s %s %s", f.Type.Name, e.Value, c.Name) + } + } + } + } + } + if !uml { + output.WriteString(fmt.Sprintf("%s%s: %s\n", f.GetVisibility(), f.Name, f.Type.Name)) + } + } + for _, m := range c.Methods { + params := "" + for _, p := range m.Parameters { + params += fmt.Sprintf("%s: %s, ", p.Name, p.Type.Name) + } + params = strings.TrimSuffix(params, ", ") + mod := "" + if strings.Contains(m.Modifiers, "abstract") { + mod += " {abstract} " + } + if strings.Contains(m.Modifiers, "static") { + mod += " {static} " + } + output.WriteString(fmt.Sprintf("%s%s%s(%s): %s\n", m.GetVisibility(), mod, m.Name, params, m.ReturnType.Name)) + } + output.WriteString(fmt.Sprintln("}")) + output.WriteString(fmt.Sprintln(after)) + + for _, a := range c.Annotations { + if a.TypeName == "Docs" { + for _, e := range a.Elements { + switch e.QualifiedName { + case "projlab.Docs.uml": + e.Value = strings.ReplaceAll(e.Value, "\\\"", "🍆") + e.Value = strings.ReplaceAll(e.Value, "\"", "") + e.Value = strings.ReplaceAll(e.Value, "🍆", "\"") + output.WriteString(fmt.Sprintln(e.Value)) + } + } + } + } + + output.WriteString(fmt.Sprintln()) + } + + almostDone := output.String() + lines := strings.Split(almostDone, "\n") + skip := make(map[string][]string) + for _, l := range lines { + if arrowRegex.MatchString(l) { + words := strings.Split(l, " ") + one := words[0] + other := words[len(words)-1] + skip[one] = append(skip[one], other) + skip[other] = append(skip[other], one) + } + } + + for _, c := range classes { + out, e := json.Marshal(*c) + if e != nil { + panic(e) + } + str := string(out) + for _, c2 := range classes { + if c2.Name == c.Name { + continue + } + if strings.Contains(str, c2.Name) { + skips := skip[c2.Name] + shouldSkip := false + for _, s := range skips { + if c.Name == s { + shouldSkip = true + break + } + } + if !shouldSkip { + output.WriteString(fmt.Sprintf("%s ..> %s\n", c.Name, c2.Name)) + } + } + } + } + + output.WriteString(fmt.Sprintln("@enduml")) + e := ioutil.WriteFile(classFile, output.Bytes(), os.ModePerm) + if e != nil { + panic(e) + } + +} + +func fakePipeline() { + cmd := exec.Command("plantuml", "-tsvg", classFile) + b, e := cmd.CombinedOutput() + if e != nil { + fmt.Println(string(b)) + panic(e) + } + + outfile := strings.ReplaceAll(classFile, ".txt", ".eps") + cmd = exec.Command("inkscape", "-o", outfile, strings.ReplaceAll(classFile, ".txt", ".svg")) + b, e = cmd.CombinedOutput() + if e != nil { + fmt.Println(string(b)) + panic(e) + } + + fmt.Println(outfile) +} diff --git a/plab/javadoc.go b/plab/javadoc.go index f495e31..1c02f09 100644 --- a/plab/javadoc.go +++ b/plab/javadoc.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "os/exec" + "os/user" "path/filepath" "sort" "strings" @@ -20,6 +21,22 @@ type InternalType struct { QualifiedName string DocString string Annotations []Annot + Modifiers string +} + +func (i *InternalType) GetVisibility() string { + visibilities := map[string]string{ + "public": "+", + "private": "-", + "protected": "#", + "package": "~", + } + for k, v := range visibilities { + if strings.Contains(i.Modifiers, k) { + return v + } + } + return visibilities["package"] } func (i *InternalType) GetAnnot() []Annot { @@ -44,13 +61,11 @@ type Method struct { Parameters []*Params ReturnType InternalType Exceptions []struct{} // TODO - Modifiers string } type Field struct { InternalType - Modifiers string - Type InternalType + Type InternalType } type Class struct { @@ -64,9 +79,9 @@ type Class struct { InternalType realClass *Class } - Methods []*Method - Modifiers string - Fields []*Field + Methods []*Method + + Fields []*Field } func (c *Class) getMethod(name string) *Method { @@ -118,9 +133,14 @@ func genJavadoc(src string) error { return e } + doclet := "/root/jsonDoclet.jar" + if usr, err := user.Current(); err == nil { + doclet = usr.HomeDir + "/jsonDoclet.jar" + } + cmd := exec.Command("javadoc", "-doclet", "com.raidandfade.JsonDoclet.Main", - "-docletpath", "/root/jsonDoclet.jar", + "-docletpath", doclet, "-sourcepath", p, "-private", "-subpackages", "projlab") @@ -149,6 +169,24 @@ func readJson(fname string) (*Class, error) { return nil, e } + if c.Name == "Docs" { + return nil, nil + } + + for _, a := range c.Annotations { + if a.TypeName == "Docs" { + for _, e := range a.Elements { + switch e.QualifiedName { + case "projlab.Docs.skip": + e.Value = strings.ReplaceAll(e.Value, "\"", "") + if e.Value == "true" { + return nil, nil + } + } + } + } + } + replaceFieldType := func(field interface{}, newType string) { field.(*Field).Type.Name = newType } @@ -167,7 +205,9 @@ func readJson(fname string) (*Class, error) { switch e.QualifiedName { case "projlab.Docs.type": e.Value = strings.ReplaceAll(e.Value, "\"", "") - replace(arr[i], e.Value) + if e.Value != "" { + replace(arr[i], e.Value) + } } } } @@ -175,15 +215,15 @@ func readJson(fname string) (*Class, error) { } } - tmp := make([]annotated, 0, len(c.Fields)) - for i := range tmp { - tmp[i] = &c.Fields[i].InternalType + tmp := make([]annotated, len(c.Fields)) + for i := range c.Fields { + tmp[i] = c.Fields[i] } do(tmp, replaceFieldType) - tmp = make([]annotated, 0, len(c.Methods)) - for i := range tmp { - tmp[i] = &c.Methods[i].InternalType + tmp = make([]annotated, len(c.Methods)) + for i := range c.Methods { + tmp[i] = c.Methods[i] } do(tmp, replaceMethodType) @@ -213,7 +253,9 @@ func readJavadoc() (map[string]*Class, error) { if e != nil { return nil, e } - m[c.QualifiedName] = c + if c != nil { + m[c.QualifiedName] = c + } } return m, nil @@ -321,10 +363,6 @@ func printLatex(c map[string]*Class) { } func printClassDoc(c *Class) { - if c.QualifiedName == "projlab.Docs" { - return - } - fmt.Printf("\\subsubsection{\\texttt{%s \\textcolor{blue}{%s}}}\n", c.Modifiers, c.Name) fmt.Println("\\begin{itemize}") diff --git a/plab/main.go b/plab/main.go index f80af2c..23ef0b8 100644 --- a/plab/main.go +++ b/plab/main.go @@ -3,7 +3,7 @@ package main import "os" func main() { - mainCmds := CmdFrom(os.Args[0], "go projlab tool", Timetable, Javadoc) + mainCmds := CmdFrom(os.Args[0], "go projlab tool", Timetable, Javadoc, Classdiag) if len(os.Args) < 2 { os.Args = append(os.Args, "help") } -- GitLab