mirror of
https://github.com/autistic-symposium/web3-starter-py.git
synced 2025-05-17 22:20:22 -04:00
Add some private projects
This commit is contained in:
parent
c8cd5cdbc4
commit
07aa882d51
80 changed files with 5216 additions and 41 deletions
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1 +0,0 @@
|
|||
custom: [paypal.me/miasteinkirch, "https://www.buymeacoffee.com/miavonpizza"]
|
37
.github/workflows/pythonapp.yml
vendored
37
.github/workflows/pythonapp.yml
vendored
|
@ -1,37 +0,0 @@
|
|||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: Python application
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
pip install flake8
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pip install pytest
|
||||
pytest
|
11
README.md
11
README.md
|
@ -1,6 +1,7 @@
|
|||
# ✨ Examples and Boilerplates for Python ✨
|
||||
# ✨🐍 [Scratch Space] Python: small projects and boilerplates
|
||||
|
||||
 [](https://github.com/bt3gl/Awesome_Entrepreneur)
|
||||
|
||||
### Boilerplates
|
||||
|
||||
* [CLI with Click](https://github.com/bt3gl-labs/Python-Boilerplates/tree/master/Click_app).
|
||||
* [CLI with Argparse](https://github.com/bt3gl/Awesome_Python_Boilerplates/tree/master/Argparse_app).
|
||||
|
@ -9,7 +10,11 @@
|
|||
* [Security examples](https://github.com/bt3gl/Awesome_Python_Examples/tree/master/Security_examples).
|
||||
* [Concurrence examples](https://github.com/bt3gl/Awesome_Python_Examples/tree/master/Concurrence_examples).
|
||||
|
||||
### Good Python Readings
|
||||
|
||||
### Small projects
|
||||
|
||||
|
||||
### Good Readings
|
||||
|
||||
* [Google Style Guide](https://google.github.io/styleguide/pyguide.html).
|
||||
|
||||
|
|
0
art_and_logic/.gitkeep
Normal file
0
art_and_logic/.gitkeep
Normal file
70
art_and_logic/1-efun/.gitignore
vendored
Normal file
70
art_and_logic/1-efun/.gitignore
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# pyenv,
|
||||
.python-version
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# Others
|
||||
.DS_Store
|
BIN
art_and_logic/1-efun/AlpcPart1-190801.pdf
Normal file
BIN
art_and_logic/1-efun/AlpcPart1-190801.pdf
Normal file
Binary file not shown.
28
art_and_logic/1-efun/Makefile
Normal file
28
art_and_logic/1-efun/Makefile
Normal file
|
@ -0,0 +1,28 @@
|
|||
.PHONY: setup install clean test lint
|
||||
|
||||
default: test
|
||||
|
||||
setup:
|
||||
pip install -r requirements.txt
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
|
||||
clean:
|
||||
@find . -type f -name '*.pyc' -delete
|
||||
@find . -type d -name '__pycache__' | xargs rm -rf
|
||||
@find . -type d -name '*.ropeproject' | xargs rm -rf
|
||||
@rm -rf build/
|
||||
@rm -rf dist/
|
||||
@rm -rf venv/
|
||||
@rm -f src/*.egg*
|
||||
@rm -f MANIFEST
|
||||
@rm -rf docs/build/
|
||||
@rm -f .coverage.*
|
||||
|
||||
test:
|
||||
@tox -- -s
|
||||
|
||||
lint:
|
||||
@tox -e lint
|
||||
|
80
art_and_logic/1-efun/README.md
Normal file
80
art_and_logic/1-efun/README.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
# Efun: Art + Logic Enconding + Decoding
|
||||
|
||||
This program i) converts and encodes a 14-bit decimal input value to a 2-byte hexadecimal, ii) decodes hexadecimal representations to 14-bit decimal (as described in [this doc](https://github.com/bt3gl/PRIV-Interview_take_home_projects/blob/master/art_and_logic/1-efun/AlpcPart1-190801.pdf).
|
||||
|
||||
|
||||
## Installing
|
||||
|
||||
Create and source virtual enviroment. You can use [virtualenv](https://virtualenv.pypa.io/en/latest/) or [conda](https://docs.conda.io/en/latest/):
|
||||
|
||||
```bash
|
||||
virtualenv venv
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
make setup
|
||||
```
|
||||
|
||||
Install Efun:
|
||||
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Encoding
|
||||
|
||||
To enconde a integer, run:
|
||||
|
||||
```bash
|
||||
efun -e <integer>
|
||||
```
|
||||
|
||||
Note that the value must be in the range `[-8192, 8191]`.
|
||||
|
||||
#### Decoding
|
||||
|
||||
To decode an integer, run:
|
||||
|
||||
```bash
|
||||
efun -d <integer>
|
||||
```
|
||||
|
||||
Note that the value must be in the range `[0x0000, 0x7F7F]`.
|
||||
|
||||
|
||||
## Developer
|
||||
|
||||
### Running tests
|
||||
|
||||
You can run tests with:
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
You can lint your code with:
|
||||
|
||||
```bash
|
||||
make lint
|
||||
```
|
||||
|
||||
### Cleaning up
|
||||
|
||||
Clean residual compilation and installation files with:
|
||||
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
|
||||
|
||||
----
|
||||
|
||||
Thank you for reading my code!
|
13
art_and_logic/1-efun/data/ConvertedData.txt
Normal file
13
art_and_logic/1-efun/data/ConvertedData.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Encoding function
|
||||
6111 -> 6f5f
|
||||
340 -> 4254
|
||||
-2628 -> 2b3c
|
||||
-255 -> 3e01
|
||||
7550 -> 7a7e
|
||||
|
||||
# Decoding function
|
||||
0A0A -> -6902
|
||||
0029 -> -8151
|
||||
3F0F -> -113
|
||||
4400 -> 512
|
||||
5E7F -> 3967
|
41
art_and_logic/1-efun/main_test.py
Normal file
41
art_and_logic/1-efun/main_test.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import unittest
|
||||
import src.main as m
|
||||
|
||||
|
||||
class EfunTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.e = m.Efun()
|
||||
self.test_data = [
|
||||
(-4096, 0x2000, (0x20, 0x00)),
|
||||
(-8192, 0x0000, (0x00, 0x00)),
|
||||
(0, 0x4000, (0x40, 0x00)),
|
||||
(2048, 0x5000, (0x50, 0x00)),
|
||||
(8191, 0x7F7F, (0x7F, 0x7F)),
|
||||
(6111, 0x6F5F, (0x6F, 0x5F)),
|
||||
(340, 0x4254, (0x42, 0x54)),
|
||||
(-2628, 0x2B3C, (0x2B, 0x3C)),
|
||||
(-255, 0x3E01, (0x3E, 0x01)),
|
||||
(-6902, 0x0A0A, (0x0A, 0x0A)),
|
||||
(-8151, 0x0029, (0x00, 0x29)),
|
||||
(-113, 0x3F0F, (0x3F, 0x0F)),
|
||||
(512, 0x4400, (0x44, 0x00)),
|
||||
(3967, 0x5E7F, (0x5E, 0x7F)),
|
||||
]
|
||||
|
||||
def test_extract(self):
|
||||
for item in self.test_data:
|
||||
self.assertEqual(self.e._extract_two_bytes(item[1]), item[2])
|
||||
|
||||
def test_decode(self):
|
||||
for item in self.test_data:
|
||||
by1, by2 = self.e._extract_two_bytes(item[1])
|
||||
self.e._decode(by1, by2)
|
||||
self.assertEqual(self.e.dec_byte, item[0])
|
||||
|
||||
def test_encode(self):
|
||||
for item in self.test_data:
|
||||
self.e._encode(item[0])
|
||||
self.assertEqual(self.e.enc_byte, hex(item[1]))
|
||||
|
2
art_and_logic/1-efun/requirements.txt
Normal file
2
art_and_logic/1-efun/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
tox==3.14.3
|
||||
virtualenv==20.0.1
|
15
art_and_logic/1-efun/setup.py
Normal file
15
art_and_logic/1-efun/setup.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='efun',
|
||||
version='0.0.1',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
author='Mia von Steinkirch',
|
||||
install_requires=[
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
efun=src.main:main
|
||||
''',
|
||||
)
|
1
art_and_logic/1-efun/src/__init__.py
Normal file
1
art_and_logic/1-efun/src/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf8 -*-
|
154
art_and_logic/1-efun/src/main.py
Normal file
154
art_and_logic/1-efun/src/main.py
Normal file
|
@ -0,0 +1,154 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
class Efun(object):
|
||||
|
||||
def __init__(self):
|
||||
self.enc_byte = None
|
||||
self.dec_byte = None
|
||||
self.enc_range = [-8192, 8191]
|
||||
self.dec_range = [0x0000, 0x7F7F]
|
||||
|
||||
def _decode(self, higher_byte, lower_byte):
|
||||
"""
|
||||
Converts encoded hex value back into the original signed integer.
|
||||
|
||||
Argument:
|
||||
higher_byte {string} -- higher byter hex input to be decoded.
|
||||
lower_byte {string} -- lower byter hex input to be decoded.
|
||||
|
||||
Returns:
|
||||
Decoded value string in decimal base.
|
||||
"""
|
||||
|
||||
# Left shift 7 to get higher byte.
|
||||
higher_byte <<= 7
|
||||
|
||||
# 'OR' byte withing the decimal lower limit.
|
||||
self.dec_byte = (lower_byte | higher_byte) + self.enc_range[0]
|
||||
|
||||
def _encode(self, value):
|
||||
"""
|
||||
Gets a signed integer and return a 4 character string,
|
||||
such that after the encoding is complete, the most
|
||||
significant bit of each byte has been cleared.
|
||||
|
||||
Argument:
|
||||
value {string} -- integer input value to be encoded.
|
||||
|
||||
Returns:
|
||||
Encoded 2-byte string.
|
||||
"""
|
||||
|
||||
# Add 8192 for unsigned
|
||||
sign_int = int(value)
|
||||
unsign_int = sign_int + 8192
|
||||
|
||||
# 'AND' with 0b0000000001111111 for lower byte and
|
||||
# 'AND' with 0b0011111110000000 for higher byter
|
||||
lower_byte = unsign_int & 0x007F
|
||||
higher_byte = ((unsign_int & 0x3F80) << 1)
|
||||
|
||||
# Return hex enconding of sum of the two bytes
|
||||
self.enc_byte = hex(lower_byte + higher_byte)
|
||||
|
||||
def _extract_two_bytes(self, value):
|
||||
"""
|
||||
Extract two bytes from a hexadecimal string.
|
||||
|
||||
Arguments:
|
||||
value {hex} -- hexadecimal between 0x0000 and 0x7F7F.
|
||||
|
||||
Returns:
|
||||
higher_byte, lower_byte -- byte strings.
|
||||
"""
|
||||
|
||||
# 'And' with 0b0000000001111111 for lower byte
|
||||
lower_byte = value & 0x00FF
|
||||
|
||||
# Left shift 8 positions for higher byte
|
||||
higher_byte = value >> 8
|
||||
|
||||
return higher_byte, lower_byte
|
||||
|
||||
def run_decode(self, value):
|
||||
"""
|
||||
Verifies if an input is valid 2-byte hexadecimal,
|
||||
returning its decoded value.
|
||||
|
||||
Arguments:
|
||||
value {string} -- hexadecimal input value.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
"""
|
||||
|
||||
# Convert to hexadecimal.
|
||||
try:
|
||||
value = int(value, 16)
|
||||
|
||||
except ValueError as e:
|
||||
print('Could not convert value to hexadecimal: {}.'.fomart(e))
|
||||
return -1
|
||||
|
||||
# Verify whether the value is within the range,
|
||||
# and then decode it.
|
||||
if self.dec_range[0] <= value <= self.dec_range[1]:
|
||||
self._decode(*self._extract_two_bytes(value))
|
||||
print('{0} decodes as {1}'.format(hex(value), self.dec_byte))
|
||||
|
||||
else:
|
||||
print('Value {0} is out of range of [{1}, {2}]'.format(hex(value),
|
||||
hex(self.dec_range[0], hex(self.dec_range[1]))))
|
||||
|
||||
def run_encode(self, value):
|
||||
"""
|
||||
Verifies whether an input is valid 14-bit integer,
|
||||
returning its encoded value.
|
||||
|
||||
Arguments:
|
||||
value {byte} -- 14-bit signed integer.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
"""
|
||||
int_value = int.from_bytes(value, byteorder='big', signed=True)
|
||||
|
||||
if self.enc_range[0] <= int_value <= self.enc_range[1]:
|
||||
self._encode(int_value)
|
||||
print('Value {0} encodes as {1}'.format(int_value, self.enc_byte))
|
||||
|
||||
else:
|
||||
print('Value {0} out of range: {1}'.format(int_value, self.enc_byte))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# Creater an instance of Efun().
|
||||
e = Efun()
|
||||
|
||||
# Set strings for Argparse.
|
||||
description = 'Efun is an Art + Logic Enconding + Decoding application.'
|
||||
encode_help = 'Enter an INT value in the range {}.'.format(e.enc_range)
|
||||
decode_help = 'Enter an HEX value in the range {}.'.format(e.dec_range)
|
||||
|
||||
# Run CLI menu.
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('-e', '--encode', type=int, help=encode_help)
|
||||
group.add_argument('-d', '--decode', help=decode_help)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get the 14-byte input, enconde/decode it, and print results.
|
||||
bit_range = 14
|
||||
if args.encode:
|
||||
sig_bytes = args.encode.to_bytes(bit_range, byteorder='big', signed=True)
|
||||
e.run_encode(sig_bytes)
|
||||
else:
|
||||
e.run_decode(args.decode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
1
art_and_logic/1-efun/test/__init__.py
Normal file
1
art_and_logic/1-efun/test/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf8 -*-
|
12
art_and_logic/1-efun/tox.ini
Normal file
12
art_and_logic/1-efun/tox.ini
Normal file
|
@ -0,0 +1,12 @@
|
|||
[tox]
|
||||
envlist = py27
|
||||
|
||||
[testenv]
|
||||
deps = pytest
|
||||
commands =
|
||||
pytest
|
||||
|
||||
[testenv:lint]
|
||||
skip_install = true
|
||||
deps = flake8
|
||||
commands = flake8 src/
|
5
art_and_logic/2-epen/.env_example
Normal file
5
art_and_logic/2-epen/.env_example
Normal file
|
@ -0,0 +1,5 @@
|
|||
LOG_LEVEL = INFO
|
||||
LOG_FORMAT = %(levelname)s: %(message)s
|
||||
BOUNDARY_LIMIT_ENC = -8192, 8191
|
||||
BOUNDARY_LIMIT_DEC = 0x0000, 0x7F7F
|
||||
INPUT_STREAM_FILE = './data/InputStream.txt'
|
71
art_and_logic/2-epen/.gitignore
vendored
Normal file
71
art_and_logic/2-epen/.gitignore
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# pyenv,
|
||||
.python-version
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# Others
|
||||
.DS_Store
|
||||
.env
|
BIN
art_and_logic/2-epen/AlpcPart2-190801.pdf
Normal file
BIN
art_and_logic/2-epen/AlpcPart2-190801.pdf
Normal file
Binary file not shown.
29
art_and_logic/2-epen/Makefile
Normal file
29
art_and_logic/2-epen/Makefile
Normal file
|
@ -0,0 +1,29 @@
|
|||
.PHONY: setup install clean test lint run
|
||||
|
||||
default: test
|
||||
|
||||
setup:
|
||||
pip install -r requirements.txt
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
|
||||
run:
|
||||
epen
|
||||
|
||||
clean:
|
||||
@find . -type f -name '*.pyc' -delete
|
||||
@find . -type d -name '__pycache__' | xargs rm -rf
|
||||
@find . -type d -name '*.ropeproject' | xargs rm -rf
|
||||
@rm -rf build/
|
||||
@rm -rf dist/
|
||||
@rm -rf *.egg*
|
||||
@rm -f MANIFEST
|
||||
@rm -rf docs/build/
|
||||
@rm -f .coverage.*
|
||||
|
||||
test:
|
||||
@tox -- -s
|
||||
|
||||
lint:
|
||||
@tox -e lint
|
143
art_and_logic/2-epen/README.md
Normal file
143
art_and_logic/2-epen/README.md
Normal file
|
@ -0,0 +1,143 @@
|
|||
# EPen: Art + Logic Pen Language
|
||||
|
||||
This program creates an interface for a digital pen for drawing, as described on [this document](https://github.com/bt3gl/PRIV-Interview_take_home_projects/blob/master/art_and_logic/2-epen/AlpcPart2-190801.pdf).
|
||||
|
||||
----
|
||||
|
||||
## Installing
|
||||
|
||||
Create and source virtual enviroment. You can use [virtualenv](https://virtualenv.pypa.io/en/latest/) or [conda](https://docs.conda.io/en/latest/):
|
||||
|
||||
```bash
|
||||
virtualenv venv
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
Create and customize an enviroment file:
|
||||
|
||||
```bash
|
||||
cp .env_example .env
|
||||
vim .env
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
make setup
|
||||
```
|
||||
|
||||
Install EPen:
|
||||
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
-----
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Edit the stream data to be read by the program:
|
||||
|
||||
```
|
||||
vim data/InputStream.txt
|
||||
```
|
||||
|
||||
Run this program with:
|
||||
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
or simply
|
||||
|
||||
```bash
|
||||
epen
|
||||
```
|
||||
|
||||
These commands run `./src/main.py`, which calls a class called `Epen()`.
|
||||
This is the main class that defines all the functionalities of this application.
|
||||
|
||||
-----
|
||||
|
||||
## Examples
|
||||
|
||||
The stream:
|
||||
|
||||
```bash
|
||||
F0A04000417F4000417FC040004000804001C05F205F20804000
|
||||
```
|
||||
|
||||
prints
|
||||
|
||||
```bash
|
||||
CLR;
|
||||
CO 0 255 0 255;
|
||||
MV (0, 0);
|
||||
PEN DOWN;
|
||||
MV (4000, 4000);
|
||||
PEN UP;
|
||||
```
|
||||
|
||||
The stream:
|
||||
|
||||
|
||||
```bash
|
||||
F0A0417F40004000417FC067086708804001C0670840004000187818784000804000
|
||||
```
|
||||
|
||||
prints
|
||||
|
||||
```bash
|
||||
CLR;
|
||||
CO 255 0 0 255;
|
||||
MV (5000, 5000);
|
||||
PEN DOWN;
|
||||
MV (8191, 5000);
|
||||
PEN UP;
|
||||
MV (8191, 0);
|
||||
PEN DOWN;
|
||||
MV (5000, 0);
|
||||
PEN UP;
|
||||
```
|
||||
|
||||
And the stream:
|
||||
|
||||
```bash
|
||||
F0A040004000417F417FC04000400080400047684F5057384000804001C05F204000400001400140400040007E405B2C4000804000
|
||||
```
|
||||
|
||||
prints
|
||||
|
||||
```bash
|
||||
CLR;
|
||||
CO 0 0 255 255;
|
||||
MV (0, 0);
|
||||
PEN DOWN;
|
||||
MV (4000, 0) (4000, -8000) (-4000, -8000) (-4000, 0) (-500, 0);
|
||||
PEN UP;
|
||||
(base)
|
||||
```
|
||||
|
||||
|
||||
## Developer
|
||||
|
||||
### Linting
|
||||
|
||||
You can lint the code with:
|
||||
|
||||
```bash
|
||||
make lint
|
||||
```
|
||||
|
||||
### Cleaning up
|
||||
|
||||
Clean residual compilation and installation files with:
|
||||
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
|
||||
|
||||
----
|
||||
|
||||
Thank you for reading my code!
|
13
art_and_logic/2-epen/data/ConvertedData.txt
Normal file
13
art_and_logic/2-epen/data/ConvertedData.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Encoding function
|
||||
6111 -> 6f5f
|
||||
340 -> 4254
|
||||
-2628 -> 2b3c
|
||||
-255 -> 3e01
|
||||
7550 -> 7a7e
|
||||
|
||||
# Decoding function
|
||||
0A0A -> -6902
|
||||
0029 -> -8151
|
||||
3F0F -> -113
|
||||
4400 -> 512
|
||||
5E7F -> 3967
|
3
art_and_logic/2-epen/data/InputStream.txt
Normal file
3
art_and_logic/2-epen/data/InputStream.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
#F0A04000417F4000417FC040004000804001C05F205F20804000
|
||||
#F0A0417F40004000417FC067086708804001C0670840004000187818784000804000
|
||||
F0A040004000417F417FC04000400080400047684F5057384000804001C05F204000400001400140400040007E405B2C4000804000
|
4
art_and_logic/2-epen/requirements.txt
Normal file
4
art_and_logic/2-epen/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
tox==3.14.3
|
||||
virtualenv==20.0.1
|
||||
python-dotenv==0.11.0
|
||||
numpy==1.18.1
|
13
art_and_logic/2-epen/setup.py
Normal file
13
art_and_logic/2-epen/setup.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='epen',
|
||||
version='0.0.1',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
author='Mia von Steinkirch',
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
epen=src.main:main
|
||||
''',
|
||||
)
|
304
art_and_logic/2-epen/src/Epen.py
Normal file
304
art_and_logic/2-epen/src/Epen.py
Normal file
|
@ -0,0 +1,304 @@
|
|||
# -*- coding: utf8 -*-
|
||||
|
||||
import sys
|
||||
|
||||
import logging as l
|
||||
|
||||
import src.encoder as enc
|
||||
from src.utils import get_boundary_limits
|
||||
|
||||
|
||||
class EPen(object):
|
||||
|
||||
def __init__(self):
|
||||
self.boundary = enc.get_boundary_limits('enc')
|
||||
self.color = None
|
||||
self.pen_location = None
|
||||
self.is_pen_down = None
|
||||
|
||||
def _parse_clear(self):
|
||||
"""
|
||||
Runs clear operations and prints CLEAR STDOUT.
|
||||
"""
|
||||
self.pen_location = [0, 0]
|
||||
self.color = [0, 0, 0, 255]
|
||||
self.is_pen_down = False
|
||||
print('CLR;')
|
||||
|
||||
def _parse_pen_state(self, substr):
|
||||
"""
|
||||
Sets pen to be UP or DOWN and prints PEN STATE STDOUT.
|
||||
|
||||
Arguments:
|
||||
substr {str} -- parameters for this opcode, which is
|
||||
either 0 (for pen up) or any other (for pen down)
|
||||
"""
|
||||
if substr:
|
||||
pen_state = enc.run_decoder(substr)
|
||||
else:
|
||||
l.error('Could not parse pen state string {}'.format(substr))
|
||||
|
||||
if pen_state == 0:
|
||||
if self.is_pen_down:
|
||||
self.is_pen_down = False
|
||||
print('PEN UP;')
|
||||
else:
|
||||
if not self.is_pen_down:
|
||||
self.is_pen_down = True
|
||||
print('PEN DOWN;')
|
||||
|
||||
def _parse_color(self, substr):
|
||||
"""
|
||||
Sets pen color and prints COLOR SET STDOUT.
|
||||
|
||||
Arguments:
|
||||
substr {str} -- RGBA parameters for this opcode,
|
||||
which is represented by four integers in the range
|
||||
[0, 255] defining colors (red, green, blue) and alpha.
|
||||
"""
|
||||
red = enc.run_decoder(substr[:4])
|
||||
green = enc.run_decoder(substr[4:8])
|
||||
blue = enc.run_decoder(substr[8:12])
|
||||
alpha = enc.run_decoder(substr[12:16])
|
||||
|
||||
self.color = [red, green, blue, alpha]
|
||||
print('CO {0} {1} {2} {3};'.format(*self.color))
|
||||
|
||||
def _parse_pen_move(self, substr):
|
||||
"""
|
||||
Parses a substring of parameters and usex this data
|
||||
as pair of coordinates to move the pen inside the
|
||||
square boundary limit.
|
||||
|
||||
Arguments:
|
||||
substr {str} -- (x,y) pairs with directions to where
|
||||
the pen should move to (at least one pair).
|
||||
|
||||
Returns:
|
||||
i - an integer indicating how much of the input
|
||||
substring was used in this opcode.
|
||||
"""
|
||||
i = 0
|
||||
move_list = []
|
||||
|
||||
while i < len(substr):
|
||||
try:
|
||||
# Ends if find another opcode.
|
||||
if substr[i:i+2] in set(['F0', '80', 'A0']):
|
||||
break
|
||||
|
||||
if len(substr[i:]) > 8:
|
||||
x_coords = enc.run_decoder(substr[i:i+4])
|
||||
y_coords = enc.run_decoder(substr[i+4:i+8])
|
||||
move_list.append([x_coords, y_coords])
|
||||
i += 8
|
||||
else:
|
||||
break
|
||||
|
||||
except (KeyError, ValueError) as e:
|
||||
l.error('Ill-formated MV data: {}'.format(e))
|
||||
|
||||
self._move_pen(move_list)
|
||||
return i
|
||||
|
||||
def _print_mv(self, cmd_list=None):
|
||||
"""
|
||||
Prints MV PEN STDOUT. If a string with a list of commands
|
||||
is given, print that instead of pen_location.
|
||||
|
||||
Arguments:
|
||||
cmd_list {list (optional)} -- a list with pairs of
|
||||
(x, y) coordinates.
|
||||
"""
|
||||
if cmd_list:
|
||||
cmd_str = ' '.join(cmd_list)
|
||||
print('MV {};'.format(cmd_str))
|
||||
else:
|
||||
print('MV ({0}, {1});'.format(self.pen_location[0], self.pen_location[1]))
|
||||
|
||||
def _fix_boundary(self, coord):
|
||||
"""
|
||||
Check if the coordinate exceeds
|
||||
the boundary limit, returning this
|
||||
value in that case.
|
||||
|
||||
Arguments:
|
||||
coord {int} -- x or y coordinate.
|
||||
|
||||
Returns:
|
||||
An integer representing the fixed coordinate.
|
||||
"""
|
||||
if coord < self.boundary[0]:
|
||||
return self.boundary[0]
|
||||
elif coord > self.boundary[1]:
|
||||
return self.boundary[1]
|
||||
else:
|
||||
return coord
|
||||
|
||||
def _fix_boundaries(self, x, y):
|
||||
"""
|
||||
Checks if a set of (x, y) coordinates
|
||||
exceeds the boundary limits, returning
|
||||
a fixed (x, y) pair.
|
||||
|
||||
Arguments:
|
||||
coord {list} -- (x, y) coordinates.
|
||||
|
||||
Returns:
|
||||
A (x, y) pair representing the fixed coordinates.
|
||||
"""
|
||||
return self._fix_boundary(x), self._fix_boundary(y)
|
||||
|
||||
def _is_inside_boundary(self, xymove=None):
|
||||
"""
|
||||
Checks whether a set of coordinate is inside
|
||||
of the boundary, returning True if the case,
|
||||
or False otherwise.
|
||||
|
||||
Arguments:
|
||||
xymove {tuple} - (x, y) tuple.
|
||||
|
||||
Returns:
|
||||
True if the coordinates were fixed, False
|
||||
otherwise.
|
||||
"""
|
||||
if xymove is None:
|
||||
xymove = self.pen_location
|
||||
|
||||
x_fix = self._fix_boundary(xymove[0])
|
||||
y_fix = self._fix_boundary(xymove[1])
|
||||
return (xymove[0] == x_fix and xymove[1] == y_fix)
|
||||
|
||||
def _is_on_the_boundary(self, xymove=None):
|
||||
"""
|
||||
Checks whether a coordinate is on the boundary,
|
||||
returning True if the case, or False otherwise.
|
||||
|
||||
Arguments:
|
||||
xymove {tuple} - (x, y) tuple.
|
||||
|
||||
Returns:
|
||||
True if any of the (x, y) coordinates are
|
||||
on the boundary, False otherwise.
|
||||
"""
|
||||
if xymove is None:
|
||||
x, y = self.pen_location
|
||||
|
||||
return x == self.boundary[0] or x == self.boundary[1] or \
|
||||
y == self.boundary[0] or y == self.boundary[1]
|
||||
|
||||
|
||||
def _get_next_position(self, xymove):
|
||||
"""
|
||||
Givens a move coordinates (x, y), returns the next
|
||||
destination coordinate for the pen.
|
||||
|
||||
Arguments:
|
||||
xymove {tuple} - (x, y) tuple.
|
||||
|
||||
Returns:
|
||||
The next (x, y) coordinates to move the pen to.
|
||||
"""
|
||||
return self.pen_location[0] + xymove[0], \
|
||||
self.pen_location[1] + xymove[1]
|
||||
|
||||
def _move_pen(self, move_list):
|
||||
"""
|
||||
Changes the location of the pen relative to its current location.
|
||||
|
||||
Arguments:
|
||||
move_list {list} -- list of coordinates to move the pen.
|
||||
"""
|
||||
# Loop over the list of (x ,y) moves, adding them to
|
||||
# a dictionary, together with a boolean False if x or y
|
||||
# is outside the limits.
|
||||
tablet_dict = {}
|
||||
for n, xymove in enumerate(move_list, 1):
|
||||
self.pen_location = self._get_next_position(xymove)
|
||||
|
||||
if self._is_inside_boundary():
|
||||
tablet_dict[n] = [self.pen_location, True]
|
||||
else:
|
||||
tablet_dict[n] = [self.pen_location, False]
|
||||
|
||||
|
||||
# Find the positions and print it as pen down.
|
||||
if self.is_pen_down:
|
||||
cmd_list = []
|
||||
|
||||
for i in sorted(tablet_dict):
|
||||
x = tablet_dict[i][0][0]
|
||||
y = tablet_dict[i][0][1]
|
||||
is_inside = tablet_dict[i][1]
|
||||
|
||||
if is_inside:
|
||||
cmd_list.append('({0}, {1})'.format(x, y))
|
||||
else:
|
||||
break
|
||||
|
||||
if cmd_list:
|
||||
self._print_mv(cmd_list)
|
||||
|
||||
# It broke the boundary at some point.
|
||||
if i < len(tablet_dict):
|
||||
for item in range(i, len(tablet_dict)+1):
|
||||
coords, is_inside = tablet_dict[item]
|
||||
self.pen_location = self._fix_boundaries(coords[0], coords[1])
|
||||
self._print_mv()
|
||||
if item != len(tablet_dict):
|
||||
if not is_inside:
|
||||
if self.is_pen_down:
|
||||
self._parse_pen_state('4000')
|
||||
else:
|
||||
self._parse_pen_state('4001')
|
||||
else:
|
||||
coords, is_inside = tablet_dict[i]
|
||||
self.pen_location = self._fix_boundaries(coords[0], coords[1])
|
||||
if not is_inside:
|
||||
self._parse_pen_state('4000')
|
||||
|
||||
# Pen is up.
|
||||
else:
|
||||
last_position = tablet_dict[sorted(tablet_dict)[-1]][0]
|
||||
self.pen_location = self._fix_boundaries(*last_position)
|
||||
self._print_mv()
|
||||
|
||||
def parse_stream(self, stream_str):
|
||||
"""
|
||||
Parses a stream string and run its commands and their
|
||||
adjacent parameters.
|
||||
|
||||
Arguments:
|
||||
stream_str {str} - data stream of actions be taken.
|
||||
"""
|
||||
i = 0
|
||||
|
||||
while i < len(stream_str):
|
||||
|
||||
# Opcode is CLEAR.
|
||||
if stream_str[i:i+2] == 'F0':
|
||||
l.debug('Parsing clear opcode...')
|
||||
self._parse_clear()
|
||||
i += 2
|
||||
|
||||
# Opcode is SET PEN STATE.
|
||||
elif stream_str[i:i+2] == '80':
|
||||
l.debug('Parsing pen state opcode...')
|
||||
self._parse_pen_state(stream_str[i+2:i+6])
|
||||
i += 6
|
||||
|
||||
# Opcode is SET COLOR.
|
||||
elif stream_str[i:i+2] == 'A0':
|
||||
l.debug('Parsing color opcode...')
|
||||
self._parse_color(stream_str[i+2:i+18])
|
||||
i += 18
|
||||
|
||||
# Opcode is MOVE PEN.
|
||||
elif stream_str[i:i+2] == 'C0':
|
||||
l.debug('Parsing pen move opcode...')
|
||||
i += 2 + self._parse_pen_move(stream_str[i+2:])
|
||||
|
||||
# Opcode is not identified.
|
||||
else:
|
||||
l.debug('Non-identificated char: {}'.format(stream_str[i]))
|
||||
i += 1
|
2
art_and_logic/2-epen/src/__init__.py
Normal file
2
art_and_logic/2-epen/src/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf8 -*-
|
||||
|
130
art_and_logic/2-epen/src/encoder.py
Normal file
130
art_and_logic/2-epen/src/encoder.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
# -*- coding: utf8 -*-
|
||||
|
||||
import logging as l
|
||||
|
||||
from src.utils import get_boundary_limits
|
||||
|
||||
|
||||
def decoder(higher_byte, lower_byte):
|
||||
"""
|
||||
Converts encoded hex value back into the original signed integer.
|
||||
|
||||
Argument:
|
||||
higher_byte {string} -- higher byter hex input to be decoded.
|
||||
lower_byte {string} -- lower byter hex input to be decoded.
|
||||
|
||||
Returns:
|
||||
Decoded value string in decimal base.
|
||||
"""
|
||||
min_lim, _ = get_boundary_limits('enc')
|
||||
|
||||
# Left shift 7 to get higher byte.
|
||||
higher_byte <<= 7
|
||||
|
||||
# 'OR' byte withing the decimal lower limit.
|
||||
encoding_range = get_boundary_limits
|
||||
return (lower_byte | higher_byte) + min_lim
|
||||
|
||||
|
||||
def encoder(value):
|
||||
"""
|
||||
Gets a signed integer and return a 4 character string,
|
||||
such that after the encoding is complete, the most
|
||||
significant bit of each byte has been cleared.
|
||||
|
||||
Argument:
|
||||
value {string} -- integer input value to be encoded.
|
||||
|
||||
Returns:
|
||||
Encoded 2-byte string.
|
||||
"""
|
||||
# Add 8192 for unsigned
|
||||
_, max_lim = get_boundary_limits('enc')
|
||||
sign_int = int(value)
|
||||
unsign_int = sign_int + max_lim + 1
|
||||
|
||||
# 'AND' with 0b0000000001111111 for lower byte and
|
||||
# 'AND' with 0b0011111110000000 for higher byter
|
||||
lower_byte = unsign_int & 0x007F
|
||||
higher_byte = ((unsign_int & 0x3F80) << 1)
|
||||
|
||||
# Return hex encoding of the sum of the two bytes
|
||||
return hex(lower_byte + higher_byte)
|
||||
|
||||
|
||||
def extract_two_bytes(value):
|
||||
"""
|
||||
Extract two bytes from a hexadecimal string.
|
||||
|
||||
Arguments:
|
||||
value {hex} -- hexadecimal between 0x0000 and 0x7F7F.
|
||||
|
||||
Returns:
|
||||
higher_byte, lower_byte -- byte strings.
|
||||
"""
|
||||
# 'And' with 0b0000000001111111 for lower byte
|
||||
lower_byte = value & 0x00FF
|
||||
|
||||
# Left shift 8 positions for higher byte
|
||||
higher_byte = value >> 8
|
||||
|
||||
return higher_byte, lower_byte
|
||||
|
||||
|
||||
def run_decoder(input_hex):
|
||||
"""
|
||||
Verifies if an input is valid 2-byte hexadecimal,
|
||||
returning its decoded value.
|
||||
|
||||
Arguments:
|
||||
value {string} -- hexadecimal input value.
|
||||
|
||||
Returns:
|
||||
Decoded byte or -1 (if error).
|
||||
"""
|
||||
min_lim, max_lim = get_boundary_limits('dec')
|
||||
|
||||
# Convert to hexadecimal.
|
||||
try:
|
||||
l.debug('Converting {} to decimal...'.format(input_hex))
|
||||
input_dec = int(input_hex, 16)
|
||||
except ValueError as e:
|
||||
l.error('Could not convert {0} decimal: {1}'.format(input_hex, e))
|
||||
return -1
|
||||
|
||||
# Verify whether the input is within the range,
|
||||
# and then decode it.
|
||||
if min_lim <= input_dec <= max_lim:
|
||||
dec_byte = decoder(*extract_two_bytes(input_dec))
|
||||
l.debug('{0} decodes as {1}'.format(input_dec, dec_byte))
|
||||
return dec_byte
|
||||
|
||||
else:
|
||||
l.debug('{0} is out of range of [{1}, {2}].'.format(input_dec,
|
||||
hex(min_lim), hex(max_lim)))
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
def run_encoder(value):
|
||||
"""
|
||||
Verifies whether a byte input is a valid 14-bit integer,
|
||||
returning its encoded value.
|
||||
|
||||
Arguments:
|
||||
value {byte} -- 14-bit signed integer.
|
||||
|
||||
Returns:
|
||||
Encoded byte or -1 (if error).
|
||||
"""
|
||||
int_value = int.from_bytes(value, byteorder='big', signed=True)
|
||||
min_lim, max_lim = get_boundary_limits('enc')
|
||||
|
||||
if min_lim <= int_value <= max_lim:
|
||||
enc_byte = encoder(int_value)
|
||||
l.debug('Value {0} encodes as {1}'.format(value, enc_byte))
|
||||
return enc_byte
|
||||
|
||||
else:
|
||||
l.error(int('Value {0} out of range: {1}'.format(value, enc_byte)))
|
||||
return -1
|
28
art_and_logic/2-epen/src/main.py
Normal file
28
art_and_logic/2-epen/src/main.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import logging as l
|
||||
|
||||
import src.Epen as Epen
|
||||
import src.utils as utils
|
||||
|
||||
from src.settings import LOG_LEVEL, LOG_FORMAT, INPUT_STREAM_FILE
|
||||
|
||||
# Define log level: choose between DEBUG or INFO.
|
||||
l.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
p = Epen.EPen()
|
||||
|
||||
# Get input stream from file.
|
||||
input_stream = utils.parse_input_stream(INPUT_STREAM_FILE)
|
||||
|
||||
# Extract and run list of commands from stream input.
|
||||
if input_stream:
|
||||
p.parse_stream(input_stream)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
16
art_and_logic/2-epen/src/settings.py
Normal file
16
art_and_logic/2-epen/src/settings.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Load all env variables from .env file
|
||||
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
|
||||
env_path = Path('.') / '.env'
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
|
||||
# General constants
|
||||
LOG_LEVEL = os.getenv('LOG_LEVEL')
|
||||
LOG_FORMAT = os.getenv('LOG_FORMAT')
|
||||
BOUNDARY_LIMIT_ENC = os.getenv('BOUNDARY_LIMIT_ENC')
|
||||
BOUNDARY_LIMIT_DEC = os.getenv('BOUNDARY_LIMIT_DEC')
|
||||
INPUT_STREAM_FILE = os.getenv('INPUT_STREAM_FILE')
|
54
art_and_logic/2-epen/src/utils.py
Normal file
54
art_and_logic/2-epen/src/utils.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging as l
|
||||
|
||||
from src.settings import BOUNDARY_LIMIT_ENC, BOUNDARY_LIMIT_DEC
|
||||
|
||||
|
||||
def parse_input_stream(filename):
|
||||
"""
|
||||
Loads a file with the input stream with
|
||||
encoded commands.
|
||||
|
||||
Returns:
|
||||
filename -- location of the file in disk.
|
||||
"""
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for input_stream in lines:
|
||||
if input_stream[0] != '#':
|
||||
l.debug('Input stream:\n{}'.format(input_stream))
|
||||
return input_stream.strip()
|
||||
|
||||
except (KeyError, OSError, TypeError) as e:
|
||||
l.error('Could not open input stream file {0}: {1}.'.format(filename, e))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def get_boundary_limits(lim_base):
|
||||
"""
|
||||
Extract boundary limits from env file, returning two integers
|
||||
representing these limits, either in the encoded or
|
||||
the decode base.
|
||||
|
||||
Arguments:
|
||||
lim_base {string}: 'enc' for the encoded limit
|
||||
representation or 'dec' for the encoded limit
|
||||
representation.
|
||||
"""
|
||||
try:
|
||||
if lim_base == 'enc':
|
||||
enc_range = tuple(BOUNDARY_LIMIT_ENC.split(', '))
|
||||
return int(enc_range[0]), int(enc_range[1])
|
||||
|
||||
elif lim_base == 'dec':
|
||||
dec_range = tuple(BOUNDARY_LIMIT_DEC.split(', '))
|
||||
return int(dec_range[0], 16), int(dec_range[1], 16)
|
||||
|
||||
except (KeyError, ValueError, AttributeError) as e:
|
||||
l.error('Could not extract boundary limits from .env file: {}'.format(e))
|
||||
return -1
|
12
art_and_logic/2-epen/tox.ini
Normal file
12
art_and_logic/2-epen/tox.ini
Normal file
|
@ -0,0 +1,12 @@
|
|||
[tox]
|
||||
envlist = py27
|
||||
|
||||
[testenv]
|
||||
deps = pytest
|
||||
commands =
|
||||
pytest
|
||||
|
||||
[testenv:lint]
|
||||
skip_install = true
|
||||
deps = flake8
|
||||
commands = flake8 src/
|
39
emotive.io/1.py
Normal file
39
emotive.io/1.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Examples from the exercise.
|
||||
id_list1 = [122, 144, 122, 144, 182]
|
||||
id_list1_return = [122, 123, 144, 145, 182]
|
||||
id_list2 = [13458, 13890, 13890, 144568, 144568]
|
||||
id_list2_return = [13458, 13890, 13891, 144568, 144569]
|
||||
|
||||
# Additional examples,
|
||||
id_list3 = []
|
||||
id_list4 = [0, 0, 0, 0, 0]
|
||||
id_list4_return = [0, 1, 2, 3, 4]
|
||||
|
||||
|
||||
def deduplicate_copilots_ids(id_list):
|
||||
''' Take an array of unsorted integer IDs and use sequential
|
||||
addition to deduplicate. Returns a sorted list of these IDs.'''
|
||||
|
||||
aux_dict = {}
|
||||
for k in id_list:
|
||||
if k in aux_dict.keys():
|
||||
aux_dict[k] = aux_dict[k] + 1
|
||||
else:
|
||||
aux_dict[k] = 1
|
||||
|
||||
new_list = []
|
||||
for key, value in aux_dict.items():
|
||||
for i in range(0, value):
|
||||
new_list.append(key + i)
|
||||
|
||||
# Note: sorted() used Timsort algorithm, which is nLog(n)
|
||||
return sorted(new_list)
|
||||
|
||||
|
||||
# Tests should pass.
|
||||
assert(deduplicate_copilots_ids(id_list1) == id_list1_return)
|
||||
assert(deduplicate_copilots_ids(id_list2) == id_list2_return)
|
||||
assert(deduplicate_copilots_ids(id_list3) == [])
|
||||
assert(deduplicate_copilots_ids(id_list4) == id_list4_return)
|
81
emotive.io/2.py
Normal file
81
emotive.io/2.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
from pprint import pprint
|
||||
from pymongo import MongoClient
|
||||
|
||||
|
||||
class AWS_fail(object):
|
||||
def __init__(self):
|
||||
self.client = MongoClient('localhost', 27017)
|
||||
self.db = self.client.aws_fail
|
||||
|
||||
def print_collection(self):
|
||||
''' Print the entire items collection. Debug purposes.'''
|
||||
for item in self.db.items.find():
|
||||
pprint(item)
|
||||
|
||||
def item_exists(self, sku):
|
||||
''' Check whether a given sku entry exists in the collection.'''
|
||||
return self.db.items.find_one({'sku_number': sku})
|
||||
|
||||
def add_item_db(self, sku):
|
||||
''' Add a new sku to the collection or increment its value.'''
|
||||
if not self.item_exists(sku):
|
||||
self.db.items.insert_one({'sku_number': sku, 'quantity': 1})
|
||||
else:
|
||||
self.db.items.update_one({'sku_number': sku}, {'$inc': {'quantity': 1}})
|
||||
|
||||
def rm_item_db(self, sku):
|
||||
''' Remove a sku item from the database.'''
|
||||
if self.item_exists(sku):
|
||||
self.db.items.delete_one({'sku_number': sku})
|
||||
|
||||
def check_quantity_count(self, z):
|
||||
''' Check whether the sku item has the given z quantity.'''
|
||||
z_found = self.db.items.find_one({'quantity': int(z)})
|
||||
if z_found:
|
||||
pprint(1)
|
||||
else:
|
||||
pprint(0)
|
||||
|
||||
def cleanup(self):
|
||||
self.db.items.drop()
|
||||
|
||||
|
||||
def load_input(filename):
|
||||
''' Load input file with list of db operations.'''
|
||||
try:
|
||||
with open(filename) as f:
|
||||
input_data = f.readlines()
|
||||
return input_data[0], input_data[1:]
|
||||
except IOError:
|
||||
pprint('Could not read file: {}. Exiting.').format(f)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def run_operation(operation, aws_fail):
|
||||
''' Run the db operations for the given db collection.'''
|
||||
if operation[0] == '1':
|
||||
aws_fail.add_item_db(operation[1])
|
||||
elif operation[0] == '2':
|
||||
aws_fail.rm_item_db(operation[1])
|
||||
elif operation[0] == '3':
|
||||
aws_fail.check_quantity_count(operation[1])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
aws_fail = AWS_fail()
|
||||
|
||||
filename = 'input_for_2.txt'
|
||||
num_operations, operation_list = load_input(filename)
|
||||
|
||||
# We could use len(operation_list) here, but let's use the input info
|
||||
# because len() is not optimized for a large number of operations
|
||||
for i in range(int(num_operations)):
|
||||
operation = operation_list[i].strip('\n').split(',')
|
||||
run_operation(operation, aws_fail)
|
||||
|
||||
aws_fail.cleanup()
|
||||
|
83
emotive.io/3.md
Normal file
83
emotive.io/3.md
Normal file
|
@ -0,0 +1,83 @@
|
|||
# Ex 3
|
||||
|
||||
```
|
||||
Bitly is an important service that basically maps and shortens long URLs and replaces them with a different URL of the form bit.ly/[Some Hashcode].
|
||||
Emotive uses Bitly to match URLs to a bitly link that gets sent out to customers and redirect to the longer link.
|
||||
|
||||
The hashcode is usually of the form A-Z and 0-9. Assume Bitly is down for a week and you need to architect a system that functions like bitly.
|
||||
Walk through the brief architecture of such a system, talk about the calls, the servers, and the design of the database.
|
||||
Be prepared to talk about scaling such a system to higher levels. How would you handle one URL? How would you handle 10 million URLs?
|
||||
```
|
||||
|
||||
|
||||
## Handling one URL
|
||||
|
||||
|
||||
If we only need to handle a few URLs, we could simply have a web server (say in an AWS EC2 instance or in an AWS S3 bucket) that takes and process the given long URL, returning the short URL:
|
||||
|
||||
- For each given long URL, we could create a simple HTML page with URL redirect tag in the head and add that to S3 bucket. The S3 bucket hosting the website would open the page which automatically redirects.
|
||||
- Or we could have a web app running in EC2 with the algorithm that would do the encode/decoding. This would compute a unique hash (e.g. MD5) of the given long URL, and the hash then could be encoded to base36 or even base64 (depending on how we want the final short URL to look).
|
||||
|
||||
|
||||
In any of the cases above, since we don’t need to worry about scalability, we would keep our data in a small key-value database. We could either use:
|
||||
|
||||
- a RDBMS, which provides acid properties. We would have two tables: one for storing information about the URL mappings and other for the user info.
|
||||
- a smaller key-value database such as AWS SimpleDB, or depending on the cases, an in-memory key-value database such as Redis or Memcached. The short URL is the key, the long URL (and any metadata such as date/time), the value.
|
||||
|
||||
|
||||
The API should have the following methods:
|
||||
|
||||
- **add new URL**:
|
||||
- get long link URL
|
||||
- encode the link URL to a small URL
|
||||
- saves both URLs and metadata in a key-value database
|
||||
- (optional) save or log request information (for analytics)
|
||||
- (optional) add an expiration date for the URLs
|
||||
|
||||
- **retrieve long URL, given the short URL**
|
||||
- do a database lookup with the short URL:
|
||||
- if the URL exists in the database, returns the long URL
|
||||
- (optional) log or saves the request information (analytics)
|
||||
- if URL does not exist (or expired) return error
|
||||
|
||||
- **delete URL entry**
|
||||
- do a database lookup with the shorter URL:
|
||||
- if the URL exists in the database, delete the entry
|
||||
- if URL does not exist (or expired) return error
|
||||
|
||||
|
||||
|
||||
## Handling 10 million URLs
|
||||
|
||||
|
||||
Now we are concerned about things such as high availability and avoiding URL collisions. Additionally, we might be interested in profit $$, so gathering and processing analytics/metadata/request info etc. is a must.
|
||||
|
||||
If we are still using the EC2 or S3 architecture, we might horizontally scale it with a proxy or a pool of hosts (and manager of a pool of hosts). ZooKeeper is a distributed coordination service to manage a large set of hosts and offers key-value store. We could use AWS Elastic Load Balancing as well if we opted for EC2 instances.
|
||||
|
||||
We would like to have a more scalable key/value database, such as AWS DynamoDB.
|
||||
|
||||
We could have a cache (such as Redis or Memcached) with the most popular URLs, for quick retrieval (and we would take extra care with expiration).
|
||||
|
||||
However, in nowadays applications, the architecture lined above is becoming obsolete. Best options have distinct asynchronous and synchronous pipelines.
|
||||
|
||||
For synchronous requests, we can use microservices in containers (and orchestrated by Kubernetes).
|
||||
|
||||
For asynchronous requests, depending on the volume of requests, we could either couple microservices with a distributed streaming platforms (such as Kafka), or create a pipeline with a queue (such as AWS SQS) and serverless functions (such as AWS Lambda), with the database.
|
||||
|
||||
#### Microservices for URL conversion (synchronous)
|
||||
|
||||
The URL conversion should be as faster as possible. We could process the information into backend microservices running into (Docker) containers in a Kubernetes cluster (such as AWS EK). This is ideal for:
|
||||
- the URL shortener algorithm
|
||||
- a Spam checker algorithm
|
||||
|
||||
Vertical scaling is straightforward as well (in Kubernetes, we could scale by just adding more pods).
|
||||
|
||||
|
||||
#### Distributed streaming platform and Event-driven serverless computing for Event Analytics (asynchronous)
|
||||
|
||||
Whenever a link is shortened, we can send a message in a topic in a distributed streaming platform, such as Kafka or AWS Kinesis. Consumers would pick up the data and save into a database, or process the information into backend services running either in event-driven serverless computing platforms (such as AWS Lambda functions) or into microservices. This is ideal for:
|
||||
|
||||
- Data analysis, process, machine learning
|
||||
- Elasticsearch, log process
|
||||
- extra-availability services, such as writing the short and long URLs into a S3 (say into HDFS format), and then connect a local Memcached instance to cache S3 lookups
|
||||
|
BIN
emotive.io/Emotive Take Home 48 Hour Test.pdf
Normal file
BIN
emotive.io/Emotive Take Home 48 Hour Test.pdf
Normal file
Binary file not shown.
40
emotive.io/README.md
Normal file
40
emotive.io/README.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
# emotive.io take home test
|
||||
|
||||
(Private) Repository with Emotive.io take home Project.
|
||||
|
||||
## Ex 1:
|
||||
|
||||
Run:
|
||||
|
||||
```
|
||||
python3 1.py
|
||||
```
|
||||
|
||||
Assertions should pass so nothing should be sent to STDOUT.
|
||||
|
||||
|
||||
## Ex 2:
|
||||
|
||||
Create a virtual env and install dependencies:
|
||||
|
||||
```
|
||||
virtualenv venv
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```
|
||||
python3 2.py
|
||||
```
|
||||
|
||||
Should print:
|
||||
|
||||
```
|
||||
0
|
||||
1
|
||||
```
|
||||
|
||||
## Ex 3:
|
||||
|
||||
Find solution in `3.md`.
|
7
emotive.io/input_for_2.txt
Normal file
7
emotive.io/input_for_2.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
6
|
||||
1, 114
|
||||
1, 115
|
||||
1, 116
|
||||
2, 115
|
||||
3, 2
|
||||
3, 1
|
1
emotive.io/requirements.txt
Normal file
1
emotive.io/requirements.txt
Normal file
|
@ -0,0 +1 @@
|
|||
pymongo==3.9.0
|
121
medium/posts.py
Normal file
121
medium/posts.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
import json
|
||||
|
||||
|
||||
def solution(postContentString, deltasString):
|
||||
postContent = json.loads(postContentString)
|
||||
deltas = json.loads(deltasString)
|
||||
|
||||
# -------------------------------------
|
||||
# Loop to each delta action from deltas.
|
||||
# -------------------------------------
|
||||
for delta in deltas:
|
||||
|
||||
# ----------------------
|
||||
# Get delta action data.
|
||||
# ----------------------
|
||||
try:
|
||||
delta_type = delta['type']
|
||||
delta_pindex = delta['paragraphIndex']
|
||||
|
||||
# ---------------------------------------
|
||||
# Check if delta paragraph index is valid.
|
||||
# ---------------------------------------
|
||||
if delta_pindex < 0 or delta_pindex >= len(postContent['paragraphs']):
|
||||
continue
|
||||
|
||||
except (KeyError, IndexError) as e:
|
||||
continue
|
||||
|
||||
# ------------------------------
|
||||
# Execute update paragraph delta.
|
||||
# ------------------------------
|
||||
if delta_type == 'updateParagraph':
|
||||
try:
|
||||
delta_paragraph_text = delta['paragraph']['text']
|
||||
postContent['paragraphs'][delta_pindex]['text'] = delta_paragraph_text
|
||||
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
# ----------------------------
|
||||
# Execute add paragraph delta.
|
||||
# ----------------------------
|
||||
elif delta_type == 'addParagraph':
|
||||
try:
|
||||
delta_paragraph = delta['paragraph']
|
||||
postContent['paragraphs'].insert(delta_pindex, delta_paragraph)
|
||||
|
||||
# ------------------
|
||||
# Fix section index.
|
||||
# ------------------
|
||||
i = delta_pindex
|
||||
while i < len(postContent['sections'][delta_pindex:]) + 1:
|
||||
postContent['sections'][i]['startIndex'] += 1
|
||||
i += 1
|
||||
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
# -------------------------------
|
||||
# Execute delete paragraph delta.
|
||||
# -------------------------------
|
||||
elif delta_type == 'deleteParagraph':
|
||||
try:
|
||||
postContent['paragraphs'].pop(delta_pindex)
|
||||
|
||||
# ------------------
|
||||
# Fix section index.
|
||||
# ------------------
|
||||
|
||||
# Fix section indexes after removal.
|
||||
i = delta_pindex
|
||||
while i < len(postContent['sections'][delta_pindex:]) + 1:
|
||||
postContent['sections'][i]['startIndex'] -= 1
|
||||
i += 1
|
||||
|
||||
# Remove any empty section.
|
||||
i = 1
|
||||
for i, section in enumerate(postContent['sections']):
|
||||
if section['startIndex'] <= 0:
|
||||
postContent['sections'].pop(i)
|
||||
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
# ------------------
|
||||
# Print out results.
|
||||
# ------------------
|
||||
try:
|
||||
# -------------------------------------------
|
||||
# Extract sections and paragraph, after delta.
|
||||
# -------------------------------------------
|
||||
sections = postContent['sections']
|
||||
paragraphs = postContent['paragraphs']
|
||||
|
||||
# ------------------------
|
||||
# Find all section indexes.
|
||||
# ------------------------
|
||||
section_indexes = set()
|
||||
for section in sections:
|
||||
section_indexes.add(section['startIndex'])
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Loop over paragraphs to create the resulting string.
|
||||
# ---------------------------------------------------
|
||||
res = []
|
||||
|
||||
for i, paragraph in enumerate(paragraphs, start=1):
|
||||
|
||||
if i == len(paragraphs):
|
||||
res.append(f'{paragraph["text"]}')
|
||||
|
||||
elif i in section_indexes:
|
||||
res.append(f'{paragraph["text"]}\n-\n')
|
||||
|
||||
else:
|
||||
res.append(f'{paragraph["text"]}\n')
|
||||
|
||||
except (KeyError, ValueError) as e:
|
||||
print(f'Error: Input data is ill-formatted: {e}')
|
||||
|
||||
return ''.join(res)
|
104
netflix/.gitignore
vendored
Normal file
104
netflix/.gitignore
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
4
netflix/Makefile
Normal file
4
netflix/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
run:
|
||||
python3 find_glob_boundary.py
|
||||
|
||||
.PHONY: run
|
103
netflix/find_glob_boundary.py
Normal file
103
netflix/find_glob_boundary.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
|
||||
def _find_left_boundary(glob):
|
||||
"""
|
||||
Given a glob, search for left boundary, returning a tuple with
|
||||
boundary values or None if boundary was not found.
|
||||
"""
|
||||
row, ncells = 0, 0
|
||||
|
||||
while row < len(glob):
|
||||
col = 0
|
||||
|
||||
while col < len(glob[row]):
|
||||
ncells = ncells + 1
|
||||
if glob[row][col] == 1:
|
||||
return (row, col), ncells
|
||||
else:
|
||||
col = col + 1
|
||||
row = row + 1
|
||||
|
||||
return None, ncells
|
||||
|
||||
|
||||
def _find_right_boundary(glob):
|
||||
"""
|
||||
Given a glob, search for right boundary, returning a tuple with
|
||||
boundary values or None if boundary was not found.
|
||||
"""
|
||||
row, ncells = len(glob) - 1, 0
|
||||
|
||||
while row > 0:
|
||||
col = len(glob[row]) - 1
|
||||
|
||||
while col > 0:
|
||||
ncells = ncells + 1
|
||||
if glob[row][col] == 1:
|
||||
return (row, col), ncells
|
||||
else:
|
||||
col = col - 1
|
||||
|
||||
row = row - 1
|
||||
|
||||
return None, ncells
|
||||
|
||||
|
||||
def _print_results(n_cells, top_left, bottom_right):
|
||||
"""
|
||||
Print results in the desired format.
|
||||
"""
|
||||
print("Cell Reads : {}".format(n_cells))
|
||||
print("Boundary:")
|
||||
print(" Top Left: {}".format(top_left))
|
||||
print(" Bottom Right: {}".format(bottom_right))
|
||||
|
||||
|
||||
def main(glob):
|
||||
"""
|
||||
Grab a NxN glob and print out the results.
|
||||
"""
|
||||
print("\nTesting {}".format(glob))
|
||||
|
||||
if len(glob[0]) != len(glob):
|
||||
print("Matrix needs to be NxN.")
|
||||
|
||||
else:
|
||||
top_left, nleft = _find_left_boundary(glob)
|
||||
if top_left:
|
||||
bottom_right, nright = _find_right_boundary(glob)
|
||||
if not bottom_right:
|
||||
bottom_right = top_left
|
||||
|
||||
_print_results(nleft + nright, top_left, bottom_right)
|
||||
|
||||
else:
|
||||
print("Could not find left boundary, maybe there is no 1s in your glob?")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
globs = []
|
||||
|
||||
globs.append([[0, 1]])
|
||||
globs.append([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
|
||||
globs.append([[0, 0], [0,0]])
|
||||
globs.append([[1, 0, 0], [0, 0, 0], [0, 0, 0]])
|
||||
globs.append([[0, 1, 0], [0, 0, 0], [0, 0, 0]])
|
||||
globs.append([[0, 1, 1], [0, 1, 1], [0, 0, 0]])
|
||||
|
||||
for glob in globs:
|
||||
main(glob)
|
||||
|
||||
|
BIN
netflix/problem.pdf
Normal file
BIN
netflix/problem.pdf
Normal file
Binary file not shown.
BIN
soundcloud/.DS_Store
vendored
Normal file
BIN
soundcloud/.DS_Store
vendored
Normal file
Binary file not shown.
0
soundcloud/.gitkeep
Normal file
0
soundcloud/.gitkeep
Normal file
3
soundcloud/solution/.gitignore
vendored
Normal file
3
soundcloud/solution/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.python-version
|
||||
__pycache__
|
||||
*.pyc
|
50
soundcloud/solution/README.md
Normal file
50
soundcloud/solution/README.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
## SoundCloud backend engineering code review interview exercise
|
||||
|
||||
### Introduction
|
||||
|
||||
Welcome to the SoundCloud backend Pull Request review challenge!
|
||||
|
||||
This repository contains a simulation of a microservice. The service is
|
||||
intentionally simplistic. The code for the service is python, but only using
|
||||
basic aspects of the language.
|
||||
We intend this to read like pseudocode - and in particular
|
||||
_we do not judge your submission based on your knowledge of Python_. Exposure to
|
||||
any popular programming language(s) should be enough to effectively work this exercise.
|
||||
|
||||
You will be reviewing Pull Request #1 (present under the "Pull Requests" tab).
|
||||
It's meant to be a simplified simulation of the sort of code review you might
|
||||
encounter in your everyday work.
|
||||
|
||||
> Also see: [a quick introduction to GitHub pull requests](https://help.github.com/en/articles/about-pull-request-reviews#about-pull-request-reviews).
|
||||
|
||||
## Your challenge
|
||||
|
||||
The code should be robust and reasonably feature-complete.
|
||||
We only want to merge this Pull Request once you and your (imaginary) colleague are confident that:
|
||||
|
||||
- The implementation actually supports the requested feature.
|
||||
- The implementation is likely to work, including proper handling of
|
||||
easily-foreseeable corner cases.
|
||||
- The implementation is not easy for a mailcious user to attack.
|
||||
- Other engineers can productively and confidently change this code, meaning:
|
||||
- The code is clear and straightforward to reason through.
|
||||
- It has high-quality unit tests and good test coverage.
|
||||
- It's well-organized and code elements have intention-revealing names.
|
||||
- There are comments, where those are useful.
|
||||
|
||||
In short, we are interested in gaining some insight into what is important to
|
||||
you when you review code. We'll be concentrating especially on what you feel
|
||||
is important to change, and the way you communicate that. Please provide change
|
||||
requests, ask questions, and otherwise make comments as you normally would when
|
||||
reviewing code.
|
||||
|
||||
The only GitHub review features that matter in this exercise are
|
||||
commenting-related: commenting on the whole Pull Request, and comments targeted at specific
|
||||
areas of the code. If you prefer to make use of Github's markdown features
|
||||
in your comments, please feel free to do so - but if you haven't used these features
|
||||
before, please do not use them.
|
||||
|
||||
As a general guideline, this exercise should take about 30-60 minutes. You are free
|
||||
to take more (or less) time than that, however.
|
||||
|
||||
When you're done, please notify your SoundCloud recruiter contact via email.
|
49
soundcloud/solution/src/clients/product_detail_client.py
Normal file
49
soundcloud/solution/src/clients/product_detail_client.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
"""
|
||||
Interview note:
|
||||
This is a client "stub", here for demonstration and documentation
|
||||
purposes only.
|
||||
|
||||
In a real production environment, this client would send requests over
|
||||
the network to a product detail backend service.
|
||||
|
||||
The product detail backend service is responsible for retrieving product
|
||||
detail records from its datastore (e.g., a mysql database), and
|
||||
returning them to callers.
|
||||
"""
|
||||
class ProductDetailClient:
|
||||
|
||||
def lookup(product_token):
|
||||
"""
|
||||
Given a product token, makes a blocking call to the lookup endpoint of
|
||||
the product detail backend service,
|
||||
and returns a corresponding product detail record, if found.
|
||||
If no record is found, returns None.
|
||||
|
||||
Example result:
|
||||
|
||||
{"token": "AAA",
|
||||
"description": "Red Bike",
|
||||
"price_cents": 45000}
|
||||
"""
|
||||
pass
|
||||
|
||||
def batch_lookup(product_tokens):
|
||||
"""
|
||||
Given a list of product tokens, makes a blocking call to the
|
||||
batch lookup endpoint of the product detail backend service,
|
||||
and returns a map of product token to product detail record, for all
|
||||
that are found.
|
||||
Any records that cannot be found are ommitted from the result.
|
||||
|
||||
Example result:
|
||||
|
||||
{"AAA": {
|
||||
"token": "AAA",
|
||||
"description": "Red Bike",
|
||||
"price_cents": 45000},
|
||||
"BBB": {
|
||||
"token": "BBB",
|
||||
"description": "Blue Bike",
|
||||
"price_cents": 37500}}
|
||||
"""
|
||||
pass
|
44
soundcloud/solution/src/clients/product_search_client.py
Normal file
44
soundcloud/solution/src/clients/product_search_client.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
Interview note:
|
||||
This is a client "stub", here for demonstration and documentation
|
||||
purposes only.
|
||||
|
||||
In a real production environment, this client would send requests over
|
||||
the network to a product search backend service.
|
||||
|
||||
The product search backend service is responsible for performing lookups
|
||||
against a product search index (e.g., elasticsearch) and returning product
|
||||
tokens that are search result "hits".
|
||||
"""
|
||||
class ProductSearchClient:
|
||||
|
||||
def search_for_all_in_price_range(lower_cents, upper_cents, start_record, num_records):
|
||||
"""
|
||||
Given a price range plus pagination information,
|
||||
returns a list of product tokens of products
|
||||
whose price falls in the range.
|
||||
|
||||
The price range is inclusive - that is,
|
||||
a given price "p" matches if:
|
||||
|
||||
upper_cents >= p >= lower_cents
|
||||
|
||||
If no products have prices in the price range,
|
||||
returns an empty list.
|
||||
|
||||
Product tokens are ordered by price, from lowest to highest.
|
||||
|
||||
Out of the total list of possible product tokens,
|
||||
only the "page" of product tokens starting at the list position
|
||||
start_record, and ending at start_record+num_records, are returned.
|
||||
|
||||
If start_record+num_records is greater than the number of actual
|
||||
result records, the resulting list size will be (accordingly)
|
||||
smaller than num_records.
|
||||
|
||||
Example result:
|
||||
|
||||
["AAA",
|
||||
"BBB"]
|
||||
"""
|
||||
pass
|
43
soundcloud/solution/src/service.py
Normal file
43
soundcloud/solution/src/service.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import re
|
||||
|
||||
TOKEN_REGEX = re.compile("^[0-9A-F-]+$")
|
||||
|
||||
class Service:
|
||||
def __init__(self, product_detail_client, product_search_client):
|
||||
self.product_detail_client = product_detail_client
|
||||
self.product_search_client = product_search_client
|
||||
|
||||
def lookup(self, product_token):
|
||||
"""
|
||||
Given a valid product token,
|
||||
lookup product detail information from the product detail backend service.
|
||||
|
||||
- Results in Bad Request if the token is invalid
|
||||
- Results in Not Found if the product is unknown to the backend service
|
||||
"""
|
||||
if not TOKEN_REGEX.match(product_token):
|
||||
return {"status": 400}
|
||||
|
||||
lookup_result = self.product_detail_client.lookup(product_token)
|
||||
if lookup_result:
|
||||
return {"status": 200, "product": lookup_result}
|
||||
else:
|
||||
return {"status": 404}
|
||||
|
||||
def search_by_price_range(self, lower_cents, upper_cents, start_record, num_records):
|
||||
page_of_product_tokens = \
|
||||
self.product_search_client.search_for_all_in_price_range(
|
||||
lower_cents,
|
||||
upper_cents,
|
||||
start_record,
|
||||
num_records)
|
||||
|
||||
product_detail_results = []
|
||||
for product_token in page_of_product_tokens:
|
||||
product_detail_results.append(self.lookup(product_token)["product"])
|
||||
|
||||
return {"status": 200, "products": product_detail_results}
|
||||
|
||||
if __name__ == "__main__":
|
||||
a = Service()
|
||||
print(a.lookup('aaa'))
|
95
soundcloud/solution/test/service_test.py
Normal file
95
soundcloud/solution/test/service_test.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../src"))
|
||||
|
||||
from service import Service
|
||||
|
||||
|
||||
class ProductDetailClientTestDouble:
|
||||
def __init__(self):
|
||||
self.contents = {}
|
||||
|
||||
def lookup(self, product_token):
|
||||
if product_token in self.contents:
|
||||
return self.contents[product_token]
|
||||
else:
|
||||
return None
|
||||
|
||||
class ProductSearchClientTestDouble:
|
||||
def __init__(self):
|
||||
self.search_to_results = {}
|
||||
|
||||
def search_for_all_in_price_range(self, lower_cents, upper_cents, start_record, num_records):
|
||||
return self.search_to_results[(lower_cents, upper_cents, start_record, num_records)]
|
||||
|
||||
class ServiceTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.detail_client = ProductDetailClientTestDouble()
|
||||
self.search_client = ProductSearchClientTestDouble()
|
||||
self.service = Service(self.detail_client, self.search_client)
|
||||
|
||||
def test_lookup_success(self):
|
||||
expected_product = {
|
||||
"token": "AAA",
|
||||
"description": "Red Bike",
|
||||
"price_cents": 45000
|
||||
}
|
||||
|
||||
self.detail_client.contents["AAA"] = expected_product
|
||||
|
||||
self.assertEqual(
|
||||
self.service.lookup("AAA"),
|
||||
{"status": 200,
|
||||
"product": expected_product})
|
||||
|
||||
def test_lookup_not_found(self):
|
||||
self.assertEqual(
|
||||
self.service.lookup("AAA"),
|
||||
{"status": 404})
|
||||
|
||||
def test_lookup_bad_input(self):
|
||||
self.assertEqual(
|
||||
self.service.lookup("this-is-not-a-uuid"),
|
||||
{"status": 400})
|
||||
|
||||
def test_search_price_range(self):
|
||||
brown_bike = {
|
||||
"token": "AAA",
|
||||
"description": "Brown Bike",
|
||||
"price_cents": 10000
|
||||
}
|
||||
|
||||
blue_bike = {
|
||||
"token": "BBB",
|
||||
"description": "Blue Bike",
|
||||
"price_cents": 37500
|
||||
}
|
||||
|
||||
red_bike = {
|
||||
"token": "CCC",
|
||||
"description": "Red Bike",
|
||||
"price_cents": 45000
|
||||
}
|
||||
|
||||
gray_bike = {
|
||||
"token": "DDD",
|
||||
"description": "Gray Bike",
|
||||
"price_cents": 99900
|
||||
}
|
||||
|
||||
self.detail_client.contents["AAA"] = brown_bike
|
||||
self.detail_client.contents["BBB"] = blue_bike
|
||||
self.detail_client.contents["CCC"] = red_bike
|
||||
self.detail_client.contents["DDD"] = gray_bike
|
||||
|
||||
self.search_client.search_to_results[(30000, 50000, 0, 10)] = ["BBB", "CCC"]
|
||||
|
||||
self.assertEqual(
|
||||
self.service.search_by_price_range(30000, 50000, 0, 10),
|
||||
{"status": 200,
|
||||
"products": [blue_bike, red_bike]})
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
BIN
soundcloud/soundcloud_problem_statement/.DS_Store
vendored
Normal file
BIN
soundcloud/soundcloud_problem_statement/.DS_Store
vendored
Normal file
Binary file not shown.
218
soundcloud/soundcloud_problem_statement/INSTRUCTIONS.md
Normal file
218
soundcloud/soundcloud_problem_statement/INSTRUCTIONS.md
Normal file
|
@ -0,0 +1,218 @@
|
|||
# Follower Maze Code Challenge Instructions
|
||||
|
||||
Thank you for applying for a backend engineering position at SoundCloud.
|
||||
We ask engineering candidates to work and complete this code challenge.
|
||||
Your submission is part of the basis upon which we evaluate your candidacy.
|
||||
|
||||
We hope it’s indeed challenging (and maybe even fun).
|
||||
|
||||
The challenge has two parts:
|
||||
1. **Refactor** an existing client-server application and provide a README explaining your approach
|
||||
1. **Extend** the server application with a small feature request
|
||||
|
||||
Our aim with these instructions is to leave no doubt about the details of the challenge and the expectations of
|
||||
your submission - however, if you have questions of any sort _please ask_.
|
||||
Also: please completely read the instructions before starting!
|
||||
|
||||
## Time Expectations
|
||||
|
||||
Based on internal testing, we roughly estimate the full Follower-Maze challenge will take
|
||||
somewhere around 2-7 hours of your time.
|
||||
|
||||
* Our goal for this time budget is to keep the time investment predictable for candidates -
|
||||
it is not intended to create significant “time pressure”.
|
||||
* The length of time you take to submit your work is not a factor in how we review it.
|
||||
* Many candidates will work the challenge and submit it back within a day or two.
|
||||
Or some might submit it a week later, depending on personal circumstances.
|
||||
|
||||
## Discretion
|
||||
In order to ensure continued fairness to all past and future participants,
|
||||
please do not share any of the details of this code challenge with others,
|
||||
including the work you submit.
|
||||
|
||||
## Anonymity
|
||||
Code challenge submissions are treated as a form of [Blind Audition](https://en.wikipedia.org/wiki/Blind_audition):
|
||||
reviewers don’t have access to a candidate’s personal information,
|
||||
including name and background.
|
||||
Please help us by keeping your submission free of information
|
||||
that would be revealing of who you are, or any personal attributes.
|
||||
|
||||
## Part 1: Follower Maze - Refactor
|
||||
|
||||
### Overview
|
||||
|
||||
Follower-Maze is a **client-server application** built for social networking.
|
||||
Users follow other users, post messages, send private messages, unfollow users, and so on.
|
||||
* Each of these actions is an _event_, created by an _event-source_ and sent to the _server_.
|
||||
* Each event is processed by the _server_.
|
||||
* The processing of the event may have follow-on effects on other _user-clients_.
|
||||
|
||||
The **server** listens for two kinds of **clients**:
|
||||
* **1 event source**, which emits events.
|
||||
* **N user-clients** that connect to the server.
|
||||
|
||||
The server routes certain events to certain clients, according to the social networking domain rules.
|
||||
Please see the [Specification](#specification) section for more detail.
|
||||
|
||||
### Running the End-to-End Tester
|
||||
|
||||
Please see the repository [README](README.md) for instructions on how to run client/server implementations,
|
||||
and the end-to-end tester.
|
||||
|
||||
|
||||
You have the choice of one of six programming language implementations:
|
||||
please choose the language you’re most comfortable in,
|
||||
and try running the client/server implementation for that language,
|
||||
and then run the end-to-end tester to successful completion.
|
||||
|
||||
### Specification
|
||||
|
||||
This specification is just for your reference.
|
||||
The provided implementations are based on this specification.
|
||||
|
||||
|
||||
#### Client/Server Protocol
|
||||
|
||||
* The protocol is line-based i.e. a LF control character terminates each message.
|
||||
All strings are encoded in UTF-8.
|
||||
* Data is transmitted over TCP sockets.
|
||||
* The _event source_ connects on port 9090 and will start sending events as soon as the connection is accepted.
|
||||
* The _user clients_ connect on port 9099 and identify themselves with their user ID.
|
||||
For example, once connected a _user client_ may send down `2932\r\n`,
|
||||
indicating that it is representing user 2932.
|
||||
* After the identification is sent,
|
||||
the user client starts waiting for events to be sent to them by the server.
|
||||
|
||||
#### Events
|
||||
|
||||
There are five possible events.
|
||||
The table below describes payloads sent by the event source and what each represents:
|
||||
|
||||
```
|
||||
| Payload | Sequence # | Type | From User Id | To User Id |
|
||||
|---------------|------------|---------------|--------------|------------|
|
||||
| 666|F|60|50 | 666 | Follow | 60 | 50 |
|
||||
| 1|U|12|9 | 1 | Unfollow | 12 | 9 |
|
||||
| 542532|B | 542532 | Broadcast | - | - |
|
||||
| 43|P|32|56 | 43 | Private Msg | 32 | 56 |
|
||||
| 634|S|32 | 634 | Status Update | 32 | - |
|
||||
```
|
||||
|
||||
#### Event Rules
|
||||
|
||||
* Each message begins with a sequence number.
|
||||
* However, the event source _does not send events in any given order_.
|
||||
In particular, sequence number has no effect on the order in which events are sent.
|
||||
* Events _may_ generate notifications for _user clients_.
|
||||
If there is a respective target _user client_ connected,
|
||||
the notification is routed according to the following rules:
|
||||
* Follow: Only the `To User Id` is notified
|
||||
* A follow event from user A to user B means that user A now follows user B.
|
||||
* Unfollow: No clients are notified
|
||||
* An unfollow event from user A to user B means that user A stopped following user B.
|
||||
* Broadcast: All connected _user clients_ are notified
|
||||
* Private Message: Only the `To User Id` is notified
|
||||
* Status Update: All current followers of the `From User ID` are notified
|
||||
* If there are no _user clients_ connected for a user,
|
||||
any notifications for them are currently ignored.
|
||||
_User clients_ are notified of events _in the correct sequence-number order_,
|
||||
regardless of the order in which the _event source_ sent them.
|
||||
|
||||
### Challenge
|
||||
We have provided the client and server code - this is your starting point.
|
||||
However, the code is in poor shape:
|
||||
it’s condensed into giant blocks of code in a single file,
|
||||
making it hard for you to understand, debug, and test.
|
||||
**Your task is to refactor the code so that it’s _easy to test,
|
||||
easy to understand and easy to maintain._**
|
||||
|
||||
### Guidance
|
||||
|
||||
**Time budget:** We roughly estimate this portion will take between 2-6 hours.
|
||||
|
||||
The **primary goals** of this exercise are:
|
||||
* To provide structure and clarity to the server code base by introducing appropriate abstractions.
|
||||
* To produce code that accommodates change and is easy to test.
|
||||
* To maintain correctness of the server program.
|
||||
* To document your approach.
|
||||
|
||||
Some **non-goals**:
|
||||
* To introduce functionality that was not asked for.
|
||||
* To make the solution production-ready.
|
||||
* **HOWEVER** we ARE interested in what top improvements you think need to be made
|
||||
to make it production-ready (see README details below).
|
||||
* To write unit tests; your focus should be on writing testable code first,
|
||||
and while you're free to write unit tests with this part of the exercise,
|
||||
it is not a stated requirement here.
|
||||
|
||||
## Part 2: Follower Maze - Extension
|
||||
|
||||
### The Problem
|
||||
|
||||
In Part 1, we asked you to refactor an existing solution to the follower-maze problem.
|
||||
The original specification of the problem leaves the question of bad event data unanswered,
|
||||
and it simplifies the case of undeliverable notifications in case of user disconnects.
|
||||
Your task is to **add a [Dead Letter Queue (DLQ)](https://en.wikipedia.org/wiki/Dead_letter_queue)
|
||||
to the server**, so that undeliverable or malformed messages aren't lost.
|
||||
|
||||
“Dead letters” build up and ultimately must be dealt with.
|
||||
For the purposes of this interview problem,
|
||||
it's OK to simply log them to the console.
|
||||
But please **include a short explanation in your README** of how you would store and process
|
||||
that data in a production scenario.
|
||||
|
||||
|
||||
### Guidance
|
||||
|
||||
**Time budget:** We roughly estimate this portion will take between 1-2 hours.
|
||||
|
||||
The **primary goals** of this exercise are:
|
||||
* To introduce a DLQ implementation
|
||||
* To capture messages that fall outside of the original specification of the protocol, for example:
|
||||
* Malformed messages
|
||||
* New message types not yet supported by the server
|
||||
* To capture messages that cannot be delivered because the target user is not connected
|
||||
* To write unit tests demonstrating the DLQ operates as specified
|
||||
* To explain your approach to handling “dead letters” in production, in your README
|
||||
|
||||
Some **non-goals**:
|
||||
|
||||
* To introduce functionality or improvements that were not asked for
|
||||
* To make the solution production-ready
|
||||
(but you're welcome to comment on that aspect in your README file)
|
||||
|
||||
## Constraints
|
||||
|
||||
* Pick one of the six programming languages,
|
||||
and base your solution on that language’s initial implementation.
|
||||
* Please restrict yourself to the standard library - don’t introduce 3rd party libraries.
|
||||
**Exception:** libraries that facilitate unit testing are acceptable.
|
||||
|
||||
## Deliverables
|
||||
|
||||
### Deliverables For Part 1
|
||||
|
||||
* Your refactored version of the code.
|
||||
* A README file detailing:
|
||||
* The approach you took and reasoning you applied to arrive at your final solution.
|
||||
* Explanation of any design trade-offs or short-cuts you may have taken.
|
||||
* How to run the server, in case you made changes to the setup we provided.
|
||||
* The top priorities you would have for additions or modifications to make the solution production-ready.
|
||||
|
||||
_Remember: unit tests are not required for Part 1_
|
||||
|
||||
### Deliverables For Part 2
|
||||
* A working implementation of a DLQ that handles messages falling into the categories specified above.
|
||||
* Unit tests demonstrating the newly added functionality is working as specified.
|
||||
* In your README:
|
||||
* The approach you took and reasoning you applied to arrive at your final solution;
|
||||
pay particular attention to design trade-offs or short-cuts you may have taken.
|
||||
* Explanation of what more you would do with DLQ messages in a production scenario
|
||||
(i.e. aside from simply logging them to the console).
|
||||
|
||||
## Tips
|
||||
* Run the end-to-end tester regularly as you change code.
|
||||
* We're looking for a simple, straight-forward, clean & concise solution.
|
||||
* If you have technical questions, please ask!
|
||||
|
||||
|
56
soundcloud/soundcloud_problem_statement/README.md
Normal file
56
soundcloud/soundcloud_problem_statement/README.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
**Please [read the instructions first](INSTRUCTIONS.md).**
|
||||
|
||||
|
||||
## Running the server
|
||||
|
||||
### go
|
||||
```
|
||||
cd go
|
||||
go run main.go
|
||||
```
|
||||
|
||||
### java
|
||||
```
|
||||
cd java
|
||||
gradle run
|
||||
```
|
||||
|
||||
### js
|
||||
```
|
||||
cd js
|
||||
node index.js
|
||||
```
|
||||
|
||||
### python
|
||||
Please use a modern version of python 3, managed via a tool like pyenv.
|
||||
```
|
||||
cd python
|
||||
python main.py
|
||||
```
|
||||
|
||||
### ruby
|
||||
Please use a modern version of cruby, managed via a tool like rvm.
|
||||
```
|
||||
cd ruby
|
||||
ruby main.rb
|
||||
```
|
||||
|
||||
### scala
|
||||
```
|
||||
cd scala
|
||||
sbt run
|
||||
```
|
||||
|
||||
## About the tester
|
||||
|
||||
The tester makes socket connections to the server:
|
||||
|
||||
- The event source connects on port 9090 and will start sending events as soon as the connection is accepted.
|
||||
- The user clients can connect on port 9099,
|
||||
and communicate with server following events specification and rules outlined in challenge instructions.
|
||||
|
||||
## Running the tester
|
||||
|
||||
From the project root, run:
|
||||
|
||||
`tester/run100k.sh`
|
3
soundcloud/soundcloud_problem_statement/go/go.mod
Normal file
3
soundcloud/soundcloud_problem_statement/go/go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module followermaze
|
||||
|
||||
go 1.13
|
187
soundcloud/soundcloud_problem_statement/go/main.go
Normal file
187
soundcloud/soundcloud_problem_statement/go/main.go
Normal file
|
@ -0,0 +1,187 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const eventPort = 9090
|
||||
const clientPort = 9099
|
||||
|
||||
func main() {
|
||||
clientPool := make(map[int]net.Conn)
|
||||
followRegistry := map[int]map[int]bool{}
|
||||
|
||||
seqNoToMessage := make(map[int][]string)
|
||||
|
||||
go func() {
|
||||
lastSeqNo := 0
|
||||
|
||||
eventListener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", eventPort))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer eventListener.Close()
|
||||
|
||||
fmt.Printf("Listening for events on %d\n", eventPort)
|
||||
|
||||
outer:
|
||||
for {
|
||||
conn, err := eventListener.Accept()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
for {
|
||||
payloadRaw, err := reader.ReadString('\n')
|
||||
|
||||
if err == io.EOF {
|
||||
conn.Close()
|
||||
continue outer
|
||||
|
||||
} else if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
payload := strings.TrimSpace(payloadRaw)
|
||||
|
||||
fmt.Printf("Message received: %s\n", payload)
|
||||
|
||||
payloadParts := strings.Split(payload, "|")
|
||||
|
||||
incomingSeqNo, err := strconv.Atoi(payloadParts[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
seqNoToMessage[incomingSeqNo] = payloadParts
|
||||
|
||||
for {
|
||||
|
||||
nextMessage, ok := seqNoToMessage[lastSeqNo+1]
|
||||
delete(seqNoToMessage, lastSeqNo+1)
|
||||
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
nextPayload := strings.Join(nextMessage, "|") + "\n"
|
||||
kind := strings.TrimSpace(nextMessage[1])
|
||||
|
||||
switch kind {
|
||||
case "F":
|
||||
fromUserID, err := strconv.Atoi(nextMessage[2])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
toUserID, err := strconv.Atoi(nextMessage[3])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if _, ok := followRegistry[toUserID]; !ok {
|
||||
followRegistry[toUserID] = make(map[int]bool)
|
||||
}
|
||||
|
||||
followers, _ := followRegistry[toUserID]
|
||||
followers[fromUserID] = true
|
||||
|
||||
clientConn, ok := clientPool[toUserID]
|
||||
if ok {
|
||||
fmt.Fprint(clientConn, nextPayload)
|
||||
}
|
||||
|
||||
case "U":
|
||||
fromUserID, err := strconv.Atoi(nextMessage[2])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
toUserID, err := strconv.Atoi(nextMessage[3])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if followers, ok := followRegistry[toUserID]; ok {
|
||||
delete(followers, fromUserID)
|
||||
}
|
||||
|
||||
case "P":
|
||||
toUserID, err := strconv.Atoi(nextMessage[3])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if clientConn, ok := clientPool[toUserID]; ok {
|
||||
fmt.Fprint(clientConn, nextPayload)
|
||||
}
|
||||
|
||||
case "B":
|
||||
for _, clientConn := range clientPool {
|
||||
fmt.Fprint(clientConn, nextPayload)
|
||||
}
|
||||
|
||||
case "S":
|
||||
fromUserID, err := strconv.Atoi(nextMessage[2])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if followers, ok := followRegistry[fromUserID]; ok {
|
||||
for follower := range followers {
|
||||
clientConn, ok := clientPool[follower]
|
||||
if ok {
|
||||
fmt.Fprint(clientConn, nextPayload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSeqNo = lastSeqNo + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
eventListener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", clientPort))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer eventListener.Close()
|
||||
|
||||
fmt.Printf("Listening for client requests on %d\n", clientPort)
|
||||
|
||||
for {
|
||||
conn, err := eventListener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
userIDRaw, err := reader.ReadString('\n')
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
userIDStr := strings.TrimSpace(userIDRaw)
|
||||
|
||||
userID, err := strconv.Atoi(userIDStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
clientPool[userID] = conn
|
||||
|
||||
fmt.Printf("User connected: %d (%d total)\n", userID, len(clientPool))
|
||||
}
|
||||
}
|
26
soundcloud/soundcloud_problem_statement/java/build.gradle
Normal file
26
soundcloud/soundcloud_problem_statement/java/build.gradle
Normal file
|
@ -0,0 +1,26 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName = "com.soundcloud.maze.Main"
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile "junit:junit:4.12"
|
||||
testCompile "org.assertj:assertj-core:1.7.1"
|
||||
}
|
84
soundcloud/soundcloud_problem_statement/java/gradlew.bat
vendored
Normal file
84
soundcloud/soundcloud_problem_statement/java/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1,153 @@
|
|||
package com.soundcloud.maze;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
public class Main {
|
||||
|
||||
private final static int EVENT_PORT = 9090;
|
||||
private final static int CLIENT_PORT = 9099;
|
||||
|
||||
private static long lastSeqNo = 0L;
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Map<Long, Socket> clientPool = new ConcurrentHashMap<>();
|
||||
Map<Long, List<String>> seqNoToMessage = new HashMap<>();
|
||||
|
||||
Map<Long, Set<Long>> followRegistry = new HashMap<>();
|
||||
|
||||
new Thread(() -> {
|
||||
System.out.println("Listening for events on " + EVENT_PORT);
|
||||
try (Socket eventSocket = new ServerSocket(EVENT_PORT).accept()) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(eventSocket.getInputStream()))) {
|
||||
reader.lines().forEach(payload -> {
|
||||
System.out.println("Message received: " + payload);
|
||||
|
||||
List<String> payloadParts = Arrays.asList(payload.split("\\|"));
|
||||
seqNoToMessage.put(Long.parseLong(payloadParts.get(0)), payloadParts);
|
||||
|
||||
while (seqNoToMessage.containsKey(lastSeqNo + 1)) {
|
||||
List<String> nextMessage = seqNoToMessage.get(lastSeqNo + 1);
|
||||
String nextPayload = String.join("|", nextMessage);
|
||||
|
||||
long seqNo = Long.parseLong(nextMessage.get(0));
|
||||
String kind = nextMessage.get(1);
|
||||
|
||||
switch (kind) {
|
||||
case "F": {
|
||||
long fromUserId = Long.parseLong(nextMessage.get(2));
|
||||
long toUserId = Long.parseLong(nextMessage.get(3));
|
||||
|
||||
Set<Long> followers = followRegistry.getOrDefault(toUserId, new HashSet<>());
|
||||
followers.add(fromUserId);
|
||||
followRegistry.put(toUserId, followers);
|
||||
|
||||
try {
|
||||
Socket socket = clientPool.get(toUserId);
|
||||
if (socket != null) {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||
writer.write(nextPayload + "\n");
|
||||
writer.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "U": {
|
||||
long fromUserId = Long.parseLong(nextMessage.get(2));
|
||||
long toUserId = Long.parseLong(nextMessage.get(3));
|
||||
|
||||
Set<Long> followers = followRegistry.getOrDefault(toUserId, new HashSet<>());
|
||||
followers.remove(fromUserId);
|
||||
followRegistry.put(toUserId, followers);
|
||||
}
|
||||
break;
|
||||
|
||||
case "P": {
|
||||
long toUserId = Long.parseLong(nextMessage.get(3));
|
||||
|
||||
try {
|
||||
Socket socket = clientPool.get(toUserId);
|
||||
if (socket != null) {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||
writer.write(nextPayload + "\n");
|
||||
writer.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "B": {
|
||||
clientPool.values().forEach(socket -> {
|
||||
try {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||
writer.write(nextPayload + "\n");
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "S": {
|
||||
long fromUserId = Long.parseLong(nextMessage.get(2));
|
||||
|
||||
Set<Long> followers = followRegistry.getOrDefault(fromUserId, emptySet());
|
||||
|
||||
followers.forEach(follower -> {
|
||||
try {
|
||||
Socket socket = clientPool.get(follower);
|
||||
if (socket != null) {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||
writer.write(nextPayload + "\n");
|
||||
writer.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
lastSeqNo = seqNo;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
new Thread(() -> {
|
||||
System.out.println("Listening for client requests on " + CLIENT_PORT);
|
||||
try {
|
||||
ServerSocket serverSocket = new ServerSocket(CLIENT_PORT);
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
while (clientSocket != null) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
||||
String userId = reader.readLine();
|
||||
if (userId != null) {
|
||||
clientPool.put(Long.parseLong(userId), clientSocket);
|
||||
System.out.println("User connected: " + userId + " (" + clientPool.size() + " total)");
|
||||
}
|
||||
clientSocket = serverSocket.accept();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
}
|
124
soundcloud/soundcloud_problem_statement/js/index.js
Normal file
124
soundcloud/soundcloud_problem_statement/js/index.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
const net = require("net");
|
||||
const readline = require("readline");
|
||||
|
||||
const EVENT_PORT = 9090;
|
||||
const CLIENT_PORT = 9099;
|
||||
|
||||
const clientPool = {};
|
||||
const followRegistry = {};
|
||||
|
||||
let lastSeqNo = 0;
|
||||
|
||||
net
|
||||
.createServer(socket => {
|
||||
const seqNoToMessage = {};
|
||||
const readInterface = readline.createInterface({ input: socket });
|
||||
readInterface.on("line", payload => {
|
||||
console.log(`Message received: ${payload}`);
|
||||
|
||||
const payloadParts = payload.split("|");
|
||||
seqNoToMessage[parseInt(payloadParts[0])] = payloadParts;
|
||||
|
||||
while (seqNoToMessage[lastSeqNo + 1]) {
|
||||
const nextMessage = seqNoToMessage[lastSeqNo + 1];
|
||||
const nextPayload = nextMessage.join("|");
|
||||
|
||||
const seqNo = parseInt(nextMessage[0]);
|
||||
const kind = nextMessage[1];
|
||||
|
||||
switch (kind) {
|
||||
case "F":
|
||||
{
|
||||
const fromUserId = parseInt(nextMessage[2]);
|
||||
const toUserId = parseInt(nextMessage[3]);
|
||||
|
||||
const followers = followRegistry[toUserId] || new Set([]);
|
||||
followers.add(fromUserId);
|
||||
followRegistry[toUserId] = followers;
|
||||
|
||||
const socket = clientPool[toUserId];
|
||||
if (socket) {
|
||||
socket.write(nextPayload + "\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "U":
|
||||
{
|
||||
const fromUserId = parseInt(nextMessage[2]);
|
||||
const toUserId = parseInt(nextMessage[3]);
|
||||
|
||||
const followers = followRegistry[toUserId] || new Set([]);
|
||||
followers.delete(fromUserId);
|
||||
followRegistry[toUserId] = followers;
|
||||
}
|
||||
break;
|
||||
|
||||
case "P":
|
||||
{
|
||||
const toUserId = parseInt(nextMessage[3]);
|
||||
|
||||
const socket = clientPool[toUserId];
|
||||
if (socket) {
|
||||
socket.write(nextPayload + "\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "B":
|
||||
{
|
||||
for (let toUserId in clientPool) {
|
||||
const socket = clientPool[toUserId];
|
||||
if (socket) {
|
||||
socket.write(nextPayload + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "S":
|
||||
{
|
||||
const fromUserId = parseInt(nextMessage[2]);
|
||||
|
||||
const followers = followRegistry[fromUserId] || new Set([]);
|
||||
|
||||
followers.forEach(follower => {
|
||||
const socket = clientPool[follower];
|
||||
if (socket) {
|
||||
socket.write(nextPayload + "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
lastSeqNo = seqNo;
|
||||
}
|
||||
});
|
||||
})
|
||||
.listen(EVENT_PORT, "127.0.0.1", err => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.log(`Listening for events on ${EVENT_PORT}`);
|
||||
});
|
||||
|
||||
net
|
||||
.createServer(clientSocket => {
|
||||
const readInterface = readline.createInterface({ input: clientSocket });
|
||||
readInterface.on("line", userIdString => {
|
||||
if (userIdString != null) {
|
||||
clientPool[parseInt(userIdString)] = clientSocket;
|
||||
|
||||
console.log(
|
||||
`User connected: ${userIdString} (${clientPool.length} total)`
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
.listen(CLIENT_PORT, "127.0.0.1", err => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.log(`Listening for client requests on ${CLIENT_PORT}`);
|
||||
});
|
988
soundcloud/soundcloud_problem_statement/js/package-lock.json
generated
Normal file
988
soundcloud/soundcloud_problem_statement/js/package-lock.json
generated
Normal file
|
@ -0,0 +1,988 @@
|
|||
{
|
||||
"name": "js",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
|
||||
"integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.0.0",
|
||||
"esutils": "^2.0.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
|
||||
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz",
|
||||
"integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
|
||||
"dev": true
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
||||
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"ansi-escapes": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
|
||||
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"astral-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
|
||||
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"chardet": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"dev": true
|
||||
},
|
||||
"cli-cursor": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
|
||||
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"restore-cursor": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"cli-width": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
|
||||
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nice-try": "^1.0.4",
|
||||
"path-key": "^2.0.1",
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"deep-is": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
|
||||
"dev": true
|
||||
},
|
||||
"doctrine": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"eslint": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.2.2.tgz",
|
||||
"integrity": "sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"ajv": "^6.10.0",
|
||||
"chalk": "^2.1.0",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"debug": "^4.0.1",
|
||||
"doctrine": "^3.0.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^1.4.2",
|
||||
"eslint-visitor-keys": "^1.1.0",
|
||||
"espree": "^6.1.1",
|
||||
"esquery": "^1.0.1",
|
||||
"esutils": "^2.0.2",
|
||||
"file-entry-cache": "^5.0.1",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"glob-parent": "^5.0.0",
|
||||
"globals": "^11.7.0",
|
||||
"ignore": "^4.0.6",
|
||||
"import-fresh": "^3.0.0",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"inquirer": "^6.4.1",
|
||||
"is-glob": "^4.0.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"levn": "^0.3.0",
|
||||
"lodash": "^4.17.14",
|
||||
"minimatch": "^3.0.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.8.2",
|
||||
"progress": "^2.0.0",
|
||||
"regexpp": "^2.0.1",
|
||||
"semver": "^6.1.2",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"strip-json-comments": "^3.0.1",
|
||||
"table": "^5.2.3",
|
||||
"text-table": "^0.2.0",
|
||||
"v8-compile-cache": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
|
||||
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esrecurse": "^4.1.0",
|
||||
"estraverse": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"eslint-utils": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
|
||||
"integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-visitor-keys": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
|
||||
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
|
||||
"dev": true
|
||||
},
|
||||
"espree": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz",
|
||||
"integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^7.0.0",
|
||||
"acorn-jsx": "^5.0.2",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
},
|
||||
"esquery": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
|
||||
"integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estraverse": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"esrecurse": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
|
||||
"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estraverse": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true
|
||||
},
|
||||
"external-editor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chardet": "^0.7.0",
|
||||
"iconv-lite": "^0.4.24",
|
||||
"tmp": "^0.0.33"
|
||||
}
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-json-stable-stringify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"dev": true
|
||||
},
|
||||
"figures": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
|
||||
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"file-entry-cache": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
|
||||
"integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flat-cache": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"flat-cache": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
|
||||
"integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flatted": "^2.0.0",
|
||||
"rimraf": "2.6.3",
|
||||
"write": "1.0.3"
|
||||
}
|
||||
},
|
||||
"flatted": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
|
||||
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"functional-red-black-tree": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
|
||||
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
||||
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
|
||||
"integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||
"dev": true
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
|
||||
"integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"parent-module": "^1.0.0",
|
||||
"resolve-from": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"imurmurhash": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
|
||||
"integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-escapes": "^3.2.0",
|
||||
"chalk": "^2.4.2",
|
||||
"cli-cursor": "^2.1.0",
|
||||
"cli-width": "^2.0.0",
|
||||
"external-editor": "^3.0.3",
|
||||
"figures": "^2.0.0",
|
||||
"lodash": "^4.17.12",
|
||||
"mute-stream": "0.0.7",
|
||||
"run-async": "^2.2.0",
|
||||
"rxjs": "^6.4.0",
|
||||
"string-width": "^2.1.0",
|
||||
"strip-ansi": "^5.1.0",
|
||||
"through": "^2.3.6"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-promise": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
|
||||
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"json-stable-stringify-without-jsonify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
|
||||
"dev": true
|
||||
},
|
||||
"levn": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"mimic-fn": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"mute-stream": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
|
||||
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
|
||||
"dev": true
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
|
||||
"dev": true
|
||||
},
|
||||
"nice-try": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"onetime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
|
||||
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mimic-fn": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"optionator": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
|
||||
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"deep-is": "~0.1.3",
|
||||
"fast-levenshtein": "~2.0.4",
|
||||
"levn": "~0.3.0",
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2",
|
||||
"wordwrap": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"callsites": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
|
||||
"dev": true
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
|
||||
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
|
||||
"dev": true
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"dev": true
|
||||
},
|
||||
"regexpp": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
|
||||
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
|
||||
"dev": true
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"dev": true
|
||||
},
|
||||
"restore-cursor": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
|
||||
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"onetime": "^2.0.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"run-async": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
|
||||
"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-promise": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz",
|
||||
"integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
|
||||
"dev": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
|
||||
"dev": true
|
||||
},
|
||||
"slice-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
|
||||
"integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.0",
|
||||
"astral-regex": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
|
||||
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"version": "5.4.6",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
|
||||
"integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.10.2",
|
||||
"lodash": "^4.17.14",
|
||||
"slice-ansi": "^2.1.0",
|
||||
"string-width": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"string-width": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^7.0.1",
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
|
||||
"dev": true
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
|
||||
"dev": true
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
|
||||
"dev": true
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prelude-ls": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
|
||||
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
|
||||
"dev": true
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"write": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
|
||||
"integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mkdirp": "^0.5.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
soundcloud/soundcloud_problem_statement/js/package.json
Normal file
11
soundcloud/soundcloud_problem_statement/js/package.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "js",
|
||||
"version": "0.0.1",
|
||||
"description": "follower maze refactoring challenge in javascript",
|
||||
"main": "index.js",
|
||||
"author": "SoundCloud",
|
||||
"devDependencies": {
|
||||
"prettier": "1.18.2",
|
||||
"eslint": "6.2.2"
|
||||
}
|
||||
}
|
98
soundcloud/soundcloud_problem_statement/python/main.py
Normal file
98
soundcloud/soundcloud_problem_statement/python/main.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
import socket
|
||||
import threading
|
||||
|
||||
EVENT_PORT = 9090
|
||||
CLIENT_PORT = 9099
|
||||
client_pool = {}
|
||||
follow_registry = {}
|
||||
seq_no_to_message = {}
|
||||
|
||||
def event_server():
|
||||
last_seq_no = 0
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
|
||||
server.bind(("127.0.0.1", EVENT_PORT))
|
||||
server.listen()
|
||||
print("Listening for events on %d" % EVENT_PORT)
|
||||
while True:
|
||||
event_socket, address = server.accept()
|
||||
print('Accepted connection from {}:{}'.format(address[0], address[1]))
|
||||
with event_socket:
|
||||
with event_socket.makefile() as socket_file:
|
||||
for payload_line in socket_file:
|
||||
payload = payload_line.strip()
|
||||
print("Message received: %s" % payload)
|
||||
payload_parts = payload.split("|")
|
||||
|
||||
seq_no_to_message[int(payload_parts[0])] = payload_parts
|
||||
|
||||
while last_seq_no + 1 in seq_no_to_message:
|
||||
next_seq_no = last_seq_no + 1
|
||||
next_message = seq_no_to_message[next_seq_no]
|
||||
del seq_no_to_message[next_seq_no]
|
||||
|
||||
kind = next_message[1]
|
||||
next_payload = "|".join(next_message)
|
||||
|
||||
if kind == "F":
|
||||
from_user_id = int(next_message[2])
|
||||
to_user_id = int(next_message[3])
|
||||
|
||||
if to_user_id not in follow_registry:
|
||||
follow_registry[to_user_id] = set([])
|
||||
follow_registry[to_user_id].add(from_user_id)
|
||||
|
||||
#if to_user_id in client_pool:
|
||||
# client_pool[to_user_id].sendall(bytes(next_payload + "\n", "UTF-8"))
|
||||
|
||||
elif kind == "U":
|
||||
from_user_id = int(next_message[2])
|
||||
to_user_id = int(next_message[3])
|
||||
|
||||
if to_user_id not in follow_registry:
|
||||
follow_registry[to_user_id] = set([])
|
||||
follow_registry[to_user_id].remove(from_user_id)
|
||||
|
||||
elif kind == "P":
|
||||
to_user_id = int(next_message[3])
|
||||
|
||||
if to_user_id in client_pool:
|
||||
client_pool[to_user_id].sendall(bytes(next_payload + "\n", "UTF-8"))
|
||||
|
||||
elif kind == "B":
|
||||
for client_id in client_pool:
|
||||
client_pool[client_id].sendall(bytes(next_payload + "\n", "UTF-8"))
|
||||
|
||||
elif kind == "S":
|
||||
from_user_id = int(next_message[2])
|
||||
|
||||
if from_user_id in follow_registry:
|
||||
for follower in follow_registry[from_user_id]:
|
||||
if follower in client_pool:
|
||||
client_pool[follower].sendall(bytes(next_payload + "\n", "UTF-8"))
|
||||
|
||||
last_seq_no = next_seq_no
|
||||
|
||||
def client_connection_server():
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
|
||||
server.bind(("127.0.0.1", CLIENT_PORT))
|
||||
server.listen()
|
||||
print("Listening for client requests on %d" % CLIENT_PORT)
|
||||
while True:
|
||||
client_socket, address = server.accept()
|
||||
with client_socket.makefile() as f:
|
||||
user_id_string = f.readline()
|
||||
if user_id_string:
|
||||
user_id = int(user_id_string)
|
||||
client_pool[user_id] = client_socket
|
||||
print("User connected: %d (%d total)" % (user_id, len(client_pool)))
|
||||
|
||||
if __name__ == "__main__":
|
||||
thread1 = threading.Thread(target=event_server)
|
||||
thread1.start()
|
||||
|
||||
thread2 = threading.Thread(target=client_connection_server)
|
||||
thread2.start()
|
||||
|
||||
thread1.join()
|
||||
thread2.join()
|
106
soundcloud/soundcloud_problem_statement/ruby/main.rb
Normal file
106
soundcloud/soundcloud_problem_statement/ruby/main.rb
Normal file
|
@ -0,0 +1,106 @@
|
|||
require 'set'
|
||||
require 'socket'
|
||||
|
||||
EVENT_PORT = 9090
|
||||
CLIENT_PORT = 9099
|
||||
|
||||
client_pool = {}
|
||||
seq_no_to_message = {}
|
||||
follow_registry = {}
|
||||
last_seq_no = 0
|
||||
|
||||
thread1 = Thread.new do
|
||||
puts("Listening for events on #{EVENT_PORT}")
|
||||
server = TCPServer.open(EVENT_PORT)
|
||||
loop do
|
||||
Thread.fork(server.accept) do |event_socket|
|
||||
event_socket.each_line do |payload|
|
||||
puts("Message received: #{payload}")
|
||||
|
||||
payload_parts = payload.split('|')
|
||||
seq_no_to_message[payload_parts[0].to_i] = payload_parts
|
||||
|
||||
while seq_no_to_message[last_seq_no + 1]
|
||||
next_message = seq_no_to_message[last_seq_no + 1]
|
||||
next_payload = next_message.join('|')
|
||||
|
||||
seq_no = next_message[0].to_i
|
||||
kind = next_message[1].strip
|
||||
|
||||
case kind
|
||||
when 'F'
|
||||
from_user_id = next_message[2].to_i
|
||||
to_user_id = next_message[3].to_i
|
||||
|
||||
followers = follow_registry[to_user_id] || Set.new
|
||||
followers << from_user_id
|
||||
follow_registry[to_user_id] = followers
|
||||
|
||||
socket = client_pool[to_user_id]
|
||||
if socket
|
||||
socket.puts(next_payload)
|
||||
socket.flush
|
||||
end
|
||||
|
||||
when 'U'
|
||||
from_user_id = next_message[2].to_i
|
||||
to_user_id = next_message[3].to_i
|
||||
|
||||
followers = follow_registry[to_user_id] || Set.new
|
||||
followers.delete(from_user_id)
|
||||
follow_registry[to_user_id] = followers
|
||||
|
||||
when 'P'
|
||||
to_user_id = next_message[3].to_i
|
||||
|
||||
socket = client_pool[to_user_id]
|
||||
if socket
|
||||
socket.puts(next_payload)
|
||||
socket.flush
|
||||
end
|
||||
|
||||
when 'B'
|
||||
client_pool.values.each do |socket|
|
||||
socket.puts(next_payload)
|
||||
socket.flush
|
||||
end
|
||||
|
||||
when 'S'
|
||||
from_user_id = next_message[2].to_i
|
||||
|
||||
followers = follow_registry[from_user_id] || Set.new
|
||||
followers.each do |follower|
|
||||
socket = client_pool[follower]
|
||||
|
||||
if socket
|
||||
socket.puts(next_payload)
|
||||
socket.flush
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
last_seq_no = seq_no
|
||||
end
|
||||
end
|
||||
event_socket.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
thread2 = Thread.new do
|
||||
puts("Listening for client requests on #{CLIENT_PORT}")
|
||||
server = TCPServer.open(CLIENT_PORT)
|
||||
loop do
|
||||
Thread.fork(server.accept) do |socket|
|
||||
user_id_string = socket.gets
|
||||
if user_id_string
|
||||
user_id = user_id_string.to_i
|
||||
client_pool[user_id] = socket
|
||||
puts("User connected: #{user_id} (#{client_pool.size} total)")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
thread1.join
|
||||
thread2.join
|
3
soundcloud/soundcloud_problem_statement/scala/build.sbt
Normal file
3
soundcloud/soundcloud_problem_statement/scala/build.sbt
Normal file
|
@ -0,0 +1,3 @@
|
|||
name := "maze"
|
||||
|
||||
scalaVersion := "2.12.8"
|
|
@ -0,0 +1 @@
|
|||
sbt.version = 1.2.8
|
|
@ -0,0 +1,140 @@
|
|||
package com.soundcloud.maze
|
||||
|
||||
import java.io._
|
||||
import java.net.{ServerSocket, Socket}
|
||||
|
||||
import scala.collection.concurrent.TrieMap
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration.Duration
|
||||
import scala.concurrent.{Await, ExecutionContext, Future}
|
||||
import scala.io.Source
|
||||
import scala.util.Try
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
object Main {
|
||||
|
||||
private val EventPort = 9090
|
||||
private val ClientPort = 9099
|
||||
|
||||
private var lastSeqNo = 0L
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
|
||||
val clientPool = new TrieMap[Long, Socket]
|
||||
|
||||
val messagesBySeqNo = new mutable.HashMap[Long, List[String]]
|
||||
val followRegistry = new mutable.HashMap[Long, Set[Long]]
|
||||
|
||||
implicit val ec = ExecutionContext.global
|
||||
|
||||
val eventsAsync = Future {
|
||||
|
||||
println(s"Listening for events on $EventPort")
|
||||
val eventSocket = new ServerSocket(EventPort).accept()
|
||||
|
||||
Try {
|
||||
val reader = new BufferedReader(new InputStreamReader(eventSocket.getInputStream()))
|
||||
|
||||
Try {
|
||||
reader.lines().iterator().asScala.foreach { payload =>
|
||||
println(s"Message received: $payload")
|
||||
val message = payload.split("\\|").toList
|
||||
|
||||
messagesBySeqNo += message(0).toLong -> message
|
||||
|
||||
while (messagesBySeqNo.get(lastSeqNo + 1L).isDefined) {
|
||||
val nextMessage = messagesBySeqNo(lastSeqNo + 1)
|
||||
|
||||
messagesBySeqNo -= lastSeqNo + 1L
|
||||
|
||||
val nextPayload = nextMessage.mkString("|")
|
||||
val seqNo = nextMessage(0).toLong
|
||||
val kind = nextMessage(1)
|
||||
|
||||
kind match {
|
||||
case "F" =>
|
||||
val fromUserId = nextMessage(2).toLong
|
||||
val toUserId = nextMessage(3).toLong
|
||||
val followers = followRegistry.getOrElse(toUserId, Set.empty)
|
||||
val newFollowers = followers + fromUserId
|
||||
|
||||
followRegistry.put(toUserId, newFollowers)
|
||||
|
||||
clientPool.get(toUserId).foreach { socket =>
|
||||
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
|
||||
writer.write(s"$nextPayload\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
case "U" =>
|
||||
val fromUserId = nextMessage(2).toLong
|
||||
val toUserId = nextMessage(3).toLong
|
||||
val followers = followRegistry.getOrElse(toUserId, Set.empty)
|
||||
val newFollowers = followers - fromUserId
|
||||
|
||||
followRegistry.put(toUserId, newFollowers)
|
||||
|
||||
case "P" =>
|
||||
val toUserId = nextMessage(3).toLong
|
||||
|
||||
clientPool.get(toUserId).foreach { socket =>
|
||||
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
|
||||
writer.write(s"$nextPayload\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
case "B" =>
|
||||
clientPool.values.foreach { socket =>
|
||||
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
|
||||
writer.write(s"$nextPayload\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
case "S" =>
|
||||
val fromUserId = nextMessage(2).toLong
|
||||
val followers = followRegistry.getOrElse(fromUserId, Set.empty)
|
||||
|
||||
followers.foreach { follower =>
|
||||
clientPool.get(follower).foreach { socket =>
|
||||
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
|
||||
writer.write(s"$nextPayload\n")
|
||||
writer.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSeqNo = seqNo
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reader != null) reader.close()
|
||||
}
|
||||
if (eventSocket != null) eventSocket.close()
|
||||
}
|
||||
|
||||
val clientsAsync = Future {
|
||||
|
||||
println(s"Listening for client requests on $ClientPort")
|
||||
val serverSocket = new ServerSocket(ClientPort)
|
||||
var maybeClientSocket = Option(serverSocket.accept())
|
||||
|
||||
while (maybeClientSocket.nonEmpty) {
|
||||
maybeClientSocket.foreach { clientSocket =>
|
||||
val bufferedSource = Source.fromInputStream(clientSocket.getInputStream())
|
||||
val userId = bufferedSource.bufferedReader().readLine()
|
||||
|
||||
if (userId != null) {
|
||||
clientPool.put(userId.toLong, clientSocket)
|
||||
println(s"User connected: $userId (${clientPool.size} total)")
|
||||
}
|
||||
|
||||
maybeClientSocket = Option(serverSocket.accept())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Await.result(Future.sequence(Seq(eventsAsync, clientsAsync)), Duration.Inf)
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
6
soundcloud/soundcloud_problem_statement/tester/run100k.sh
Executable file
6
soundcloud/soundcloud_problem_statement/tester/run100k.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
this_dir=$(dirname $0)
|
||||
export totalEvents=100000
|
||||
export concurrencyLevel=1
|
||||
time java -server -Xmx1G -jar "${this_dir}/follower-maze-2.0.jar"
|
6
soundcloud/soundcloud_problem_statement/tester/run30.sh
Executable file
6
soundcloud/soundcloud_problem_statement/tester/run30.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash -ex
|
||||
|
||||
this_dir=$(dirname $0)
|
||||
export totalEvents=30
|
||||
export concurrencyLevel=1
|
||||
time java -server -Xmx1G -jar "${this_dir}/follower-maze-2.0.jar"
|
BIN
talks-I-gave/WWCode - Python 2 to 3_ What you need to know .pdf
Normal file
BIN
talks-I-gave/WWCode - Python 2 to 3_ What you need to know .pdf
Normal file
Binary file not shown.
1
zapata/.keep
Normal file
1
zapata/.keep
Normal file
|
@ -0,0 +1 @@
|
|||
|
1
zapata/example/.keep
Normal file
1
zapata/example/.keep
Normal file
|
@ -0,0 +1 @@
|
|||
|
31
zapata/example/main/example.proto
Normal file
31
zapata/example/main/example.proto
Normal file
|
@ -0,0 +1,31 @@
|
|||
syntax = "proto3";
|
||||
package tutorial;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";
|
||||
|
||||
message Person {
|
||||
string name = 1;
|
||||
int32 id = 2; // Unique ID number for this person.
|
||||
string email = 3;
|
||||
|
||||
enum PhoneType {
|
||||
MOBILE = 0;
|
||||
HOME = 1;
|
||||
WORK = 2;
|
||||
}
|
||||
|
||||
message PhoneNumber {
|
||||
string number = 1;
|
||||
PhoneType type = 2;
|
||||
}
|
||||
|
||||
repeated PhoneNumber phones = 4;
|
||||
|
||||
google.protobuf.Timestamp last_updated = 5;
|
||||
}
|
||||
|
||||
// Our address book file is just one of these.
|
||||
message AddressBook {
|
||||
repeated Person people = 1;
|
||||
}
|
|
@ -0,0 +1,392 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.20.1-devel
|
||||
// protoc v3.11.4
|
||||
// source: example.proto
|
||||
|
||||
package tutorialpb
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type Person_PhoneType int32
|
||||
|
||||
const (
|
||||
Person_MOBILE Person_PhoneType = 0
|
||||
Person_HOME Person_PhoneType = 1
|
||||
Person_WORK Person_PhoneType = 2
|
||||
)
|
||||
|
||||
// Enum value maps for Person_PhoneType.
|
||||
var (
|
||||
Person_PhoneType_name = map[int32]string{
|
||||
0: "MOBILE",
|
||||
1: "HOME",
|
||||
2: "WORK",
|
||||
}
|
||||
Person_PhoneType_value = map[string]int32{
|
||||
"MOBILE": 0,
|
||||
"HOME": 1,
|
||||
"WORK": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Person_PhoneType) Enum() *Person_PhoneType {
|
||||
p := new(Person_PhoneType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Person_PhoneType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Person_PhoneType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_example_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Person_PhoneType) Type() protoreflect.EnumType {
|
||||
return &file_example_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Person_PhoneType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Person_PhoneType.Descriptor instead.
|
||||
func (Person_PhoneType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_example_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Id int32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` // Unique ID number for this person.
|
||||
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
|
||||
Phones []*Person_PhoneNumber `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"`
|
||||
LastUpdated *timestamp.Timestamp `protobuf:"bytes,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Person) Reset() {
|
||||
*x = Person{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_example_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Person) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Person) ProtoMessage() {}
|
||||
|
||||
func (x *Person) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_example_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Person.ProtoReflect.Descriptor instead.
|
||||
func (*Person) Descriptor() ([]byte, []int) {
|
||||
return file_example_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Person) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Person) GetId() int32 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Person) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Person) GetPhones() []*Person_PhoneNumber {
|
||||
if x != nil {
|
||||
return x.Phones
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Person) GetLastUpdated() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.LastUpdated
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Our address book file is just one of these.
|
||||
type AddressBook struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
People []*Person `protobuf:"bytes,1,rep,name=people,proto3" json:"people,omitempty"`
|
||||
}
|
||||
|
||||
func (x *AddressBook) Reset() {
|
||||
*x = AddressBook{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_example_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AddressBook) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AddressBook) ProtoMessage() {}
|
||||
|
||||
func (x *AddressBook) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_example_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AddressBook.ProtoReflect.Descriptor instead.
|
||||
func (*AddressBook) Descriptor() ([]byte, []int) {
|
||||
return file_example_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *AddressBook) GetPeople() []*Person {
|
||||
if x != nil {
|
||||
return x.People
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Person_PhoneNumber struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Number string `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"`
|
||||
Type Person_PhoneType `protobuf:"varint,2,opt,name=type,proto3,enum=tutorial.Person_PhoneType" json:"type,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Person_PhoneNumber) Reset() {
|
||||
*x = Person_PhoneNumber{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_example_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Person_PhoneNumber) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Person_PhoneNumber) ProtoMessage() {}
|
||||
|
||||
func (x *Person_PhoneNumber) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_example_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Person_PhoneNumber.ProtoReflect.Descriptor instead.
|
||||
func (*Person_PhoneNumber) Descriptor() ([]byte, []int) {
|
||||
return file_example_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *Person_PhoneNumber) GetNumber() string {
|
||||
if x != nil {
|
||||
return x.Number
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Person_PhoneNumber) GetType() Person_PhoneType {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return Person_MOBILE
|
||||
}
|
||||
|
||||
var File_example_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_example_proto_rawDesc = []byte{
|
||||
0x0a, 0x0d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x08, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbb, 0x02, 0x0a, 0x06, 0x50,
|
||||
0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61,
|
||||
0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12,
|
||||
0x34, 0x0a, 0x06, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x1c, 0x2e, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f,
|
||||
0x6e, 0x2e, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x06, 0x70,
|
||||
0x68, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x64, 0x1a, 0x55, 0x0a, 0x0b, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d,
|
||||
0x62, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x04, 0x74,
|
||||
0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x74, 0x75, 0x74, 0x6f,
|
||||
0x72, 0x69, 0x61, 0x6c, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x50, 0x68, 0x6f, 0x6e,
|
||||
0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2b, 0x0a, 0x09, 0x50,
|
||||
0x68, 0x6f, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x4f, 0x42, 0x49,
|
||||
0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x08,
|
||||
0x0a, 0x04, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x02, 0x22, 0x37, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72,
|
||||
0x65, 0x73, 0x73, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x28, 0x0a, 0x06, 0x70, 0x65, 0x6f, 0x70, 0x6c,
|
||||
0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69,
|
||||
0x61, 0x6c, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x70, 0x65, 0x6f, 0x70, 0x6c,
|
||||
0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
|
||||
0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x70, 0x62, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_example_proto_rawDescOnce sync.Once
|
||||
file_example_proto_rawDescData = file_example_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_example_proto_rawDescGZIP() []byte {
|
||||
file_example_proto_rawDescOnce.Do(func() {
|
||||
file_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_example_proto_rawDescData)
|
||||
})
|
||||
return file_example_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_example_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_example_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_example_proto_goTypes = []interface{}{
|
||||
(Person_PhoneType)(0), // 0: tutorial.Person.PhoneType
|
||||
(*Person)(nil), // 1: tutorial.Person
|
||||
(*AddressBook)(nil), // 2: tutorial.AddressBook
|
||||
(*Person_PhoneNumber)(nil), // 3: tutorial.Person.PhoneNumber
|
||||
(*timestamp.Timestamp)(nil), // 4: google.protobuf.Timestamp
|
||||
}
|
||||
var file_example_proto_depIdxs = []int32{
|
||||
3, // 0: tutorial.Person.phones:type_name -> tutorial.Person.PhoneNumber
|
||||
4, // 1: tutorial.Person.last_updated:type_name -> google.protobuf.Timestamp
|
||||
1, // 2: tutorial.AddressBook.people:type_name -> tutorial.Person
|
||||
0, // 3: tutorial.Person.PhoneNumber.type:type_name -> tutorial.Person.PhoneType
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_example_proto_init() }
|
||||
func file_example_proto_init() {
|
||||
if File_example_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_example_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Person); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_example_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AddressBook); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_example_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Person_PhoneNumber); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_example_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_example_proto_goTypes,
|
||||
DependencyIndexes: file_example_proto_depIdxs,
|
||||
EnumInfos: file_example_proto_enumTypes,
|
||||
MessageInfos: file_example_proto_msgTypes,
|
||||
}.Build()
|
||||
File_example_proto = out.File
|
||||
file_example_proto_rawDesc = nil
|
||||
file_example_proto_goTypes = nil
|
||||
file_example_proto_depIdxs = nil
|
||||
}
|
73
zapata/example/main/main.go
Normal file
73
zapata/example/main/main.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
// 1. transform the struct to protocol buffer
|
||||
// 2. send protobuf message via HTTP to so some server
|
||||
// 3. In the server side, read that message and transform back into that initial
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/DeDiS/protobuf"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Id int32
|
||||
Email *string
|
||||
Phone map[string]PhoneNumber
|
||||
}
|
||||
|
||||
type PhoneType uint32
|
||||
|
||||
const (
|
||||
MOBILE PhoneType = iota
|
||||
HOME
|
||||
WORK
|
||||
)
|
||||
|
||||
type PhoneNumber struct {
|
||||
Number string
|
||||
CountryCode string
|
||||
}
|
||||
|
||||
func main() {
|
||||
email := "john.doe@example.com"
|
||||
// mobile := MOBILE
|
||||
// work := WORK
|
||||
phoneNumbers := make(map[string]PhoneNumber)
|
||||
phoneNumbers["mobile"] = PhoneNumber{
|
||||
Number: "00000",
|
||||
CountryCode: "+1",
|
||||
}
|
||||
phoneNumbers["work"] = PhoneNumber{
|
||||
Number: "11111",
|
||||
CountryCode: "+2",
|
||||
}
|
||||
|
||||
person := Person{
|
||||
Name: "John Doe",
|
||||
Id: 123,
|
||||
Email: &email,
|
||||
Phone: phoneNumbers,
|
||||
}
|
||||
|
||||
//fmt.Println(person)
|
||||
|
||||
buf, err := protobuf.Encode(&person)
|
||||
if err != nil {
|
||||
fmt.Println("hit first error")
|
||||
fmt.Println(err.Error())
|
||||
} else {
|
||||
//fmt.Println(string(buf))
|
||||
|
||||
var person2 Person
|
||||
err = protobuf.Decode(buf, &person2)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
} else {
|
||||
fmt.Printf("%+v\n", person2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
389
zapata/example/main/tutorial/example.pb.go
Normal file
389
zapata/example/main/tutorial/example.pb.go
Normal file
|
@ -0,0 +1,389 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.20.1-devel
|
||||
// protoc v3.11.4
|
||||
// source: example.proto
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type Person_PhoneType int32
|
||||
|
||||
const (
|
||||
Person_MOBILE Person_PhoneType = 0
|
||||
Person_HOME Person_PhoneType = 1
|
||||
Person_WORK Person_PhoneType = 2
|
||||
)
|
||||
|
||||
// Enum value maps for Person_PhoneType.
|
||||
var (
|
||||
Person_PhoneType_name = map[int32]string{
|
||||
0: "MOBILE",
|
||||
1: "HOME",
|
||||
2: "WORK",
|
||||
}
|
||||
Person_PhoneType_value = map[string]int32{
|
||||
"MOBILE": 0,
|
||||
"HOME": 1,
|
||||
"WORK": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Person_PhoneType) Enum() *Person_PhoneType {
|
||||
p := new(Person_PhoneType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Person_PhoneType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Person_PhoneType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_example_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Person_PhoneType) Type() protoreflect.EnumType {
|
||||
return &file_example_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Person_PhoneType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Person_PhoneType.Descriptor instead.
|
||||
func (Person_PhoneType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_example_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Id int32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` // Unique ID number for this person.
|
||||
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
|
||||
Phones []*Person_PhoneNumber `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"`
|
||||
LastUpdated *timestamp.Timestamp `protobuf:"bytes,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Person) Reset() {
|
||||
*x = Person{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_example_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Person) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Person) ProtoMessage() {}
|
||||
|
||||
func (x *Person) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_example_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Person.ProtoReflect.Descriptor instead.
|
||||
func (*Person) Descriptor() ([]byte, []int) {
|
||||
return file_example_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Person) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Person) GetId() int32 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Person) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Person) GetPhones() []*Person_PhoneNumber {
|
||||
if x != nil {
|
||||
return x.Phones
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Person) GetLastUpdated() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.LastUpdated
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Our address book file is just one of these.
|
||||
type AddressBook struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
People []*Person `protobuf:"bytes,1,rep,name=people,proto3" json:"people,omitempty"`
|
||||
}
|
||||
|
||||
func (x *AddressBook) Reset() {
|
||||
*x = AddressBook{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_example_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AddressBook) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AddressBook) ProtoMessage() {}
|
||||
|
||||
func (x *AddressBook) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_example_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AddressBook.ProtoReflect.Descriptor instead.
|
||||
func (*AddressBook) Descriptor() ([]byte, []int) {
|
||||
return file_example_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *AddressBook) GetPeople() []*Person {
|
||||
if x != nil {
|
||||
return x.People
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Person_PhoneNumber struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Number string `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"`
|
||||
Type Person_PhoneType `protobuf:"varint,2,opt,name=type,proto3,enum=tutorial.Person_PhoneType" json:"type,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Person_PhoneNumber) Reset() {
|
||||
*x = Person_PhoneNumber{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_example_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Person_PhoneNumber) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Person_PhoneNumber) ProtoMessage() {}
|
||||
|
||||
func (x *Person_PhoneNumber) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_example_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Person_PhoneNumber.ProtoReflect.Descriptor instead.
|
||||
func (*Person_PhoneNumber) Descriptor() ([]byte, []int) {
|
||||
return file_example_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *Person_PhoneNumber) GetNumber() string {
|
||||
if x != nil {
|
||||
return x.Number
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Person_PhoneNumber) GetType() Person_PhoneType {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return Person_MOBILE
|
||||
}
|
||||
|
||||
var File_example_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_example_proto_rawDesc = []byte{
|
||||
0x0a, 0x0d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x08, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbb, 0x02, 0x0a, 0x06, 0x50,
|
||||
0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61,
|
||||
0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12,
|
||||
0x34, 0x0a, 0x06, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x1c, 0x2e, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f,
|
||||
0x6e, 0x2e, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x06, 0x70,
|
||||
0x68, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x64, 0x1a, 0x55, 0x0a, 0x0b, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d,
|
||||
0x62, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x04, 0x74,
|
||||
0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x74, 0x75, 0x74, 0x6f,
|
||||
0x72, 0x69, 0x61, 0x6c, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x50, 0x68, 0x6f, 0x6e,
|
||||
0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2b, 0x0a, 0x09, 0x50,
|
||||
0x68, 0x6f, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x4f, 0x42, 0x49,
|
||||
0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x08,
|
||||
0x0a, 0x04, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x02, 0x22, 0x37, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72,
|
||||
0x65, 0x73, 0x73, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x28, 0x0a, 0x06, 0x70, 0x65, 0x6f, 0x70, 0x6c,
|
||||
0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x75, 0x74, 0x6f, 0x72, 0x69,
|
||||
0x61, 0x6c, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x70, 0x65, 0x6f, 0x70, 0x6c,
|
||||
0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_example_proto_rawDescOnce sync.Once
|
||||
file_example_proto_rawDescData = file_example_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_example_proto_rawDescGZIP() []byte {
|
||||
file_example_proto_rawDescOnce.Do(func() {
|
||||
file_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_example_proto_rawDescData)
|
||||
})
|
||||
return file_example_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_example_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_example_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_example_proto_goTypes = []interface{}{
|
||||
(Person_PhoneType)(0), // 0: tutorial.Person.PhoneType
|
||||
(*Person)(nil), // 1: tutorial.Person
|
||||
(*AddressBook)(nil), // 2: tutorial.AddressBook
|
||||
(*Person_PhoneNumber)(nil), // 3: tutorial.Person.PhoneNumber
|
||||
(*timestamp.Timestamp)(nil), // 4: google.protobuf.Timestamp
|
||||
}
|
||||
var file_example_proto_depIdxs = []int32{
|
||||
3, // 0: tutorial.Person.phones:type_name -> tutorial.Person.PhoneNumber
|
||||
4, // 1: tutorial.Person.last_updated:type_name -> google.protobuf.Timestamp
|
||||
1, // 2: tutorial.AddressBook.people:type_name -> tutorial.Person
|
||||
0, // 3: tutorial.Person.PhoneNumber.type:type_name -> tutorial.Person.PhoneType
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_example_proto_init() }
|
||||
func file_example_proto_init() {
|
||||
if File_example_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_example_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Person); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_example_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AddressBook); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_example_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Person_PhoneNumber); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_example_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_example_proto_goTypes,
|
||||
DependencyIndexes: file_example_proto_depIdxs,
|
||||
EnumInfos: file_example_proto_enumTypes,
|
||||
MessageInfos: file_example_proto_msgTypes,
|
||||
}.Build()
|
||||
File_example_proto = out.File
|
||||
file_example_proto_rawDesc = nil
|
||||
file_example_proto_goTypes = nil
|
||||
file_example_proto_depIdxs = nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue