1 | import fs from 'fs'
|
2 | import path from 'path'
|
3 | import { SoftwareEnvironment, SoftwarePackage } from '@stencila/schema'
|
4 |
|
5 | import DockerParser from './DockerParser'
|
6 | import parsers from './parsers'
|
7 |
|
8 | import DockerGenerator from './DockerGenerator'
|
9 | import DockerBuilder from './DockerBuilder'
|
10 | import DockerExecutor from './DockerExecutor'
|
11 | import IUrlFetcher from './IUrlFetcher'
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | export default class DockerCompiler {
|
17 |
|
18 | |
19 |
|
20 |
|
21 | private readonly urlFetcher: IUrlFetcher
|
22 |
|
23 | constructor (urlFetcher: IUrlFetcher) {
|
24 | this.urlFetcher = urlFetcher
|
25 | }
|
26 |
|
27 | |
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | async compile (source: string, build: boolean = true, comments: boolean = true, stencila: boolean = false): Promise<SoftwareEnvironment | null> {
|
36 | let folder
|
37 | if (source.substring(0, 7) === 'file://') {
|
38 | folder = source.substring(7)
|
39 | } else {
|
40 | folder = source
|
41 | }
|
42 |
|
43 | let dockerfile
|
44 | let environ
|
45 |
|
46 | if (fs.existsSync(path.join(folder, 'Dockerfile'))) {
|
47 |
|
48 | dockerfile = 'Dockerfile'
|
49 | environ = await new DockerParser(this.urlFetcher, folder).parse()
|
50 | } else {
|
51 | if (fs.existsSync(path.join(folder, 'environ.jsonld'))) {
|
52 |
|
53 | const jsonld = fs.readFileSync(path.join(folder, 'environ.jsonld'), 'utf8')
|
54 | const initializer = JSON.parse(jsonld)
|
55 | environ = new SoftwareEnvironment(initializer)
|
56 | } else {
|
57 |
|
58 |
|
59 | environ = new SoftwareEnvironment()
|
60 | environ.name = path.basename(folder)
|
61 | for (let ParserClass of parsers) {
|
62 | const parser = new ParserClass(this.urlFetcher, folder)
|
63 | const pkg = await parser.parse()
|
64 | if (pkg) environ.softwareRequirements.push(pkg)
|
65 | }
|
66 |
|
67 |
|
68 | const jsonld = JSON.stringify(environ.toJSONLD(), null, ' ')
|
69 | fs.writeFileSync(path.join(folder, '.environ.jsonld'), jsonld)
|
70 | }
|
71 |
|
72 |
|
73 | dockerfile = '.Dockerfile'
|
74 | new DockerGenerator(this.urlFetcher, environ, folder).generate(comments, stencila)
|
75 | }
|
76 |
|
77 | if (build) {
|
78 |
|
79 | let name = (environ && environ.name) || undefined
|
80 |
|
81 | const builder = new DockerBuilder()
|
82 | await builder.build(folder, name, dockerfile)
|
83 | }
|
84 |
|
85 | return environ
|
86 | }
|
87 |
|
88 | |
89 |
|
90 |
|
91 |
|
92 |
|
93 | async execute (source: string, command: string = '') {
|
94 | let folder
|
95 | if (source.substring(0, 7) === 'file://') {
|
96 | folder = source.substring(7)
|
97 | } else {
|
98 | folder = source
|
99 | }
|
100 |
|
101 |
|
102 | let environ = await this.compile(source)
|
103 | if (!environ) throw new Error('Environment not created')
|
104 | if (!environ.name) throw new Error('Environment does not have a name')
|
105 |
|
106 |
|
107 | const executor = new DockerExecutor()
|
108 | return executor.execute(environ.name, folder, command)
|
109 | }
|
110 |
|
111 | |
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 | async who (folder: string, maxDepth: number = 100): Promise<object> {
|
119 | let environ = await this.compile(folder, false)
|
120 | if (!environ) throw new Error('Environment not created')
|
121 |
|
122 | const people: {[key: string]: Array<string>} = {}
|
123 |
|
124 | |
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | function get (pkg: SoftwarePackage | SoftwareEnvironment, depth: number = 0) {
|
131 | let all = pkg.authors.concat(pkg.contributors).concat(pkg.creators)
|
132 | for (let person of all) {
|
133 | const name = person.name
|
134 | if (people[name]) {
|
135 | if (!people[name].includes(pkg.name)) people[name].push(pkg.name)
|
136 | } else {
|
137 | people[name] = [pkg.name]
|
138 | }
|
139 | }
|
140 |
|
141 | if (depth < maxDepth) {
|
142 | for (let req of pkg.softwareRequirements) {
|
143 | get(req, depth + 1)
|
144 | }
|
145 | }
|
146 | }
|
147 | get(environ)
|
148 |
|
149 | return people
|
150 | }
|
151 | }
|