Name | javascript-utils JSON |
Version |
1.0.0
JSON |
| download |
home_page | |
Summary | Python utilities inspired by npm packages |
upload_time | 2022-12-26 20:27:27 |
maintainer | |
docs_url | None |
author | |
requires_python | >=3.7 |
license | MIT License Copyright (c) 2022 Rob Brieler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
keywords |
config
configuration
stubs
sinon
testing
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# JS Utils
This package contains python implementations of several npm packages I missed when moving from Node.js to Python. Currently the package contains implementions of the `config` npm package as well as `stubs` from the `sinon` package.
## Config
Converts json files in a config directory into a Config NamedTuple. Based off the npm package `config`.
### Usage
Within a configuration directory (`./config` by default), provide a json configuration file
for each environment your application will run in. For example, your project structure might look like:
```
.
└── project/
├── config/
│ ├── develop.json
│ ├── production.json
│ ├── qa.json
│ └── test.json
├── jsutils/
│ └── project files
└── test/
└── test files
```
Where each file in the config directory has contents like:
```json
{
"int_key": 13,
"array_key": ['a', 'b', 'c'],
"object_key": {
"application_name": "jsutils",
"application_creator": "me"
}
}
```
Then from your application:
```python
from config import get_config
config = get_config()
print(config) # Config(int_key=13, array_key=('a', 'b', 'c'), object_key=ObjectKey(application_name='jsutils', application_creator='me')
```
By default, the module will load the configuration file corresponding to the value in `os.environ['ENV']`. If no value is present, it will default to `'test'`. If there is no file for the specified (or default) environment, an `ImportException` will be raised. The environment can be passed directly to the factory function as can the directory.
```python
from config import get_config
config = get_config(directory='./deep/config/directory/', environment='production')
```
#### Defaults
A default file can be used to provide values that are static accross environments. If keys in the environment files duplicate keys in the default file, the environment file values will be used. For example, if your config directory had the structure:
```
.
└── config/
├── default.json
└── test.json
```
And default.json had contents
```json
{
"default_key": "always the same",
"override_key": "default value"
}
```
While test.json had contents
```json
{
"override_key": "overridden value",
"environment_specific_key": "something test environment specific"
}
```
Then with `environment='test'`, the config object would have the following attributes and values:
```python
config.default_key # "always the same"
config.overriden_key # "overridden value"
config.environment_specific_key # "something test environment specifc"
```
### Custom Environment Variables
An optional custom environment variables file can be added to add environment variables to your configuration object at the time of construction. Your config directory would look like:
```
.
└── config/
├── custom_environment_variables.json
├── default.json
└── test.json
```
And default.json had contents
```json
{
"default_key": "always the same",
"override_key": "default value"
}
```
While test.json had contents
```json
{
"override_key": "overridden value",
"environment_specific_key": "something test environment specific"
}
```
And custom_environment_variables.json had contents
```json
{
"environment_key_1": "ENV_KEY_1",
"nested_environment_keys": {
"nested_key_1": "NESTED_ENV_KEY_1"
}
}
```
Then with `environment='test'`, the config object would have the following attributes and values:
```python
config.default_key # "always the same"
config.overriden_key # "overridden value"
config.environment_specific_key # "something test environment specifc"
config.environment_key_1 # value at os.environ['ENV_KEY_1']
config.nested_environment_keys.nested_key_1 # value at os.environ['NESTED_ENV_KEY_1]
```
### `.has` and `.get`
The Config class comes prebuilt with `has` and `get` methods that can be used to test if a property is present and to fetch a property. These methods also accept "deep" paths to objects.
For example, if you `default.json` looked like:
```json
{
"top_level_key": "I'm at the top",
"nested_top": {
"nested_key": "It's safe inside"
}
}
```
Then you can test for and fetch the nested property `nested_top.nested_key` as follows:
```python
config.has('nested_top.nested_key') # True
config.get('nested_top.nested_key') # "It's safe inside"
```
### Future Development
* **Support for Additional File Types**: Since `json` is not commonly used in python applications for configuration, to make this library more broadly appealing, I would like to support additional file types such as `.yaml`, `.ini`, `.cfg`, and others.
* **Secrets Addons**: I would like to make the library extensible so that if you are managing secrets with a third party system like Vault or AWS Secrets Manager, addons could provide ways to fetch those values into your configuration object.
## Stubs
This class and its factory function provides an alternative to the Mock objects from `unittest.mock`. In particular the behavior of these stubs is more flexible and versatile. Based off the stubs in the npm package `sinon`.
### The Stub class
The stub can be instantiated with a set of attributes.
```python
from stubs impor get_stub, Stub
stub1 = Stub(attrs={'prop1': 'value1', 'prop2': 1.61803})
stub1.prop1 # 'value1'
stub1.prop2 # 1.61803
stub2 = get_stub(attrs={'prop3': 3.14159, 'prop4': False})
stub2.prop3 # 3.14159
stub2.prop4 # False
```
### Default Behavior
Stubs can be configured to return a specific value or to raise an exception by default. If both are configured, the exception will be raised.
```python
from stubs import get_stub
stub1 = get_stub()
stub1.returns('Hello there')
result1 = stub()
result1 # 'Hello there'
stub2 = get_stub()
stub2.raises(RuntimeError('Oh no'))
stub2() # RuntimeError: Oh no
stub3 = get_stub()
stub3.raises(RuntimeError('Its broken'))
stub3.returns('Oh happy day')
stub3() # RuntimeError: Its broken
```
If behavior for a stub has not been configured, it will return `None` when called.
### Asserting on Calls
The stub has a helper method to assert that a call with a specific signature was made to the stub. If you need to assert that a specific call (i.e. the seventh call) was made with a specific signature, you'll have to fall back to using the stub's call history attribute. Stubs are compatible with the ANY value from unittest.mock.
```python
from unittest.mock import ANY
from stubs import get_stub
def test_should_know_all_calls():
stub = get_stub()
stub.returns('some_value')
stub('first call')
assert stub.called_with('first call')
stub('second call', second_call_kwarg=1.414)
assert stub.called_with('second call', second_call_kwarg=1.414)
assert stub.call_history[1] == (('second_call',), {'second_call_kwarg': 1.414})
assert stub.called_with(ANY, second_call_kwarg=1.414)
```
### Fake Behavior
If your stub needs more complicated behavior, you can provide a function that the stub will pass the arguments to. The fake behavior takes priority over all other behaviors.
```python
from stubs import get_stub
def fake_func(arg1, args2):
return args1 + arg2
stub = get_stub()
stub.calls_fake(fake_func)
stub(1, 2) # 3
stub('first', 'second') # 'firstsecond'
```
### Behavior on Specific Calls
The stub can be configured to return a value or raise an exception on a specified 0 index based call. The stub will fall back to its default behavior if the specified call has not been configured. There are convenience functions for setting first, second, and third call behavior. Method chaining is required for proper use.
```python
from stubs import get_stub
stub = get_stub()
stub.return('default_value')
stub.on_call(1).returns('second_call_value')
stub.on_third_call().returns('third_call_value')
stub.on_call(3).raises(RuntimeError('Boom'))
stub() # 'default_value'
stub() # 'second_call_value'
stub() # 'third_call_value'
stub() # RuntimeError: Boom raised
stub() # 'default_value'
```
### Behavior for Specific Call Signatures
The stub can be configured to have specific behavior for specific call arguments. This behavior can be based on a call count or fall back to a default behavior. Call signature behavior supercedes call count and default behavior when applicable, but is superceded in priority by fake behaviors. Method chaining is required for proper use.
```python
from stubs import get_stub
stub = get_stub()
stub.with_args('first', second_arg='second').returns(42)
stub.with_args('first', second_arg='second').on_call(1).returns(0)
stub.returns('i')
stub() # 'i'
stub('first', second_arg='second') # 42
stub('first', second_arg='second') # 0
stub('first', second_arg='second') # 42
```
### Overriding Methods on Objects
The Stub class and get_stub function can also be used to replace a method on an existing object with a stub. The created original behavior of the methods can be restored with the restore method.
```python
from stubs import get_stub, Stub
class SomeClass:
def method_1(self):
return 'hello'
def method_2(self):
return 'goodbye'
a_class = someClass()
stub_1 = get_stub(a_class, 'method_1')
stub_1.returns('hola')
a_class.method_1() # 'hola'
stub_2 = Stub(a_class, 'method_2')
stub_2.returns('adios')
a_class.method_2() # 'adios
stub_1.restore()
stub_2.restore()
a_class.method_1() # 'hello'
a_class.method_2() # 'goodbye'
```
Raw data
{
"_id": null,
"home_page": "",
"name": "javascript-utils",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": "",
"keywords": "config,configuration,stubs,sinon,testing",
"author": "",
"author_email": "Robert Brieler <rob.brieler@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/10/49/d3c7f4a617245287af7a70e60495a838cd141a20bf5e8b55131e4ef813fa/javascript-utils-1.0.0.tar.gz",
"platform": null,
"description": "# JS Utils\r\nThis package contains python implementations of several npm packages I missed when moving from Node.js to Python. Currently the package contains implementions of the `config` npm package as well as `stubs` from the `sinon` package.\r\n\r\n## Config\r\nConverts json files in a config directory into a Config NamedTuple. Based off the npm package `config`.\r\n\r\n### Usage\r\nWithin a configuration directory (`./config` by default), provide a json configuration file\r\nfor each environment your application will run in. For example, your project structure might look like:\r\n\r\n```\r\n.\r\n\u2514\u2500\u2500 project/\r\n \u251c\u2500\u2500 config/\r\n \u2502 \u251c\u2500\u2500 develop.json\r\n \u2502 \u251c\u2500\u2500 production.json\r\n \u2502 \u251c\u2500\u2500 qa.json\r\n \u2502 \u2514\u2500\u2500 test.json\r\n \u251c\u2500\u2500 jsutils/\r\n \u2502 \u2514\u2500\u2500 project files\r\n \u2514\u2500\u2500 test/\r\n \u2514\u2500\u2500 test files\r\n```\r\n\r\nWhere each file in the config directory has contents like:\r\n```json\r\n{\r\n \"int_key\": 13,\r\n \"array_key\": ['a', 'b', 'c'],\r\n \"object_key\": {\r\n \"application_name\": \"jsutils\",\r\n \"application_creator\": \"me\"\r\n }\r\n}\r\n```\r\n\r\nThen from your application:\r\n```python\r\nfrom config import get_config\r\n\r\nconfig = get_config()\r\nprint(config) # Config(int_key=13, array_key=('a', 'b', 'c'), object_key=ObjectKey(application_name='jsutils', application_creator='me')\r\n```\r\n\r\nBy default, the module will load the configuration file corresponding to the value in `os.environ['ENV']`. If no value is present, it will default to `'test'`. If there is no file for the specified (or default) environment, an `ImportException` will be raised. The environment can be passed directly to the factory function as can the directory.\r\n\r\n```python\r\nfrom config import get_config\r\n\r\nconfig = get_config(directory='./deep/config/directory/', environment='production')\r\n```\r\n\r\n#### Defaults\r\n\r\nA default file can be used to provide values that are static accross environments. If keys in the environment files duplicate keys in the default file, the environment file values will be used. For example, if your config directory had the structure:\r\n\r\n```\r\n.\r\n\u2514\u2500\u2500 config/\r\n \u251c\u2500\u2500 default.json\r\n \u2514\u2500\u2500 test.json\r\n```\r\n\r\nAnd default.json had contents\r\n```json\r\n{\r\n \"default_key\": \"always the same\",\r\n \"override_key\": \"default value\"\r\n}\r\n```\r\n\r\nWhile test.json had contents\r\n```json\r\n{\r\n \"override_key\": \"overridden value\",\r\n \"environment_specific_key\": \"something test environment specific\"\r\n}\r\n```\r\n\r\nThen with `environment='test'`, the config object would have the following attributes and values:\r\n```python\r\nconfig.default_key # \"always the same\"\r\nconfig.overriden_key # \"overridden value\"\r\nconfig.environment_specific_key # \"something test environment specifc\"\r\n```\r\n\r\n### Custom Environment Variables\r\nAn optional custom environment variables file can be added to add environment variables to your configuration object at the time of construction. Your config directory would look like:\r\n\r\n```\r\n.\r\n\u2514\u2500\u2500 config/\r\n \u251c\u2500\u2500 custom_environment_variables.json\r\n \u251c\u2500\u2500 default.json\r\n \u2514\u2500\u2500 test.json\r\n```\r\n\r\nAnd default.json had contents\r\n```json\r\n{\r\n \"default_key\": \"always the same\",\r\n \"override_key\": \"default value\"\r\n}\r\n```\r\n\r\nWhile test.json had contents\r\n```json\r\n{\r\n \"override_key\": \"overridden value\",\r\n \"environment_specific_key\": \"something test environment specific\"\r\n}\r\n```\r\n\r\nAnd custom_environment_variables.json had contents\r\n```json\r\n{\r\n \"environment_key_1\": \"ENV_KEY_1\",\r\n \"nested_environment_keys\": {\r\n \"nested_key_1\": \"NESTED_ENV_KEY_1\"\r\n }\r\n}\r\n```\r\n\r\nThen with `environment='test'`, the config object would have the following attributes and values:\r\n```python\r\nconfig.default_key # \"always the same\"\r\nconfig.overriden_key # \"overridden value\"\r\nconfig.environment_specific_key # \"something test environment specifc\"\r\nconfig.environment_key_1 # value at os.environ['ENV_KEY_1']\r\nconfig.nested_environment_keys.nested_key_1 # value at os.environ['NESTED_ENV_KEY_1]\r\n```\r\n\r\n### `.has` and `.get`\r\nThe Config class comes prebuilt with `has` and `get` methods that can be used to test if a property is present and to fetch a property. These methods also accept \"deep\" paths to objects.\r\n\r\nFor example, if you `default.json` looked like:\r\n```json\r\n{\r\n \"top_level_key\": \"I'm at the top\",\r\n \"nested_top\": {\r\n \"nested_key\": \"It's safe inside\"\r\n }\r\n}\r\n```\r\n\r\nThen you can test for and fetch the nested property `nested_top.nested_key` as follows:\r\n```python\r\nconfig.has('nested_top.nested_key') # True\r\nconfig.get('nested_top.nested_key') # \"It's safe inside\"\r\n```\r\n\r\n### Future Development\r\n * **Support for Additional File Types**: Since `json` is not commonly used in python applications for configuration, to make this library more broadly appealing, I would like to support additional file types such as `.yaml`, `.ini`, `.cfg`, and others.\r\n\r\n * **Secrets Addons**: I would like to make the library extensible so that if you are managing secrets with a third party system like Vault or AWS Secrets Manager, addons could provide ways to fetch those values into your configuration object.\r\n\r\n\r\n## Stubs\r\nThis class and its factory function provides an alternative to the Mock objects from `unittest.mock`. In particular the behavior of these stubs is more flexible and versatile. Based off the stubs in the npm package `sinon`. \r\n\r\n### The Stub class\r\nThe stub can be instantiated with a set of attributes.\r\n\r\n```python\r\nfrom stubs impor get_stub, Stub\r\n\r\nstub1 = Stub(attrs={'prop1': 'value1', 'prop2': 1.61803})\r\nstub1.prop1 # 'value1'\r\nstub1.prop2 # 1.61803\r\n\r\nstub2 = get_stub(attrs={'prop3': 3.14159, 'prop4': False})\r\nstub2.prop3 # 3.14159\r\nstub2.prop4 # False\r\n```\r\n\r\n### Default Behavior\r\nStubs can be configured to return a specific value or to raise an exception by default. If both are configured, the exception will be raised.\r\n\r\n```python\r\nfrom stubs import get_stub\r\nstub1 = get_stub()\r\nstub1.returns('Hello there')\r\n\r\nresult1 = stub()\r\nresult1 # 'Hello there'\r\n\r\nstub2 = get_stub()\r\nstub2.raises(RuntimeError('Oh no'))\r\nstub2() # RuntimeError: Oh no\r\n\r\nstub3 = get_stub()\r\nstub3.raises(RuntimeError('Its broken'))\r\nstub3.returns('Oh happy day')\r\nstub3() # RuntimeError: Its broken\r\n```\r\n\r\nIf behavior for a stub has not been configured, it will return `None` when called.\r\n\r\n### Asserting on Calls\r\nThe stub has a helper method to assert that a call with a specific signature was made to the stub. If you need to assert that a specific call (i.e. the seventh call) was made with a specific signature, you'll have to fall back to using the stub's call history attribute. Stubs are compatible with the ANY value from unittest.mock.\r\n\r\n```python\r\nfrom unittest.mock import ANY\r\n\r\nfrom stubs import get_stub\r\n\r\ndef test_should_know_all_calls():\r\n stub = get_stub()\r\n stub.returns('some_value')\r\n\r\n stub('first call')\r\n assert stub.called_with('first call')\r\n\r\n stub('second call', second_call_kwarg=1.414)\r\n assert stub.called_with('second call', second_call_kwarg=1.414)\r\n\r\n assert stub.call_history[1] == (('second_call',), {'second_call_kwarg': 1.414})\r\n assert stub.called_with(ANY, second_call_kwarg=1.414)\r\n```\r\n\r\n### Fake Behavior\r\nIf your stub needs more complicated behavior, you can provide a function that the stub will pass the arguments to. The fake behavior takes priority over all other behaviors.\r\n\r\n```python\r\nfrom stubs import get_stub\r\n\r\ndef fake_func(arg1, args2):\r\n return args1 + arg2\r\n\r\nstub = get_stub()\r\nstub.calls_fake(fake_func)\r\n\r\nstub(1, 2) # 3\r\nstub('first', 'second') # 'firstsecond'\r\n```\r\n\r\n\r\n### Behavior on Specific Calls\r\nThe stub can be configured to return a value or raise an exception on a specified 0 index based call. The stub will fall back to its default behavior if the specified call has not been configured. There are convenience functions for setting first, second, and third call behavior. Method chaining is required for proper use.\r\n\r\n```python\r\nfrom stubs import get_stub\r\n\r\nstub = get_stub()\r\nstub.return('default_value')\r\nstub.on_call(1).returns('second_call_value')\r\nstub.on_third_call().returns('third_call_value')\r\nstub.on_call(3).raises(RuntimeError('Boom'))\r\n\r\nstub() # 'default_value'\r\nstub() # 'second_call_value'\r\nstub() # 'third_call_value'\r\nstub() # RuntimeError: Boom raised\r\nstub() # 'default_value'\r\n```\r\n\r\n### Behavior for Specific Call Signatures\r\nThe stub can be configured to have specific behavior for specific call arguments. This behavior can be based on a call count or fall back to a default behavior. Call signature behavior supercedes call count and default behavior when applicable, but is superceded in priority by fake behaviors. Method chaining is required for proper use.\r\n\r\n```python\r\nfrom stubs import get_stub\r\n\r\nstub = get_stub()\r\nstub.with_args('first', second_arg='second').returns(42)\r\nstub.with_args('first', second_arg='second').on_call(1).returns(0)\r\nstub.returns('i')\r\n\r\nstub() # 'i'\r\nstub('first', second_arg='second') # 42\r\nstub('first', second_arg='second') # 0\r\nstub('first', second_arg='second') # 42\r\n```\r\n\r\n\r\n### Overriding Methods on Objects\r\nThe Stub class and get_stub function can also be used to replace a method on an existing object with a stub. The created original behavior of the methods can be restored with the restore method.\r\n\r\n```python\r\nfrom stubs import get_stub, Stub\r\n\r\nclass SomeClass:\r\n def method_1(self):\r\n return 'hello'\r\n\r\n def method_2(self):\r\n return 'goodbye'\r\n\r\na_class = someClass()\r\n\r\nstub_1 = get_stub(a_class, 'method_1')\r\nstub_1.returns('hola')\r\n\r\na_class.method_1() # 'hola'\r\n\r\nstub_2 = Stub(a_class, 'method_2')\r\nstub_2.returns('adios')\r\na_class.method_2() # 'adios\r\n\r\nstub_1.restore()\r\nstub_2.restore()\r\n\r\na_class.method_1() # 'hello'\r\na_class.method_2() # 'goodbye'\r\n```\r\n",
"bugtrack_url": null,
"license": "MIT License Copyright (c) 2022 Rob Brieler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
"summary": "Python utilities inspired by npm packages",
"version": "1.0.0",
"split_keywords": [
"config",
"configuration",
"stubs",
"sinon",
"testing"
],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "4ac128e37e64d6e56efdf360b216a5d6",
"sha256": "5b0ca4a1432d8e8c5e96d5be8251362c2473de9d26d72dd323502d56cc4bbf4a"
},
"downloads": -1,
"filename": "javascript_utils-1.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "4ac128e37e64d6e56efdf360b216a5d6",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 13429,
"upload_time": "2022-12-26T20:27:25",
"upload_time_iso_8601": "2022-12-26T20:27:25.956777Z",
"url": "https://files.pythonhosted.org/packages/79/21/b5fbc7ca899fd3c6f4ff308ca0573049afcc24dfb0e8bbba4a43dfc9a2c1/javascript_utils-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "bf6fdd7e279847614a7ae0984357d297",
"sha256": "ce0c10e9dbdeed84fd4ddd05f565e9f6a20c7db75debef398f54a45a41572272"
},
"downloads": -1,
"filename": "javascript-utils-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "bf6fdd7e279847614a7ae0984357d297",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 14150,
"upload_time": "2022-12-26T20:27:27",
"upload_time_iso_8601": "2022-12-26T20:27:27.899851Z",
"url": "https://files.pythonhosted.org/packages/10/49/d3c7f4a617245287af7a70e60495a838cd141a20bf5e8b55131e4ef813fa/javascript-utils-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2022-12-26 20:27:27",
"github": false,
"gitlab": false,
"bitbucket": false,
"lcname": "javascript-utils"
}