mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-06 23:59:18 +08:00
latest entity work
This commit is contained in:
parent
75e289e234
commit
355d975803
4
.idea/dictionaries/ericf.xml
generated
4
.idea/dictionaries/ericf.xml
generated
@ -219,6 +219,7 @@
|
|||||||
<w>ccompiler</w>
|
<w>ccompiler</w>
|
||||||
<w>cdrk</w>
|
<w>cdrk</w>
|
||||||
<w>cdull</w>
|
<w>cdull</w>
|
||||||
|
<w>cdval</w>
|
||||||
<w>centeuro</w>
|
<w>centeuro</w>
|
||||||
<w>centiseconds</w>
|
<w>centiseconds</w>
|
||||||
<w>cfconfig</w>
|
<w>cfconfig</w>
|
||||||
@ -287,6 +288,7 @@
|
|||||||
<w>compat</w>
|
<w>compat</w>
|
||||||
<w>compileall</w>
|
<w>compileall</w>
|
||||||
<w>compilelocations</w>
|
<w>compilelocations</w>
|
||||||
|
<w>compounddict</w>
|
||||||
<w>compoundlist</w>
|
<w>compoundlist</w>
|
||||||
<w>configerror</w>
|
<w>configerror</w>
|
||||||
<w>confighash</w>
|
<w>confighash</w>
|
||||||
@ -518,6 +520,7 @@
|
|||||||
<w>farthestpt</w>
|
<w>farthestpt</w>
|
||||||
<w>fbase</w>
|
<w>fbase</w>
|
||||||
<w>fclose</w>
|
<w>fclose</w>
|
||||||
|
<w>fcmd</w>
|
||||||
<w>fcntlmodule</w>
|
<w>fcntlmodule</w>
|
||||||
<w>fcode</w>
|
<w>fcode</w>
|
||||||
<w>fcontents</w>
|
<w>fcontents</w>
|
||||||
@ -591,6 +594,7 @@
|
|||||||
<w>fpathrel</w>
|
<w>fpathrel</w>
|
||||||
<w>fpathshort</w>
|
<w>fpathshort</w>
|
||||||
<w>fprint</w>
|
<w>fprint</w>
|
||||||
|
<w>fproject</w>
|
||||||
<w>fpsc</w>
|
<w>fpsc</w>
|
||||||
<w>framerate</w>
|
<w>framerate</w>
|
||||||
<w>freefly</w>
|
<w>freefly</w>
|
||||||
|
|||||||
@ -30,6 +30,7 @@ from bafoundation.entity._support import (BaseField, BoundCompoundValue,
|
|||||||
BoundListField, BoundDictField,
|
BoundListField, BoundDictField,
|
||||||
BoundCompoundListField,
|
BoundCompoundListField,
|
||||||
BoundCompoundDictField)
|
BoundCompoundDictField)
|
||||||
|
from bafoundation.entity.util import have_matching_fields
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Dict, Type, List, Any
|
from typing import Dict, Type, List, Any
|
||||||
@ -203,6 +204,7 @@ class ListField(BaseField, Generic[T]):
|
|||||||
# When accessed on a FieldInspector we return a sub-field FieldInspector.
|
# When accessed on a FieldInspector we return a sub-field FieldInspector.
|
||||||
# When accessed on an instance we return a BoundListField.
|
# When accessed on an instance we return a BoundListField.
|
||||||
|
|
||||||
|
# noinspection DuplicatedCode
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
# Access via type gives our field; via an instance gives a bound field.
|
# Access via type gives our field; via an instance gives a bound field.
|
||||||
@ -277,6 +279,7 @@ class DictField(BaseField, Generic[TK, T]):
|
|||||||
# change the dict, but we can prune completely if empty (and allowed)
|
# change the dict, but we can prune completely if empty (and allowed)
|
||||||
return not data and not self._store_default
|
return not data and not self._store_default
|
||||||
|
|
||||||
|
# noinspection DuplicatedCode
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
# Return our field if accessed via type and bound-dict-field
|
# Return our field if accessed via type and bound-dict-field
|
||||||
@ -354,6 +357,7 @@ class CompoundListField(BaseField, Generic[TC]):
|
|||||||
# We can also optionally prune the whole list if empty and allowed.
|
# We can also optionally prune the whole list if empty and allowed.
|
||||||
return not data and not self._store_default
|
return not data and not self._store_default
|
||||||
|
|
||||||
|
# noinspection DuplicatedCode
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -370,10 +374,10 @@ class CompoundListField(BaseField, Generic[TC]):
|
|||||||
...
|
...
|
||||||
|
|
||||||
# Note:
|
# Note:
|
||||||
# When setting the list, we tell the type-checker that we accept
|
# When setting the list, we tell the type-checker that we also accept
|
||||||
# a raw list of CompoundValue objects, but at runtime we actually
|
# a raw list of CompoundValue objects, but at runtime we actually
|
||||||
# always deal with BoundCompoundValue objects (see note in
|
# always deal with BoundCompoundValue objects (see note in
|
||||||
# BoundCompoundListField)
|
# BoundCompoundListField for why we accept CompoundValue objs)
|
||||||
@overload
|
@overload
|
||||||
def __set__(self, obj: Any, value: List[TC]) -> None:
|
def __set__(self, obj: Any, value: List[TC]) -> None:
|
||||||
...
|
...
|
||||||
@ -392,7 +396,7 @@ class CompoundListField(BaseField, Generic[TC]):
|
|||||||
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
|
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
|
||||||
|
|
||||||
# If we were passed a BoundCompoundListField,
|
# If we were passed a BoundCompoundListField,
|
||||||
# simply convert it to a list of BoundCompoundValue objects which
|
# simply convert it to a flat list of BoundCompoundValue objects which
|
||||||
# is what we work with natively here.
|
# is what we work with natively here.
|
||||||
if isinstance(value, BoundCompoundListField):
|
if isinstance(value, BoundCompoundListField):
|
||||||
value = list(value)
|
value = list(value)
|
||||||
@ -406,10 +410,28 @@ class CompoundListField(BaseField, Generic[TC]):
|
|||||||
# be sure the underlying data will line up; for example two
|
# be sure the underlying data will line up; for example two
|
||||||
# CompoundListFields with different child_field values should not
|
# CompoundListFields with different child_field values should not
|
||||||
# be inter-assignable.
|
# be inter-assignable.
|
||||||
if (not all(isinstance(i, BoundCompoundValue) for i in value)
|
if not all(isinstance(i, BoundCompoundValue) for i in value):
|
||||||
or not all(i.d_value is self.d_value for i in value)):
|
|
||||||
raise ValueError('CompoundListField assignment must be a '
|
raise ValueError('CompoundListField assignment must be a '
|
||||||
'list containing only its existing children.')
|
'list containing only BoundCompoundValue objs.')
|
||||||
|
|
||||||
|
# Make sure the data all has the same CompoundValue type and
|
||||||
|
# compare that type against ours once to make sure its fields match.
|
||||||
|
# (this will not allow passing CompoundValues from multiple sources
|
||||||
|
# but I don't know if that would ever come up..)
|
||||||
|
for i, val in enumerate(value):
|
||||||
|
if i == 0:
|
||||||
|
# Do the full field comparison on the first value only..
|
||||||
|
if not have_matching_fields(val.d_value, self.d_value):
|
||||||
|
raise ValueError(
|
||||||
|
'CompoundListField assignment must be a '
|
||||||
|
'list containing matching CompoundValues.')
|
||||||
|
else:
|
||||||
|
# For all remaining values, just ensure they match the first.
|
||||||
|
if val.d_value is not value[0].d_value:
|
||||||
|
raise ValueError(
|
||||||
|
'CompoundListField assignment cannot contain '
|
||||||
|
'multiple CompoundValue types as sources.')
|
||||||
|
|
||||||
data[self.d_key] = self.filter_input([i.d_data for i in value],
|
data[self.d_key] = self.filter_input([i.d_data for i in value],
|
||||||
error=error)
|
error=error)
|
||||||
|
|
||||||
@ -468,6 +490,7 @@ class CompoundDictField(BaseField, Generic[TK, TC]):
|
|||||||
|
|
||||||
# ONLY overriding these in type-checker land to clarify types.
|
# ONLY overriding these in type-checker land to clarify types.
|
||||||
# (see note in BaseField)
|
# (see note in BaseField)
|
||||||
|
# noinspection DuplicatedCode
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -485,14 +508,35 @@ class CompoundDictField(BaseField, Generic[TK, TC]):
|
|||||||
def __get__(self, obj: Any, cls: Any = None) -> Any:
|
def __get__(self, obj: Any, cls: Any = None) -> Any:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
# Note:
|
||||||
|
# When setting the dict, we tell the type-checker that we also accept
|
||||||
|
# a raw dict of CompoundValue objects, but at runtime we actually
|
||||||
|
# always deal with BoundCompoundValue objects (see note in
|
||||||
|
# BoundCompoundDictField for why we accept CompoundValue objs)
|
||||||
|
@overload
|
||||||
def __set__(self, obj: Any, value: Dict[TK, TC]) -> None:
|
def __set__(self, obj: Any, value: Dict[TK, TC]) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __set__(self, obj: Any, value: BoundCompoundDictField[TK,
|
||||||
|
TC]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def __set__(self, obj: Any, value: Any) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
def get_with_data(self, data: Any) -> Any:
|
def get_with_data(self, data: Any) -> Any:
|
||||||
assert self.d_key in data
|
assert self.d_key in data
|
||||||
return BoundCompoundDictField(self, data[self.d_key])
|
return BoundCompoundDictField(self, data[self.d_key])
|
||||||
|
|
||||||
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
|
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
|
||||||
|
|
||||||
|
# If we were passed a BoundCompoundDictField,
|
||||||
|
# simply convert it to a flat dict of BoundCompoundValue objects which
|
||||||
|
# is what we work with natively here.
|
||||||
|
if isinstance(value, BoundCompoundDictField):
|
||||||
|
value = dict(value.items())
|
||||||
|
|
||||||
if not isinstance(value, dict):
|
if not isinstance(value, dict):
|
||||||
raise TypeError('CompoundDictField expected dict value on set.')
|
raise TypeError('CompoundDictField expected dict value on set.')
|
||||||
|
|
||||||
@ -501,10 +545,31 @@ class CompoundDictField(BaseField, Generic[TK, TC]):
|
|||||||
# be sure the underlying data will line up; for example two
|
# be sure the underlying data will line up; for example two
|
||||||
# CompoundListFields with different child_field values should not
|
# CompoundListFields with different child_field values should not
|
||||||
# be inter-assignable.
|
# be inter-assignable.
|
||||||
if (not all(isinstance(i, self.d_keytype) for i in value.keys())
|
if (not all(isinstance(i, BoundCompoundValue)
|
||||||
or not all(
|
for i in value.values())):
|
||||||
isinstance(i, BoundCompoundValue) for i in value.values())
|
|
||||||
or not all(i.d_value is self.d_value for i in value.values())):
|
|
||||||
raise ValueError('CompoundDictField assignment must be a '
|
raise ValueError('CompoundDictField assignment must be a '
|
||||||
'dict containing only its existing children.')
|
'dict containing only BoundCompoundValues.')
|
||||||
data[self.d_key] = {key: val.d_data for key, val in value.items()}
|
|
||||||
|
# Make sure the data all has the same CompoundValue type and
|
||||||
|
# compare that type against ours once to make sure its fields match.
|
||||||
|
# (this will not allow passing CompoundValues from multiple sources
|
||||||
|
# but I don't know if that would ever come up..)
|
||||||
|
first_value: Any = None
|
||||||
|
for i, val in enumerate(value.values()):
|
||||||
|
if i == 0:
|
||||||
|
first_value = val.d_value
|
||||||
|
# Do the full field comparison on the first value only..
|
||||||
|
if not have_matching_fields(val.d_value, self.d_value):
|
||||||
|
raise ValueError(
|
||||||
|
'CompoundListField assignment must be a '
|
||||||
|
'list containing matching CompoundValues.')
|
||||||
|
else:
|
||||||
|
# For all remaining values, just ensure they match the first.
|
||||||
|
if val.d_value is not first_value:
|
||||||
|
raise ValueError(
|
||||||
|
'CompoundListField assignment cannot contain '
|
||||||
|
'multiple CompoundValue types as sources.')
|
||||||
|
|
||||||
|
data[self.d_key] = self.filter_input(
|
||||||
|
{key: val.d_data
|
||||||
|
for key, val in value.items()}, error=error)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||||
<!--DOCSHASH=096fe9ca51969f120799a3a00bd55cfb-->
|
<!--DOCSHASH=2f4ba682ad3b48baa2f8a93eb9948cb0-->
|
||||||
<h4><em>last updated on 2019-12-19 for Ballistica version 1.5.0 build 20001</em></h4>
|
<h4><em>last updated on 2019-12-21 for Ballistica version 1.5.0 build 20001</em></h4>
|
||||||
<p>This page documents the Python classes and functions in the 'ba' module,
|
<p>This page documents the Python classes and functions in the 'ba' module,
|
||||||
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@ -72,7 +72,11 @@ class EntityTest(entity.Entity):
|
|||||||
tval2 = entity.Field('t2', entity.DateTimeValue())
|
tval2 = entity.Field('t2', entity.DateTimeValue())
|
||||||
str_int_dict = entity.DictField('sd', str, entity.IntValue())
|
str_int_dict = entity.DictField('sd', str, entity.IntValue())
|
||||||
compoundlist = entity.CompoundListField('l', CompoundTest())
|
compoundlist = entity.CompoundListField('l', CompoundTest())
|
||||||
tdval = entity.CompoundDictField('td', str, CompoundTest())
|
compoundlist2 = entity.CompoundListField('l2', CompoundTest())
|
||||||
|
compoundlist3 = entity.CompoundListField('l3', CompoundTest2())
|
||||||
|
compounddict = entity.CompoundDictField('td', str, CompoundTest())
|
||||||
|
compounddict2 = entity.CompoundDictField('td2', str, CompoundTest())
|
||||||
|
compounddict3 = entity.CompoundDictField('td3', str, CompoundTest2())
|
||||||
fval2 = entity.Field('f2', entity.Float3Value())
|
fval2 = entity.Field('f2', entity.Float3Value())
|
||||||
|
|
||||||
|
|
||||||
@ -111,8 +115,10 @@ def test_entity_values() -> None:
|
|||||||
|
|
||||||
# Simple value dict field.
|
# Simple value dict field.
|
||||||
assert 'foo' not in ent.str_int_dict
|
assert 'foo' not in ent.str_int_dict
|
||||||
|
# Set with incorrect key type should give TypeError.
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
ent.str_int_dict[0] = 123 # type: ignore
|
ent.str_int_dict[0] = 123 # type: ignore
|
||||||
|
# And set with incorrect value type should do same.
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
ent.str_int_dict['foo'] = 'bar' # type: ignore
|
ent.str_int_dict['foo'] = 'bar' # type: ignore
|
||||||
ent.str_int_dict['foo'] = 123
|
ent.str_int_dict['foo'] = 123
|
||||||
@ -124,6 +130,9 @@ def test_entity_values() -> None:
|
|||||||
# (which seems to get a bit ugly, but may be worth revisiting)
|
# (which seems to get a bit ugly, but may be worth revisiting)
|
||||||
# assert dict(ent.str_int_dict) == {'foo': 123}
|
# assert dict(ent.str_int_dict) == {'foo': 123}
|
||||||
|
|
||||||
|
# Passing key/value pairs as a list works though..
|
||||||
|
assert dict(ent.str_int_dict.items()) == {'foo': 123}
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyTypeHints
|
# noinspection PyTypeHints
|
||||||
def test_entity_values_2() -> None:
|
def test_entity_values_2() -> None:
|
||||||
@ -154,6 +163,17 @@ def test_entity_values_2() -> None:
|
|||||||
assert len(ent.compoundlist) == 1
|
assert len(ent.compoundlist) == 1
|
||||||
assert static_type_equals(ent.compoundlist[0], CompoundTest)
|
assert static_type_equals(ent.compoundlist[0], CompoundTest)
|
||||||
|
|
||||||
|
# Compound dict field.
|
||||||
|
cdval = ent.compounddict.add('foo')
|
||||||
|
assert static_type_equals(cdval, CompoundTest)
|
||||||
|
# Set with incorrect key type should give TypeError.
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
_cdval2 = ent.compounddict.add(1) # type: ignore
|
||||||
|
# Hmm; should this throw a TypeError and not a KeyError?..
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
_cdval3 = ent.compounddict[1] # type: ignore
|
||||||
|
assert static_type_equals(ent.compounddict['foo'], CompoundTest)
|
||||||
|
|
||||||
# Enum value
|
# Enum value
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ent.enumval = None # type: ignore
|
ent.enumval = None # type: ignore
|
||||||
@ -171,6 +191,7 @@ def test_entity_values_2() -> None:
|
|||||||
assert static_type_equals(ent.grp.compoundlist[0].subval, bool)
|
assert static_type_equals(ent.grp.compoundlist[0].subval, bool)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyTypeHints
|
||||||
def test_field_copies() -> None:
|
def test_field_copies() -> None:
|
||||||
"""Test copying various values between fields."""
|
"""Test copying various values between fields."""
|
||||||
ent1 = EntityTest()
|
ent1 = EntityTest()
|
||||||
@ -208,10 +229,43 @@ def test_field_copies() -> None:
|
|||||||
val.isubval = 356
|
val.isubval = 356
|
||||||
assert ent1.compoundlist[0].isubval == 356
|
assert ent1.compoundlist[0].isubval == 356
|
||||||
assert len(ent1.compoundlist) == 1
|
assert len(ent1.compoundlist) == 1
|
||||||
|
ent1.compoundlist.append()
|
||||||
|
assert len(ent1.compoundlist) == 2
|
||||||
assert len(ent2.compoundlist) == 0
|
assert len(ent2.compoundlist) == 0
|
||||||
|
# Copying to the same field on different obj should work.
|
||||||
ent2.compoundlist = ent1.compoundlist
|
ent2.compoundlist = ent1.compoundlist
|
||||||
assert ent2.compoundlist[0].isubval == 356
|
assert ent2.compoundlist[0].isubval == 356
|
||||||
assert len(ent2.compoundlist) == 1
|
assert len(ent2.compoundlist) == 2
|
||||||
|
# Cross-field assigns should work too if the field layouts match..
|
||||||
|
ent1.compoundlist2 = ent1.compoundlist
|
||||||
|
# And not if they don't...
|
||||||
|
# (in this case mypy errors too but that may not always be the case)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ent1.compoundlist3 = ent1.compoundlist # type: ignore
|
||||||
|
|
||||||
|
# Copying a CompoundDict
|
||||||
|
ent1.compounddict.add('foo')
|
||||||
|
ent1.compounddict.add('bar')
|
||||||
|
assert static_type_equals(ent1.compounddict['foo'].isubval, int)
|
||||||
|
ent1.compounddict['foo'].isubval = 23
|
||||||
|
# Copying to the same field on different obj should work.
|
||||||
|
ent2.compounddict = ent1.compounddict
|
||||||
|
assert ent2.compounddict.keys() == ['foo', 'bar']
|
||||||
|
assert ent2.compounddict['foo'].isubval == 23
|
||||||
|
# Cross field assigns should work too if the field layouts match..
|
||||||
|
ent1.compounddict2 = ent1.compounddict
|
||||||
|
# ..And should fail otherwise.
|
||||||
|
# (mypy catches this too, but that may not always be the case if
|
||||||
|
# two CompoundValues have the same type but different layouts based
|
||||||
|
# on their __init__ args or whatnot)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ent1.compounddict3 = ent1.compounddict # type: ignore
|
||||||
|
# Make sure invalid key types get caught when setting a full dict:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ent1.compounddict2 = {
|
||||||
|
'foo': ent1.compounddict['foo'],
|
||||||
|
2: ent1.compounddict['bar'], # type: ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_field_access_from_type() -> None:
|
def test_field_access_from_type() -> None:
|
||||||
|
|||||||
@ -357,19 +357,26 @@ def sync_all() -> None:
|
|||||||
This assumes that there is a 'sync-full' and 'sync-list' Makefile target
|
This assumes that there is a 'sync-full' and 'sync-list' Makefile target
|
||||||
under each project.
|
under each project.
|
||||||
"""
|
"""
|
||||||
|
import concurrent.futures
|
||||||
print(f'{CLRBLU}Updating formatting for all projects...{CLREND}')
|
print(f'{CLRBLU}Updating formatting for all projects...{CLREND}')
|
||||||
projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
|
projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
|
||||||
if projects_str is None:
|
if projects_str is None:
|
||||||
raise CleanError('EFROTOOL_SYNC_PROJECTS is not defined.')
|
raise CleanError('EFROTOOL_SYNC_PROJECTS is not defined.')
|
||||||
|
projects = projects_str.split(':')
|
||||||
|
|
||||||
|
def _format_project(fproject: str) -> None:
|
||||||
|
fcmd = f'cd "{fproject}" && make format'
|
||||||
|
print(fcmd)
|
||||||
|
subprocess.run(fcmd, shell=True, check=True)
|
||||||
|
|
||||||
# No matter what we're doing (even if just listing), run formatting
|
# No matter what we're doing (even if just listing), run formatting
|
||||||
# in all projects before beginning. Otherwise if we do a sync and then
|
# in all projects before beginning. Otherwise if we do a sync and then
|
||||||
# a preflight we'll often wind up getting out-of-sync errors due to
|
# a preflight we'll often wind up getting out-of-sync errors due to
|
||||||
# formatting changing after the sync.
|
# formatting changing after the sync.
|
||||||
for project in projects_str.split(':'):
|
with concurrent.futures.ThreadPoolExecutor(
|
||||||
cmd = f'cd "{project}" && make format'
|
max_workers=len(projects)) as executor:
|
||||||
print(cmd)
|
# Converting this to a list will propagate any errors.
|
||||||
subprocess.run(cmd, shell=True, check=True)
|
list(executor.map(_format_project, projects))
|
||||||
|
|
||||||
if len(sys.argv) > 2 and sys.argv[2] == 'list':
|
if len(sys.argv) > 2 and sys.argv[2] == 'list':
|
||||||
# List mode
|
# List mode
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user