Add some private projects

This commit is contained in:
bt3gl 2022-03-23 18:25:34 +04:00
parent c8cd5cdbc4
commit 07aa882d51
80 changed files with 5216 additions and 41 deletions

1
.github/FUNDING.yml vendored
View file

@ -1 +0,0 @@
custom: [paypal.me/miasteinkirch, "https://www.buymeacoffee.com/miavonpizza"]

View file

@ -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

View file

@ -1,6 +1,7 @@
# ✨ Examples and Boilerplates for Python ✨
# ✨🐍 [Scratch Space] Python: small projects and boilerplates
![License: WTFPL](https://img.shields.io/badge/License-WTFPL-brightgreen.svg) [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](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
View file

70
art_and_logic/1-efun/.gitignore vendored Normal file
View 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

Binary file not shown.

View 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

View 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!

View 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

View 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]))

View file

@ -0,0 +1,2 @@
tox==3.14.3
virtualenv==20.0.1

View 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
''',
)

View file

@ -0,0 +1 @@
# -*- coding: utf8 -*-

View 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()

View file

@ -0,0 +1 @@
# -*- coding: utf8 -*-

View file

@ -0,0 +1,12 @@
[tox]
envlist = py27
[testenv]
deps = pytest
commands =
pytest
[testenv:lint]
skip_install = true
deps = flake8
commands = flake8 src/

View 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
View 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

Binary file not shown.

View 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

View 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!

View 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

View file

@ -0,0 +1,3 @@
#F0A04000417F4000417FC040004000804001C05F205F20804000
#F0A0417F40004000417FC067086708804001C0670840004000187818784000804000
F0A040004000417F417FC04000400080400047684F5057384000804001C05F204000400001400140400040007E405B2C4000804000

View file

@ -0,0 +1,4 @@
tox==3.14.3
virtualenv==20.0.1
python-dotenv==0.11.0
numpy==1.18.1

View 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
''',
)

View 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

View file

@ -0,0 +1,2 @@
# -*- coding: utf8 -*-

View 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

View 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()

View 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')

View 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

View 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
View 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
View 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
View 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 dont 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

Binary file not shown.

40
emotive.io/README.md Normal file
View 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`.

View file

@ -0,0 +1,7 @@
6
1, 114
1, 115
1, 116
2, 115
3, 2
3, 1

View file

@ -0,0 +1 @@
pymongo==3.9.0

121
medium/posts.py Normal file
View 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
View 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
View file

@ -0,0 +1,4 @@
run:
python3 find_glob_boundary.py
.PHONY: run

View 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

Binary file not shown.

BIN
soundcloud/.DS_Store vendored Normal file

Binary file not shown.

0
soundcloud/.gitkeep Normal file
View file

3
soundcloud/solution/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.python-version
__pycache__
*.pyc

View 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.

View 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

View 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

View 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'))

View 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()

Binary file not shown.

View 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 its 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 dont have access to a candidates 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 youre 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:
its 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 its _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 languages initial implementation.
* Please restrict yourself to the standard library - dont 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!

View 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`

View file

@ -0,0 +1,3 @@
module followermaze
go 1.13

View 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))
}
}

View 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"
}

View 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

View file

@ -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();
}
}

View 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}`);
});

View 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"
}
}
}
}

View 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"
}
}

View 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()

View 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

View file

@ -0,0 +1,3 @@
name := "maze"
scalaVersion := "2.12.8"

View file

@ -0,0 +1 @@
sbt.version = 1.2.8

View file

@ -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)
}
}

View 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"

View 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"

1
zapata/.keep Normal file
View file

@ -0,0 +1 @@

1
zapata/example/.keep Normal file
View file

@ -0,0 +1 @@

View 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;
}

View file

@ -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
}

View 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)
}
}
}

View 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
}