|
| 1 | +# WV Linewise |
| 2 | + |
| 3 | +## The Potential User |
| 4 | + |
| 5 | +You're a software developer and UNIX system administrator. |
| 6 | + |
| 7 | +You loooove the command line. |
| 8 | + |
| 9 | +You have probably looked at GNU Plot because it could draw graphs on the terminal but feel it's output isn't suitable for you. |
| 10 | + |
| 11 | +Also you write your SQL in VIM and have it integrated with TMUX to send your query to `psql` you get mildly annoyed because of the lack of horizontal scrolling. |
| 12 | + |
| 13 | +Maybe you use i3... |
| 14 | + |
| 15 | +You write software for a living, sometimes you write great code. Your code is sometimes deployed into big integrated projects but sometimes it's only about getting data from A to B, a one off and while it works, you wonder if `BLAH BLAH | SOMETHING | X | jq BLAH | Y | Z` might have worked, and may have been faster if only you could add some interactivity to X. |
| 16 | + |
| 17 | +You need one foot in both worlds... how... |
| 18 | + |
| 19 | +## What it does |
| 20 | + |
| 21 | +[Boscop's Web-View](https://github.com/Boscop/web-view) has provided us with a lightweight library for showing HTML/CSS/JS within a window, using the OS's standard browser. If you know HTML/CSS/JS but shudder at the weight of Electron, this may be your thing. As a bonus, it's written in Rust too! |
| 22 | + |
| 23 | +This project provides some Rust functions for streaming STDIN or files into Boscop's Web-View and a higher level TypeScript / JavaScript API to pull them in as a stream of lines. You can also write to STDOUT using this API enabling you to put something like a web page, right in the middle of a UNIX pipeline. I find this quite exciting. |
| 24 | + |
| 25 | +Can you think of any things which you would like to do using UNIX pipes which you think you may want to add some interactivity or graphics to? I can think of many... |
| 26 | + |
| 27 | +## Example |
| 28 | + |
| 29 | +This program could be invoked like the following: |
| 30 | + |
| 31 | + |
| 32 | + # Prepare lookups.csv |
| 33 | + echo '"First Name","Telephone Number"' > lookups.csv |
| 34 | + echo 'Alice,"01922 123 456"' >> lookups.csv |
| 35 | + echo 'Ben,"0800 100 232"' >> lookups.csv |
| 36 | + echo 'Jack,"01882 556216"' >> lookups.csv |
| 37 | + |
| 38 | + # write a file that we will pipe to stdin |
| 39 | + echo 'Name,Age' > stdin.csv |
| 40 | + echo 'Ben,21' >> stdin.csv |
| 41 | + echo 'Alice,31' >> stdin.csv |
| 42 | + echo 'Jane,27' >> stdin.csv |
| 43 | + |
| 44 | + cat stdin.csv | wv-linewise \ |
| 45 | + --code tables \ |
| 46 | + --stream this_is_stdin=- \ |
| 47 | + --stream lookups=lookups.tsv |
| 48 | + --param this_is_stdin='["Name"]' \ |
| 49 | + --param lookups='["First Name"]' \ |
| 50 | + --param request_count=2 |
| 51 | + |
| 52 | + |
| 53 | +The data input above would cause the following messages to be sent between WV Linewise and the embedded web page. |
| 54 | + |
| 55 | + < { "msg": "params" } |
| 56 | + > { "type": "params", "params": [{ "name": "this_is_stdin", value: "[\"Name\"]"}, {"name": "lookups": "value": "[\"First Name\"]"}] } |
| 57 | + < {"msg":"streamList"} |
| 58 | + > {"streams":["this_is_stdin","lookups"],"type":"streamList"} |
| 59 | + < { "msg": "out", "descriptor": 1, "data": "Line Number,Name,Age,Telephone Number" } |
| 60 | + < { "msg": "streamStart", "name": "this_is_stdin", "count": 2 } |
| 61 | + < { "msg": "streamStart", "name": "lookups", "count": 2 } |
| 62 | + > { "type": "details", "name": "this_is_stdin", "details": { "rewindable": false } |
| 63 | + > { "type": "details", "name": "lookups", "details": { "rewindable": false } |
| 64 | + > { "type": "line", "name": "this_is_stdin", "data": "Name,Age" } |
| 65 | + > { "type": "line", "name": "this_is_stdin", "data": "Ben,21" } |
| 66 | + > { "type": "paused", "name": "this_is_stdin" } |
| 67 | + > { "type": "line", "name": "lookups", "data": "\"First Name\",\"Telephone Number\"" } |
| 68 | + < { "msg": "streamContinue", "name": "this_is_stdin" } |
| 69 | + > { "type": "line", "name": "lookups", "data": "Alice,\"01922 123 456\"" } |
| 70 | + > { "type": "paused", "name": "lookups" } |
| 71 | + > { "type": "line", "name": "this_is_stdin", "data": "Alice,31" } |
| 72 | + < { "msg": "out", "descriptor": 1, "data": "2,Alice,31,\"01922 123 456\"" } |
| 73 | + > { "type": "line", "name": "this_is_stdin", "data": "Jane,27" } |
| 74 | + > { "type": "finished", "name": "this_is_stdin" } |
| 75 | + < { "msg": "streamContinue", "name": "lookups" } |
| 76 | + > { "type": "line", "name": "lookups", "data": "Ben,\"0800 100 232\"" } |
| 77 | + < { "msg": "out", "descriptor": 1, "data": "1,Ben,21,\"0800 100 232"" } |
| 78 | + > { "type": "line", "name": "lookups", "data": "Jack,\"01882 556216\"" } |
| 79 | + > { "type": "finished", "name": "lookups" } |
| 80 | + < { "msg": "exit", "status": 0 } |
| 81 | + |
| 82 | +NOTE: `<` are messages from TypeScript / JavaScript to WV Linewise, `>` are the responses. |
| 83 | + |
| 84 | +WV Linewise will then exit with a status code of 0 and the following data will have already been written to STDOUT: |
| 85 | + |
| 86 | + Line Number,Name,Age,Telephone Number |
| 87 | + 2,Alice,31,"01922 123 456" |
| 88 | + 1,Ben,21,"0800 100 232" |
| 89 | + |
| 90 | + |
| 91 | +## APIs |
| 92 | + |
| 93 | +There are two TypeScript / JavaScript APIs I created to control the sending / receiving of messages. These are listed below: |
| 94 | + |
| 95 | +### The original Light Wrapper API |
| 96 | + |
| 97 | +If for whatever reason you don't want to use the Buffer API, the original API is a light weight message / event based API. In writing it I was merely trying to add some type safety over the raw messages. See the example below |
| 98 | + |
| 99 | +```typescript |
| 100 | +import { RawWvLinewise, WvLinewise, RESPONSE_TYPE, MessageErrorResponse, ErrorResponse, ParamsResponse, LineResponse, PausedResponse } from "wv-linewise-js-lib"; |
| 101 | + |
| 102 | +async function processLightWeight() { |
| 103 | + |
| 104 | +let lineCount = 0; |
| 105 | +const wvl: WvLinewise = new WvLinewise(new RawWvLinewise(external as any)); |
| 106 | + |
| 107 | +// Upon error, just raise it so it's caught by the global error handler. |
| 108 | +wvl.on(RESPONSE_TYPE.MESSAGE_ERROR, (msg: MessageErrorResponse) => { |
| 109 | + throw new Error(`MSG ERROR: ${JSON.stringify(msg)}`) |
| 110 | +}); |
| 111 | + |
| 112 | +// Upon error, just raise it so it's caught by the global error handler. |
| 113 | +wvl.on(RESPONSE_TYPE.ERROR, (msg: ErrorResponse) => { |
| 114 | + throw new Error(`MSG ERROR: ${JSON.stringify(msg)}`) |
| 115 | +}); |
| 116 | + |
| 117 | +// Request the parameters the user passed in on the command line |
| 118 | +function getParams(wvl: WvLinewise): Promise<ParamsResponse> { |
| 119 | + return new Promise((resolve) => { |
| 120 | + let f = (resp: ParamsResponse) => { |
| 121 | + resolve(resp); |
| 122 | + }; |
| 123 | + wvl.once(RESPONSE_TYPE.PARAMS, f); |
| 124 | + wvl.requestParams(); |
| 125 | + }); |
| 126 | +} |
| 127 | + |
| 128 | +function getRequestQuantity(paramsResponse: ParamsResponse): number { |
| 129 | + for (let p of paramsResponse.params) { |
| 130 | + if (p.name == "quantity") { |
| 131 | + return parseInt(p.value, 10); |
| 132 | + } |
| 133 | + } |
| 134 | + return 1000; |
| 135 | +} |
| 136 | + |
| 137 | +// Because all of our code is blocking (we're not waiting for animations etc) |
| 138 | +// we're going to have processed the data immediately, so when WV Linewise |
| 139 | +// pauses we can just start it right up again. |
| 140 | +wvl.on(RESPONSE_TYPE.PAUSED, (resp: PausedResponse) => { |
| 141 | + if (resp.name == "in") { |
| 142 | + wvl.streamContinue("in"); |
| 143 | + } |
| 144 | +}); |
| 145 | + |
| 146 | +// This function will get fired on every line, with the line that came from |
| 147 | +// the "in" stream, which could be STDIN or a file. |
| 148 | +wvl.on(RESPONSE_TYPE.LINE, (resp: LineResponse) => { |
| 149 | + if (resp.name == "in") { |
| 150 | + lineCount = lineCount + 1; |
| 151 | + } |
| 152 | + document.body.innerText = `The file has ${lineCount} lines` |
| 153 | +}); |
| 154 | + |
| 155 | + |
| 156 | +// Start WV Linewise processing lines |
| 157 | +wvl.streamStart("in", getRequestQuantity(await getParams(wvl))); |
| 158 | + |
| 159 | +} |
| 160 | + |
| 161 | +processLightWeight(); |
| 162 | +``` |
| 163 | + |
| 164 | +### The buffer API |
| 165 | + |
| 166 | +The WvLinewiseBuffer will allow you to disregard the messages for the purposes of reading the streams. It uses a low watermark and a quantity to request (3rd and 4th parameters) to try and make sure there's always lines available. Because it's built upon Promises it can handle situations where the buffer is empty and not fail. |
| 167 | + |
| 168 | +```typescript |
| 169 | +async function processBuffer(wvl: WvLinewise) { |
| 170 | + |
| 171 | + let buffer = new WvLinewiseBuffer(wvl, "in", 100, 200); |
| 172 | + let line: string|null = ""; |
| 173 | + let lineCount = 0; |
| 174 | + |
| 175 | + while (line !== null) { |
| 176 | + line = await buffer.shift(); |
| 177 | + if (line === null) { |
| 178 | + continue; |
| 179 | + } |
| 180 | + document.body.innerText = `The file has ${lineCount} lines and the last line was ${line}`; |
| 181 | + } |
| 182 | +} |
| 183 | + |
| 184 | +const wvl: WvLinewise = new WvLinewise(new RawWvLinewise(external as any)); |
| 185 | +processBuffer(wvl); |
| 186 | +``` |
| 187 | + |
| 188 | +## Example Applications |
| 189 | + |
| 190 | +### [Discover Types](./examples/discover-types) |
| 191 | + |
| 192 | +#### About |
| 193 | +DiscoverTypes is an application for identifying the types of fields within a CSV file. It does this by comparing every cell against every regular expression the user has supplied. Because the CSV file could be HUGE it does not load the whole thing into memory but inspects it line by line as it passes through. |
| 194 | + |
| 195 | +#### Screenshot |
| 196 | + |
| 197 | + |
| 198 | + |
0 commit comments