[Git][debian-gis-team/grass][experimental] 4 commits: New upstream version 7.8.6~rc2
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Tue Aug 10 19:40:34 BST 2021
Bas Couwenberg pushed to branch experimental at Debian GIS Project / grass
Commits:
ba8139ed by Bas Couwenberg at 2021-08-10T20:04:57+02:00
New upstream version 7.8.6~rc2
- - - - -
9f20c28b by Bas Couwenberg at 2021-08-10T20:08:36+02:00
Update upstream source from tag 'upstream/7.8.6_rc2'
Update to upstream version '7.8.6~rc2'
with Debian dir b59e50b8db0744ab8655e357e1d5502d339b25fa
- - - - -
6a07ca49 by Bas Couwenberg at 2021-08-10T20:08:53+02:00
New upstream release candidate.
- - - - -
27ac9773 by Bas Couwenberg at 2021-08-10T20:10:10+02:00
Set distribution to experimental.
- - - - -
26 changed files:
- INSTALL
- debian/changelog
- general/g.parser/standard_option.c
- gui/wxpython/gui_core/dialogs.py
- gui/wxpython/web_services/dialogs.py
- include/VERSION
- include/gis.h
- lib/cairodriver/text.c
- lib/gis/parser_standard_options.c
- lib/python/ctypes/ctypesgencore/parser/lex.py
- lib/python/docs/src/pygrass_modules.rst
- lib/python/gunittest/case.py
- lib/python/gunittest/gmodules.py
- lib/python/pygrass/modules/interface/module.py
- lib/python/pygrass/modules/interface/typedict.py
- lib/python/temporal/temporal_vector_algebra.py
- lib/raster/put_row.c
- locale/po/grasslibs_fr.po
- locale/po/grasslibs_pt_BR.po
- locale/po/grassmods_fr.po
- locale/po/grassmods_pt_BR.po
- locale/po/grasswxpy_pt_BR.po
- scripts/.flake8
- scripts/g.extension/g.extension.py
- temporal/t.rast.accumulate/t.rast.accumulate.py
- temporal/t.rast.neighbors/t.rast.neighbors.py
Changes:
=====================================
INSTALL
=====================================
@@ -68,7 +68,10 @@ The command,
./configure --help
explains the options used to disable the compilation of non-mandatory
-GRASS modules. See REQUIREMENTS.html for details.
+GRASS modules. See REQUIREMENTS.html for details on dependencies.
+Detailed Wiki notes for various operating systems (MS-Windows, GNU/Linux
+distributions, FreeBSD, AIX, etc) are available at:
+https://grasswiki.osgeo.org/wiki/Compile_and_Install
First step of the compilation (-g for debugging, or -O2 for optimization):
@@ -97,11 +100,7 @@ Explanation of make targets:
Next step is the compilation itself:
- make
-
-Detailed Wiki notes for various operating systems (MS-Windows, GNU/Linux distros,
-FreeBSD, AIX, etc) are available at:
-https://grasswiki.osgeo.org/wiki/Compile_and_Install
+ make
Note for Solaris users (see also Wiki page above):
=====================================
debian/changelog
=====================================
@@ -1,9 +1,10 @@
-grass (7.8.6~rc1-1~exp2) UNRELEASED; urgency=medium
+grass (7.8.6~rc2-1~exp1) experimental; urgency=medium
+ * New upstream release candidate.
* Add libjs-jquery to grass-doc dependencies.
(closes: #988928)
- -- Bas Couwenberg <sebastic at debian.org> Fri, 21 May 2021 18:36:58 +0200
+ -- Bas Couwenberg <sebastic at debian.org> Tue, 10 Aug 2021 20:09:50 +0200
grass (7.8.6~rc1-1~exp1) experimental; urgency=medium
=====================================
general/g.parser/standard_option.c
=====================================
@@ -65,6 +65,7 @@ static char* STD_OPT_STRINGS[] = {"G_OPT_UNDEFINED",
"G_OPT_M_DIR",
"G_OPT_M_REGION",
"G_OPT_M_NULL_VALUE",
+ "G_OPT_M_NPROCS",
"G_OPT_STDS_INPUT",
"G_OPT_STDS_INPUTS",
"G_OPT_STDS_OUTPUT",
=====================================
gui/wxpython/gui_core/dialogs.py
=====================================
@@ -596,14 +596,22 @@ class SavedRegion(wx.Dialog):
self._selection.SetFocus()
self._selection.Bind(wx.EVT_TEXT, self.OnRegion)
- sizer.Add(box, proportion=0, flag=wx.GROW |
- wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
+ sizer.Add(
+ box,
+ proportion=0,
+ flag=wx.GROW | wx.ALL,
+ border=5,
+ )
line = wx.StaticLine(
- parent=self, id=wx.ID_ANY, size=(
- 20, -1), style=wx.LI_HORIZONTAL)
- sizer.Add(line, proportion=0, flag=wx.GROW |
- wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT, border=5)
+ parent=self, id=wx.ID_ANY, size=(20, -1), style=wx.LI_HORIZONTAL
+ )
+ sizer.Add(
+ line,
+ proportion=0,
+ flag=wx.GROW | wx.LEFT | wx.RIGHT,
+ border=5,
+ )
btnsizer = wx.StdDialogButtonSizer()
=====================================
gui/wxpython/web_services/dialogs.py
=====================================
@@ -9,7 +9,7 @@ List of classes:
- dialogs::WSPropertiesDialog
- dialogs::SaveWMSLayerDialog
-(C) 2009-2013 by the GRASS Development Team
+(C) 2009-2021 by the GRASS Development Team
This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.
@@ -66,10 +66,17 @@ class WSDialogBase(wx.Dialog):
# TODO: should be in file
self.default_servers = {
- 'OSM-WMS-EUROPE':
- ['http://watzmann-geog.urz.uni-heidelberg.de/cached/osm', '', ''],
- 'irs.gis-lab.info (OSM)': ['http://irs.gis-lab.info', '', ''],
- 'NASA GIBS WMTS': ['http://gibs.earthdata.nasa.gov/wmts/epsg4326/best/wmts.cgi', '', '']}
+ 'OSM-WMS': [
+ 'https://ows.terrestris.de/osm/service?',
+ '',
+ '',
+ ],
+ 'tiles.maps.eox.at (Sentinel-2)': [
+ 'https://tiles.maps.eox.at/wms',
+ '',
+ '',
+ ],
+ }
# holds reference to web service panel which is showed
self.active_ws_panel = None
@@ -986,9 +993,12 @@ class SaveWMSLayerDialog(wx.Dialog):
selSizer.Add(selTitleSizer, proportion=0,
flag=wx.EXPAND)
- selSizer.Add(sel, proportion=1,
- flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_VERTICAL,
- border=5)
+ selSizer.Add(
+ sel,
+ proportion=1,
+ flag=wx.EXPAND | wx.ALL,
+ border=5,
+ )
return selSizer
=====================================
include/VERSION
=====================================
@@ -1,4 +1,4 @@
7
8
-6RC1
+6RC2
2021
=====================================
include/gis.h
=====================================
@@ -99,12 +99,12 @@ static const char *GRASS_copyright __attribute__ ((unused))
#define U_DEGREES 8
#define U_USFEET 9
/* Temporal units from the datetime library */
-#define U_YEARS DATETIME_YEAR
-#define U_MONTHS DATETIME_MONTH
-#define U_DAYS DATETIME_DAY
-#define U_HOURS DATETIME_HOUR
-#define U_MINUTES DATETIME_MINUTE
-#define U_SECONDS DATETIME_SECOND
+#define U_YEARS DATETIME_YEAR
+#define U_MONTHS DATETIME_MONTH
+#define U_DAYS DATETIME_DAY
+#define U_HOURS DATETIME_HOUR
+#define U_MINUTES DATETIME_MINUTE
+#define U_SECONDS DATETIME_SECOND
/*! \brief Projection code - XY coordinate system (unreferenced data) */
#define PROJECTION_XY 0
@@ -302,10 +302,11 @@ typedef enum
G_OPT_M_DBASE, /*!< dbase */
G_OPT_M_COORDS, /*!< coordinates */
G_OPT_M_COLR, /*!< color rules */
- G_OPT_M_DIR, /*!< directory input */
+ G_OPT_M_DIR, /*!< directory input */
G_OPT_M_REGION, /*!< saved region */
G_OPT_M_NULL_VALUE, /*!< null value string */
-
+ G_OPT_M_NPROCS, /*!< number of threads for parallel computing */
+
G_OPT_STDS_INPUT, /*!< old input space time dataset of type strds, str3ds or stvds */
G_OPT_STDS_INPUTS, /*!< old input space time datasets */
G_OPT_STDS_OUTPUT, /*!< new output space time dataset */
@@ -321,7 +322,7 @@ typedef enum
G_OPT_STVDS_OUTPUT, /*!< new output space time vector dataset */
G_OPT_MAP_INPUT, /*!< old input map of type raster, vector or raster3d */
G_OPT_MAP_INPUTS, /*!< old input maps of type raster, vector or raster3d */
- G_OPT_STDS_TYPE, /*!< the type of a space time dataset: strds, str3ds, stvds */
+ G_OPT_STDS_TYPE, /*!< the type of a space time dataset: strds, str3ds, stvds */
G_OPT_MAP_TYPE, /*!< The type of an input map: raster, vect, rast3d */
G_OPT_T_TYPE, /*!< The temporal type of a space time dataset */
G_OPT_T_WHERE, /*!< A temporal GIS framework SQL WHERE statement */
@@ -447,15 +448,15 @@ struct Cell_head
/*! \brief Resolution - east to west cell size for 2D data */
double ew_res;
/*! \brief Resolution - east to west cell size for 3D data */
- double ew_res3;
+ double ew_res3;
/*! \brief Resolution - north to south cell size for 2D data */
- double ns_res;
+ double ns_res;
/*! \brief Resolution - north to south cell size for 3D data */
- double ns_res3;
+ double ns_res3;
/*! \brief Resolution - top to bottom cell size for 3D data */
- double tb_res;
+ double tb_res;
/*! \brief Extent coordinates (north) */
- double north;
+ double north;
/*! \brief Extent coordinates (south) */
double south;
/*! \brief Extent coordinates (east) */
=====================================
lib/cairodriver/text.c
=====================================
@@ -17,8 +17,13 @@
#if CAIRO_HAS_FT_FONT
#include <cairo-ft.h>
+#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1,10,0) || defined(CAIRO_HAS_FC_FONT)
+#define USE_FONTCONFIG 1
#include <fontconfig/fontconfig.h>
+#else
+#define USE_FONTCONFIG 0
#endif
+#endif /* CAIRO_HAS_FT_FONT */
#ifdef HAVE_ICONV_H
#include <iconv.h>
@@ -170,7 +175,7 @@ static void set_font_toy(const char *name)
G_free(font);
}
-#if CAIRO_HAS_FT_FONT
+#if USE_FONTCONFIG
static void fc_init(void)
{
@@ -281,7 +286,7 @@ static int is_toy_font(const char *name)
*/
void Cairo_set_font(const char *name)
{
-#if CAIRO_HAS_FT_FONT
+#if USE_FONTCONFIG
if (is_toy_font(name))
set_font_toy(name);
else
@@ -322,7 +327,7 @@ static void font_list_toy(char ***list, int *count, int verbose)
void Cairo_font_list(char ***list, int *count)
{
font_list_toy(list, count, 0);
-#if CAIRO_HAS_FT_FONT
+#if USE_FONTCONFIG
font_list_fc(list, count, 0);
#endif
}
@@ -336,7 +341,7 @@ void Cairo_font_list(char ***list, int *count)
void Cairo_font_info(char ***list, int *count)
{
font_list_toy(list, count, 1);
-#if CAIRO_HAS_FT_FONT
+#if USE_FONTCONFIG
font_list_fc(list, count, 1);
#endif
}
=====================================
lib/gis/parser_standard_options.c
=====================================
@@ -109,6 +109,7 @@
- G_OPT_M_COLR
- G_OPT_M_REGION
- G_OPT_M_NULL_VALUE
+ - G_OPT_M_NPROCS
- temporal GIS framework
- G_OPT_STDS_INPUT
@@ -755,6 +756,15 @@ struct Option *G_define_standard_option(int opt)
Opt->description = _("Name of saved region");
break;
+ case G_OPT_M_NPROCS:
+ Opt->key = "nprocs";
+ Opt->type = TYPE_INTEGER;
+ Opt->required = NO;
+ Opt->multiple = NO;
+ Opt->answer = "1";
+ Opt->description = _("Number of threads for parallel computing");
+ break;
+
/* Spatio-temporal modules of the temporal GIS framework */
case G_OPT_STDS_INPUT:
Opt->key = "input";
=====================================
lib/python/ctypes/ctypesgencore/parser/lex.py
=====================================
@@ -348,7 +348,7 @@ class Lexer:
break
# if func not callable, it means it's an ignored token
- if not isinstance(func, collections.Callable):
+ if not isinstance(func, collections.abc.Callable):
break
# If token is processed by a function, call it
@@ -720,7 +720,7 @@ def lex(module=None, object=None, debug=0, optimize=0,
states, tokname = _statetoken(f, stateinfo)
toknames[f] = tokname
- if isinstance(t, collections.Callable):
+ if isinstance(t, collections.abc.Callable):
for s in states:
funcsym[s].append((f, t))
elif (isinstance(t, bytes) or isinstance(t, str)):
=====================================
lib/python/docs/src/pygrass_modules.rst
=====================================
@@ -168,32 +168,16 @@ User or developer can check which parameters have been set, with: ::
print "Aspect is not computed"
-After we set the parameters and run the module, the execution of the module
-instantiate a popen attribute to the class. The `Popen`_ class allow user
-to kill/wait/ the process. ::
+After we set the parameters, we can run the module in the background with
+`finish_=False`. Then call `wait()` and retrieve the returncode. ::
>>> slope_aspect = Module('r.slope.aspect')
>>> slope_aspect(elevation='elevation', slope='slp', aspect='asp', overwrite=True, finish_=False)
- >>> slope_aspect.popen.wait() # *.kill(), *.terminate()
+ >>> slope_aspect.wait()
+ Module('r.slope.aspect')
+ >>> slope_aspect.returncode
0
- >>> out, err = slope_aspect.popen.communicate()
- >>> print err #doctest: +NORMALIZE_WHITESPACE
- 100%
- Aspect raster map <asp> complete
- Slope raster map <slp> complete
- <BLANKLINE>
-On the above example we use a new parameter `finish_`, if is set to True, the
-run method, automatically store the stdout and stderr to stdout and stderr
-attributes of the class: ::
-
- >>> slope_aspect = Module('r.slope.aspect')
- >>> slope_aspect(elevation='elevation', slope='slp', aspect='asp', overwrite=True, finish_=True)
- >>> print slope_aspect.stderr #doctest: +NORMALIZE_WHITESPACE
- 100%
- Aspect raster map <asp> complete
- Slope raster map <slp> complete
- <BLANKLINE>
Another example of use: ::
@@ -202,9 +186,7 @@ Another example of use: ::
>>> from grass.script.utils import parse_key_val
>>> parse_key_val(info.outputs.stdout)
{'max': '156.3299', 'min': '55.57879'}
- >>> info = Module("r.info", map="elevation", flags="r", finish_=False)
- >>> category = Module("r.category", map="elevation",
- ... stdin_=info.popen.stdout, finish_=True)
+
Launching GRASS GIS modules in parallel
---------------------------------------
=====================================
lib/python/gunittest/case.py
=====================================
@@ -1093,9 +1093,9 @@ class TestCase(unittest.TestCase):
errors += "\nSee available vector maps:\n"
errors += call_module('g.list', type='vector')
# TODO: message format, parameters
- raise CalledModuleError(module.popen.returncode, module.name,
- module.get_python(),
- errors=errors)
+ raise CalledModuleError(
+ module.returncode, module.name, module.get_python(), errors=errors
+ )
# TODO: use this also in assert and apply when appropriate
if expecting_stdout and not module.outputs.stdout.strip():
@@ -1153,14 +1153,15 @@ class TestCase(unittest.TestCase):
print(text_to_string(module.outputs.stderr))
# TODO: message format
# TODO: stderr?
- stdmsg = ('Running <{m.name}> module ended'
- ' with non-zero return code ({m.popen.returncode})\n'
- 'Called: {code}\n'
- 'See the following errors:\n'
- '{errors}'.format(
- m=module, code=module.get_python(),
- errors=module.outputs.stderr
- ))
+ stdmsg = (
+ "Running <{m.name}> module ended"
+ " with non-zero return code ({m.returncode})\n"
+ "Called: {code}\n"
+ "See the following errors:\n"
+ "{errors}".format(
+ m=module, code=module.get_python(), errors=module.outputs.stderr
+ )
+ )
self.fail(self._formatMessage(msg, stdmsg))
print(text_to_string(module.outputs.stdout))
print(text_to_string(module.outputs.stderr))
=====================================
lib/python/gunittest/gmodules.py
=====================================
@@ -26,14 +26,14 @@ class SimpleModule(Module):
... overwrite=True)
>>> mapcalc.run()
Module('r.mapcalc')
- >>> mapcalc.popen.returncode
+ >>> mapcalc.returncode
0
>>> colors = SimpleModule('r.colors',
... map='test_a', rules='-', stdin_='1 red')
>>> colors.run()
Module('r.colors')
- >>> colors.popen.returncode
+ >>> colors.returncode
0
>>> str(colors.inputs.stdin)
'1 red'
=====================================
lib/python/pygrass/modules/interface/module.py
=====================================
@@ -53,7 +53,7 @@ class ParallelModuleQueue(object):
Check with a queue size of 3 and 5 processes
>>> import copy
- >>> from grass.pygrass.modules import Module, ParallelModuleQueue
+ >>> from grass.pygrass.modules import Module, MultiModule, ParallelModuleQueue
>>> mapcalc_list = []
Setting run_ to False is important, otherwise a parallel processing is not possible
@@ -72,7 +72,7 @@ class ParallelModuleQueue(object):
>>> queue.get_max_num_procs()
3
>>> for mapcalc in mapcalc_list:
- ... print(mapcalc.popen.returncode)
+ ... print(mapcalc.returncode)
0
0
0
@@ -95,7 +95,7 @@ class ParallelModuleQueue(object):
>>> queue.get_max_num_procs()
8
>>> for mapcalc in mapcalc_list:
- ... print(mapcalc.popen.returncode)
+ ... print(mapcalc.returncode)
0
0
0
@@ -122,7 +122,7 @@ class ParallelModuleQueue(object):
>>> queue.get_max_num_procs()
3
>>> for proc in proc_list:
- ... print(proc.popen.returncode)
+ ... print(proc.returncode)
0
0
0
@@ -165,7 +165,7 @@ class ParallelModuleQueue(object):
>>> queue.get_max_num_procs()
8
>>> for mapcalc in mapcalc_list:
- ... print(mapcalc.popen.returncode)
+ ... print(mapcalc.returncode)
0
0
0
@@ -206,7 +206,7 @@ class ParallelModuleQueue(object):
>>> queue.get_max_num_procs()
3
>>> for mapcalc in mapcalc_list:
- ... print(mapcalc.popen.returncode)
+ ... print(mapcalc.returncode)
0
0
0
@@ -366,7 +366,7 @@ class Module(object):
... overwrite=True, run_=False)
>>> mapcalc.run()
Module('r.mapcalc')
- >>> mapcalc.popen.returncode
+ >>> mapcalc.returncode
0
>>> mapcalc = Module("r.mapcalc", expression="test_a = 1",
@@ -374,12 +374,12 @@ class Module(object):
>>> mapcalc.run()
Module('r.mapcalc')
>>> p = mapcalc.wait()
- >>> p.popen.returncode
+ >>> p.returncode
0
>>> mapcalc.run()
Module('r.mapcalc')
>>> p = mapcalc.wait()
- >>> p.popen.returncode
+ >>> p.returncode
0
>>> colors = Module("r.colors", map="test_a", rules="-",
@@ -388,7 +388,7 @@ class Module(object):
>>> colors.run()
Module('r.colors')
>>> p = mapcalc.wait()
- >>> p.popen.returncode
+ >>> p.returncode
0
>>> colors.inputs["stdin"].value
'1 red'
@@ -399,43 +399,47 @@ class Module(object):
>>> colors = Module("r.colors", map="test_a", rules="-",
... run_=False, finish_=False, stdin_=PIPE)
+ >>> colors.inputs["stdin"].value = "1 red"
>>> colors.run()
Module('r.colors')
- >>> stdout, stderr = colors.popen.communicate(input=b"1 red")
- >>> colors.popen.returncode
+ >>> colors.wait()
+ Module('r.colors')
+ >>> colors.returncode
0
- >>> stdout
- >>> stderr
>>> colors = Module("r.colors", map="test_a", rules="-",
... run_=False, finish_=False,
... stdin_=PIPE, stderr_=PIPE)
+ >>> colors.inputs["stdin"].value = "1 red"
>>> colors.run()
Module('r.colors')
- >>> stdout, stderr = colors.popen.communicate(input=b"1 red")
- >>> colors.popen.returncode
+ >>> colors.wait()
+ Module('r.colors')
+ >>> colors.outputs["stderr"].value.strip()
+ "Color table for raster map <test_a> set to 'rules'"
+
+ >>> colors.returncode
0
- >>> stdout
- >>> stderr.strip()
- b"Color table for raster map <test_a> set to 'rules'"
Run a second time
+ >>> colors.inputs["stdin"].value = "1 red"
>>> colors.run()
Module('r.colors')
- >>> stdout, stderr = colors.popen.communicate(input=b"1 blue")
- >>> colors.popen.returncode
+ >>> colors.wait()
+ Module('r.colors')
+ >>> colors.outputs["stderr"].value.strip()
+ "Color table for raster map <test_a> set to 'rules'"
+
+ >>> colors.returncode
0
- >>> stdout
- >>> stderr.strip()
- b"Color table for raster map <test_a> set to 'rules'"
Multiple run test
>>> colors = Module("r.colors", map="test_a",
... color="ryb", run_=False)
>>> colors.get_bash()
- 'r.colors map=test_a color=ryb'
+ 'r.colors map=test_a color=ryb offset=0.0 scale=1.0'
>>> colors.run()
Module('r.colors')
>>> colors(color="gyr")
@@ -575,18 +579,24 @@ class Module(object):
self.stdin = None
self.stdout_ = None
self.stderr_ = None
- diz = {'name': 'stdin', 'required': False,
- 'multiple': False, 'type': 'all',
- 'value': None}
- self.inputs['stdin'] = Parameter(diz=diz)
- diz['name'] = 'stdout'
- self.outputs['stdout'] = Parameter(diz=diz)
- diz['name'] = 'stderr'
- self.outputs['stderr'] = Parameter(diz=diz)
- self.popen = None
+ diz = {
+ "name": "stdin",
+ "required": False,
+ "multiple": False,
+ "type": "all",
+ "value": None,
+ }
+ self.inputs["stdin"] = Parameter(diz=diz)
+ diz["name"] = "stdout"
+ self.outputs["stdout"] = Parameter(diz=diz)
+ diz["name"] = "stderr"
+ self.outputs["stderr"] = Parameter(diz=diz)
+ self._popen = None
self.time = None
- self.start_time = None # This variable will be set in the run() function
- self._finished = False # This variable is set True if wait() was successfully called
+ self.start_time = None # This variable will be set in the run() function
+ # This variable is set True if wait() was successfully called
+ self._finished = False
+ self.returncode = None
if args or kargs:
self.__call__(*args, **kargs)
@@ -761,11 +771,13 @@ class Module(object):
cmd = self.make_cmd()
self.start_time = time.time()
- self.popen = Popen(cmd,
- stdin=self.stdin_,
- stdout=self.stdout_,
- stderr=self.stderr_,
- env=self.env_)
+ self._popen = Popen(
+ cmd,
+ stdin=self.stdin_,
+ stdout=self.stdout_,
+ stderr=self.stderr_,
+ env=self.env_,
+ )
if self.finish_ is True:
self.wait()
@@ -781,17 +793,21 @@ class Module(object):
if self._finished is False:
if self.stdin:
self.stdin = encode(self.stdin)
- stdout, stderr = self.popen.communicate(input=self.stdin)
- self.outputs['stdout'].value = decode(stdout) if stdout else ''
- self.outputs['stderr'].value = decode(stderr) if stderr else ''
+ stdout, stderr = self._popen.communicate(input=self.stdin)
+ self.outputs["stdout"].value = decode(stdout) if stdout else ""
+ self.outputs["stderr"].value = decode(stderr) if stderr else ""
self.time = time.time() - self.start_time
-
+ self.returncode = self._popen.returncode
self._finished = True
- if self.popen.poll():
- raise CalledModuleError(returncode=self.popen.returncode,
- code=self.get_bash(),
- module=self.name, errors=stderr)
+ if self._popen.poll():
+ raise CalledModuleError(
+ returncode=self._popen.returncode,
+ code=self.get_bash(),
+ module=self.name,
+ errors=stderr,
+ )
+ self._popen = None
return self
@@ -836,38 +852,38 @@ class MultiModule(object):
>>> mm = MultiModule(module_list=[region_1, region_2])
>>> mm.run()
>>> m_list = mm.get_modules()
- >>> m_list[0].popen.returncode
+ >>> m_list[0].returncode
0
- >>> m_list[1].popen.returncode
+ >>> m_list[1].returncode
0
Asynchronous module run, setting finish = False
- >>> region_1 = Module("g.region", run_=False) # doctest: +SKIP
- >>> region_1.flags.p = True # doctest: +SKIP
- >>> region_2 = copy.deepcopy(region_1) # doctest: +SKIP
- >>> region_2.flags.p = True # doctest: +SKIP
- >>> region_3 = copy.deepcopy(region_1) # doctest: +SKIP
- >>> region_3.flags.p = True # doctest: +SKIP
- >>> region_4 = copy.deepcopy(region_1) # doctest: +SKIP
- >>> region_4.flags.p = True # doctest: +SKIP
- >>> region_5 = copy.deepcopy(region_1) # doctest: +SKIP
- >>> region_5.flags.p = True # doctest: +SKIP
+ >>> region_1 = Module("g.region", run_=False)
+ >>> region_1.flags.p = True
+ >>> region_2 = copy.deepcopy(region_1)
+ >>> region_2.flags.p = True
+ >>> region_3 = copy.deepcopy(region_1)
+ >>> region_3.flags.p = True
+ >>> region_4 = copy.deepcopy(region_1)
+ >>> region_4.flags.p = True
+ >>> region_5 = copy.deepcopy(region_1)
+ >>> region_5.flags.p = True
>>> mm = MultiModule(module_list=[region_1, region_2, region_3, region_4, region_5],
- ... sync=False) # doctest: +SKIP
- >>> t = mm.run() # doctest: +SKIP
- >>> isinstance(t, Process) # doctest: +SKIP
+ ... sync=False)
+ >>> t = mm.run()
+ >>> isinstance(t, Process)
True
- >>> m_list = mm.wait() # doctest: +SKIP
- >>> m_list[0].popen.returncode # doctest: +SKIP
+ >>> m_list = mm.wait()
+ >>> m_list[0].returncode
0
- >>> m_list[1].popen.returncode # doctest: +SKIP
+ >>> m_list[1].returncode
0
- >>> m_list[2].popen.returncode # doctest: +SKIP
+ >>> m_list[2].returncode
0
- >>> m_list[3].popen.returncode # doctest: +SKIP
+ >>> m_list[3].returncode
0
- >>> m_list[4].popen.returncode # doctest: +SKIP
+ >>> m_list[4].returncode
0
Asynchronous module run, setting finish = False and using temporary region
@@ -880,15 +896,15 @@ class MultiModule(object):
>>> isinstance(t, Process)
True
>>> m_list = mm.wait()
- >>> m_list[0].popen.returncode
+ >>> m_list[0].returncode
0
- >>> m_list[1].popen.returncode
+ >>> m_list[1].returncode
0
- >>> m_list[2].popen.returncode
+ >>> m_list[2].returncode
0
- >>> m_list[3].popen.returncode
+ >>> m_list[3].returncode
0
- >>> m_list[4].popen.returncode
+ >>> m_list[4].returncode
0
"""
=====================================
lib/python/pygrass/modules/interface/typedict.py
=====================================
@@ -55,6 +55,18 @@ class TypeDict(OrderedDict):
obj[k] = deepcopy(v)
return obj
+ def __reduce__(self):
+ inst_dict = vars(self).copy()
+ for k in vars(TypeDict(self._type)):
+ inst_dict.pop(k, None)
+ return (
+ self.__class__,
+ (self._type,),
+ inst_dict or None,
+ None,
+ iter(self.items()),
+ )
+
def used(self):
key_dict = {}
for key in self:
=====================================
lib/python/temporal/temporal_vector_algebra.py
=====================================
@@ -416,11 +416,12 @@ class TemporalVectorAlgebraParser(TemporalAlgebraParser):
self.msgr.message("Run command:\n" + cmd.get_bash())
cmd.run()
- if cmd.popen.returncode != 0:
- self.msgr.fatal(_("Error starting %s : \n%s")
- %(cmd.get_bash(),
- cmd.popen.stderr))
- mapname = cmd.outputs['output'].value
+ if cmd.returncode != 0:
+ self.msgr.fatal(
+ _("Error starting %s : \n%s")
+ % (cmd.get_bash(), cmd.outputs.stderr)
+ )
+ mapname = cmd.outputs["output"].value
if mapname.find("@") >= 0:
map_test = map_i.get_new_instance(mapname)
else:
@@ -490,9 +491,13 @@ class TemporalVectorAlgebraParser(TemporalAlgebraParser):
map_i.update_all(dbif=dbif)
elif map_i.is_in_db(dbif=dbif) and self.overwrite == False:
# Raise error if map exists and no overwrite flag is given.
- self.msgr.fatal(_("Error vector map %s exist in temporal database. "
- "Use overwrite flag. : \n%s") \
- %(map_i.get_map_id(), cmd.popen.stderr))
+ self.msgr.fatal(
+ _(
+ "Error vector map %s exist in temporal database. "
+ "Use overwrite flag. : \n%s"
+ )
+ % (map_i.get_map_id(), cmd.outputs.stderr)
+ )
else:
# Insert map into temporal database.
map_i.insert(dbif=dbif)
=====================================
lib/raster/put_row.c
=====================================
@@ -388,14 +388,14 @@ static void put_data(int fd, char *null_buf, const CELL * cell,
nwrite++;
if (write(fcb->data_fd, compressed_buf, nwrite) != nwrite)
- G_fatal_error(_("Error writing compressed data for row %d of <%s>"),
- row, fcb->name);
+ G_fatal_error(_("Error writing compressed data for row %d of <%s>: %s"),
+ row, fcb->name, strerror(errno));
}
else {
nwrite = nbytes * n + 1;
if (write(fcb->data_fd, work_buf, nwrite) != nwrite)
- G_fatal_error(_("Error writing compressed data for row %d of <%s>"),
- row, fcb->name);
+ G_fatal_error(_("Error writing compressed data for row %d of <%s>: %s"),
+ row, fcb->name, strerror(errno));
}
G_free(compressed_buf);
@@ -404,8 +404,8 @@ static void put_data(int fd, char *null_buf, const CELL * cell,
nwrite = fcb->nbytes * n;
if (write(fcb->data_fd, work_buf, nwrite) != nwrite)
- G_fatal_error(_("Error writing uncompressed data for row %d of <%s>"),
- row, fcb->name);
+ G_fatal_error(_("Error writing uncompressed data for row %d of <%s>: %s"),
+ row, fcb->name, strerror(errno));
}
G_free(work_buf);
@@ -518,13 +518,13 @@ static void write_null_bits_compressed(const unsigned char *flags,
if (nwrite > 0 && nwrite < size) {
if (write(fcb->null_fd, compressed_buf, nwrite) != nwrite)
- G_fatal_error(_("Error writing compressed null data for row %d of <%s>"),
- row, fcb->name);
+ G_fatal_error(_("Error writing compressed null data for row %d of <%s>: %s"),
+ row, fcb->name, strerror(errno));
}
else {
if (write(fcb->null_fd, flags, size) != size)
- G_fatal_error(_("Error writing compressed null data for row %d of <%s>"),
- row, fcb->name);
+ G_fatal_error(_("Error writing compressed null data for row %d of <%s>: %s"),
+ row, fcb->name, strerror(errno));
}
G_free(compressed_buf);
@@ -557,10 +557,12 @@ void Rast__write_null_bits(int fd, const unsigned char *flags)
offset = (off_t) size * row;
if (lseek(fcb->null_fd, offset, SEEK_SET) < 0)
- G_fatal_error(_("Error writing null row %d of <%s>"), row, fcb->name);
+ G_fatal_error(_("Error writing null row %d of <%s>"),
+ row, fcb->name);
if (write(fcb->null_fd, flags, size) != size)
- G_fatal_error(_("Error writing null row %d of <%s>"), row, fcb->name);
+ G_fatal_error(_("Error writing null row %d of <%s>: %s"),
+ row, fcb->name, strerror(errno));
}
static void convert_and_write_if(int fd, const void *vbuf)
=====================================
locale/po/grasslibs_fr.po
=====================================
@@ -85,7 +85,7 @@ msgstr "afficher les paramètres de configuration GRASS"
#: ../lib/init/grass.py:337
msgid "options: arch,build,compiler,date,path,revision,svn_revision,version"
-msgstr ""
+msgstr "options: arch,build,compiler,date,path,revision,svn_revision,version"
#: ../lib/init/grass.py:338 ../lib/gis/parser_rest.c:179
#: ../lib/gis/parser_html.c:189
@@ -380,6 +380,12 @@ msgid ""
"\n"
"Check the <{file}> file."
msgstr ""
+"Erreur de lecture des informations du chemin des données depuis g.gisenv.\n"
+"GISDBASE={gisbase}\n"
+"LOCATION_NAME={location}\n"
+"MAPSET={mapset}\n"
+"\n"
+"Vérifier le fichier <{file}>."
#: ../lib/init/grass.py:1409
#, python-format
@@ -1361,7 +1367,7 @@ msgstr "Seulement %d%% de la fenêtre sauvegardée dans \"%s\" chevauche(nt) la
#: ../lib/gis/seek.c:54 ../lib/gis/seek.c:60
#, c-format
msgid "Unable to seek: %s"
-msgstr ""
+msgstr "Recherche de : %s impossible"
#: ../lib/gis/seek.c:58
msgid "Seek offset out of range"
@@ -2459,7 +2465,7 @@ msgstr "Maximum de mémoire à utiliser (en MB)"
#: ../lib/gis/parser_standard_options.c:264
msgid "Cache size for raster rows"
-msgstr ""
+msgstr "Taille de cache pour les lignes du raster"
#: ../lib/gis/parser_standard_options.c:272
msgid "Name of input raster map"
@@ -2760,7 +2766,7 @@ msgstr "Nom du jeu de données raster temporel en sortie"
#: ../lib/gis/parser_standard_options.c:816
msgid "Name of the output space time raster datasets"
-msgstr ""
+msgstr "Nom du jeu de données raster temporel en sortie"
#: ../lib/gis/parser_standard_options.c:824
msgid "Name of the input space time vector dataset"
@@ -5052,7 +5058,7 @@ msgstr "Impossible de sélectionner les attributs pour cat = %d"
#: ../lib/vector/Vlib/ascii.c:724
msgid "Unable to fetch data from table"
-msgstr "Impossible de récupérer les données depis la table"
+msgstr "Impossible de récupérer les données depuis la table"
#: ../lib/vector/Vlib/ascii.c:756 ../lib/vector/Vlib/net_build.c:216
#: ../lib/vector/Vlib/net_build.c:501 ../lib/vector/Vlib/net_build.c:519
@@ -8379,11 +8385,11 @@ msgstr ". Voir les erreus dans la sortie (d'erreurs)."
#: ../lib/python/imaging/operations.py:128
#: ../lib/python/imaging/operations.py:160
msgid "Install PIL or Pillow to use this function"
-msgstr ""
+msgstr "Installer PIL ou Pillow pour utiliser cette fonction"
#: ../lib/python/imaging/operations.py:162
msgid "Install a newer version of PIL or Pillow to use this function (missing ImageOps module)"
-msgstr ""
+msgstr "Installer une version plus récente de PIL ou Pillow pour utiliser cette fonction (Module ImageOps manquant)"
#: ../lib/python/script/task.py:58
msgid "unknown"
@@ -8415,6 +8421,9 @@ msgid ""
"\n"
"Details: <{det}>"
msgstr ""
+"Impossible de récupérer la description de l'interface pour la commande '<{cmd}>'.\n"
+"\n"
+"Détails: <{det}>"
#: ../lib/python/script/task.py:524
#, python-brace-format
@@ -9833,12 +9842,12 @@ msgstr "proj_create() a échoué pour '%s'"
#: ../lib/proj/do_proj.c:284
#, c-format
msgid "Unrecognized SRID '%s'"
-msgstr ""
+msgstr "SRID '%s' non reconnu"
#: ../lib/proj/do_proj.c:298
#, c-format
msgid "Unrecognized WKT '%s'"
-msgstr ""
+msgstr "WKT '%s' non reconnu"
#: ../lib/proj/do_proj.c:316
msgid "Unable to create PROJ object"
@@ -9874,22 +9883,22 @@ msgstr ""
#: ../lib/proj/do_proj.c:573
#, c-format
msgid "Found %d possible transformations"
-msgstr ""
+msgstr "%d Possibles transformations trouvées"
#: ../lib/proj/do_proj.c:585
#, c-format
msgid "proj_normalize_for_visualization() failed for operation %d"
-msgstr ""
+msgstr "proj_normalize_for_visualization() a échoué pour %d"
#: ../lib/proj/do_proj.c:596
#, c-format
msgid "Operation %d:"
-msgstr ""
+msgstr "Operation %d:"
#: ../lib/proj/do_proj.c:598
#, c-format
msgid "Description: %s"
-msgstr ""
+msgstr "Description: %s"
#: ../lib/proj/do_proj.c:603
#, c-format
@@ -9904,7 +9913,7 @@ msgstr ""
#: ../lib/proj/do_proj.c:615
#, c-format
msgid "Remarks: %s"
-msgstr ""
+msgstr "Remarques: %s"
#: ../lib/proj/do_proj.c:620
#, c-format
@@ -9917,7 +9926,7 @@ msgstr ""
#: ../lib/proj/do_proj.c:635
msgid "See also output of:"
-msgstr ""
+msgstr "Voir aussi la sortie de:"
#: ../lib/proj/do_proj.c:638
#, c-format
@@ -9927,7 +9936,7 @@ msgstr ""
#: ../lib/proj/do_proj.c:740
#, c-format
msgid "proj_normalize_for_visualization() failed for '%s'"
-msgstr ""
+msgstr "proj_normalize_for_visualization() a échoué pour '%s'"
#: ../lib/proj/do_proj.c:753
#, c-format
@@ -10535,3 +10544,6 @@ msgstr ""
msgid "Unable to seek"
msgstr "Recherche impossible"
+
+#~ msgid "Invalid value <%s> for parameter <%s>"
+#~ msgstr "Valeur <%s> manquante pour le paramètre <%s>"
=====================================
locale/po/grasslibs_pt_BR.po
=====================================
@@ -424,7 +424,7 @@ msgstr ""
#: ../lib/init/grass.py:1469
msgid "Building user fontcap..."
-msgstr ""
+msgstr "Construindo o fontcap do usuário..."
#: ../lib/init/grass.py:1530
msgid "The SHELL variable is not set"
@@ -736,7 +736,7 @@ msgstr "Registro no banco de dados para categoria %d não encontrado"
#: ../lib/rst/interp_float/vinput2d.c:192
msgid "Negative value of smoothing detected: sm must be >= 0"
-msgstr ""
+msgstr "Valor negativo de suavização detectado: sm deve ser >= 0"
#: ../lib/rst/interp_float/vinput2d.c:256
msgid "Strip exists with insufficient data"
@@ -764,7 +764,7 @@ msgstr "Não existem pontos na região atual"
#: ../lib/rst/interp_float/vinput2d.c:287
#, c-format
msgid "Segmentation parameters set to invalid values: npmin= %d, segmax= %d for smooth connection of segments, npmin > segmax (see manual)"
-msgstr ""
+msgstr "Parâmetros de segmentação definidos com valores inválidos: npmin= %d, segmax= %dpara conexão suave de segmentos, npmin > segmax (consulte o manual)"
#: ../lib/rst/interp_float/vinput2d.c:293
#, c-format
@@ -1311,7 +1311,7 @@ msgstr "Não foi possível abrir arquivo de saída <%s>: %s"
#: ../lib/gis/key_value3.c:39
#, c-format
msgid "Error closing output file <%s>: %s"
-msgstr "Erro fechando arquivo de saída <%s>: %s"
+msgstr "Erro ao fechar arquivo de saída <%s>: %s"
#: ../lib/gis/get_ellipse.c:263 ../lib/proj/ellipse.c:243
#, c-format
@@ -1962,7 +1962,7 @@ msgstr "Erro lendo arquivo <%s>: %s"
#: ../lib/gis/key_value3.c:67
#, c-format
msgid "Error closing input file <%s>: %s"
-msgstr "Erro fechando arquivo de entrada <%s>: %s"
+msgstr "Erro ao fechar arquivo de entrada <%s>: %s"
#: ../lib/gis/cmprbzip.c:97 ../lib/gis/cmprbzip.c:176
msgid "GRASS needs to be compiled with BZIP2 for BZIP2 compression"
@@ -2021,7 +2021,7 @@ msgstr "Não foi possível escrever %d bytes: %s"
#: ../lib/gis/token.c:169
msgid "parse error"
-msgstr "erro de análise"
+msgstr "erro de interpretação"
#: ../lib/gis/env.c:311
msgid "GISRC - variable not set"
@@ -2584,7 +2584,7 @@ msgstr "O método de compactação usado no mapa raster3d de saída"
#: ../lib/gis/parser_standard_options.c:474
msgid "The dimensions of the tiles used in the output raster3d map (XxYxZ or default: 16x16x8)"
-msgstr "As dimensões dos tiles usados no mapa raster3d de saída (XxYxZ ou padrão: 16x16x8)"
+msgstr "As dimensões dos mosaicos usados no mapa raster3d de saída (XxYxZ ou padrão: 16x16x8)"
#: ../lib/gis/parser_standard_options.c:484
msgid "Name of input vector map"
@@ -4106,17 +4106,17 @@ msgstr ""
#: ../lib/vector/vedit/delete.c:72
#, c-format
msgid "No area found for centroid %d"
-msgstr "Nenhuma área encontrada para o centróide %d"
+msgstr "Nenhuma área encontrada para o centroide %d"
#: ../lib/vector/vedit/delete.c:76
#, c-format
msgid "Duplicate centroid %d, unable to delete area"
-msgstr "Centróide %d duplicado, não é possível excluir área"
+msgstr "Centroide %d duplicado, não é possível excluir área"
#: ../lib/vector/vedit/delete.c:105
#, c-format
msgid "Area %d without centroid"
-msgstr "Área %d sem centróide"
+msgstr "Área %d sem centroide"
#: ../lib/vector/vedit/delete.c:121
#, c-format
@@ -4130,11 +4130,11 @@ msgstr "Não é possível definir a categoria %d para (ID da feição %d)"
#: ../lib/vector/vedit/select.c:231
msgid "Unknown query tool"
-msgstr ""
+msgstr "Ferramenta de consulta desconhecida"
#: ../lib/vector/neta/spanningtree.c:109
msgid "Computing minimum spanning tree..."
-msgstr ""
+msgstr "Calculando a árvore de recobrimento mínima..."
#: ../lib/vector/neta/timetables.c:46 ../lib/vector/neta/timetables.c:203
#: ../lib/vector/neta/timetables.c:244 ../lib/vector/neta/timetables.c:286
@@ -4267,7 +4267,7 @@ msgstr "Não é possível construir o centroide para a área %d. Ignorado."
#: ../lib/vector/Vlib/read_sfa.c:95
#, c-format
msgid "Centroid %d: invalid area %d"
-msgstr "Centróide%d: área inválida%d"
+msgstr "Centroide%d: área inválida%d"
#: ../lib/vector/Vlib/read_sfa.c:117 ../lib/vector/Vlib/read_pg.c:192
#, c-format
@@ -4401,7 +4401,7 @@ msgstr "Número de fronteiras: %d"
#: ../lib/vector/Vlib/build.c:906
#, c-format
msgid "Number of centroids: %d"
-msgstr "Número de centróides: %d"
+msgstr "Número de centroides: %d"
#: ../lib/vector/Vlib/build.c:909
#, c-format
@@ -4426,12 +4426,12 @@ msgstr "Número de ilhas: %d"
#: ../lib/vector/Vlib/build.c:964
#, c-format
msgid "Number of areas without centroid: %d"
-msgstr "Número de áreas sem centróide: %d"
+msgstr "Número de áreas sem centroide: %d"
#: ../lib/vector/Vlib/build.c:969
#, c-format
msgid "Number of centroids exceeds number of areas: %d > %d"
-msgstr "O número de centróides excede o número de áreas: %d > %d"
+msgstr "O número de centroides excede o número de áreas: %d > %d"
#: ../lib/vector/Vlib/build.c:973
#, c-format
@@ -4441,12 +4441,12 @@ msgstr "Número de fronteiras incorretas: %d"
#: ../lib/vector/Vlib/build.c:977
#, c-format
msgid "Number of centroids outside area: %d"
-msgstr "Número de centróides fora da área: %d"
+msgstr "Número de centroides fora da área: %d"
#: ../lib/vector/Vlib/build.c:981
#, c-format
msgid "Number of duplicate centroids: %d"
-msgstr "Número de centróides duplicados: %d"
+msgstr "Número de centroides duplicados: %d"
#: ../lib/vector/Vlib/build.c:985
msgid "Number of areas: -"
@@ -4818,7 +4818,7 @@ msgstr "Linha ignorada (tipo de feição de saída: %s)"
#: ../lib/vector/Vlib/write_pg.c:1266
#, c-format
msgid "Centroid skipped (output feature type: %s)"
-msgstr "Centróide ignorado (tipo de feição de saída: %s)"
+msgstr "Centroide ignorado (tipo de feição de saída: %s)"
#: ../lib/vector/Vlib/write_pg.c:1273
#, c-format
@@ -5326,7 +5326,7 @@ msgstr "Extensão PostGIS Topology não encontrada no banco de dados <%s>"
#: ../lib/vector/Vlib/open_pg.c:667
msgid "Empty bounding box"
-msgstr ""
+msgstr "Retângulo envolvente vazio"
#: ../lib/vector/Vlib/open_pg.c:768
#, c-format
@@ -5350,7 +5350,7 @@ msgstr "Ilha %d sem fronteira detectada"
#: ../lib/vector/Vlib/open_pg.c:1128
msgid "Unable to get map bounding box from topology"
-msgstr ""
+msgstr "Não foi possível obter o retângulo envolvente do mapa da topologia"
#: ../lib/vector/Vlib/open_pg.c:1135
#, c-format
@@ -5358,6 +5358,8 @@ msgid ""
"Unable to parse map bounding box:\n"
"%s"
msgstr ""
+"Não é possível analisar o retângulo envolvente do mapa:\n"
+"%s"
#: ../lib/vector/Vlib/open_pg.c:1159
#, c-format
@@ -5391,7 +5393,7 @@ msgstr "Inconsistência na topologia: número de linhas %d (deveria ser %d)"
#: ../lib/vector/Vlib/open_pg.c:1668
#, c-format
msgid "Inconsistency in topology: number of centroids %d (should be %d)"
-msgstr "Inconsistência na topologia: número de centróides %d (deveria ser %d)"
+msgstr "Inconsistência na topologia: número de centroides %d (deveria ser %d)"
#: ../lib/vector/Vlib/dangles.c:148
msgid "Changed"
@@ -5447,7 +5449,7 @@ msgstr "%d novas linhas e fronteiras"
#: ../lib/vector/Vlib/write_sfa.c:117
msgid "Unable to calculate centroid for area"
-msgstr "Não foi possível calcular centróide para área"
+msgstr "Não foi possível calcular centroide para área"
#: ../lib/vector/Vlib/write_sfa.c:219
msgid "Attempt to delete dead feature"
@@ -5582,16 +5584,16 @@ msgstr "Não foi possível ler o mapa vetorial"
#, c-format
msgid "One primitive registered"
msgid_plural "%d primitives registered"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Uma primitiva registrada"
+msgstr[1] "%d primitivas registradas"
#: ../lib/vector/Vlib/build_nat.c:119 ../lib/vector/Vlib/build_sfa.c:386
#: ../lib/vector/Vlib/build_sfa.c:665
#, c-format
msgid "One vertex registered"
msgid_plural "%jd vertices registered"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Um vértice registrado"
+msgstr[1] "%jd vértices registrados"
#: ../lib/vector/Vlib/build_nat.c:132
msgid "Building areas..."
@@ -5601,15 +5603,15 @@ msgstr "Construindo áreas..."
#, c-format
msgid "One area built"
msgid_plural "%d areas built"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Uma área construída"
+msgstr[1] "%d áreas construídas"
#: ../lib/vector/Vlib/build_nat.c:157
#, c-format
msgid "One isle built"
msgid_plural "%d isles built"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Uma ilha construída"
+msgstr[1] "%d ilhas construídas"
#: ../lib/vector/Vlib/build_nat.c:168
msgid "Attaching islands..."
@@ -5617,7 +5619,7 @@ msgstr "Anexando ilhas..."
#: ../lib/vector/Vlib/build_nat.c:188
msgid "Attaching centroids..."
-msgstr "Anexando centróides..."
+msgstr "Anexando centroides..."
#: ../lib/vector/Vlib/graph.c:138
msgid "Unable to add network arc"
@@ -5983,7 +5985,7 @@ msgstr "Acesso aleatório não suportado. Chave primária não definida."
#: ../lib/vector/Vlib/read_pg.c:652
msgid "Inconsistency in topology: detected centroid (should be point)"
-msgstr "Inconsistência na topologia: centróide detectado (deveria ser ponto)"
+msgstr "Inconsistência na topologia: centroide detectado (deveria ser ponto)"
#: ../lib/vector/Vlib/read_pg.c:662
msgid "Inconsistency in topology: detected boundary (should be line)"
@@ -6020,7 +6022,7 @@ msgstr "Dados corrompidos"
#: ../lib/vector/Vlib/read_pg.c:1252
#, c-format
msgid "Unable to parse '%s'"
-msgstr "Não foi possível analisar '%s'"
+msgstr "Não foi possível interpretar '%s'"
#: ../lib/vector/Vlib/read_pg.c:1420 ../lib/vector/Vlib/close_pg.c:66
#, c-format
@@ -6091,8 +6093,8 @@ msgstr "Não é possível selecionar registros da tabela <%s>"
#, c-format
msgid "One category loaded"
msgid_plural "%d categories loaded"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Uma categoria carregada"
+msgstr[1] "%d categorias carregadas"
#: ../lib/vector/Vlib/cats.c:627
#, c-format
@@ -6107,7 +6109,7 @@ msgstr "%d erros na opção '%s'"
#: ../lib/vector/Vlib/build_sfa.c:279 ../lib/vector/Vlib/build_sfa.c:546
#, c-format
msgid "Unable to calculate centroid for area %d"
-msgstr "Não foi possível calcular centróide para área %d"
+msgstr "Não foi possível calcular centroide para área %d"
#: ../lib/vector/Vlib/build_sfa.c:305
#, c-format
@@ -6124,8 +6126,8 @@ msgstr "Tipo de feição OGR %d não é suportada"
#, c-format
msgid "One feature without geometry skipped"
msgid_plural "%d features without geometry skipped"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Uma feição sem geometria ignorada"
+msgstr[1] "%d feições sem geometria ignoradas"
#: ../lib/vector/Vlib/build_sfa.c:725
#, c-format
@@ -6134,7 +6136,7 @@ msgstr "%s: Formato nativo não suportado"
#: ../lib/vector/Vlib/build_sfa.c:751
msgid "Feature index is built only for non-native formats. Nothing to dump."
-msgstr ""
+msgstr "O índice de feições é criado apenas para formatos não nativos. Nada para descartar."
#: ../lib/vector/Vlib/box.c:264
#, c-format
@@ -6164,12 +6166,12 @@ msgstr "Topologia não disponível para mapa vetorial <%s>. Registrando primitiv
#: ../lib/vector/Vlib/break_lines.c:574
#, c-format
msgid "Intersections: %d"
-msgstr ""
+msgstr "Intersecções: %d"
#: ../lib/vector/Vlib/color_write.c:71 ../lib/raster/color_write.c:81
#, c-format
msgid "Qualified name <%s> doesn't match mapset <%s>"
-msgstr ""
+msgstr "O nome qualificado <%s> não corresponde ao mapset <%s>"
#: ../lib/vector/Vlib/color_write.c:91 ../lib/raster/color_write.c:96
#, c-format
@@ -6187,7 +6189,7 @@ msgstr "%d áreas de tamanho total %g removidas"
#: ../lib/vector/Vlib/remove_areas.c:395
msgid "Could not delete line from coor"
-msgstr ""
+msgstr "Não foi possível excluir linha de coor"
#: ../lib/vector/Vlib/remove_areas.c:458
msgid "dissolve_neighbour > 0, failed to build new area"
@@ -6339,7 +6341,7 @@ msgstr "Segmento fora da linha, nenhum segmento criado"
#: ../lib/vector/Vlib/net_build.c:101 ../lib/vector/Vlib/net_build.c:727
msgid "Building graph..."
-msgstr ""
+msgstr "Construindo gráfico..."
#: ../lib/vector/Vlib/net_build.c:143 ../lib/vector/Vlib/net_build.c:775
msgid "Unable to build network graph"
@@ -6364,11 +6366,11 @@ msgstr ""
#: ../lib/vector/Vlib/net_build.c:988
#, c-format
msgid "Data type of column <%s> not supported (must be numeric)"
-msgstr ""
+msgstr "Tipo de dados da coluna <%s> não suportado (deve ser numérico)"
#: ../lib/vector/Vlib/net_build.c:202 ../lib/vector/Vlib/net_build.c:967
msgid "Setting node costs..."
-msgstr ""
+msgstr "Definindo custos de nó..."
#: ../lib/vector/Vlib/net_build.c:277 ../lib/vector/Vlib/net_build.c:1032
#, c-format
@@ -6377,7 +6379,7 @@ msgstr "Registro do banco de dados para o nó %d (cat = %d) não encontrado (cos
#: ../lib/vector/Vlib/net_build.c:332 ../lib/vector/Vlib/net_build.c:352
msgid "Cannot add network arc for virtual node connection."
-msgstr ""
+msgstr "Não é possível adicionar arco de rede para conexão de nó virtual."
#: ../lib/vector/Vlib/net_build.c:364
#, c-format
@@ -6385,6 +6387,8 @@ msgid ""
"There exists more than one point of node <%d> with unique category field <%d>.\n"
"The unique categories layer is not valid therefore you will probably get incorrect results."
msgstr ""
+"Existe mais de um ponto de nó <%d> com campo de categoria exclusivo <%d>.\n"
+"A camada de categorias exclusivas não é válida, portanto, você provavelmente obterá resultados incorretos."
#: ../lib/vector/Vlib/net_build.c:393
#, c-format
@@ -6393,6 +6397,9 @@ msgid ""
"Cost for the intersection was set to 0.\n"
"The unique categories layer is not valid therefore you will probably get incorrect results."
msgstr ""
+"Não foi possível encontrar o ponto que representa a interseção <%d> no campo de categorias exclusivas <%d>.\n"
+"O custo da interseção foi definido como 0.\n"
+"A camada de categorias exclusivas não é válida, portanto, você provavelmente obterá resultados incorretos."
#: ../lib/vector/Vlib/net_build.c:407
#, c-format
@@ -6401,6 +6408,9 @@ msgid ""
"Cost for the intersection was set to 0.\n"
"The unique categories layer is not valid therefore you will probably get incorrect results."
msgstr ""
+"Não foi possível encontrar o nó para o ponto que representa a interseção <%d> no campo de categorias exclusivas <%d>.\n"
+"O custo da interseção foi definido como 0.\n"
+"A camada de categorias exclusivas não é válida, portanto, você provavelmente obterá resultados incorretos."
#: ../lib/vector/Vlib/net_build.c:432
#, c-format
@@ -6480,7 +6490,7 @@ msgstr ""
#: ../lib/vector/Vlib/snap.c:527 ../lib/vector/Vlib/snap.c:890
#, c-format
msgid "New vertices: %d"
-msgstr ""
+msgstr "Novos vértices: %d"
#: ../lib/vector/Vlib/snap.c:917
msgid "Reading features..."
@@ -6557,7 +6567,7 @@ msgstr "Atualizando dados TopoGeometry..."
#: ../lib/vector/Vlib/build_pg.c:464
#, c-format
msgid "Unsupported topo geometry type %d"
-msgstr ""
+msgstr "Tipo de geometria %d de topologia não suportado "
#: ../lib/vector/Vlib/build_pg.c:493
#, c-format
@@ -6566,7 +6576,7 @@ msgstr "Não foi possível criar <%s.%s>"
#: ../lib/vector/Vlib/build_pg.c:644
msgid "Unable to write nodes, offset array mismatch"
-msgstr ""
+msgstr "Não foi possível escrever nós, incompatibilidade de array de deslocamento"
#: ../lib/vector/Vlib/build_pg.c:673
msgid "Unable to write nodes"
@@ -6584,7 +6594,7 @@ msgstr "Não foi possível escrever linhas"
#: ../lib/vector/Vlib/build_pg.c:792
#, c-format
msgid "Topology for centroid %d not available. Area %d skipped"
-msgstr "Topologia para centróide %d não disponível. Área %d ignorada"
+msgstr "Topologia para centroide %d não disponível. Área %d ignorada"
#: ../lib/vector/Vlib/build_pg.c:1002
msgid "Create simple features topology from topogeometry data..."
@@ -6757,7 +6767,7 @@ msgstr "Não foi possível ler arquivo de entrada <%s>"
#: ../lib/cairodriver/read_xid.c:20
#, c-format
msgid "Unable to parse input file <%s>"
-msgstr "Não foi possível analisar o arquivo de entrada <%s>"
+msgstr "Não foi possível interpretar o arquivo de entrada <%s>"
#: ../lib/cairodriver/raster.c:109
msgid "Failed to create cairo surface"
@@ -7046,7 +7056,7 @@ msgstr "Leia o mapa g3d <%s> na memória"
#: ../lib/gpde/n_arrays_io.c:364 ../lib/gpde/n_arrays_io.c:464
msgid "Error closing g3d file"
-msgstr "Erro fechando arquivo g3d"
+msgstr "Erro ao fechar arquivo g3d"
#: ../lib/gpde/n_arrays_io.c:420
#, c-format
@@ -7609,7 +7619,7 @@ msgstr "Formato de granulação incorreto: %s"
#: ../lib/python/temporal/abstract_space_time_dataset.py:1777
#, python-format
msgid "Unable to snap dataset <%(ds)s> of type %(type)s in the temporal database. The mapset of the dataset does not match the current mapset"
-msgstr "Não foi possível ajustar o conjunto de dados <%(ds)s> do tipo %(type)s no banco de dados temporal. O mapset do conjunto de dados não corresponde ao mapset atual"
+msgstr "Não foi possível ligar o conjunto de dados <%(ds)s> do tipo %(type)s ao banco de dados temporal. O mapset do conjunto de dados não corresponde ao mapset atual"
#: ../lib/python/temporal/abstract_space_time_dataset.py:1879
#, python-format
@@ -8022,7 +8032,7 @@ msgstr "Fusos horários não são suportados"
#: ../lib/python/temporal/datetime_math.py:811
#, python-format
msgid "Unable to parse time string: %s"
-msgstr "Não foi possível analisar string <%s>"
+msgstr "Não foi possível interpretar string <%s>"
#: ../lib/python/temporal/sampling.py:93 ../lib/python/temporal/sampling.py:98
#, python-format
@@ -8391,7 +8401,7 @@ msgstr ""
#: ../lib/python/script/task.py:524
#, python-brace-format
msgid "Cannot parse interface description of<{name}> module: {error}"
-msgstr "Não é possível analisar a descrição da interface do módulo <{name}>: {error}"
+msgstr "Não é possível interpretar a descrição da interface do módulo <{name}>: {error}"
#: ../lib/python/script/raster3d.py:92
#, python-format
@@ -8578,12 +8588,12 @@ msgstr ""
#: ../lib/raster3d/range.c:88
#, c-format
msgid "Error reading range file for [%s in %s]"
-msgstr ""
+msgstr "Erro ao ler arquivo de intervalo para [%s in %s]"
#: ../lib/raster3d/range.c:150
#, c-format
msgid "Unable to open range file for <%s>"
-msgstr ""
+msgstr "Não foi possível abrir o arquivo de intervalo para <%s>"
#: ../lib/raster3d/close.c:47
#, c-format
@@ -8597,11 +8607,11 @@ msgstr "Não foi possível mover o mapa raster temporário <%s> para o mapa rast
#: ../lib/raster3d/close.c:79
msgid "Unable to flush all tiles"
-msgstr ""
+msgstr "Não foi possível limpar todos os mosaicos"
#: ../lib/raster3d/close.c:84
msgid "Unable to flush index"
-msgstr ""
+msgstr "Não foi possível limpar o índice"
#: ../lib/raster3d/close.c:94
msgid "Unable to position file"
@@ -9172,7 +9182,7 @@ msgstr "Erro ao ler linha nula %d para <%s>"
#: ../lib/raster/close.c:56
#, c-format
msgid "Unable to flush file %s for raster map %s: %s"
-msgstr ""
+msgstr "Não foi possível limpar o arquivo %s para o mapa raster %s: %s"
#: ../lib/raster/close.c:64
#, c-format
@@ -9224,11 +9234,11 @@ msgstr "não consegui escrever as informações de categoria para [%s] em [%s]"
#: ../lib/raster/get_cellhd.c:66
#, c-format
msgid "Unable to read header file for raster map <%s@%s>. It is a reclass of raster map <%s@%s> %s"
-msgstr ""
+msgstr "Não foi possível ler o arquivo de cabeçalho do mapa raster <%s@%s>. É uma reclassificação do mapa raster <%s@%s> %s"
#: ../lib/raster/get_cellhd.c:70
msgid "which is missing."
-msgstr ""
+msgstr "que está faltando."
#: ../lib/raster/get_cellhd.c:71
msgid "whose header file can't be opened."
@@ -9303,12 +9313,12 @@ msgstr "Suporte de cor para <%s@%s> %s"
#: ../lib/raster/open.c:192
#, c-format
msgid "Unable to open raster map <%s@%s> since it is a reclass of raster map <%s@%s> which does not exist"
-msgstr ""
+msgstr "Não foi possível abrir o mapa raster <%s@%s> uma vez que é uma reclassificaçãao do mapa raster <%s@%s> que não existe"
#: ../lib/raster/open.c:197
#, c-format
msgid "Error reading reclass file for raster map <%s>"
-msgstr ""
+msgstr "Erro ao ler arquivo de reclassificação para mapa raster <%s>"
#: ../lib/raster/open.c:208
#, c-format
@@ -9549,7 +9559,7 @@ msgstr "O driver <%s> não suporta escrita direta. Usando o driver MEM para um c
#: ../lib/raster/gdal.c:513
msgid "Unable to get in-memory raster driver"
-msgstr ""
+msgstr "Não foi possível obter o driver de raster na memória"
#: ../lib/raster/gdal.c:520
#, c-format
@@ -9672,12 +9682,12 @@ msgstr "Rast_set_null_value: tipo de dados errado!"
#: ../lib/raster/reclass.c:168
#, c-format
msgid "Too many reclass categories for <%s@%s>"
-msgstr ""
+msgstr "Muitas categorias de reclassificação para <%s@%s>"
#: ../lib/raster/reclass.c:171
#, c-format
msgid "Illegal reclass format in header file for <%s@%s>"
-msgstr ""
+msgstr "Formato de reclassificação ilegal no arquivo de cabeçalho para <%s@%s>"
#: ../lib/raster/reclass.c:273
msgid "Illegal reclass request"
@@ -9960,7 +9970,7 @@ msgstr "Erro no arquivo da tabela de dados <%s>, linha %d"
#: ../lib/proj/convert.c:194
msgid "Unable parse GRASS PROJ_INFO file"
-msgstr "Não foi possível analisar o arquivo GRASS PROJ_INFO"
+msgstr "Não foi possível interpretar o arquivo GRASS PROJ_INFO"
#: ../lib/proj/convert.c:201
msgid "Unable get PROJ.4-style parameter string"
@@ -9969,7 +9979,7 @@ msgstr "Não é possível obter string de parâmetro com estilo PROJ.4"
#: ../lib/proj/convert.c:219
#, c-format
msgid "OGR can't parse PROJ.4-style parameter string: %s (OGR Error code was %d)"
-msgstr "O OGR não consegue analisar a string de parâmetros no estilo PROJ.4: %s (o código de erro OGR era %d)"
+msgstr "O OGR não consegue interpretar a string de parâmetros no estilo PROJ.4: %s (o código de erro OGR era %d)"
#: ../lib/proj/convert.c:232
#, c-format
=====================================
locale/po/grassmods_fr.po
=====================================
@@ -9277,7 +9277,7 @@ msgstr "Installer"
#: ../locale/scriptstrings/g.extension_to_translate.c:26
msgid "Download source code and exit"
-msgstr "Télecharger le code qource et quitter"
+msgstr "Télécharger le code source et quitter"
#: ../locale/scriptstrings/g.extension_to_translate.c:28
msgid "Do not install new extension, just compile it"
@@ -26441,7 +26441,7 @@ msgstr "Impossible d'ouvrir la carte vecteur <%s>"
#: ../vector/v.colors/scan_attr.c:24 ../doc/vector/v.example/main.c:109
#, c-format
msgid "Database connection not defined for layer %d"
-msgstr ""
+msgstr "La connexion à la base de données n'a pas été définie pour la couche %d"
#: ../display/d.vect.chart/plot.c:86 ../display/d.vect/attr.c:110
#: ../vector/v.in.db/main.c:225
@@ -29502,7 +29502,7 @@ msgstr "Impossible de sélectionner les attributs pour cat = %d"
#: ../vector/v.lrs/v.lrs.label/main.c:331 ../vector/v.out.ogr/attrb.c:53
#: ../vector/v.overlay/main.c:503
msgid "Unable to fetch data from table"
-msgstr ""
+msgstr "Impossible de récupérer les données depuis la table"
#: ../vector/v.out.vtk/writeVTK.c:632
msgid "Cannot export attribute table fields for layer < 1. Skipping export"
@@ -29525,7 +29525,7 @@ msgstr ""
#: ../scripts/db.univar/db.univar.py:107
#, c-format, python-format
msgid "Unable to describe table <%s>"
-msgstr ""
+msgstr "Impossible de décrire la table <%s>"
#: ../vector/v.out.vtk/writeVTK.c:673
msgid "No numerical attributes found. Skipping export"
@@ -30061,7 +30061,7 @@ msgstr ""
#: ../vector/v.out.ogr/list.c:116 ../vector/v.in.ogr/main.c:442
#: ../raster/r.in.gdal/main.c:314 ../raster/r.external/list.c:23
msgid "Supported formats:"
-msgstr ""
+msgstr "Formats supportés : "
#: ../vector/v.external/list.c:111
msgid "GRASS is not compiled with PostgreSQL support"
@@ -32716,7 +32716,7 @@ msgstr ""
#: ../vector/v.surf.bspline/main.c:882 ../vector/v.out.postgis/main.c:178
#: ../vector/v.net.components/main.c:213 ../raster/r.resamp.bspline/main.c:723
msgid "Writing output..."
-msgstr ""
+msgstr "Écriture de la sortie ..."
#: ../vector/v.kernel/main.c:68
#, c-format
@@ -33704,7 +33704,7 @@ msgstr ""
#: ../vector/v.out.postgis/args.c:66
msgid "Do not export attribute table"
-msgstr ""
+msgstr "Ne pas exporter de table attributaire"
#: ../vector/v.out.postgis/args.c:72
msgid "Export PostGIS topology instead of simple features"
@@ -33755,26 +33755,26 @@ msgstr ""
#: ../vector/v.out.postgis/main.c:111
#, c-format
msgid "Unable to open vector map <%s> on topological level"
-msgstr ""
+msgstr "Impossible d'ouvrir la carte vecteur <%s> au niveau topologique"
#: ../vector/v.out.postgis/main.c:135
#, c-format
msgid "Unable to create PostGIS layer <%s>"
-msgstr ""
+msgstr "Impossible de créer la couche PostGIS <%s>"
#: ../vector/v.out.postgis/main.c:160
#, c-format
msgid "Feature type '%s' not supported"
-msgstr ""
+msgstr "Type d'entité '%s' non supporté"
#: ../vector/v.out.postgis/main.c:165
#, c-format
msgid "Feature type %d is not supported"
-msgstr ""
+msgstr "Type d'entité %d non supporté"
#: ../vector/v.out.postgis/main.c:172
msgid "Copying features failed"
-msgstr ""
+msgstr "Échec de la copie des objets"
#: ../vector/v.out.postgis/main.c:185
#, c-format
@@ -33808,7 +33808,7 @@ msgstr ""
#: ../vector/v.out.postgis/table.c:27
#, c-format
msgid "No database connection for layer <%s>"
-msgstr ""
+msgstr "Connexion à la base de données non définie pour la couche <%s>"
#: ../vector/v.out.postgis/table.c:50
#, c-format
@@ -35123,7 +35123,7 @@ msgstr "Position de référence"
#: ../vector/v.label/main.c:132 ../vector/v.label/main.c:140
#: ../vector/v.label/main.c:148
msgid "Font"
-msgstr ""
+msgstr "Police"
#: ../vector/v.lrs/v.lrs.label/main.c:187 ../vector/v.label.sa/main.c:77
#: ../vector/v.label/main.c:129
@@ -43118,17 +43118,17 @@ msgstr ""
#: ../doc/vector/v.example/main.c:53
msgid "My first vector module"
-msgstr ""
+msgstr "Mon module vecteur de départ."
#: ../doc/vector/v.example/main.c:175
#, c-format
msgid "Unable to get attribute data for cat %d"
-msgstr ""
+msgstr "Impossible de sélectionner les attributs pour cat = %d"
#: ../doc/vector/v.example/main.c:184
#, c-format
msgid "Error while retrieving database record for cat %d"
-msgstr ""
+msgstr "Aucun enregistrement pour la catégorie cat = %d"
#: ../doc/vector/v.example/main.c:217
#, c-format
@@ -43137,7 +43137,7 @@ msgstr "Impossible de copier la table d'attributs vers la carte vecteur <%s>"
#: ../doc/raster/r.example/main.c:86
msgid "My first raster module"
-msgstr ""
+msgstr "Mon module raster de départ"
#: ../db/db.select/main.c:53 ../db/db.columns/main.c:44
#: ../db/db.describe/main.c:50 ../db/db.copy/main.c:100
=====================================
locale/po/grassmods_pt_BR.po
=====================================
The diff for this file was not included because it is too large.
=====================================
locale/po/grasswxpy_pt_BR.po
=====================================
The diff for this file was not included because it is too large.
=====================================
scripts/.flake8
=====================================
@@ -1,5 +1,6 @@
[flake8]
ignore =
+ E203, # white space before ':'
E265, # block comment should start with '# '
E266, # too many leading '#' for block comment
E402, # module level import not at top of file
=====================================
scripts/g.extension/g.extension.py
=====================================
@@ -8,13 +8,15 @@
# Vaclav Petras <wenzeslaus gmail com> (support for general sources)
# PURPOSE: Tool to download and install extensions into local installation
#
-# COPYRIGHT: (C) 2009-2019 by Markus Neteler, and the GRASS Development Team
+# COPYRIGHT: (C) 2009-2021 by Markus Neteler, and the GRASS Development Team
#
# This program is free software under the GNU General
# Public License (>=v2). Read the file COPYING that
# comes with GRASS for details.
#
-# TODO: - add sudo support where needed (i.e. check first permission to write into
+# TODO: - update temporary workaround of using grass7 subdir of addon-repo, see
+# https://github.com/OSGeo/grass-addons/issues/528
+# - add sudo support where needed (i.e. check first permission to write into
# $GISBASE directory)
# - fix toolbox support in install_private_extension_xml()
#############################################################################
@@ -75,7 +77,6 @@
#% description: Specific branch to fetch addon from (only used when fetching from git)
#% required: no
#% multiple: no
-#% answer: main
#%end
#%flag
@@ -128,6 +129,10 @@
#% suppress_required: yes
#%end
#%flag
+#% key: o
+#% description: url refers to a fork of the official extension repository
+#%end
+#%flag
#% key: j
#% description: Generates JSON file containing the download URLs of the official Addons
#% guisection: Install
@@ -139,6 +144,8 @@
#% required: extension, -l, -c, -g, -a, -j
#% exclusive: extension, -l, -c, -g
#% exclusive: extension, -l, -c, -a
+#% requires: -o, url
+#% requires: branch, url
#%end
# TODO: solve addon-extension(-module) confusion
@@ -161,14 +168,16 @@ from distutils.dir_util import copy_tree
from six.moves.urllib import request as urlrequest
from six.moves.urllib.error import HTTPError, URLError
+from six.moves.urllib.parse import urlparse
# Get the XML parsing exceptions to catch. The behavior changed with Python 2.7
# and ElementTree 1.3.
from xml.parsers import expat # TODO: works for any Python?
-if hasattr(etree, 'ParseError'):
+
+if hasattr(etree, "ParseError"):
ETREE_EXCEPTIONS = (etree.ParseError, expat.ExpatError)
else:
- ETREE_EXCEPTIONS = (expat.ExpatError)
+ ETREE_EXCEPTIONS = expat.ExpatError
import grass.script as gscript
from grass.script.utils import try_rmdir
@@ -182,6 +191,7 @@ HEADERS = {
"User-Agent": "Mozilla/5.0",
}
HTTP_STATUS_CODES = list(http.HTTPStatus)
+GIT_URL = "https://github.com/OSGeo/grass-addons"
def replace_shebang_win(python_file):
@@ -213,7 +223,7 @@ def urlretrieve(url, filename, *args, **kwargs):
"""
request = urlrequest.Request(url, headers=HEADERS)
response = urlrequest.urlopen(request, *args, **kwargs)
- with open(filename, 'wb') as f:
+ with open(filename, "wb") as f:
f.write(response.read())
@@ -225,8 +235,89 @@ def urlopen(url, *args, **kwargs):
return urlrequest.urlopen(request, *args, **kwargs)
-def download_addons_paths_file(
- url, response_format, *args, **kwargs):
+def get_version_branch(major_version):
+ """Check if version branch for the current GRASS version exists,
+ if not, take branch for the previous version
+ For the official repo we assume that at least one version branch is present"""
+ version_branch = "grass{}".format(major_version)
+ try:
+ urlrequest.urlopen(
+ "{GIT_URL}/tree/{version_branch}/src".format(
+ GIT_URL=GIT_URL, version_branch=version_branch
+ )
+ )
+ except URLError:
+ version_branch = "grass{}".format(int(major_version) - 1)
+ return version_branch
+
+
+def get_github_branches(
+ github_api_url="https://api.github.com/repos/OSGeo/grass-addons/branches",
+ version_only=True,
+):
+ """Get ordered list of branch names in repo using github API
+ For the official repo we assume that at least one version branch is present
+ Due to strict rate limits in the github API (60 calls per hour) this function
+ is currently not used."""
+ req = urlrequest.urlopen(github_api_url)
+ content = json.loads(req.read())
+ branches = [repo_branch["name"] for repo_branch in content]
+ if version_only:
+ branches = [
+ version_branch
+ for version_branch in branches
+ if version_branch.startswith("grass")
+ ]
+ branches.sort()
+ return branches
+
+
+def get_default_branch(full_url):
+ """Get default branch for repository in known hosting services
+ (currently only implemented for github, gitlab and bitbucket API)
+ In all other cases "main" is used as default"""
+ # Parse URL
+ url_parts = urlparse(full_url)
+ # Get organization and repository component
+ try:
+ organization, repository = url_parts.path.split("/")[1:3]
+ except URLError:
+ gscript.fatal(
+ _(
+ "Cannot retrieve organization and repository from URL: <{}>.".format(
+ full_url
+ )
+ )
+ )
+ # Construct API call and retrieve default branch
+ api_calls = {
+ "github.com": "https://api.github.com/repos/{organization}/{repository}".format(
+ organization=organization, repository=repository
+ ),
+ "gitlab.com": "https://gitlab.com/api/v4/projects/{organization}%2F{repository}".format(
+ organization=organization, repository=repository
+ ),
+ "bitbucket.org": "https://api.bitbucket.org/2.0/repositories/{organization}/{repository}/branching-model?".format(
+ organization=organization, repository=repository
+ ),
+ }
+ # Try to get default branch via API. The API call is known to fail a) if the full_url
+ # does not belong to an implemented hosting service or b) if the rate limit of the
+ # API is exceeded
+ try:
+ req = urlrequest.urlopen(api_calls.get(url_parts.netloc))
+ content = json.loads(req.read())
+ # For github and gitlab
+ default_branch = content.get("default_branch")
+ # For bitbucket
+ if not default_branch:
+ default_branch = content.get("development").get("name")
+ except URLError:
+ default_branch = "main"
+ return default_branch
+
+
+def download_addons_paths_file(url, response_format, *args, **kwargs):
"""Generates JSON file containing the download URLs of the official
Addons
@@ -252,7 +343,7 @@ def download_addons_paths_file(
),
),
)
- if response_format not in response.getheader('Content-Type'):
+ if response_format not in response.getheader("Content-Type"):
gscript.fatal(
_(
"Wrong downloaded file format. "
@@ -266,10 +357,7 @@ def download_addons_paths_file(
return response
except HTTPError as err:
- if (
- err.code == 403 and
- err.msg == 'rate limit exceeded'
- ):
+ if err.code == 403 and err.msg == "rate limit exceeded":
gscript.warning(
_(
"The download of the json file with add-ons paths "
@@ -280,7 +368,7 @@ def download_addons_paths_file(
)
else:
return download_addons_paths_file(
- url=url.replace('main', 'master'),
+ url=url.replace("main", "master"),
response_format=response_format,
)
except URLError:
@@ -296,7 +384,7 @@ def download_addons_paths_file(
def etree_fromfile(filename):
"""Create XML element tree from a given file name"""
- with open(filename, 'r') as file_:
+ with open(filename, "r") as file_:
return etree.fromstring(file_.read())
@@ -308,12 +396,11 @@ def etree_fromurl(url):
def check_progs():
"""Check if the necessary programs are available"""
- # TODO: we need svn for the Subversion repo downloads
- # also git would be tested once supported
- for prog in ('make', 'gcc'):
- if not grass.find_program(prog, '--help'):
- grass.fatal(_("'%s' required. Please install '%s' first.")
- % (prog, prog))
+ # git to be tested once supported instead of `svn`
+ for prog in ("make", "gcc", "svn"):
+ if not grass.find_program(prog, "--help"):
+ grass.fatal(_("'%s' required. Please install '%s' first.") % (prog, prog))
+
# expand prefix to class name
@@ -330,19 +417,19 @@ def expand_module_class_name(class_letters):
'vector'
"""
name = {
- 'd': 'display',
- 'db': 'db',
- 'g': 'general',
- 'i': 'imagery',
- 'm': 'misc',
- 'ps': 'postscript',
- 'p': 'paint',
- 'r': 'raster',
- 'r3': 'raster3d',
- 's': 'sites',
- 't': 'temporal',
- 'v': 'vector',
- 'wx': 'gui/wxpython'
+ "d": "display",
+ "db": "db",
+ "g": "general",
+ "i": "imagery",
+ "m": "misc",
+ "ps": "postscript",
+ "p": "paint",
+ "r": "raster",
+ "r3": "raster3d",
+ "s": "sites",
+ "t": "temporal",
+ "v": "vector",
+ "wx": "gui/wxpython",
}
return name.get(class_letters, class_letters)
@@ -358,13 +445,13 @@ def get_module_class_name(module_name):
>>> get_module_class_name('v.to.rast')
'vector'
"""
- classchar = module_name.split('.', 1)[0]
+ classchar = module_name.split(".", 1)[0]
return expand_module_class_name(classchar)
def get_installed_extensions(force=False):
"""Get list of installed extensions or toolboxes (if -t is set)"""
- if flags['t']:
+ if flags["t"]:
return get_installed_toolboxes(force)
# TODO: extension != module
@@ -379,8 +466,8 @@ def list_installed_extensions(toolboxes=False):
grass.message(_("List of installed extensions (toolboxes):"))
else:
grass.message(_("List of installed extensions (modules):"))
- sys.stdout.write('\n'.join(elist))
- sys.stdout.write('\n')
+ sys.stdout.write("\n".join(elist))
+ sys.stdout.write("\n")
else:
if toolboxes:
grass.info(_("No extension (toolbox) installed"))
@@ -395,7 +482,7 @@ def get_installed_toolboxes(force=False):
Creates a new toolboxes file if it is not possible
to read the current one.
"""
- xml_file = os.path.join(options['prefix'], 'toolboxes.xml')
+ xml_file = os.path.join(options["prefix"], "toolboxes.xml")
if not os.path.exists(xml_file):
write_xml_toolboxes(xml_file)
# read XML file
@@ -406,8 +493,8 @@ def get_installed_toolboxes(force=False):
write_xml_toolboxes(xml_file)
return []
ret = list()
- for tnode in tree.findall('toolbox'):
- ret.append(tnode.get('code'))
+ for tnode in tree.findall("toolbox"):
+ ret.append(tnode.get("code"))
return ret
@@ -418,7 +505,7 @@ def get_installed_modules(force=False):
Creates a new modules file if it is not possible
to read the current one.
"""
- xml_file = os.path.join(options['prefix'], 'modules.xml')
+ xml_file = os.path.join(options["prefix"], "modules.xml")
if not os.path.exists(xml_file):
if force:
write_xml_modules(xml_file)
@@ -433,20 +520,21 @@ def get_installed_modules(force=False):
write_xml_modules(xml_file)
return []
ret = list()
- for tnode in tree.findall('task'):
- if flags['g']:
+ for tnode in tree.findall("task"):
+ if flags["g"]:
desc, keyw = get_optional_params(tnode)
- ret.append('name={0}'.format(tnode.get('name').strip()))
- ret.append('description={0}'.format(desc))
- ret.append('keywords={0}'.format(keyw))
- ret.append('executables={0}'.format(','.join(
- get_module_executables(tnode))
- ))
+ ret.append("name={0}".format(tnode.get("name").strip()))
+ ret.append("description={0}".format(desc))
+ ret.append("keywords={0}".format(keyw))
+ ret.append(
+ "executables={0}".format(",".join(get_module_executables(tnode)))
+ )
else:
- ret.append(tnode.get('name').strip())
+ ret.append(tnode.get("name").strip())
return ret
+
# list extensions (read XML file from grass.osgeo.org/addons)
@@ -456,22 +544,22 @@ def list_available_extensions(url):
For toolboxes it lists also all modules.
"""
gscript.debug("list_available_extensions(url={0})".format(url))
- if flags['t']:
+ if flags["t"]:
grass.message(_("List of available extensions (toolboxes):"))
tlist = get_available_toolboxes(url)
tkeys = sorted(tlist.keys())
for toolbox_code in tkeys:
toolbox_data = tlist[toolbox_code]
- if flags['g']:
- print('toolbox_name=' + toolbox_data['name'])
- print('toolbox_code=' + toolbox_code)
+ if flags["g"]:
+ print("toolbox_name=" + toolbox_data["name"])
+ print("toolbox_code=" + toolbox_code)
else:
- print('%s (%s)' % (toolbox_data['name'], toolbox_code))
- if flags['c'] or flags['g']:
- list_available_modules(url, toolbox_data['modules'])
+ print("%s (%s)" % (toolbox_data["name"], toolbox_code))
+ if flags["c"] or flags["g"]:
+ list_available_modules(url, toolbox_data["modules"])
else:
- if toolbox_data['modules']:
- print(os.linesep.join(['* ' + x for x in toolbox_data['modules']]))
+ if toolbox_data["modules"]:
+ print(os.linesep.join(["* " + x for x in toolbox_data["modules"]]))
else:
grass.message(_("List of available extensions (modules):"))
# TODO: extensions with several modules + lib
@@ -484,18 +572,20 @@ def get_available_toolboxes(url):
url = url + "toolboxes.xml"
try:
tree = etree_fromurl(url)
- for tnode in tree.findall('toolbox'):
+ for tnode in tree.findall("toolbox"):
mlist = list()
clist = list()
- tdict[tnode.get('code')] = {'name': tnode.get('name'),
- 'correlate': clist,
- 'modules': mlist}
+ tdict[tnode.get("code")] = {
+ "name": tnode.get("name"),
+ "correlate": clist,
+ "modules": mlist,
+ }
- for cnode in tnode.findall('correlate'):
- clist.append(cnode.get('name'))
+ for cnode in tnode.findall("correlate"):
+ clist.append(cnode.get("name"))
- for mnode in tnode.findall('task'):
- mlist.append(mnode.get('name'))
+ for mnode in tnode.findall("task"):
+ mlist.append(mnode.get("name"))
except (HTTPError, IOError, OSError):
grass.fatal(_("Unable to fetch addons metadata file"))
@@ -515,16 +605,16 @@ def get_toolbox_extensions(url, name):
try:
tree = etree_fromurl(url)
- for tnode in tree.findall('toolbox'):
- if name == tnode.get('code'):
- for enode in tnode.findall('task'):
+ for tnode in tree.findall("toolbox"):
+ if name == tnode.get("code"):
+ for enode in tnode.findall("task"):
# extension name
- ename = enode.get('name')
+ ename = enode.get("name")
edict[ename] = dict()
# list of modules installed by this extension
- edict[ename]['mlist'] = list()
+ edict[ename]["mlist"] = list()
# list of files installed by this extension
- edict[ename]['flist'] = list()
+ edict[ename]["flist"] = list()
break
except (HTTPError, IOError, OSError):
grass.fatal(_("Unable to fetch addons metadata file"))
@@ -538,9 +628,9 @@ def get_module_files(mnode):
:param mnode: XML node for a module
"""
flist = []
- if mnode.find('binary') is None:
+ if mnode.find("binary") is None:
return flist
- for file_node in mnode.find('binary').findall('file'):
+ for file_node in mnode.find("binary").findall("file"):
filepath = file_node.text
flist.append(filepath)
@@ -554,11 +644,12 @@ def get_module_executables(mnode):
"""
flist = []
for filepath in get_module_files(mnode):
- if filepath.startswith(options['prefix'] + os.path.sep + 'bin') or \
- (sys.platform != 'win32' and
- filepath.startswith(options['prefix'] + os.path.sep + 'scripts')):
+ if filepath.startswith(options["prefix"] + os.path.sep + "bin") or (
+ sys.platform != "win32"
+ and filepath.startswith(options["prefix"] + os.path.sep + "scripts")
+ ):
filename = os.path.basename(filepath)
- if sys.platform == 'win32':
+ if sys.platform == "win32":
filename = os.path.splitext(filename)[0]
flist.append(filename)
@@ -571,17 +662,17 @@ def get_optional_params(mnode):
:param mnode: XML node for a module
"""
try:
- desc = mnode.find('description').text
+ desc = mnode.find("description").text
except AttributeError:
- desc = ''
+ desc = ""
if desc is None:
- desc = ''
+ desc = ""
try:
- keyw = mnode.find('keywords').text
+ keyw = mnode.find("keywords").text
except AttributeError:
- keyw = ''
+ keyw = ""
if keyw is None:
- keyw = ''
+ keyw = ""
return desc, keyw
@@ -599,29 +690,34 @@ def list_available_modules(url, mlist=None):
try:
tree = etree_fromurl(file_url)
except ETREE_EXCEPTIONS:
- grass.warning(_("Unable to parse '%s'. Trying to scan"
- " SVN repository (may take some time)...") % file_url)
+ grass.warning(
+ _(
+ "Unable to parse '%s'. Trying to scan"
+ " SVN repository (may take some time)..."
+ )
+ % file_url
+ )
list_available_extensions_svn(url)
return
except (HTTPError, URLError, IOError, OSError):
list_available_extensions_svn(url)
return
- for mnode in tree.findall('task'):
- name = mnode.get('name').strip()
+ for mnode in tree.findall("task"):
+ name = mnode.get("name").strip()
if mlist and name not in mlist:
continue
- if flags['c'] or flags['g']:
+ if flags["c"] or flags["g"]:
desc, keyw = get_optional_params(mnode)
- if flags['g']:
- print('name=' + name)
- print('description=' + desc)
- print('keywords=' + keyw)
- elif flags['c']:
+ if flags["g"]:
+ print("name=" + name)
+ print("description=" + desc)
+ print("keywords=" + keyw)
+ elif flags["c"]:
if mlist:
- print('*', end='')
- print(name + ' - ' + desc)
+ print("*", end="")
+ print(name + " - " + desc)
else:
print(name)
@@ -643,25 +739,26 @@ def list_available_extensions_svn(url):
:param url: a directory URL (filename will be attached)
"""
gscript.debug("list_available_extensions_svn(url=%s)" % url, 2)
- grass.message(_('Fetching list of extensions from'
- ' GRASS-Addons SVN repository (be patient)...'))
+ grass.message(
+ _(
+ "Fetching list of extensions from"
+ " GRASS-Addons SVN repository (be patient)..."
+ )
+ )
pattern = re.compile(r'(<li><a href=".+">)(.+)(</a></li>)', re.IGNORECASE)
- if flags['c']:
- grass.warning(
- _("Flag 'c' ignored, addons metadata file not available"))
- if flags['g']:
- grass.warning(
- _("Flag 'g' ignored, addons metadata file not available"))
+ if flags["c"]:
+ grass.warning(_("Flag 'c' ignored, addons metadata file not available"))
+ if flags["g"]:
+ grass.warning(_("Flag 'g' ignored, addons metadata file not available"))
- prefixes = ['d', 'db', 'g', 'i', 'm', 'ps',
- 'p', 'r', 'r3', 's', 't', 'v']
+ prefixes = ["d", "db", "g", "i", "m", "ps", "p", "r", "r3", "s", "t", "v"]
for prefix in prefixes:
modclass = expand_module_class_name(prefix)
grass.verbose(_("Checking for '%s' modules...") % modclass)
# construct a full URL of a file
- file_url = '%s/%s' % (url, modclass)
+ file_url = "%s/%s" % (url, modclass)
grass.debug("url = %s" % file_url, debug=2)
try:
file_ = urlopen(url)
@@ -674,8 +771,8 @@ def list_available_extensions_svn(url):
sline = pattern.search(line)
if not sline:
continue
- name = sline.group(2).rstrip('/')
- if name.split('.', 1)[0] == prefix:
+ name = sline.group(2).rstrip("/")
+ if name.split(".", 1)[0] == prefix:
print(name)
# get_wxgui_extensions(url)
@@ -688,26 +785,28 @@ def get_wxgui_extensions(url):
:param url: a directory URL (filename will be attached)
"""
mlist = list()
- grass.debug('Fetching list of wxGUI extensions from '
- 'GRASS-Addons SVN repository (be patient)...')
+ grass.debug(
+ "Fetching list of wxGUI extensions from "
+ "GRASS-Addons SVN repository (be patient)..."
+ )
pattern = re.compile(r'(<li><a href=".+">)(.+)(</a></li>)', re.IGNORECASE)
- grass.verbose(_("Checking for '%s' modules...") % 'gui/wxpython')
+ grass.verbose(_("Checking for '%s' modules...") % "gui/wxpython")
# construct a full URL of a file
- url = '%s/%s' % (url, 'gui/wxpython')
+ url = "%s/%s" % (url, "gui/wxpython")
grass.debug("url = %s" % url, debug=2)
file_ = urlopen(url)
if not file_:
grass.warning(_("Unable to fetch '%s'") % url)
return
- for line in file.readlines():
+ for line in file_.readlines():
# list extensions
sline = pattern.search(line)
if not sline:
continue
- name = sline.group(2).rstrip('/')
- if name not in ('..', 'Makefile'):
+ name = sline.group(2).rstrip("/")
+ if name not in ("..", "Makefile"):
mlist.append(name)
return mlist
@@ -719,7 +818,7 @@ def cleanup():
try_rmdir(TMPDIR)
else:
grass.message("\n%s\n" % _("Path to the source code:"))
- sys.stderr.write('%s\n' % os.path.join(TMPDIR, options['extension']))
+ sys.stderr.write("%s\n" % os.path.join(TMPDIR, options["extension"]))
def write_xml_modules(name, tree=None):
@@ -730,38 +829,41 @@ def write_xml_modules(name, tree=None):
:param name: file name
:param tree: XML element tree
"""
- file_ = open(name, 'w')
+ file_ = open(name, "w")
file_.write('<?xml version="1.0" encoding="UTF-8"?>\n')
file_.write('<!DOCTYPE task SYSTEM "grass-addons.dtd">\n')
file_.write('<addons version="%s">\n' % version[0])
- libgis_revison = grass.version()['libgis_revision']
+ libgis_revison = grass.version()["libgis_revision"]
if tree is not None:
- for tnode in tree.findall('task'):
+ for tnode in tree.findall("task"):
indent = 4
- file_.write('%s<task name="%s">\n' %
- (' ' * indent, tnode.get('name')))
+ file_.write('%s<task name="%s">\n' % (" " * indent, tnode.get("name")))
indent += 4
- file_.write('%s<description>%s</description>\n' %
- (' ' * indent, tnode.find('description').text))
- file_.write('%s<keywords>%s</keywords>\n' %
- (' ' * indent, tnode.find('keywords').text))
- bnode = tnode.find('binary')
+ file_.write(
+ "%s<description>%s</description>\n"
+ % (" " * indent, tnode.find("description").text)
+ )
+ file_.write(
+ "%s<keywords>%s</keywords>\n"
+ % (" " * indent, tnode.find("keywords").text)
+ )
+ bnode = tnode.find("binary")
if bnode is not None:
- file_.write('%s<binary>\n' % (' ' * indent))
+ file_.write("%s<binary>\n" % (" " * indent))
indent += 4
- for fnode in bnode.findall('file'):
- file_.write('%s<file>%s</file>\n' %
- (' ' * indent, os.path.join(options['prefix'],
- fnode.text)))
+ for fnode in bnode.findall("file"):
+ file_.write(
+ "%s<file>%s</file>\n"
+ % (" " * indent, os.path.join(options["prefix"], fnode.text))
+ )
indent -= 4
- file_.write('%s</binary>\n' % (' ' * indent))
- file_.write('%s<libgis revision="%s" />\n' %
- (' ' * indent, libgis_revison))
+ file_.write("%s</binary>\n" % (" " * indent))
+ file_.write('%s<libgis revision="%s" />\n' % (" " * indent, libgis_revison))
indent -= 4
- file_.write('%s</task>\n' % (' ' * indent))
+ file_.write("%s</task>\n" % (" " * indent))
- file_.write('</addons>\n')
+ file_.write("</addons>\n")
file_.close()
@@ -773,18 +875,17 @@ def write_xml_extensions(name, tree=None):
:param name: file name
:param tree: XML element tree
"""
- file_ = open(name, 'w')
+ file_ = open(name, "w")
file_.write('<?xml version="1.0" encoding="UTF-8"?>\n')
file_.write('<!DOCTYPE task SYSTEM "grass-addons.dtd">\n')
file_.write('<addons version="%s">\n' % version[0])
- libgis_revison = grass.version()['libgis_revision']
+ libgis_revison = grass.version()["libgis_revision"]
if tree is not None:
- for tnode in tree.findall('task'):
+ for tnode in tree.findall("task"):
indent = 4
# extension name
- file_.write('%s<task name="%s">\n' %
- (' ' * indent, tnode.get('name')))
+ file_.write('%s<task name="%s">\n' % (" " * indent, tnode.get("name")))
indent += 4
"""
file_.write('%s<description>%s</description>\n' %
@@ -793,33 +894,32 @@ def write_xml_extensions(name, tree=None):
(' ' * indent, tnode.find('keywords').text))
"""
# extension files
- bnode = tnode.find('binary')
+ bnode = tnode.find("binary")
if bnode is not None:
- file_.write('%s<binary>\n' % (' ' * indent))
+ file_.write("%s<binary>\n" % (" " * indent))
indent += 4
- for fnode in bnode.findall('file'):
- file_.write('%s<file>%s</file>\n' %
- (' ' * indent, os.path.join(options['prefix'],
- fnode.text)))
+ for fnode in bnode.findall("file"):
+ file_.write(
+ "%s<file>%s</file>\n"
+ % (" " * indent, os.path.join(options["prefix"], fnode.text))
+ )
indent -= 4
- file_.write('%s</binary>\n' % (' ' * indent))
+ file_.write("%s</binary>\n" % (" " * indent))
# extension modules
- mnode = tnode.find('modules')
+ mnode = tnode.find("modules")
if mnode is not None:
- file_.write('%s<modules>\n' % (' ' * indent))
+ file_.write("%s<modules>\n" % (" " * indent))
indent += 4
- for fnode in mnode.findall('module'):
- file_.write('%s<module>%s</module>\n' %
- (' ' * indent, fnode.text))
+ for fnode in mnode.findall("module"):
+ file_.write("%s<module>%s</module>\n" % (" " * indent, fnode.text))
indent -= 4
- file_.write('%s</modules>\n' % (' ' * indent))
+ file_.write("%s</modules>\n" % (" " * indent))
- file_.write('%s<libgis revision="%s" />\n' %
- (' ' * indent, libgis_revison))
+ file_.write('%s<libgis revision="%s" />\n' % (" " * indent, libgis_revison))
indent -= 4
- file_.write('%s</task>\n' % (' ' * indent))
+ file_.write("%s</task>\n" % (" " * indent))
- file_.write('</addons>\n')
+ file_.write("</addons>\n")
file_.close()
@@ -831,38 +931,44 @@ def write_xml_toolboxes(name, tree=None):
:param name: file name
:param tree: XML element tree
"""
- file_ = open(name, 'w')
+ file_ = open(name, "w")
file_.write('<?xml version="1.0" encoding="UTF-8"?>\n')
file_.write('<!DOCTYPE toolbox SYSTEM "grass-addons.dtd">\n')
file_.write('<addons version="%s">\n' % version[0])
if tree is not None:
- for tnode in tree.findall('toolbox'):
+ for tnode in tree.findall("toolbox"):
indent = 4
- file_.write('%s<toolbox name="%s" code="%s">\n' %
- (' ' * indent, tnode.get('name'), tnode.get('code')))
+ file_.write(
+ '%s<toolbox name="%s" code="%s">\n'
+ % (" " * indent, tnode.get("name"), tnode.get("code"))
+ )
indent += 4
- for cnode in tnode.findall('correlate'):
- file_.write('%s<correlate code="%s" />\n' %
- (' ' * indent, tnode.get('code')))
- for mnode in tnode.findall('task'):
- file_.write('%s<task name="%s" />\n' %
- (' ' * indent, mnode.get('name')))
+ for cnode in tnode.findall("correlate"):
+ file_.write(
+ '%s<correlate code="%s" />\n' % (" " * indent, tnode.get("code"))
+ )
+ for mnode in tnode.findall("task"):
+ file_.write(
+ '%s<task name="%s" />\n' % (" " * indent, mnode.get("name"))
+ )
indent -= 4
- file_.write('%s</toolbox>\n' % (' ' * indent))
+ file_.write("%s</toolbox>\n" % (" " * indent))
- file_.write('</addons>\n')
+ file_.write("</addons>\n")
file_.close()
def install_extension(source, url, xmlurl, branch):
"""Install extension (e.g. one module) or a toolbox (list of modules)"""
- gisbase = os.getenv('GISBASE')
+ gisbase = os.getenv("GISBASE")
if not gisbase:
- grass.fatal(_('$GISBASE not defined'))
+ grass.fatal(_("$GISBASE not defined"))
- if options['extension'] in get_installed_extensions(force=True):
- grass.warning(_("Extension <%s> already installed. Re-installing...") %
- options['extension'])
+ if options["extension"] in get_installed_extensions(force=True):
+ grass.warning(
+ _("Extension <%s> already installed. Re-installing...")
+ % options["extension"]
+ )
# create a dictionary of extensions
# for each extension
@@ -870,16 +976,16 @@ def install_extension(source, url, xmlurl, branch):
# - a list of files installed by this extension
edict = None
- if flags['t']:
- grass.message(_("Installing toolbox <%s>...") % options['extension'])
- edict = get_toolbox_extensions(xmlurl, options['extension'])
+ if flags["t"]:
+ grass.message(_("Installing toolbox <%s>...") % options["extension"])
+ edict = get_toolbox_extensions(xmlurl, options["extension"])
else:
edict = dict()
- edict[options['extension']] = dict()
+ edict[options["extension"]] = dict()
# list of modules installed by this extension
- edict[options['extension']]['mlist'] = list()
+ edict[options["extension"]]["mlist"] = list()
# list of files installed by this extension
- edict[options['extension']]['flist'] = list()
+ edict[options["extension"]]["flist"] = list()
if not edict:
grass.warning(_("Nothing to install"))
return
@@ -894,22 +1000,29 @@ def install_extension(source, url, xmlurl, branch):
if sys.platform == "win32":
ret1, new_modules_ext, new_files_ext = install_extension_win(extension)
else:
- ret1, new_modules_ext, new_files_ext, tmp_dir = install_extension_std_platforms(extension,
- source=source, url=url, branch=branch)
- if not flags['d'] and not flags['i']:
- edict[extension]['mlist'].extend(new_modules_ext)
- edict[extension]['flist'].extend(new_files_ext)
+ (
+ ret1,
+ new_modules_ext,
+ new_files_ext,
+ tmp_dir,
+ ) = install_extension_std_platforms(
+ extension, source=source, url=url, branch=branch
+ )
+ if not flags["d"] and not flags["i"]:
+ edict[extension]["mlist"].extend(new_modules_ext)
+ edict[extension]["flist"].extend(new_files_ext)
new_modules.extend(new_modules_ext)
ret += ret1
if len(edict) > 1:
- print('-' * 60)
+ print("-" * 60)
- if flags['d'] or flags['i']:
+ if flags["d"] or flags["i"]:
return
if ret != 0:
- grass.warning(_('Installation failed, sorry.'
- ' Please check above error messages.'))
+ grass.warning(
+ _("Installation failed, sorry." " Please check above error messages.")
+ )
else:
# update extensions metadata file
grass.message(_("Updating extensions metadata file..."))
@@ -922,13 +1035,18 @@ def install_extension(source, url, xmlurl, branch):
for module in new_modules:
update_manual_page(module)
- grass.message(_("Installation of <%s> successfully finished") %
- options['extension'])
+ grass.message(
+ _("Installation of <%s> successfully finished") % options["extension"]
+ )
- if not os.getenv('GRASS_ADDON_BASE'):
- grass.warning(_('This add-on module will not function until'
- ' you set the GRASS_ADDON_BASE environment'
- ' variable (see "g.manual variables")'))
+ if not os.getenv("GRASS_ADDON_BASE"):
+ grass.warning(
+ _(
+ "This add-on module will not function until"
+ " you set the GRASS_ADDON_BASE environment"
+ ' variable (see "g.manual variables")'
+ )
+ )
def get_toolboxes_metadata(url):
@@ -943,24 +1061,23 @@ def get_toolboxes_metadata(url):
data = dict()
try:
tree = etree_fromurl(url)
- for tnode in tree.findall('toolbox'):
+ for tnode in tree.findall("toolbox"):
clist = list()
- for cnode in tnode.findall('correlate'):
- clist.append(cnode.get('code'))
+ for cnode in tnode.findall("correlate"):
+ clist.append(cnode.get("code"))
mlist = list()
- for mnode in tnode.findall('task'):
- mlist.append(mnode.get('name'))
+ for mnode in tnode.findall("task"):
+ mlist.append(mnode.get("name"))
- code = tnode.get('code')
+ code = tnode.get("code")
data[code] = {
- 'name': tnode.get('name'),
- 'correlate': clist,
- 'modules': mlist,
+ "name": tnode.get("name"),
+ "correlate": clist,
+ "modules": mlist,
}
except (HTTPError, IOError, OSError):
- grass.error(_("Unable to read addons metadata file "
- "from the remote server"))
+ grass.error(_("Unable to read addons metadata file " "from the remote server"))
return data
@@ -976,40 +1093,39 @@ def install_toolbox_xml(url, name):
grass.warning(_("No addons metadata available for <%s>") % name)
return
- xml_file = os.path.join(options['prefix'], 'toolboxes.xml')
+ xml_file = os.path.join(options["prefix"], "toolboxes.xml")
# create an empty file if not exists
if not os.path.exists(xml_file):
write_xml_modules(xml_file)
# read XML file
- with open(xml_file, 'r') as xml:
+ with open(xml_file, "r") as xml:
tree = etree.fromstring(xml.read())
# update tree
tnode = None
- for node in tree.findall('toolbox'):
- if node.get('code') == name:
+ for node in tree.findall("toolbox"):
+ if node.get("code") == name:
tnode = node
break
tdata = data[name]
if tnode is not None:
# update existing node
- for cnode in tnode.findall('correlate'):
+ for cnode in tnode.findall("correlate"):
tnode.remove(cnode)
- for mnode in tnode.findall('task'):
+ for mnode in tnode.findall("task"):
tnode.remove(mnode)
else:
# create new node for task
- tnode = etree.Element(
- 'toolbox', attrib={'name': tdata['name'], 'code': name})
+ tnode = etree.Element("toolbox", attrib={"name": tdata["name"], "code": name})
tree.append(tnode)
- for cname in tdata['correlate']:
- cnode = etree.Element('correlate', attrib={'code': cname})
+ for cname in tdata["correlate"]:
+ cnode = etree.Element("correlate", attrib={"code": cname})
tnode.append(cnode)
- for tname in tdata['modules']:
- mnode = etree.Element('task', attrib={'name': tname})
+ for tname in tdata["modules"]:
+ mnode = etree.Element("task", attrib={"name": tname})
tnode.append(mnode)
write_xml_toolboxes(xml_file, tree)
@@ -1031,36 +1147,39 @@ def get_addons_metadata(url, mlist):
try:
tree = etree_fromurl(url)
except (HTTPError, URLError, IOError, OSError) as error:
- grass.error(_("Unable to read addons metadata file"
- " from the remote server: {0}").format(error))
+ grass.error(
+ _(
+ "Unable to read addons metadata file" " from the remote server: {0}"
+ ).format(error)
+ )
return data, bin_list
except ETREE_EXCEPTIONS as error:
grass.warning(_("Unable to parse '%s': {0}").format(error) % url)
return data, bin_list
- for mnode in tree.findall('task'):
- name = mnode.get('name')
+ for mnode in tree.findall("task"):
+ name = mnode.get("name")
if name not in mlist:
continue
file_list = list()
- bnode = mnode.find('binary')
- windows = sys.platform == 'win32'
+ bnode = mnode.find("binary")
+ windows = sys.platform == "win32"
if bnode is not None:
- for fnode in bnode.findall('file'):
- path = fnode.text.split('/')
- if path[0] == 'bin':
+ for fnode in bnode.findall("file"):
+ path = fnode.text.split("/")
+ if path[0] == "bin":
bin_list.append(path[-1])
if windows:
- path[-1] += '.exe'
- elif path[0] == 'scripts':
+ path[-1] += ".exe"
+ elif path[0] == "scripts":
bin_list.append(path[-1])
if windows:
- path[-1] += '.py'
+ path[-1] += ".py"
file_list.append(os.path.sep.join(path))
desc, keyw = get_optional_params(mnode)
data[name] = {
- 'desc': desc,
- 'keyw': keyw,
- 'files': file_list,
+ "desc": desc,
+ "keyw": keyw,
+ "files": file_list,
}
return data, bin_list
@@ -1075,7 +1194,7 @@ def install_extension_xml(edict):
# # read metadata from remote server (toolboxes)
# install_toolbox_xml(url, options['extension'])
- xml_file = os.path.join(options['prefix'], 'extensions.xml')
+ xml_file = os.path.join(options["prefix"], "extensions.xml")
# create an empty file if not exists
if not os.path.exists(xml_file):
write_xml_extensions(xml_file)
@@ -1100,14 +1219,14 @@ def install_extension_xml(edict):
"""
tnode = None
- for node in tree.findall('task'):
- if node.get('name') == name:
+ for node in tree.findall("task"):
+ if node.get("name") == name:
tnode = node
break
if tnode is None:
# create new node for task
- tnode = etree.Element('task', attrib={'name': name})
+ tnode = etree.Element("task", attrib={"name": name})
"""
dnode = etree.Element('description')
dnode.text = desc
@@ -1118,25 +1237,27 @@ def install_extension_xml(edict):
"""
# create binary
- bnode = etree.Element('binary')
+ bnode = etree.Element("binary")
# list of all installed files for this extension
- for file_name in edict[name]['flist']:
- fnode = etree.Element('file')
+ for file_name in edict[name]["flist"]:
+ fnode = etree.Element("file")
fnode.text = file_name
bnode.append(fnode)
tnode.append(bnode)
# create modules
- msnode = etree.Element('modules')
+ msnode = etree.Element("modules")
# list of all installed modules for this extension
- for module_name in edict[name]['mlist']:
- mnode = etree.Element('module')
+ for module_name in edict[name]["mlist"]:
+ mnode = etree.Element("module")
mnode.text = module_name
msnode.append(mnode)
tnode.append(msnode)
tree.append(tnode)
else:
- grass.verbose("Extension already listed in metadata file; metadata not updated!")
+ grass.verbose(
+ "Extension already listed in metadata file; metadata not updated!"
+ )
write_xml_extensions(xml_file, tree)
return None
@@ -1148,7 +1269,7 @@ def install_module_xml(mlist):
"""
- xml_file = os.path.join(options['prefix'], 'modules.xml')
+ xml_file = os.path.join(options["prefix"], "modules.xml")
# create an empty file if not exists
if not os.path.exists(xml_file):
write_xml_modules(xml_file)
@@ -1163,25 +1284,28 @@ def install_module_xml(mlist):
desc = gtask.parse_interface(name).description
# mname = gtask.parse_interface(name).name
keywords = gtask.parse_interface(name).keywords
- except Exception as e:
- grass.warning(_("No metadata available for module '%s'.")
- % name)
+ except Exception as error:
+ grass.warning(
+ _("No metadata available for module '{name}': {error}").format(
+ name=name, error=error
+ )
+ )
continue
tnode = None
- for node in tree.findall('task'):
- if node.get('name') == name:
+ for node in tree.findall("task"):
+ if node.get("name") == name:
tnode = node
break
if tnode is None:
# create new node for task
- tnode = etree.Element('task', attrib={'name': name})
- dnode = etree.Element('description')
+ tnode = etree.Element("task", attrib={"name": name})
+ dnode = etree.Element("description")
dnode.text = desc
tnode.append(dnode)
- knode = etree.Element('keywords')
- knode.text = (',').join(keywords)
+ knode = etree.Element("keywords")
+ knode.text = (",").join(keywords)
tnode.append(knode)
# binary files installed with an extension are now
@@ -1220,7 +1344,9 @@ def install_module_xml(mlist):
"""
tree.append(tnode)
else:
- grass.verbose("Extension module already listed in metadata file; metadata not updated!")
+ grass.verbose(
+ "Extension module already listed in metadata file; metadata not updated!"
+ )
write_xml_modules(xml_file, tree)
return mlist
@@ -1228,35 +1354,47 @@ def install_module_xml(mlist):
def install_extension_win(name):
"""Install extension on MS Windows"""
- grass.message(_("Downloading precompiled GRASS Addons <%s>...") %
- options['extension'])
+ grass.message(
+ _("Downloading precompiled GRASS Addons <%s>...") % options["extension"]
+ )
# build base URL
- if build_platform == 'x86_64':
+ if build_platform == "x86_64":
platform = build_platform
else:
- platform = 'x86'
- base_url = "http://wingrass.fsv.cvut.cz/" \
- "grass%(major)s%(minor)s/%(platform)s/addons/" \
- "grass-%(major)s.%(minor)s.%(patch)s" % \
- {'platform': platform,
- 'major': version[0], 'minor': version[1],
- 'patch': version[2]}
+ platform = "x86"
+ base_url = (
+ "http://wingrass.fsv.cvut.cz/"
+ "grass%(major)s%(minor)s/%(platform)s/addons/"
+ "grass-%(major)s.%(minor)s.%(patch)s"
+ % {
+ "platform": platform,
+ "major": version[0],
+ "minor": version[1],
+ "patch": version[2],
+ }
+ )
# resolve ZIP URL
- source, url = resolve_source_code(url='{0}/{1}.zip'.format(base_url, name))
+ source, url = resolve_source_code(url="{0}/{1}.zip".format(base_url, name))
# to hide non-error messages from subprocesses
if grass.verbosity() <= 2:
- outdev = open(os.devnull, 'w')
+ outdev = open(os.devnull, "w")
else:
outdev = sys.stdout
# download Addons ZIP file
os.chdir(TMPDIR) # this is just to not leave something behind
srcdir = os.path.join(TMPDIR, name)
- download_source_code(source=source, url=url, name=name,
- outdev=outdev, directory=srcdir, tmpdir=TMPDIR)
+ download_source_code(
+ source=source,
+ url=url,
+ name=name,
+ outdev=outdev,
+ directory=srcdir,
+ tmpdir=TMPDIR,
+ )
# collect module names and file names
module_list = list()
@@ -1273,7 +1411,7 @@ def install_extension_win(name):
pyfiles = []
for r, d, f in os.walk(srcdir):
for file in f:
- if file.endswith('.py'):
+ if file.endswith(".py"):
pyfiles.append(os.path.join(r, file))
for filename in pyfiles:
@@ -1281,18 +1419,19 @@ def install_extension_win(name):
# collect old files
old_file_list = list()
- for r, d, f in os.walk(options['prefix']):
+ for r, d, f in os.walk(options["prefix"]):
for filename in f:
fullname = os.path.join(r, filename)
old_file_list.append(fullname)
# copy Addons copy tree to destination directory
- move_extracted_files(extract_dir=srcdir, target_dir=options['prefix'],
- files=os.listdir(srcdir))
+ move_extracted_files(
+ extract_dir=srcdir, target_dir=options["prefix"], files=os.listdir(srcdir)
+ )
# collect new files
file_list = list()
- for r, d, f in os.walk(options['prefix']):
+ for r, d, f in os.walk(options["prefix"]):
for filename in f:
fullname = os.path.join(r, filename)
if fullname not in old_file_list:
@@ -1320,11 +1459,10 @@ def download_source_code_svn(url, name, outdev, directory=None):
"""
if not directory:
directory = os.path.join(os.getcwd, name)
- classchar = name.split('.', 1)[0]
+ classchar = name.split(".", 1)[0]
moduleclass = expand_module_class_name(classchar)
- url = url + '/' + moduleclass + '/' + name
- if grass.call(['svn', 'checkout',
- url, directory], stdout=outdev) != 0:
+ url = url + "/" + moduleclass + "/" + name
+ if grass.call(["svn", "checkout", url, directory], stdout=outdev) != 0:
grass.fatal(_("GRASS Addons <%s> not found") % name)
return directory
@@ -1348,10 +1486,7 @@ def download_source_code_official_github(url, name, outdev, directory=None):
"""
if not directory:
directory = os.path.join(os.getcwd, name)
- classchar = name.split('.', 1)[0]
- moduleclass = expand_module_class_name(classchar)
- if grass.call(['svn', 'export',
- url, directory], stdout=outdev) != 0:
+ if grass.call(["svn", "export", url, directory], stdout=outdev) != 0:
grass.fatal(_("GRASS Addons <%s> not found") % name)
return directory
@@ -1375,8 +1510,7 @@ def move_extracted_files(extract_dir, target_dir, files):
if os.path.isdir(actual_file):
# shutil.copytree() replaced by copy_tree() because
# shutil's copytree() fails when subdirectory exists
- copy_tree(actual_file,
- os.path.join(target_dir, file_name))
+ copy_tree(actual_file, os.path.join(target_dir, file_name))
else:
shutil.copy(actual_file, os.path.join(target_dir, file_name))
@@ -1393,7 +1527,7 @@ def fix_newlines(directory):
"""
# skip binary files
# see https://stackoverflow.com/a/7392391
- textchars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7f})
+ textchars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F})
def is_binary_string(bytes):
return bool(bytes.translate(None, textchars))
@@ -1401,40 +1535,42 @@ def fix_newlines(directory):
for root, unused, files in os.walk(directory):
for name in files:
filename = os.path.join(root, name)
- if is_binary_string(open(filename, 'rb').read(1024)):
+ if is_binary_string(open(filename, "rb").read(1024)):
continue # ignore binary files
# read content of text file
- with open(filename, 'rb') as fd:
+ with open(filename, "rb") as fd:
data = fd.read()
# we don't expect there would be CRLF file by
# purpose if we want to allow CRLF files we would
# have to whitelite .py etc
- newdata = data.replace(b'\r\n', b'\n')
+ newdata = data.replace(b"\r\n", b"\n")
if newdata != data:
- with open(filename, 'wb') as newfile:
+ with open(filename, "wb") as newfile:
newfile.write(newdata)
def extract_zip(name, directory, tmpdir):
"""Extract a ZIP file into a directory"""
- gscript.debug("extract_zip(name={name}, directory={directory},"
- " tmpdir={tmpdir})".format(name=name, directory=directory,
- tmpdir=tmpdir), 3)
+ gscript.debug(
+ "extract_zip(name={name}, directory={directory},"
+ " tmpdir={tmpdir})".format(name=name, directory=directory, tmpdir=tmpdir),
+ 3,
+ )
try:
- zip_file = zipfile.ZipFile(name, mode='r')
+ zip_file = zipfile.ZipFile(name, mode="r")
file_list = zip_file.namelist()
# we suppose we can write to parent of the given dir
# (supposing a tmp dir)
- extract_dir = os.path.join(tmpdir, 'extract_dir')
+ extract_dir = os.path.join(tmpdir, "extract_dir")
os.mkdir(extract_dir)
for subfile in file_list:
- # this should be safe in Python 2.7.4
+ if "__pycache__" in subfile:
+ continue
zip_file.extract(subfile, extract_dir)
files = os.listdir(extract_dir)
- move_extracted_files(extract_dir=extract_dir,
- target_dir=directory, files=files)
+ move_extracted_files(extract_dir=extract_dir, target_dir=directory, files=files)
except zipfile.BadZipfile as error:
gscript.fatal(_("ZIP file is unreadable: {0}").format(error))
@@ -1442,119 +1578,154 @@ def extract_zip(name, directory, tmpdir):
# TODO: solve the other related formats
def extract_tar(name, directory, tmpdir):
"""Extract a TAR or a similar file into a directory"""
- gscript.debug("extract_tar(name={name}, directory={directory},"
- " tmpdir={tmpdir})".format(name=name, directory=directory,
- tmpdir=tmpdir), 3)
+ gscript.debug(
+ "extract_tar(name={name}, directory={directory},"
+ " tmpdir={tmpdir})".format(name=name, directory=directory, tmpdir=tmpdir),
+ 3,
+ )
try:
import tarfile # we don't need it anywhere else
+
tar = tarfile.open(name)
- extract_dir = os.path.join(tmpdir, 'extract_dir')
+ extract_dir = os.path.join(tmpdir, "extract_dir")
os.mkdir(extract_dir)
tar.extractall(path=extract_dir)
files = os.listdir(extract_dir)
- move_extracted_files(extract_dir=extract_dir,
- target_dir=directory, files=files)
+ move_extracted_files(extract_dir=extract_dir, target_dir=directory, files=files)
except tarfile.TarError as error:
gscript.fatal(_("Archive file is unreadable: {0}").format(error))
-extract_tar.supported_formats = ['tar.gz', 'gz', 'bz2', 'tar', 'gzip', 'targz']
+
+extract_tar.supported_formats = ["tar.gz", "gz", "bz2", "tar", "gzip", "targz"]
-def download_source_code(source, url, name, outdev,
- directory=None, tmpdir=None, branch=None):
+def download_source_code(
+ source, url, name, outdev, directory=None, tmpdir=None, branch=None
+):
"""Get source code to a local directory for compilation"""
- gscript.verbose(_("Type of source identified as '{source}'.")
- .format(source=source))
- if source == 'official':
- gscript.message(_("Fetching <%s> from "
- "GRASS GIS Addons repository (be patient)...") % name)
+ gscript.verbose(_("Type of source identified as '{source}'.").format(source=source))
+ if source == "official":
+ gscript.message(
+ _("Fetching <%s> from " "GRASS GIS Addons repository (be patient)...")
+ % name
+ )
+ download_source_code_official_github(url, name, outdev, directory)
+ elif source == "official_fork":
+ gscript.message(
+ _("Fetching <{name}> from " "<{url}> (be patient)...").format(
+ name=name, url=url
+ )
+ )
download_source_code_official_github(url, name, outdev, directory)
- elif source == 'svn':
- gscript.message(_("Fetching <{name}> from "
- "<{url}> (be patient)...").format(name=name, url=url))
+ elif source == "svn":
+ gscript.message(
+ _("Fetching <{name}> from " "<{url}> (be patient)...").format(
+ name=name, url=url
+ )
+ )
download_source_code_svn(url, name, outdev, directory)
- elif source in ['remote_zip']: # , 'official'
- gscript.message(_("Fetching <{name}> from "
- "<{url}> (be patient)...").format(name=name, url=url))
+ elif source in ["remote_zip"]: # , 'official'
+ gscript.message(
+ _("Fetching <{name}> from " "<{url}> (be patient)...").format(
+ name=name, url=url
+ )
+ )
# we expect that the module.zip file is not by chance in the archive
- zip_name = os.path.join(tmpdir, 'extension.zip')
+ zip_name = os.path.join(tmpdir, "extension.zip")
try:
response = urlopen(url)
except URLError:
# Try download add-on from 'master' branch if default "main" fails
- if branch == "main":
+ if not branch:
try:
- url = url.replace('main', 'master')
- gscript.message(_("Expected default branch not found. "
- "Trying again from <{url}>...")
- .format(url=url))
+ url = url.replace("main", "master")
+ gscript.message(
+ _(
+ "Expected default branch not found. "
+ "Trying again from <{url}>..."
+ ).format(url=url)
+ )
response = urlopen(url)
except URLError:
- grass.fatal(_("Extension <{name}> not found. Please check "
- "'url' and 'branch' options".format(name=name)))
+ grass.fatal(
+ _(
+ "Extension <{name}> not found. Please check "
+ "'url' and 'branch' options".format(name=name)
+ )
+ )
else:
grass.fatal(_("Extension <%s> not found") % name)
- with open(zip_name, 'wb') as out_file:
+ with open(zip_name, "wb") as out_file:
shutil.copyfileobj(response, out_file)
extract_zip(name=zip_name, directory=directory, tmpdir=tmpdir)
fix_newlines(directory)
- elif source.startswith('remote_') and \
- source.split('_')[1] in extract_tar.supported_formats:
+ elif (
+ source.startswith("remote_")
+ and source.split("_")[1] in extract_tar.supported_formats
+ ):
# we expect that the module.tar.gz file is not by chance in the archive
- archive_name = os.path.join(tmpdir,
- 'extension.' + source.split('_')[1])
+ archive_name = os.path.join(tmpdir, "extension." + source.split("_")[1])
urlretrieve(url, archive_name)
extract_tar(name=archive_name, directory=directory, tmpdir=tmpdir)
fix_newlines(directory)
- elif source == 'zip':
+ elif source == "zip":
extract_zip(name=url, directory=directory, tmpdir=tmpdir)
fix_newlines(directory)
elif source in extract_tar.supported_formats:
extract_tar(name=url, directory=directory, tmpdir=tmpdir)
fix_newlines(directory)
- elif source == 'dir':
+ elif source == "dir":
shutil.copytree(url, directory)
fix_newlines(directory)
else:
# probably programmer error
- grass.fatal(_("Unknown extension (addon) source type '{0}'."
- " Please report this to the grass-user mailing list.")
- .format(source))
+ grass.fatal(
+ _(
+ "Unknown extension (addon) source type '{0}'."
+ " Please report this to the grass-user mailing list."
+ ).format(source)
+ )
assert os.path.isdir(directory)
def install_extension_std_platforms(name, source, url, branch):
"""Install extension on standard platforms"""
- gisbase = os.getenv('GISBASE')
- source_url = 'https://github.com/OSGeo/grass-addons/tree/master/grass7/'
+ gisbase = os.getenv("GISBASE")
# to hide non-error messages from subprocesses
if grass.verbosity() <= 2:
- outdev = open(os.devnull, 'w')
+ outdev = open(os.devnull, "w")
else:
outdev = sys.stdout
os.chdir(TMPDIR) # this is just to not leave something behind
srcdir = os.path.join(TMPDIR, name)
- download_source_code(source=source, url=url, name=name,
- outdev=outdev, directory=srcdir, tmpdir=TMPDIR,
- branch=branch)
+ download_source_code(
+ source=source,
+ url=url,
+ name=name,
+ outdev=outdev,
+ directory=srcdir,
+ tmpdir=TMPDIR,
+ branch=branch,
+ )
os.chdir(srcdir)
- pgm_not_found_message = _('Module name not found.'
- ' Check module Makefile syntax (PGM variable).')
+ pgm_not_found_message = _(
+ "Module name not found." " Check module Makefile syntax (PGM variable)."
+ )
# collect module names
module_list = list()
for r, d, f in os.walk(srcdir):
for filename in f:
if filename == "Makefile":
# get the module name: PGM = <module name>
- with open(os.path.join(r, 'Makefile')) as fp:
+ with open(os.path.join(r, "Makefile")) as fp:
for line in fp.readlines():
- if re.match(r'PGM.*.=|PGM=', line):
+ if re.match(r"PGM.*.=|PGM=", line):
try:
- modulename = line.split('=')[1].strip()
+ modulename = line.split("=")[1].strip()
if modulename:
module_list.append(modulename)
else:
@@ -1567,77 +1738,76 @@ def install_extension_std_platforms(name, source, url, branch):
# r=root, d=directories, f = files
for r, d, f in os.walk(srcdir):
for file in f:
- if file.endswith('.py'):
+ if file.endswith(".py"):
pyfiles.append(os.path.join(r, file))
for filename in pyfiles:
with fileinput.FileInput(filename, inplace=True) as file:
for line in file:
- print(line.replace(
- "#!/usr/bin/env python\n",
- "#!/usr/bin/env python3\n"
- ), end='')
+ print(
+ line.replace("#!/usr/bin/env python\n", "#!/usr/bin/env python3\n"),
+ end="",
+ )
dirs = {
- 'bin': os.path.join(TMPDIR, name, 'bin'),
- 'docs': os.path.join(TMPDIR, name, 'docs'),
- 'html': os.path.join(TMPDIR, name, 'docs', 'html'),
- 'rest': os.path.join(TMPDIR, name, 'docs', 'rest'),
- 'man': os.path.join(TMPDIR, name, 'docs', 'man'),
- 'script': os.path.join(TMPDIR, name, 'scripts'),
+ "bin": os.path.join(TMPDIR, name, "bin"),
+ "docs": os.path.join(TMPDIR, name, "docs"),
+ "html": os.path.join(TMPDIR, name, "docs", "html"),
+ "rest": os.path.join(TMPDIR, name, "docs", "rest"),
+ "man": os.path.join(TMPDIR, name, "docs", "man"),
+ "script": os.path.join(TMPDIR, name, "scripts"),
# TODO: handle locales also for addons
# 'string' : os.path.join(TMPDIR, name, 'locale'),
- 'string': os.path.join(TMPDIR, name),
- 'etc': os.path.join(TMPDIR, name, 'etc'),
+ "string": os.path.join(TMPDIR, name),
+ "etc": os.path.join(TMPDIR, name, "etc"),
}
make_cmd = [
- 'make',
- 'MODULE_TOPDIR=%s' % gisbase.replace(' ', r'\ '),
- 'RUN_GISRC=%s' % os.environ['GISRC'],
- 'BIN=%s' % dirs['bin'],
- 'HTMLDIR=%s' % dirs['html'],
- 'RESTDIR=%s' % dirs['rest'],
- 'MANBASEDIR=%s' % dirs['man'],
- 'SCRIPTDIR=%s' % dirs['script'],
- 'STRINGDIR=%s' % dirs['string'],
- 'ETC=%s' % os.path.join(dirs['etc']),
- 'SOURCE_URL=%s' % source_url
+ "make",
+ "MODULE_TOPDIR=%s" % gisbase.replace(" ", r"\ "),
+ "RUN_GISRC=%s" % os.environ["GISRC"],
+ "BIN=%s" % dirs["bin"],
+ "HTMLDIR=%s" % dirs["html"],
+ "RESTDIR=%s" % dirs["rest"],
+ "MANBASEDIR=%s" % dirs["man"],
+ "SCRIPTDIR=%s" % dirs["script"],
+ "STRINGDIR=%s" % dirs["string"],
+ "ETC=%s" % os.path.join(dirs["etc"]),
+ "SOURCE_URL=%s" % url,
]
install_cmd = [
- 'make',
- 'MODULE_TOPDIR=%s' % gisbase,
- 'ARCH_DISTDIR=%s' % os.path.join(TMPDIR, name),
- 'INST_DIR=%s' % options['prefix'],
- 'install'
+ "make",
+ "MODULE_TOPDIR=%s" % gisbase,
+ "ARCH_DISTDIR=%s" % os.path.join(TMPDIR, name),
+ "INST_DIR=%s" % options["prefix"],
+ "install",
]
- if flags['d']:
+ if flags["d"]:
grass.message("\n%s\n" % _("To compile run:"))
- sys.stderr.write(' '.join(make_cmd) + '\n')
+ sys.stderr.write(" ".join(make_cmd) + "\n")
grass.message("\n%s\n" % _("To install run:"))
- sys.stderr.write(' '.join(install_cmd) + '\n')
+ sys.stderr.write(" ".join(install_cmd) + "\n")
return 0, None, None, None
os.chdir(os.path.join(TMPDIR, name))
grass.message(_("Compiling..."))
- if not os.path.exists(os.path.join(gisbase, 'include',
- 'Make', 'Module.make')):
+ if not os.path.exists(os.path.join(gisbase, "include", "Make", "Module.make")):
grass.fatal(_("Please install GRASS development package"))
- if 0 != grass.call(make_cmd,
- stdout=outdev):
- grass.fatal(_('Compilation failed, sorry.'
- ' Please check above error messages.'))
+ if 0 != grass.call(make_cmd, stdout=outdev):
+ grass.fatal(
+ _("Compilation failed, sorry." " Please check above error messages.")
+ )
- if flags['i']:
+ if flags["i"]:
return 0, None, None, None
# collect old files
old_file_list = list()
- for r, d, f in os.walk(options['prefix']):
+ for r, d, f in os.walk(options["prefix"]):
for filename in f:
fullname = os.path.join(r, filename)
old_file_list.append(fullname)
@@ -1647,7 +1817,7 @@ def install_extension_std_platforms(name, source, url, branch):
# collect new files
file_list = list()
- for r, d, f in os.walk(options['prefix']):
+ for r, d, f in os.walk(options["prefix"]):
for filename in f:
fullname = os.path.join(r, filename)
if fullname not in old_file_list:
@@ -1658,20 +1828,20 @@ def install_extension_std_platforms(name, source, url, branch):
def remove_extension(force=False):
"""Remove existing extension
- extension or toolbox with extensions if -t is given)"""
- if flags['t']:
- edict = get_toolbox_extensions(options['prefix'], options['extension'])
+ extension or toolbox with extensions if -t is given)"""
+ if flags["t"]:
+ edict = get_toolbox_extensions(options["prefix"], options["extension"])
else:
edict = dict()
- edict[options['extension']] = dict()
+ edict[options["extension"]] = dict()
# list of modules installed by this extension
- edict[options['extension']]['mlist'] = list()
+ edict[options["extension"]]["mlist"] = list()
# list of files installed by this extension
- edict[options['extension']]['flist'] = list()
+ edict[options["extension"]]["flist"] = list()
# collect modules and files installed by these extensions
mlist = list()
- xml_file = os.path.join(options['prefix'], 'extensions.xml')
+ xml_file = os.path.join(options["prefix"], "extensions.xml")
if os.path.exists(xml_file):
# read XML file
tree = None
@@ -1682,27 +1852,27 @@ def remove_extension(force=False):
write_xml_extensions(xml_file)
if tree is not None:
- for tnode in tree.findall('task'):
- ename = tnode.get('name').strip()
+ for tnode in tree.findall("task"):
+ ename = tnode.get("name").strip()
if ename in edict:
# modules installed by this extension
- mnode = tnode.find('modules')
+ mnode = tnode.find("modules")
if mnode:
- for fnode in mnode.findall('module'):
+ for fnode in mnode.findall("module"):
mname = fnode.text.strip()
- edict[ename]['mlist'].append(mname)
+ edict[ename]["mlist"].append(mname)
mlist.append(mname)
# files installed by this extension
- bnode = tnode.find('binary')
+ bnode = tnode.find("binary")
if bnode:
- for fnode in bnode.findall('file'):
+ for fnode in bnode.findall("file"):
bname = fnode.text.strip()
- edict[ename]['flist'].append(bname)
+ edict[ename]["flist"].append(bname)
else:
if force:
write_xml_extensions(xml_file)
- xml_file = os.path.join(options['prefix'], 'modules.xml')
+ xml_file = os.path.join(options["prefix"], "modules.xml")
if not os.path.exists(xml_file):
if force:
write_xml_modules(xml_file)
@@ -1719,18 +1889,18 @@ def remove_extension(force=False):
return []
if tree is not None:
- for tnode in tree.findall('task'):
- ename = tnode.get('name').strip()
+ for tnode in tree.findall("task"):
+ ename = tnode.get("name").strip()
if ename in edict:
# assume extension name == module name
- edict[ename]['mlist'].append(ename)
+ edict[ename]["mlist"].append(ename)
mlist.append(ename)
# files installed by this extension
- bnode = tnode.find('binary')
+ bnode = tnode.find("binary")
if bnode:
- for fnode in bnode.findall('file'):
+ for fnode in bnode.findall("file"):
bname = fnode.text.strip()
- edict[ename]['flist'].append(bname)
+ edict[ename]["flist"].append(bname)
if force:
grass.verbose(_("List of removed files:"))
@@ -1745,17 +1915,25 @@ def remove_extension(force=False):
remove_extension_xml(mlist, edict)
for ename in edict:
if ename in eremoved:
- grass.message(_("Extension <%s> successfully uninstalled.") %
- ename)
+ grass.message(_("Extension <%s> successfully uninstalled.") % ename)
else:
- if flags['t']:
- grass.warning(_("Toolbox <%s> not removed. "
- "Re-run '%s' with '-f' flag to force removal")
- % (options['extension'], 'g.extension'))
+ if flags["t"]:
+ grass.warning(
+ _(
+ "Toolbox <%s> not removed. "
+ "Re-run '%s' with '-f' flag to force removal"
+ )
+ % (options["extension"], "g.extension")
+ )
else:
- grass.warning(_("Extension <%s> not removed. "
- "Re-run '%s' with '-f' flag to force removal")
- % (options['extension'], 'g.extension'))
+ grass.warning(
+ _(
+ "Extension <%s> not removed. "
+ "Re-run '%s' with '-f' flag to force removal"
+ )
+ % (options["extension"], "g.extension")
+ )
+
# remove existing extension(s) (reading XML file)
@@ -1767,7 +1945,7 @@ def remove_extension_files(edict, force=False):
Fallbacks to standard layout of files on prefix path on error.
"""
# try to read XML metadata file first
- xml_file = os.path.join(options['prefix'], 'extensions.xml')
+ xml_file = os.path.join(options["prefix"], "extensions.xml")
einstalled = list()
eremoved = list()
@@ -1775,17 +1953,17 @@ def remove_extension_files(edict, force=False):
if os.path.exists(xml_file):
tree = etree_fromfile(xml_file)
if tree is not None:
- for task in tree.findall('task'):
- ename = task.get('name').strip()
+ for task in tree.findall("task"):
+ ename = task.get("name").strip()
einstalled.append(ename)
else:
tree = None
for name in edict:
removed = True
- if len(edict[name]['flist']) > 0:
+ if len(edict[name]["flist"]) > 0:
err = list()
- for fpath in edict[name]['flist']:
+ for fpath in edict[name]["flist"]:
grass.verbose(fpath)
if force:
try:
@@ -1818,21 +1996,20 @@ def remove_extension_std(name, force=False):
Any images for manuals or files installed in etc will not be
removed
"""
- for fpath in [os.path.join(options['prefix'], 'bin', name),
- os.path.join(options['prefix'], 'scripts', name),
- os.path.join(
- options['prefix'], 'docs', 'html', name + '.html'),
- os.path.join(
- options['prefix'], 'docs', 'rest', name + '.txt'),
- os.path.join(options['prefix'], 'docs', 'man', 'man1',
- name + '.1')]:
+ for fpath in [
+ os.path.join(options["prefix"], "bin", name),
+ os.path.join(options["prefix"], "scripts", name),
+ os.path.join(options["prefix"], "docs", "html", name + ".html"),
+ os.path.join(options["prefix"], "docs", "rest", name + ".txt"),
+ os.path.join(options["prefix"], "docs", "man", "man1", name + ".1"),
+ ]:
if os.path.isfile(fpath):
grass.verbose(fpath)
if force:
os.remove(fpath)
# remove module libraries under GRASS_ADDONS/etc/{name}/*
- libpath = os.path.join(options['prefix'], 'etc', name)
+ libpath = os.path.join(options["prefix"], "etc", name)
if os.path.isdir(libpath):
grass.verbose(libpath)
if force:
@@ -1841,13 +2018,13 @@ def remove_extension_std(name, force=False):
def remove_from_toolbox_xml(name):
"""Update local meta-file when removing existing toolbox"""
- xml_file = os.path.join(options['prefix'], 'toolboxes.xml')
+ xml_file = os.path.join(options["prefix"], "toolboxes.xml")
if not os.path.exists(xml_file):
return
# read XML file
tree = etree_fromfile(xml_file)
- for node in tree.findall('toolbox'):
- if node.get('code') != name:
+ for node in tree.findall("toolbox"):
+ if node.get("code") != name:
continue
tree.remove(node)
@@ -1858,32 +2035,33 @@ def remove_extension_xml(mlist, edict):
"""Update local meta-file when removing existing extension"""
if len(edict) > 1:
# update also toolboxes metadata
- remove_from_toolbox_xml(options['extension'])
+ remove_from_toolbox_xml(options["extension"])
# modules
- xml_file = os.path.join(options['prefix'], 'modules.xml')
+ xml_file = os.path.join(options["prefix"], "modules.xml")
if os.path.exists(xml_file):
# read XML file
tree = etree_fromfile(xml_file)
for name in mlist:
- for node in tree.findall('task'):
- if node.get('name') != name:
+ for node in tree.findall("task"):
+ if node.get("name") != name:
continue
tree.remove(node)
write_xml_modules(xml_file, tree)
# extensions
- xml_file = os.path.join(options['prefix'], 'extensions.xml')
+ xml_file = os.path.join(options["prefix"], "extensions.xml")
if os.path.exists(xml_file):
# read XML file
tree = etree_fromfile(xml_file)
for name in edict:
- for node in tree.findall('task'):
- if node.get('name') != name:
+ for node in tree.findall("task"):
+ if node.get("name") != name:
continue
tree.remove(node)
write_xml_extensions(xml_file, tree)
+
# check links in CSS
@@ -1895,8 +2073,8 @@ def check_style_file(name):
If the files are missing, a warning is issued.
"""
- dist_file = os.path.join(os.getenv('GISBASE'), 'docs', 'html', name)
- addons_file = os.path.join(options['prefix'], 'docs', 'html', name)
+ dist_file = os.path.join(os.getenv("GISBASE"), "docs", "html", name)
+ addons_file = os.path.join(options["prefix"], "docs", "html", name)
if os.path.isfile(addons_file):
return
@@ -1905,11 +2083,13 @@ def check_style_file(name):
shutil.copyfile(dist_file, addons_file)
except OSError as error:
grass.warning(
- _("Unable to create '{filename}': {error}."
- " Is the GRASS GIS documentation package installed?"
- " Installation continues,"
- " but documentation may not look right.").format(
- filename=addons_file, error=error))
+ _(
+ "Unable to create '{filename}': {error}."
+ " Is the GRASS GIS documentation package installed?"
+ " Installation continues,"
+ " but documentation may not look right."
+ ).format(filename=addons_file, error=error)
+ )
def create_dir(path):
@@ -1930,28 +2110,28 @@ def create_dir(path):
def check_dirs():
"""Ensure that the necessary directories in prefix path exist"""
- create_dir(os.path.join(options['prefix'], 'bin'))
- create_dir(os.path.join(options['prefix'], 'docs', 'html'))
- create_dir(os.path.join(options['prefix'], 'docs', 'rest'))
- check_style_file('grass_logo.png')
- check_style_file('grassdocs.css')
- create_dir(os.path.join(options['prefix'], 'etc'))
- create_dir(os.path.join(options['prefix'], 'docs', 'man', 'man1'))
- create_dir(os.path.join(options['prefix'], 'scripts'))
+ create_dir(os.path.join(options["prefix"], "bin"))
+ create_dir(os.path.join(options["prefix"], "docs", "html"))
+ create_dir(os.path.join(options["prefix"], "docs", "rest"))
+ check_style_file("grass_logo.png")
+ check_style_file("grassdocs.css")
+ create_dir(os.path.join(options["prefix"], "etc"))
+ create_dir(os.path.join(options["prefix"], "docs", "man", "man1"))
+ create_dir(os.path.join(options["prefix"], "scripts"))
+
# fix file URI in manual page
def update_manual_page(module):
"""Fix manual page for addons which are at different directory
- than core modules"""
- if module.split('.', 1)[0] == 'wx':
+ than core modules"""
+ if module.split(".", 1)[0] == "wx":
return # skip for GUI modules
grass.verbose(_("Manual page for <%s> updated") % module)
# read original html file
- htmlfile = os.path.join(
- options['prefix'], 'docs', 'html', module + '.html')
+ htmlfile = os.path.join(options["prefix"], "docs", "html", module + ".html")
try:
oldfile = open(htmlfile)
shtml = oldfile.read()
@@ -1968,12 +2148,12 @@ def update_manual_page(module):
pos.append(match.start(1))
# find URIs
- pattern = r'''<a href="([^"]+)">([^>]+)</a>'''
+ pattern = r"""<a href="([^"]+)">([^>]+)</a>"""
addons = get_installed_extensions(force=True)
for match in re.finditer(pattern, shtml):
- if match.group(1)[:4] == 'http':
+ if match.group(1)[:4] == "http":
continue
- if match.group(1).replace('.html', '') in addons:
+ if match.group(1).replace(".html", "") in addons:
continue
pos.append(match.start(1))
@@ -1981,15 +2161,15 @@ def update_manual_page(module):
return # no match
# replace file URIs
- prefix = 'file://' + '/'.join([os.getenv('GISBASE'), 'docs', 'html'])
- ohtml = shtml[:pos[0]]
+ prefix = "file://" + "/".join([os.getenv("GISBASE"), "docs", "html"])
+ ohtml = shtml[: pos[0]]
for i in range(1, len(pos)):
- ohtml += prefix + '/' + shtml[pos[i - 1]:pos[i]]
- ohtml += prefix + '/' + shtml[pos[-1]:]
+ ohtml += prefix + "/" + shtml[pos[i - 1] : pos[i]]
+ ohtml += prefix + "/" + shtml[pos[-1] :]
# write updated html file
try:
- newfile = open(htmlfile, 'w')
+ newfile = open(htmlfile, "w")
newfile.write(ohtml)
except IOError as error:
gscript.fatal(_("Unable for write manual page: %s") % error)
@@ -2000,21 +2180,24 @@ def update_manual_page(module):
def resolve_install_prefix(path, to_system):
"""Determine and check the path for installation"""
if to_system:
- path = os.environ['GISBASE']
- if path == '$GRASS_ADDON_BASE':
- if not os.getenv('GRASS_ADDON_BASE'):
- grass.warning(_("GRASS_ADDON_BASE is not defined, "
- "installing to ~/.grass%s/addons") % version[0])
- path = os.path.join(
- os.environ['HOME'], '.grass%s' % version[0], 'addons')
+ path = os.environ["GISBASE"]
+ if path == "$GRASS_ADDON_BASE":
+ if not os.getenv("GRASS_ADDON_BASE"):
+ grass.warning(
+ _("GRASS_ADDON_BASE is not defined, " "installing to ~/.grass%s/addons")
+ % version[0]
+ )
+ path = os.path.join(os.environ["HOME"], ".grass%s" % version[0], "addons")
else:
- path = os.environ['GRASS_ADDON_BASE']
- if os.path.exists(path) and \
- not os.access(path, os.W_OK):
- grass.fatal(_("You don't have permission to install extension to <{0}>."
- " Try to run {1} with administrator rights"
- " (su or sudo).")
- .format(path, 'g.extension'))
+ path = os.environ["GRASS_ADDON_BASE"]
+ if os.path.exists(path) and not os.access(path, os.W_OK):
+ grass.fatal(
+ _(
+ "You don't have permission to install extension to <{0}>."
+ " Try to run {1} with administrator rights"
+ " (su or sudo)."
+ ).format(path, "g.extension")
+ )
# ensure dir sep at the end for cases where path is used as URL and pasted
# together with file names
if not path.endswith(os.path.sep):
@@ -2034,45 +2217,48 @@ def resolve_xmlurl_prefix(url, source=None):
'https://grass.osgeo.org/addons/'
"""
gscript.debug("resolve_xmlurl_prefix(url={0}, source={1})".format(url, source))
- if source == 'official':
+ if source == "official":
# use pregenerated modules XML file
- url = 'https://grass.osgeo.org/addons/grass%s/' % version[0]
+ # Define branch to fetch from (latest or current version)
+ version_branch = get_version_branch(version[0])
+
+ url = "https://grass.osgeo.org/addons/{}/".format(version_branch)
# else try to get extensions XMl from SVN repository (provided URL)
# the exact action depends on subsequent code (somewhere)
- if not url.endswith('/'):
- url = url + '/'
+ if not url.endswith("/"):
+ url = url + "/"
return url
KNOWN_HOST_SERVICES_INFO = {
- 'OSGeo Trac': {
- 'domain': 'trac.osgeo.org',
- 'ignored_suffixes': ['format=zip'],
- 'possible_starts': ['', 'https://', 'http://'],
- 'url_start': 'https://',
- 'url_end': '?format=zip',
+ "OSGeo Trac": {
+ "domain": "trac.osgeo.org",
+ "ignored_suffixes": ["format=zip"],
+ "possible_starts": ["", "https://", "http://"],
+ "url_start": "https://",
+ "url_end": "?format=zip",
},
- 'GitHub': {
- 'domain': 'github.com',
- 'ignored_suffixes': ['.zip', '.tar.gz'],
- 'possible_starts': ['', 'https://', 'http://'],
- 'url_start': 'https://',
- 'url_end': '/archive/{branch}.zip',
+ "GitHub": {
+ "domain": "github.com",
+ "ignored_suffixes": [".zip", ".tar.gz"],
+ "possible_starts": ["", "https://", "http://"],
+ "url_start": "https://",
+ "url_end": "/archive/{branch}.zip",
},
- 'GitLab': {
- 'domain': 'gitlab.com',
- 'ignored_suffixes': ['.zip', '.tar.gz', '.tar.bz2', '.tar'],
- 'possible_starts': ['', 'https://', 'http://'],
- 'url_start': 'https://',
- 'url_end': '/-/archive/{branch}/{name}-{branch}.zip',
+ "GitLab": {
+ "domain": "gitlab.com",
+ "ignored_suffixes": [".zip", ".tar.gz", ".tar.bz2", ".tar"],
+ "possible_starts": ["", "https://", "http://"],
+ "url_start": "https://",
+ "url_end": "/-/archive/{branch}/{name}-{branch}.zip",
},
- 'Bitbucket': {
- 'domain': 'bitbucket.org',
- 'ignored_suffixes': ['.zip', '.tar.gz', '.gz', '.bz2'],
- 'possible_starts': ['', 'https://', 'http://'],
- 'url_start': 'https://',
- 'url_end': '/get/{branch}.zip',
+ "Bitbucket": {
+ "domain": "bitbucket.org",
+ "ignored_suffixes": [".zip", ".tar.gz", ".gz", ".bz2"],
+ "possible_starts": ["", "https://", "http://"],
+ "url_start": "https://",
+ "url_end": "/get/{branch}.zip",
},
}
@@ -2092,40 +2278,46 @@ def resolve_known_host_service(url, name, branch):
match = None
actual_start = None
for key, value in KNOWN_HOST_SERVICES_INFO.items():
- for start in value['possible_starts']:
- if url.startswith(start + value['domain']):
+ for start in value["possible_starts"]:
+ if url.startswith(start + value["domain"]):
match = value
actual_start = start
- gscript.verbose(_("Identified {0} as known hosting service")
- .format(key))
- for suffix in value['ignored_suffixes']:
+ gscript.verbose(
+ _("Identified {0} as known hosting service").format(key)
+ )
+ for suffix in value["ignored_suffixes"]:
if url.endswith(suffix):
gscript.verbose(
- _("Not using {service} as known hosting service"
- " because the URL ends with '{suffix}'")
- .format(service=key, suffix=suffix))
+ _(
+ "Not using {service} as known hosting service"
+ " because the URL ends with '{suffix}'"
+ ).format(service=key, suffix=suffix)
+ )
return None, None
if match:
if not actual_start:
- actual_start = match['url_start']
+ actual_start = match["url_start"]
else:
- actual_start = ''
- if 'branch' in match['url_end']:
- suffix = match['url_end'].format(name=name, branch=branch)
+ actual_start = ""
+ if "branch" in match["url_end"]:
+ suffix = match["url_end"].format(
+ name=name,
+ branch=branch if branch else get_default_branch(url),
+ )
else:
- suffix = match['url_end'].format(name=name)
- url = '{prefix}{base}{suffix}'.format(prefix=actual_start,
- base=url.rstrip('/'),
- suffix=suffix)
- gscript.verbose(_("Will use the following URL for download: {0}")
- .format(url))
- return 'remote_zip', url
+ suffix = match["url_end"].format(name=name)
+ url = "{prefix}{base}{suffix}".format(
+ prefix=actual_start, base=url.rstrip("/"), suffix=suffix
+ )
+ gscript.verbose(_("Will use the following URL for download: {0}").format(url))
+ return "remote_zip", url
else:
return None, None
# TODO: add also option to enforce the source type
-def resolve_source_code(url=None, name=None, branch=None):
+# TODO: workaround, https://github.com/OSGeo/grass-addons/issues/528
+def resolve_source_code(url=None, name=None, branch=None, fork=False):
"""Return type and URL or path of the source code
Local paths are not presented as URLs to be usable in standard functions.
@@ -2192,57 +2384,84 @@ def resolve_source_code(url=None, name=None, branch=None):
>>> resolve_source_code('https://bitbucket.org/joe-user/grass-module') # doctest: +SKIP
('remote_zip', 'https://bitbucket.org/joe-user/grass-module/get/default.zip')
"""
- if not url and name:
+ # Handle URL for the offical repo
+ if name and (not url or fork):
module_class = get_module_class_name(name)
# note: 'trunk' is required to make URL usable for 'svn export' call
- git_url = 'https://github.com/OSGeo/grass-addons/trunk/' \
- 'grass{version}/{module_class}/{module_name}' \
- .format(version=version[0],
- module_class=module_class, module_name=name)
- # trac_url = 'https://trac.osgeo.org/grass/browser/grass-addons/' \
- # 'grass{version}/{module_class}/{module_name}?format=zip' \
- # .format(version=version[0],
- # module_class=module_class, module_name=name)
- # return 'official', trac_url
- return 'official', git_url
+ # and fetches the default branch
+ if not branch:
+ # Fetch from default branch
+ version_branch = get_version_branch(version[0])
+ try:
+ url = url.rstrip("/") if url else GIT_URL
+ urlrequest.urlopen(
+ "{url}/tree/{version_branch}/src".format(
+ url=url, version_branch=version_branch
+ )
+ )
+ svn_reference = "branches/{}".format(version_branch)
+ except URLError:
+ svn_reference = "trunk"
+ else:
+ svn_reference = "branches/{}".format(branch)
+
+ if not url:
+ # Set URL for the given GRASS version
+ git_url = "{GIT_URL}/{svn_reference}/src/{module_class}/{name}".format(
+ GIT_URL=GIT_URL,
+ svn_reference=svn_reference,
+ module_class=module_class,
+ name=name,
+ )
+ return "official", git_url
+ else:
+ # Forks from the official repo should reflect the current structure
+ url = url.rstrip("/")
+ git_url = "{url}/{svn_reference}/src/{module_class}/{name}".format(
+ url=url,
+ svn_reference=svn_reference,
+ module_class=module_class,
+ name=name,
+ )
+ return "official_fork", git_url
# Check if URL can be found
# Catch corner case if local URL is given starting with file://
- url = url[6:] if url.startswith('file://') else url
+ url = url[6:] if url.startswith("file://") else url
if not os.path.exists(url):
url_validated = False
- if url.startswith('http'):
+ if url.startswith("http"):
try:
open_url = urlopen(url)
open_url.close()
url_validated = True
- except:
+ except URLError:
pass
else:
try:
- open_url = urlopen('http://' + url)
+ open_url = urlopen("http://" + url)
open_url.close()
url_validated = True
- except:
+ except URLError:
pass
try:
- open_url = urlopen('https://' + url)
+ open_url = urlopen("https://" + url)
open_url.close()
url_validated = True
- except:
+ except URLError:
pass
if not url_validated:
- grass.fatal(_('Cannot open URL: {}'.format(url)))
+ grass.fatal(_("Cannot open URL: {}".format(url)))
# Handle local URLs
if os.path.isdir(url):
- return 'dir', os.path.abspath(url)
+ return "dir", os.path.abspath(url)
elif os.path.exists(url):
- if url.endswith('.zip'):
- return 'zip', os.path.abspath(url)
+ if url.endswith(".zip"):
+ return "zip", os.path.abspath(url)
for suffix in extract_tar.supported_formats:
- if url.endswith('.' + suffix):
+ if url.endswith("." + suffix):
return suffix, os.path.abspath(url)
# Handle remote URLs
else:
@@ -2252,13 +2471,13 @@ def resolve_source_code(url=None, name=None, branch=None):
# we allow URL to end with =zip or ?zip and not only .zip
# unfortunately format=zip&version=89612 would require something else
# special option to force the source type would solve it
- if url.endswith('zip'):
- return 'remote_zip', url
+ if url.endswith("zip"):
+ return "remote_zip", url
for suffix in extract_tar.supported_formats:
if url.endswith(suffix):
- return 'remote_' + suffix, url
+ return "remote_" + suffix, url
# fallback to the classic behavior
- return 'svn', url
+ return "svn", url
def get_addons_paths(gg_addons_base_dir):
@@ -2266,94 +2485,103 @@ def get_addons_paths(gg_addons_base_dir):
in the $GRASS_ADDON_BASE dir. The file serves as a list of all addons,
and their paths (mkhmtl.py tool)
"""
- get_addons_paths.json_file = 'addons_paths.json'
-
- url = 'https://api.github.com/repos/OSGeo/grass-addons/git/trees/'\
- 'main?recursive=1'
+ get_addons_paths.json_file = "addons_paths.json"
+ # Define branch to fetch from (latest or current version)
+ addons_branch = get_version_branch(version[0])
+ url = "https://api.github.com/repos/OSGeo/grass-addons/git/trees/{}?recursive=1".format(
+ addons_branch
+ )
response = download_addons_paths_file(
- url=url, response_format='application/json',
+ url=url,
+ response_format="application/json",
)
if response:
addons_paths = json.loads(gscript.decode(response.read()))
- with open(os.path.join(gg_addons_base_dir, get_addons_paths.json_file),
- 'w') as f:
+ with open(
+ os.path.join(gg_addons_base_dir, get_addons_paths.json_file), "w"
+ ) as f:
json.dump(addons_paths, f)
def main():
# check dependencies
- if not flags['a'] and sys.platform != "win32":
+ if not flags["a"] and sys.platform != "win32":
check_progs()
- original_url = options['url']
- branch = options['branch']
+ original_url = options["url"]
+ branch = options["branch"]
# manage proxies
global PROXIES
- if options['proxy']:
+ if options["proxy"]:
PROXIES = {}
- for ptype, purl in (p.split('=') for p in options['proxy'].split(',')):
+ for ptype, purl in (p.split("=") for p in options["proxy"].split(",")):
PROXIES[ptype] = purl
proxy = urlrequest.ProxyHandler(PROXIES)
opener = urlrequest.build_opener(proxy)
urlrequest.install_opener(opener)
# define path
- options['prefix'] = resolve_install_prefix(path=options['prefix'],
- to_system=flags['s'])
+ options["prefix"] = resolve_install_prefix(
+ path=options["prefix"], to_system=flags["s"]
+ )
- if flags['j']:
- get_addons_paths(gg_addons_base_dir=options['prefix'])
+ if flags["j"]:
+ get_addons_paths(gg_addons_base_dir=options["prefix"])
return 0
# list available extensions
- if flags['l'] or flags['c'] or (flags['g'] and not flags['a']):
+ if flags["l"] or flags["c"] or (flags["g"] and not flags["a"]):
# using dummy extension, we don't need any extension URL now,
# but will work only as long as the function does not check
# if the URL is actually valid or something
- source, url = resolve_source_code(name='dummy',
- url=original_url,
- branch=branch)
+ source, url = resolve_source_code(
+ name="dummy", url=original_url, branch=branch, fork=flags["o"]
+ )
xmlurl = resolve_xmlurl_prefix(original_url, source=source)
list_available_extensions(xmlurl)
return 0
- elif flags['a']:
- list_installed_extensions(toolboxes=flags['t'])
+ elif flags["a"]:
+ list_installed_extensions(toolboxes=flags["t"])
return 0
- if flags['d'] or flags['i']:
- flag = 'd' if flags['d'] else 'i'
- if options['operation'] != 'add':
- grass.warning(_("Flag '{}' is relevant only to"
- " 'operation=add'. Ignoring this flag.").format(
- flag))
+ if flags["d"] or flags["i"]:
+ flag = "d" if flags["d"] else "i"
+ if options["operation"] != "add":
+ grass.warning(
+ _(
+ "Flag '{}' is relevant only to"
+ " 'operation=add'. Ignoring this flag."
+ ).format(flag)
+ )
else:
global REMOVE_TMPDIR
REMOVE_TMPDIR = False
- if options['operation'] == 'add':
+ if options["operation"] == "add":
check_dirs()
- if original_url == '':
+ if original_url == "" or flags["o"]:
"""
Query GitHub API only if extension will be downloaded
from official GRASS GIS addon repository
"""
- get_addons_paths(gg_addons_base_dir=options['prefix'])
- source, url = resolve_source_code(name=options['extension'],
- url=original_url,
- branch=branch)
+ get_addons_paths(gg_addons_base_dir=options["prefix"])
+ source, url = resolve_source_code(
+ name=options["extension"], url=original_url, branch=branch, fork=flags["o"]
+ )
xmlurl = resolve_xmlurl_prefix(original_url, source=source)
install_extension(source=source, url=url, xmlurl=xmlurl, branch=branch)
else: # remove
- remove_extension(force=flags['f'])
+ remove_extension(force=flags["f"])
return 0
if __name__ == "__main__":
- if len(sys.argv) == 2 and sys.argv[1] == '--doctest':
+ if len(sys.argv) == 2 and sys.argv[1] == "--doctest":
import doctest
+
sys.exit(doctest.testmod().failed)
options, flags = grass.parser()
global TMPDIR
@@ -2361,7 +2589,8 @@ if __name__ == "__main__":
atexit.register(cleanup)
grass_version = grass.version()
- version = grass_version['version'].split('.')
- build_platform = grass_version['build_platform'].split('-', 1)[0]
+ version = grass_version["version"].split(".")
+
+ build_platform = grass_version["build_platform"].split("-", 1)[0]
sys.exit(main())
=====================================
temporal/t.rast.accumulate/t.rast.accumulate.py
=====================================
@@ -469,7 +469,7 @@ def main():
print(accmod)
accmod.run()
- if accmod.popen.returncode != 0:
+ if accmod.returncode != 0:
dbif.close()
grass.fatal(_("Error running r.series.accumulate"))
=====================================
temporal/t.rast.neighbors/t.rast.neighbors.py
=====================================
@@ -194,8 +194,11 @@ def main():
# Check return status of all finished modules
error = 0
for proc in proc_list:
- if proc.popen.returncode != 0:
- grass.error(_("Error running module: %\n stderr: %s") %(proc.get_bash(), proc.outputs.stderr))
+ if proc.returncode != 0:
+ grass.error(
+ _("Error running module: %\n stderr: %s")
+ % (proc.get_bash(), proc.outputs.stderr)
+ )
error += 1
if error > 0:
View it on GitLab: https://salsa.debian.org/debian-gis-team/grass/-/compare/b8ea7a8fdb3f46e24f8d33474a29417c44c2c934...27ac9773d30761c8b85647006eacbcd011f7581f
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/grass/-/compare/b8ea7a8fdb3f46e24f8d33474a29417c44c2c934...27ac9773d30761c8b85647006eacbcd011f7581f
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20210810/3c38dbd3/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list