Project

General

Profile

Feature #16424 » main.go

Nico César, 05/11/2020 08:13 PM

 
1
package main
2

    
3
import (
4
	"encoding/json"
5
	"fmt"
6
	"io/ioutil"
7
	"log"
8
	"os"
9
)
10

    
11
// CWL is the toplevel object that has all the grpah
12
type CWL struct {
13
	Graph      []Graph `json:"$graph"`
14
	CwlVersion string  `json:"cwlVersion"`
15
}
16

    
17
type Hints struct {
18
	Class       string `json:"class"`
19
	LoadListing string `json:"loadListing"`
20
}
21

    
22
// Inputs are the pre-requisites for a step to run. Usually, files and directories
23
// some of them coming from other steps
24
type Inputs struct {
25
	ID    string `json:"id"`
26
	Label string `json:"label"`
27
	//most types are just string, but sometimes you'll find things like:
28
	// "type": ["null", "Directory"]
29
	// "type": { "items": "string", "type": "array" }
30
	// or even:
31
	//	"type": {
32
	//		"items": { "items": ["File", "Directory"], "type": "array" },
33
	//		"type": "array"
34
	//	  }
35

    
36
	Type interface{} `json:"type"`
37
}
38

    
39
/*
40
// this is for outputs
41
type Type struct {
42
	Items string `json:"items"`
43
	Type  string `json:"type"`
44
}*/
45

    
46
// Outputs are the produced files, directories, etc. from Steps
47
type Outputs struct {
48
	ID           string `json:"id"`
49
	Label        string `json:"label"`
50
	OutputSource string `json:"outputSource"`
51
	// most of the time this will be a {"items":...,"type":....}
52
	// but sometimes you'll find:
53
	// "type": "Directory"
54
	// or even:
55
	// "type": {
56
	// 	"items": { "items": "File", "type": "array" },
57
	//	"type": "array"
58
	//  }
59
	Type interface{} `json:"type"`
60
}
61

    
62
// Requirements is a list of requirements, usually a DockerPull will be signaling an image
63
type Requirements struct {
64
	Class                                string `json:"class"`
65
	DockerPull                           string `json:"dockerPull,omitempty"`
66
	HTTPArvadosOrgCwlDockerCollectionPDH string `json:"http://arvados.org/cwl#dockerCollectionPDH,omitempty"`
67
}
68
type In struct {
69
	ID     string `json:"id"`
70
	Source string `json:"source"`
71
}
72

    
73
// Steps is the central struct to define the acyclic graph of steps using In's and Out's
74
type Steps struct {
75
	ID  string   `json:"id"`
76
	In  []In     `json:"in"`
77
	Out []string `json:"out"`
78
	Run string   `json:"run"`
79
	// Scatter this could be  a string for one step or an array for several steps
80
	Scatter interface{} `json:"scatter,omitempty"`
81
}
82
type Graph struct {
83
	Namespaces   map[string]string `json:"$namespaces,omitempty"`
84
	Class        string            `json:"class"`
85
	Hints        []Hints           `json:"hints,omitempty"`
86
	ID           string            `json:"id"`
87
	Inputs       []Inputs          `json:"inputs"`
88
	Label        string            `json:"label,omitempty"`
89
	Outputs      []Outputs         `json:"outputs"`
90
	Requirements []Requirements    `json:"requirements,omitempty"`
91
	Steps        []Steps           `json:"steps,omitempty"`
92

    
93
	// this is usually a []string  with all the arguments,but
94
	// sometimes we can find things like:
95
	//"arguments": [
96
	//   "$(inputs.bashscript)",
97
	//   { "prefix": "-s", "valueFrom": "$(inputs.srclib)" },
98
	//   { "prefix": "-n", "valueFrom": "$(inputs.newlib)" },
99
	//   "bar",
100
	//   "$(inputs.foo)"
101
	//  ],
102
	Arguments   []interface{} `json:"arguments,omitempty"`
103
	BaseCommand string        `json:"baseCommand,omitempty"`
104
	Expression  string        `json:"expression,omitempty"`
105
}
106

    
107
//Dotty will give back a graph to print with dotty
108
func Dotty(result CWL) string {
109
	const stepStyle = `[fillcolor="#FFD700", style="rounded,filled", shape=box]`
110
	const cmdStyle = `[fillcolor="#FF9912", style="rounded,filled", shape=box]`
111
	var nodes string
112
	var edges string
113

    
114
	for _, g := range result.Graph {
115

    
116
		if g.Class == "CommandLineTool" {
117
			nodes += fmt.Sprintf("\"cmd %s\" %s;\n", g.ID, cmdStyle)
118

    
119
			for _, input := range g.Inputs {
120
				edges += fmt.Sprintf("\"%s\" -> \"cmd %s\";\n", input.ID, g.ID)
121
			}
122
			for _, output := range g.Outputs {
123
				if output.OutputSource != "" {
124
					edges += fmt.Sprintf("\"cmd %s\" -> \"%s\";\n", g.ID, output.OutputSource)
125
				} else {
126
					edges += fmt.Sprintf("\"cmd %s\" -> \"%s\";\n", g.ID, output.ID)
127
				}
128
			}
129

    
130
		}
131

    
132
		for _, step := range g.Steps {
133
			nodes += fmt.Sprintf("\"step %s\" %s;\n", step.ID, stepStyle)
134

    
135
			for _, in := range step.In {
136
				if in.Source != "" {
137
					edges += fmt.Sprintf("\"%s\" -> \"step %s\";\n", in.Source, step.ID)
138
				} else {
139
					edges += fmt.Sprintf("\"%s\" -> \"step %s\";\n", in.ID, step.ID)
140
				}
141
			}
142

    
143
			// make all dependencies for outputs
144
			for _, out := range step.Out {
145
				edges += fmt.Sprintf("\"step %s\" -> \"%s\";\n", step.ID, out)
146
			}
147
		}
148
	}
149

    
150
	return fmt.Sprintf("digraph cwlgraph {\nrankdir=LR;\n\n%s\n\n%s}\n", nodes, edges)
151
}
152

    
153
func main() {
154

    
155
	// Open our jsonFile
156
	jsonFile, err := os.Open("su92l-xvhdp-3gri0mi1vtakaf4.json")
157
	// if we os.Open returns an error then handle it
158
	if err != nil {
159
		fmt.Println(err)
160
	}
161
	// defer the closing of our jsonFile so that we can parse it later on
162
	defer jsonFile.Close()
163

    
164
	byteValue, _ := ioutil.ReadAll(jsonFile)
165

    
166
	var result CWL
167
	err = json.Unmarshal([]byte(byteValue), &result)
168
	if err != nil {
169
		log.Fatalf("Error parsing json %v", err.Error())
170
	}
171

    
172
	fmt.Printf("%s\n", Dotty(result))
173

    
174
}
(1-1/5)