mirror of
https://github.com/autistic-symposium/web3-starter-py.git
synced 2025-05-17 14:10:21 -04:00
cleanup names for dirs
This commit is contained in:
parent
0904ec1dc4
commit
0da3cbd74a
80 changed files with 62 additions and 2263 deletions
62
maze-puzzle/README.md
Normal file
62
maze-puzzle/README.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Follower Maze Code Challenge Instructions
|
||||
|
||||
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.
|
||||
|
||||
|
||||
#### 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.
|
||||
|
||||
|
||||
|
3
soundcloud/solution/.gitignore
vendored
3
soundcloud/solution/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
.python-version
|
||||
__pycache__
|
||||
*.pyc
|
|
@ -1,50 +0,0 @@
|
|||
## 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.
|
BIN
soundcloud/soundcloud_problem_statement/.DS_Store
vendored
BIN
soundcloud/soundcloud_problem_statement/.DS_Store
vendored
Binary file not shown.
|
@ -1,218 +0,0 @@
|
|||
# Follower Maze Code Challenge Instructions
|
||||
|
||||
Thank you for applying for a backend engineering position at SoundCloud.
|
||||
We ask engineering candidates to work and complete this code challenge.
|
||||
Your submission is part of the basis upon which we evaluate your candidacy.
|
||||
|
||||
We hope it’s indeed challenging (and maybe even fun).
|
||||
|
||||
The challenge has two parts:
|
||||
1. **Refactor** an existing client-server application and provide a README explaining your approach
|
||||
1. **Extend** the server application with a small feature request
|
||||
|
||||
Our aim with these instructions is to leave no doubt about the details of the challenge and the expectations of
|
||||
your submission - however, if you have questions of any sort _please ask_.
|
||||
Also: please completely read the instructions before starting!
|
||||
|
||||
## Time Expectations
|
||||
|
||||
Based on internal testing, we roughly estimate the full Follower-Maze challenge will take
|
||||
somewhere around 2-7 hours of your time.
|
||||
|
||||
* Our goal for this time budget is to keep the time investment predictable for candidates -
|
||||
it is not intended to create significant “time pressure”.
|
||||
* The length of time you take to submit your work is not a factor in how we review it.
|
||||
* Many candidates will work the challenge and submit it back within a day or two.
|
||||
Or some might submit it a week later, depending on personal circumstances.
|
||||
|
||||
## Discretion
|
||||
In order to ensure continued fairness to all past and future participants,
|
||||
please do not share any of the details of this code challenge with others,
|
||||
including the work you submit.
|
||||
|
||||
## Anonymity
|
||||
Code challenge submissions are treated as a form of [Blind Audition](https://en.wikipedia.org/wiki/Blind_audition):
|
||||
reviewers don’t have access to a candidate’s personal information,
|
||||
including name and background.
|
||||
Please help us by keeping your submission free of information
|
||||
that would be revealing of who you are, or any personal attributes.
|
||||
|
||||
## Part 1: Follower Maze - Refactor
|
||||
|
||||
### Overview
|
||||
|
||||
Follower-Maze is a **client-server application** built for social networking.
|
||||
Users follow other users, post messages, send private messages, unfollow users, and so on.
|
||||
* Each of these actions is an _event_, created by an _event-source_ and sent to the _server_.
|
||||
* Each event is processed by the _server_.
|
||||
* The processing of the event may have follow-on effects on other _user-clients_.
|
||||
|
||||
The **server** listens for two kinds of **clients**:
|
||||
* **1 event source**, which emits events.
|
||||
* **N user-clients** that connect to the server.
|
||||
|
||||
The server routes certain events to certain clients, according to the social networking domain rules.
|
||||
Please see the [Specification](#specification) section for more detail.
|
||||
|
||||
### Running the End-to-End Tester
|
||||
|
||||
Please see the repository [README](README.md) for instructions on how to run client/server implementations,
|
||||
and the end-to-end tester.
|
||||
|
||||
|
||||
You have the choice of one of six programming language implementations:
|
||||
please choose the language you’re most comfortable in,
|
||||
and try running the client/server implementation for that language,
|
||||
and then run the end-to-end tester to successful completion.
|
||||
|
||||
### Specification
|
||||
|
||||
This specification is just for your reference.
|
||||
The provided implementations are based on this specification.
|
||||
|
||||
|
||||
#### Client/Server Protocol
|
||||
|
||||
* The protocol is line-based i.e. a LF control character terminates each message.
|
||||
All strings are encoded in UTF-8.
|
||||
* Data is transmitted over TCP sockets.
|
||||
* The _event source_ connects on port 9090 and will start sending events as soon as the connection is accepted.
|
||||
* The _user clients_ connect on port 9099 and identify themselves with their user ID.
|
||||
For example, once connected a _user client_ may send down `2932\r\n`,
|
||||
indicating that it is representing user 2932.
|
||||
* After the identification is sent,
|
||||
the user client starts waiting for events to be sent to them by the server.
|
||||
|
||||
#### Events
|
||||
|
||||
There are five possible events.
|
||||
The table below describes payloads sent by the event source and what each represents:
|
||||
|
||||
```
|
||||
| Payload | Sequence # | Type | From User Id | To User Id |
|
||||
|---------------|------------|---------------|--------------|------------|
|
||||
| 666|F|60|50 | 666 | Follow | 60 | 50 |
|
||||
| 1|U|12|9 | 1 | Unfollow | 12 | 9 |
|
||||
| 542532|B | 542532 | Broadcast | - | - |
|
||||
| 43|P|32|56 | 43 | Private Msg | 32 | 56 |
|
||||
| 634|S|32 | 634 | Status Update | 32 | - |
|
||||
```
|
||||
|
||||
#### Event Rules
|
||||
|
||||
* Each message begins with a sequence number.
|
||||
* However, the event source _does not send events in any given order_.
|
||||
In particular, sequence number has no effect on the order in which events are sent.
|
||||
* Events _may_ generate notifications for _user clients_.
|
||||
If there is a respective target _user client_ connected,
|
||||
the notification is routed according to the following rules:
|
||||
* Follow: Only the `To User Id` is notified
|
||||
* A follow event from user A to user B means that user A now follows user B.
|
||||
* Unfollow: No clients are notified
|
||||
* An unfollow event from user A to user B means that user A stopped following user B.
|
||||
* Broadcast: All connected _user clients_ are notified
|
||||
* Private Message: Only the `To User Id` is notified
|
||||
* Status Update: All current followers of the `From User ID` are notified
|
||||
* If there are no _user clients_ connected for a user,
|
||||
any notifications for them are currently ignored.
|
||||
_User clients_ are notified of events _in the correct sequence-number order_,
|
||||
regardless of the order in which the _event source_ sent them.
|
||||
|
||||
### Challenge
|
||||
We have provided the client and server code - this is your starting point.
|
||||
However, the code is in poor shape:
|
||||
it’s condensed into giant blocks of code in a single file,
|
||||
making it hard for you to understand, debug, and test.
|
||||
**Your task is to refactor the code so that it’s _easy to test,
|
||||
easy to understand and easy to maintain._**
|
||||
|
||||
### Guidance
|
||||
|
||||
**Time budget:** We roughly estimate this portion will take between 2-6 hours.
|
||||
|
||||
The **primary goals** of this exercise are:
|
||||
* To provide structure and clarity to the server code base by introducing appropriate abstractions.
|
||||
* To produce code that accommodates change and is easy to test.
|
||||
* To maintain correctness of the server program.
|
||||
* To document your approach.
|
||||
|
||||
Some **non-goals**:
|
||||
* To introduce functionality that was not asked for.
|
||||
* To make the solution production-ready.
|
||||
* **HOWEVER** we ARE interested in what top improvements you think need to be made
|
||||
to make it production-ready (see README details below).
|
||||
* To write unit tests; your focus should be on writing testable code first,
|
||||
and while you're free to write unit tests with this part of the exercise,
|
||||
it is not a stated requirement here.
|
||||
|
||||
## Part 2: Follower Maze - Extension
|
||||
|
||||
### The Problem
|
||||
|
||||
In Part 1, we asked you to refactor an existing solution to the follower-maze problem.
|
||||
The original specification of the problem leaves the question of bad event data unanswered,
|
||||
and it simplifies the case of undeliverable notifications in case of user disconnects.
|
||||
Your task is to **add a [Dead Letter Queue (DLQ)](https://en.wikipedia.org/wiki/Dead_letter_queue)
|
||||
to the server**, so that undeliverable or malformed messages aren't lost.
|
||||
|
||||
“Dead letters” build up and ultimately must be dealt with.
|
||||
For the purposes of this interview problem,
|
||||
it's OK to simply log them to the console.
|
||||
But please **include a short explanation in your README** of how you would store and process
|
||||
that data in a production scenario.
|
||||
|
||||
|
||||
### Guidance
|
||||
|
||||
**Time budget:** We roughly estimate this portion will take between 1-2 hours.
|
||||
|
||||
The **primary goals** of this exercise are:
|
||||
* To introduce a DLQ implementation
|
||||
* To capture messages that fall outside of the original specification of the protocol, for example:
|
||||
* Malformed messages
|
||||
* New message types not yet supported by the server
|
||||
* To capture messages that cannot be delivered because the target user is not connected
|
||||
* To write unit tests demonstrating the DLQ operates as specified
|
||||
* To explain your approach to handling “dead letters” in production, in your README
|
||||
|
||||
Some **non-goals**:
|
||||
|
||||
* To introduce functionality or improvements that were not asked for
|
||||
* To make the solution production-ready
|
||||
(but you're welcome to comment on that aspect in your README file)
|
||||
|
||||
## Constraints
|
||||
|
||||
* Pick one of the six programming languages,
|
||||
and base your solution on that language’s initial implementation.
|
||||
* Please restrict yourself to the standard library - don’t introduce 3rd party libraries.
|
||||
**Exception:** libraries that facilitate unit testing are acceptable.
|
||||
|
||||
## Deliverables
|
||||
|
||||
### Deliverables For Part 1
|
||||
|
||||
* Your refactored version of the code.
|
||||
* A README file detailing:
|
||||
* The approach you took and reasoning you applied to arrive at your final solution.
|
||||
* Explanation of any design trade-offs or short-cuts you may have taken.
|
||||
* How to run the server, in case you made changes to the setup we provided.
|
||||
* The top priorities you would have for additions or modifications to make the solution production-ready.
|
||||
|
||||
_Remember: unit tests are not required for Part 1_
|
||||
|
||||
### Deliverables For Part 2
|
||||
* A working implementation of a DLQ that handles messages falling into the categories specified above.
|
||||
* Unit tests demonstrating the newly added functionality is working as specified.
|
||||
* In your README:
|
||||
* The approach you took and reasoning you applied to arrive at your final solution;
|
||||
pay particular attention to design trade-offs or short-cuts you may have taken.
|
||||
* Explanation of what more you would do with DLQ messages in a production scenario
|
||||
(i.e. aside from simply logging them to the console).
|
||||
|
||||
## Tips
|
||||
* Run the end-to-end tester regularly as you change code.
|
||||
* We're looking for a simple, straight-forward, clean & concise solution.
|
||||
* If you have technical questions, please ask!
|
||||
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
**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`
|
|
@ -1,3 +0,0 @@
|
|||
module followermaze
|
||||
|
||||
go 1.13
|
|
@ -1,187 +0,0 @@
|
|||
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))
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
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"
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
@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
|
|
@ -1,153 +0,0 @@
|
|||
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();
|
||||
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
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}`);
|
||||
});
|
|
@ -1,988 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
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()
|
|
@ -1,106 +0,0 @@
|
|||
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
|
|
@ -1,3 +0,0 @@
|
|||
name := "maze"
|
||||
|
||||
scalaVersion := "2.12.8"
|
|
@ -1 +0,0 @@
|
|||
sbt.version = 1.2.8
|
|
@ -1,140 +0,0 @@
|
|||
package com.soundcloud.maze
|
||||
|
||||
import java.io._
|
||||
import java.net.{ServerSocket, Socket}
|
||||
|
||||
import scala.collection.concurrent.TrieMap
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration.Duration
|
||||
import scala.concurrent.{Await, ExecutionContext, Future}
|
||||
import scala.io.Source
|
||||
import scala.util.Try
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
object Main {
|
||||
|
||||
private val EventPort = 9090
|
||||
private val ClientPort = 9099
|
||||
|
||||
private var lastSeqNo = 0L
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
|
||||
val clientPool = new TrieMap[Long, Socket]
|
||||
|
||||
val messagesBySeqNo = new mutable.HashMap[Long, List[String]]
|
||||
val followRegistry = new mutable.HashMap[Long, Set[Long]]
|
||||
|
||||
implicit val ec = ExecutionContext.global
|
||||
|
||||
val eventsAsync = Future {
|
||||
|
||||
println(s"Listening for events on $EventPort")
|
||||
val eventSocket = new ServerSocket(EventPort).accept()
|
||||
|
||||
Try {
|
||||
val reader = new BufferedReader(new InputStreamReader(eventSocket.getInputStream()))
|
||||
|
||||
Try {
|
||||
reader.lines().iterator().asScala.foreach { payload =>
|
||||
println(s"Message received: $payload")
|
||||
val message = payload.split("\\|").toList
|
||||
|
||||
messagesBySeqNo += message(0).toLong -> message
|
||||
|
||||
while (messagesBySeqNo.get(lastSeqNo + 1L).isDefined) {
|
||||
val nextMessage = messagesBySeqNo(lastSeqNo + 1)
|
||||
|
||||
messagesBySeqNo -= lastSeqNo + 1L
|
||||
|
||||
val nextPayload = nextMessage.mkString("|")
|
||||
val seqNo = nextMessage(0).toLong
|
||||
val kind = nextMessage(1)
|
||||
|
||||
kind match {
|
||||
case "F" =>
|
||||
val fromUserId = nextMessage(2).toLong
|
||||
val toUserId = nextMessage(3).toLong
|
||||
val followers = followRegistry.getOrElse(toUserId, Set.empty)
|
||||
val newFollowers = followers + fromUserId
|
||||
|
||||
followRegistry.put(toUserId, newFollowers)
|
||||
|
||||
clientPool.get(toUserId).foreach { socket =>
|
||||
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
|
||||
writer.write(s"$nextPayload\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
case "U" =>
|
||||
val fromUserId = nextMessage(2).toLong
|
||||
val toUserId = nextMessage(3).toLong
|
||||
val followers = followRegistry.getOrElse(toUserId, Set.empty)
|
||||
val newFollowers = followers - fromUserId
|
||||
|
||||
followRegistry.put(toUserId, newFollowers)
|
||||
|
||||
case "P" =>
|
||||
val toUserId = nextMessage(3).toLong
|
||||
|
||||
clientPool.get(toUserId).foreach { socket =>
|
||||
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
|
||||
writer.write(s"$nextPayload\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
case "B" =>
|
||||
clientPool.values.foreach { socket =>
|
||||
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
|
||||
writer.write(s"$nextPayload\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
case "S" =>
|
||||
val fromUserId = nextMessage(2).toLong
|
||||
val followers = followRegistry.getOrElse(fromUserId, Set.empty)
|
||||
|
||||
followers.foreach { follower =>
|
||||
clientPool.get(follower).foreach { socket =>
|
||||
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
|
||||
writer.write(s"$nextPayload\n")
|
||||
writer.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSeqNo = seqNo
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reader != null) reader.close()
|
||||
}
|
||||
if (eventSocket != null) eventSocket.close()
|
||||
}
|
||||
|
||||
val clientsAsync = Future {
|
||||
|
||||
println(s"Listening for client requests on $ClientPort")
|
||||
val serverSocket = new ServerSocket(ClientPort)
|
||||
var maybeClientSocket = Option(serverSocket.accept())
|
||||
|
||||
while (maybeClientSocket.nonEmpty) {
|
||||
maybeClientSocket.foreach { clientSocket =>
|
||||
val bufferedSource = Source.fromInputStream(clientSocket.getInputStream())
|
||||
val userId = bufferedSource.bufferedReader().readLine()
|
||||
|
||||
if (userId != null) {
|
||||
clientPool.put(userId.toLong, clientSocket)
|
||||
println(s"User connected: $userId (${clientPool.size} total)")
|
||||
}
|
||||
|
||||
maybeClientSocket = Option(serverSocket.accept())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Await.result(Future.sequence(Seq(eventsAsync, clientsAsync)), Duration.Inf)
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
|
@ -1,6 +0,0 @@
|
|||
#!/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"
|
|
@ -1,6 +0,0 @@
|
|||
#!/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"
|
Loading…
Add table
Add a link
Reference in a new issue