nitpackage: generate INI files
[nit.git] / src / nitpackage.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Helpful features about packages
16 module nitpackage
17
18 import frontend
19
20 redef class ToolContext
21 # --expand
22 var opt_expand = new OptionBool("Move singleton packages to their own directory", "--expand")
23
24 # --gen-ini
25 var opt_gen_ini = new OptionBool("Generate package.ini files", "--gen-ini")
26
27 # --force
28 var opt_force = new OptionBool("Force update of existing files", "-f", "--force")
29
30 # README handling phase
31 var readme_phase: Phase = new ReadmePhase(self, null)
32
33 redef init do
34 super
35 option_context.add_option(opt_expand, opt_force)
36 option_context.add_option(opt_gen_ini)
37 end
38 end
39
40 private class ReadmePhase
41 super Phase
42
43 redef fun process_mainmodule(mainmodule, mmodules) do
44 var mpackages = extract_mpackages(mmodules)
45 for mpackage in mpackages do
46
47 # Fictive and buggy packages are ignored
48 if not mpackage.has_source then
49 toolcontext.warning(mpackage.location, "no-source",
50 "Warning: `{mpackage}` has no source file")
51 continue
52 end
53
54 # Expand packages
55 if toolcontext.opt_expand.value and not mpackage.is_expanded then
56 var path = mpackage.expand
57 toolcontext.info("{mpackage} moved to {path}", 0)
58 end
59 if not mpackage.is_expanded then
60 toolcontext.warning(mpackage.location, "no-dir",
61 "Warning: `{mpackage}` has no package directory")
62 continue
63 end
64
65 # Create INI file
66 if toolcontext.opt_gen_ini.value then
67 if not mpackage.has_ini or toolcontext.opt_force.value then
68 var path = mpackage.gen_ini
69 toolcontext.info("generated INI file `{path}`", 0)
70 end
71 end
72 end
73 end
74
75 # Extract the list of packages from the mmodules passed as arguments
76 fun extract_mpackages(mmodules: Collection[MModule]): Collection[MPackage] do
77 var mpackages = new ArraySet[MPackage]
78 for mmodule in mmodules do
79 var mpackage = mmodule.mpackage
80 if mpackage == null then continue
81 mpackages.add mpackage
82 end
83 return mpackages.to_a
84 end
85 end
86
87 redef class MPackage
88
89 # Expand `self` in its own directory
90 private fun expand: String do
91 assert not is_expanded
92
93 var ori_path = package_path.as(not null)
94 var new_path = ori_path.dirname / name
95
96 new_path.mkdir
97 sys.system "mv {ori_path} {new_path / name}.nit"
98
99 var ini_file = "{new_path}.ini"
100 if ini_file.file_exists then
101 sys.system "mv {new_path}.ini {new_path}/package.ini"
102 end
103
104 return new_path
105 end
106
107 private var maintainer: nullable String is lazy do
108 return git_exec("git shortlog -esn . | head -n 1 | sed 's/\\s*[0-9]*\\s*//'")
109 end
110
111 private var contributors: Array[String] is lazy do
112 var contribs = git_exec("git shortlog -esn . | head -n -1 | " +
113 "sed 's/\\s*[0-9]*\\s*//'")
114 if contribs == null then return new Array[String]
115 return contribs.split("\n")
116 end
117
118 private var git_url: nullable String is lazy do
119 var git = git_exec("git remote get-url origin")
120 if git == null then return null
121 git = git.replace("git@github.com:", "https://github.com/")
122 git = git.replace("git@gitlab.com:", "https://gitlab.com/")
123 return git
124 end
125
126 private var git_dir: nullable String is lazy do
127 return git_exec("git rev-parse --show-prefix")
128 end
129
130 private var browse_url: nullable String is lazy do
131 var git = git_url
132 if git == null then return null
133 var browse = git.replace(".git", "")
134 var dir = git_dir
135 if dir == null or dir.is_empty then return browse
136 return "{browse}/tree/master/{dir}"
137 end
138
139 private var homepage_url: nullable String is lazy do
140 var git = git_url
141 if git == null then return null
142 # Special case for nit files
143 if git.has_suffix("/nit.git") then
144 return "http://nitlanguage.org"
145 end
146 return git.replace(".git", "")
147 end
148
149 private var issues_url: nullable String is lazy do
150 var git = git_url
151 if git == null then return null
152 return "{git.replace(".git", "")}/issues"
153 end
154
155 private var license: nullable String is lazy do
156 var git = git_url
157 if git == null then return null
158 # Special case for nit files
159 if git.has_suffix("/nit.git") then
160 return "Apache-2.0"
161 end
162 return null
163 end
164
165 private fun git_exec(cmd: String): nullable String do
166 var path = package_path
167 if path == null then return null
168 if not is_expanded then path = path.dirname
169 with pr = new ProcessReader("sh", "-c", "cd {path} && {cmd}") do
170 return pr.read_all.trim
171 end
172 end
173
174 private fun gen_ini: String do
175 var ini_path = self.ini_path.as(not null)
176 var ini = new ConfigTree(ini_path)
177
178 ini.update_value("package.name", name)
179 ini.update_value("package.desc", "")
180 ini.update_value("package.tags", "")
181 ini.update_value("package.maintainer", maintainer)
182 ini.update_value("package.more_contributors", contributors.join(","))
183 ini.update_value("package.license", license or else "")
184
185 ini.update_value("upstream.browse", browse_url)
186 ini.update_value("upstream.git", git_url)
187 ini.update_value("upstream.git.directory", git_dir)
188 ini.update_value("upstream.homepage", homepage_url)
189 ini.update_value("upstream.issues", issues_url)
190
191 ini.save
192 return ini_path
193 end
194 end
195
196 redef class ConfigTree
197 private fun update_value(key: String, value: nullable String) do
198 if value == null then return
199 if not has_key(key) then
200 self[key] = value
201 else
202 var old_value = self[key]
203 if not value.is_empty and old_value != value then
204 self[key] = value
205 end
206 end
207 end
208 end
209
210 # build toolcontext
211 var toolcontext = new ToolContext
212 var tpl = new Template
213 tpl.add "Usage: nitpackage [OPTION]... <file.nit>...\n"
214 tpl.add "Helpful features about packages."
215 toolcontext.tooldescription = tpl.write_to_string
216
217 # process options
218 toolcontext.process_options(args)
219 var arguments = toolcontext.option_context.rest
220
221 # build model
222 var model = new Model
223 var mbuilder = new ModelBuilder(model, toolcontext)
224 var mmodules = mbuilder.parse_full(arguments)
225
226 # process
227 if mmodules.is_empty then return
228 mbuilder.run_phases
229 toolcontext.run_global_phases(mmodules)