5df3bcdde7066ea7f0382d8108c50b598307f5ba
1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Nit package manager command line interface
25 # Command line action, passed after `picnit`
26 abstract class Command
28 # Short name of the command, specified in the command line
29 fun name
: String is abstract
31 # Short usage description
32 fun usage
: String is abstract
35 fun description
: String is abstract
37 # Apply this command consiering the `args` that follow
38 fun apply
(args
: Array[String]) do end
40 private var all_commands
: Map[String, Command]
42 init do all_commands
[name
] = self
44 # Print the help message for this command
47 print
"usage: {usage}"
49 print
" {description}"
53 # Install a new package
57 redef fun name
do return "install"
58 redef fun usage
do return "picnit install [package or git-repository]"
59 redef fun description
do return "Install a package by its name or from a git-repository"
63 if args
.length
!= 1 then
68 var package_id
= args
.first
69 if package_id
.is_package_name
then
70 # Ask a centralized server
71 # TODO choose a future safe URL
72 # TODO customizable server list
73 # TODO parse ini file in memory
75 var url
= "https://xymus.net/picnit/{package_id}.ini"
76 var ini_path
= "/tmp/{package_id}.ini"
78 if verbose
then print
"Looking for a package description at '{url}'"
80 var request
= new CurlHTTPRequest(url
)
81 request
.verbose
= verbose
82 var response
= request
.download_to_file
(ini_path
)
84 if response
isa CurlResponseFailed then
85 print_error
"Failed to contact the remote server at '{url}': {response.error_msg} ({response.error_code})"
89 assert response
isa CurlFileResponseSuccess
90 if response
.status_code
== 404 then
91 print_error
"Package not found by the server"
93 else if response
.status_code
!= 200 then
94 print_error
"Server side error: {response.status_code}"
99 print
"Found a package description:"
100 print ini_path
.to_path
.read_all
103 var ini
= new ConfigTree(ini_path
)
104 var git_repo
= ini
["upstream.git"]
105 if git_repo
== null then
106 print_error
"Package description invalid, or it does not declare a Git repository"
111 install_from_git
(git_repo
, package_id
)
113 var name
= package_id
.git_name
114 if name
!= null and name
!= "." and not name
.is_empty
then
116 install_from_git
(package_id
, name
)
118 print_error
"Failed to infer the package name"
124 private fun install_from_git
(git_repo
, name
: String)
128 var target_dir
= picnit_lib_dir
/ name
129 if target_dir
.file_exists
then
130 print_error
"Already installed"
134 var cmd
= "git clone {git_repo.escape_to_sh} {target_dir.escape_to_sh}"
135 if verbose
then print
"+ {cmd}"
137 if "NIT_TESTING".environ
== "true" then
138 # Silence git output when testing
139 cmd
+= " 2> /dev/null"
142 var proc
= new Process("sh", "-c", cmd
)
145 if proc
.status
!= 0 then
146 print_error
"Install failed"
156 redef fun name
do return "upgrade"
157 redef fun usage
do return "picnit upgrade <package>"
158 redef fun description
do return "Upgrade a package"
160 redef fun apply
(args
)
162 if args
.length
!= 1 then
167 var name
= args
.first
168 var target_dir
= picnit_lib_dir
/ name
170 if not target_dir
.file_exists
or not target_dir
.to_path
.is_dir
then
171 print_error
"Package not found"
177 var cmd
= "cd {target_dir.escape_to_sh}; git pull"
178 if verbose
then print
"+ {cmd}"
180 var proc
= new Process("sh", "-c", cmd
)
183 if proc
.status
!= 0 then
184 print_error
"Upgrade failed"
190 # Uninstall a package
191 class CommandUninstall
194 redef fun name
do return "uninstall"
195 redef fun usage
do return "picnit uninstall <package>"
196 redef fun description
do return "Uninstall a package"
198 redef fun apply
(args
)
200 if args
.length
!= 1 then
205 var name
= args
.first
206 var target_dir
= picnit_lib_dir
/ name
208 if not target_dir
.file_exists
or not target_dir
.to_path
.is_dir
then
209 print_error
"Package not found"
214 var response
= prompt
("Delete {target_dir.escape_to_sh}? [Y/n] ")
215 var accept
= response
!= null and
216 (response
.to_lower
== "y" or response
.to_lower
== "yes" or response
== "")
217 if not accept
then return
219 var cmd
= "rm -rf {target_dir.escape_to_sh}"
220 if verbose
then print
"+ {cmd}"
222 var proc
= new Process("sh", "-c", cmd
)
225 if proc
.status
!= 0 then
226 print_error
"Uninstall failed"
232 # List all installed packages
236 redef fun name
do return "list"
237 redef fun usage
do return "picnit list"
238 redef fun description
do return "List installed packages"
240 redef fun apply
(args
)
242 var files
= picnit_lib_dir
.files
244 var ini_path
= picnit_lib_dir
/ file
/ "package.ini"
245 if verbose
then print
"- Reading ini file at {ini_path}"
246 var ini
= new ConfigTree(ini_path
)
247 var tags
= ini
["package.tags"]
250 print
"{file.justify(15, 0.0)} {tags}"
258 # Show general help or help specific to a command
262 redef fun name
do return "help"
263 redef fun usage
do return "picnit help [command]"
264 redef fun description
do return "Show general help message or the help for a command"
266 redef fun apply
(args
)
268 # Try first to help about a valid action
269 if args
.length
== 1 then
270 var command
= commands
.get_or_null
(args
.first
)
271 if command
!= null then
272 command
.print_local_help
283 # General command line options
284 var opts
= new OptionContext
287 var opt_help
= new OptionBool("Show this help message", "--help", "-h")
289 # Verbose mode option
290 var opt_verbose
= new OptionBool("Print more information", "--verbose", "-v")
291 private fun verbose
: Bool do return opt_verbose
.value
293 # All command line actions, mapped to their short `name`
294 var commands
= new Map[String, Command]
296 private var command_install
= new CommandInstall(commands
)
297 private var command_list
= new CommandList(commands
)
298 private var command_update
= new CommandUpgrade(commands
)
299 private var command_uninstall
= new CommandUninstall(commands
)
300 private var command_help
= new CommandHelp(commands
)
303 redef fun picnit_lib_dir
305 if "NIT_TESTING".environ
== "true" then
306 return "/tmp/picnit-test-" + "NIT_TESTING_ID".environ
310 # Print the general help message
311 private fun print_help
313 print
"usage: picnit <command> [options]"
317 for command
in commands
.values
do
318 print
" {command.name.justify(11, 0.0)} {command.description}"
326 # Check if `git` is available, exit if not
327 private fun check_git
329 var proc
= new ProcessReader("git", "--version")
333 if proc
.status
!= 0 then
334 print_error
"Please install `git`"
340 opts
.add_option
(opt_help
, opt_verbose
)
344 if opt_help
.value
then
349 if opts
.errors
.not_empty
then
350 for error
in opts
.errors
do print error
356 if rest
.is_empty
then
361 # Find and apply action
362 var action_name
= rest
.shift
363 var action
= commands
.get_or_null
(action_name
)
364 if action
!= null then