SpaceX-cli version 1.0 :D

This commit is contained in:
Peter 2021-01-08 03:11:49 +08:00
parent b7a3d5b703
commit 9158e60476
11 changed files with 514 additions and 103 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
~/
etc/
node_modules/
#trademark reasons.

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# SpaceX-cli
## View upcoming launches in the terminal
## Write a readme for this later...
`npm install -g npc-strider/spacex-cli`
This is an enhanced version of a basic CLI bash script I was using.
Because the bash script was based on the v3 [spacexdata](https://github.com/r-spacex/SpaceX-API) api, I was forced to upgrade it to use the new v4 api.
So I decided to not just upgrade the bash script to use the new api, but also add some new features and more interactibility.
The cli isn't pretty like some others, but I think it's quite dense in relevant information.
*I'm not including space.ico in my releases to prevent trademark infringement :/ sorry.

View File

@ -1,11 +1,16 @@
const os = require('os');
const clc = require('cli-color');
const arguments = require('./tools/process-args').arguments;
if (arguments.color) {
this.STYLES = {scrollbar: {bg: 'blue'}}
if (!arguments.dump) {
if (arguments.color) {
this.STYLES = {scrollbar: {bg: 'blue'}};
} else {
this.STYLES = {scrollbar: {bg: 'white'}};
};
} else {
this.STYLES = {scrollbar: {bg: 'white'}}
this.STYLES = {scrollbar: null};
};
const override_deep = (o,v) => {
@ -20,14 +25,34 @@ const override_deep = (o,v) => {
return o_
};
this.CONTROL_MAX_WIDTH = 0;
const registerKeys = ((keys, actions) => {
var strArr = [];
for (i = 0; i < keys.length; ++i) {
if (arguments.color) {
strArr.push(keys[i]+' '+clc.underline(actions[i]));
} else {
strArr.push(keys[i]+' '+actions[i]);
};
};
const str = strArr.join(' | ');
const d = arguments.color ? 9 : 0;
if (str.length - i*d > this.CONTROL_MAX_WIDTH) {
this.CONTROL_MAX_WIDTH = str.length - i*d;
};
return str;
});
this.CONSTANT_VALUES = {
TIME_NOTIFY: 60*60*24*7, //FIXME: TESTING VALUES - Change to 60*90
TIME_BLINK: 60*60*24*7 //FIXME: TESTING VALUES - Change to 60*60*24
TIME_NOTIFY: arguments.notify_time,//60*60*24*7,
TIME_BLINK: arguments.highlight_time,
DATA_PATH: arguments.path.replace('~',os.homedir),
}
this.GETCOLORS = ((clc_) => {
var COLOR = {
NONE: ((t) => {return t}),
BLINK: clc_.blink,
GENERIC: clc_.cyan,
SUCCESS: clc_.green,
HUGE_SUCCESS: clc_.green.bold,
@ -35,14 +60,14 @@ this.GETCOLORS = ((clc_) => {
DANGER: clc_.red.bold,
INVALID: clc_.blackBright.bold,
PROGRESS: clc_.yellow,
HEADER: clc_.underline,
HEADER: clc_.magentaBright.underline,
TIME: {
LT1: clc_.green,
LTD: clc_.green,
LTM: clc_.yellow,
LTQ: clc_.blackBright.bold,
LTH: clc_.blackBright.bold
}
},
};
if (!arguments.color) {
@ -62,28 +87,49 @@ this.GETSTRING = ((COLOR_) => {
PRECISION: COLOR_.HEADER("Precision"),
FLAGS: COLOR_.HEADER("Flags"),
ROCKET: COLOR_.HEADER("Rocket type"),
CORE: COLOR_.HEADER("Core (№. reused)"),
CORE: COLOR_.HEADER("Core(№ of reuses)"),
LAUNCHPAD: COLOR_.HEADER("Launchpad"),
LAUNCHPAD_REG: COLOR_.HEADER("Launch region"),
PAYLOAD_NAME: COLOR_.HEADER("Payloads"),
PAYLOAD_CUSTOMERS: COLOR_.HEADER("Payload customers"),
JSON: COLOR_.HEADER('Key/Value'),
KEY: COLOR_.HEADER('Property'),
VALUE: COLOR_.HEADER('Value'),
},
CONTROLS: {
TABLE: '↑↓ Scroll and select | ↵ → Select launch | r Refresh | q Quit',
INFORMATION: '↑↓ Scroll | q ↵ ← Return to table | j Toggle JSON view'
TABLE: registerKeys(['↑↓', '↵ →', 'r', 'q', 'd' ],
['Scroll and select', 'Select launch', 'Refresh', 'Quit', 'Diff']),
INFORMATION: registerKeys(['↑↓', 'q ↵ ←', 'j' ],
['Scroll', 'Return to table', 'Toggle JSON view']),
DIFF: registerKeys(['↑↓', 'q ↵ ←' ],
['Scroll', 'Return to table'])
},
CORE_UNASSIGNED: COLOR_.INVALID("Unknown"),
CORE_UNASSIGNED_: "Unknown",
DOWNLOADING: COLOR_.PROGRESS("Downloading new data from spacex api . . .",),
DOWNLOADING_STAR: COLOR_.PROGRESS("*"),
OK_GENERIC: COLOR_.SUCCESS("OK."),
OK_SCROLL: COLOR_.SUCCESS("OK. [!] Warning: Scroll down to view more"),
OK_SCROLL: COLOR_.SUCCESS("OK. [!] Scroll to view more"),
LAST_DELTA: COLOR_.GENERIC('Last Δ: '),
H_WARNING: "SpaceX launches imminent!",
NEW_DATA: "New SpaceX data downloaded!",
APPID: 'spacex-cli',
SCREEN_TITLE: 'Spacex - Upcoming lunches',
DIFF: {
PREVIOUS: 'prev',
CURRENT: 'curr',
PREVIOUS_SYMBOL: '[-]',
CURRENT_SYMBOL: '[+]',
BOTH_SYMBOL: '[±]',
UNCHANGED_SYMBOL: '[=]',
UNCHANGED_SYMBOL_: ' '
}
};
})

5
package-lock.json generated
View File

@ -31,6 +31,11 @@
"type": "^1.0.1"
}
},
"diff": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w=="
},
"es5-ext": {
"version": "0.10.53",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",

View File

@ -1,11 +1,14 @@
{
"name": "spacex-cli",
"version": "0.1.0",
"version": "1.0.0",
"description": "SpaceX upcoming launch tracker",
"main": "spacex-cli.js",
"scripts": {
"test": "node spacex-cli"
},
"bin": {
"spacex-cli": "spacex-cli"
},
"keywords": [
"spacex",
"cli"
@ -14,6 +17,7 @@
"license": "GPL-3.0",
"dependencies": {
"cli-color": "^2.0.0",
"diff": "^5.0.0",
"mri": "^1.1.6",
"neo-blessed": "^0.2.0",
"node-fetch": "^2.6.1",

View File

@ -1,10 +1,11 @@
#!/usr/bin/env node
const clc = require('cli-color');
const notifier = require('node-notifier');
const blessed = require('neo-blessed');
const Diff = require('diff');
const path = require('path');
const arguments = require('./tools/process-args').arguments;
const cli_elem = require('./tools/cli-elements');
const cli_elem = require('./tools/cli-elements'); //Idk js package structure/conventions...
const date_tools = require('./tools/date-tools');
const net_tools = require('./tools/net-tools');
const format_tools = require('./tools/format-tools');
@ -16,6 +17,8 @@ const CONSTANT_VALUES = constants.CONSTANT_VALUES;
var screen = cli_elem.screen;
var table_element = cli_elem.table_element;
var diff_table_element = cli_elem.diff_table_element;
var diff_json_element = cli_elem.diff_json_element;
var status_element = cli_elem.status_element;
var countdown_element = cli_elem.countdown_element;
var controls_element = cli_elem.controls_element;
@ -30,7 +33,6 @@ const prettyPrintData = format_tools.prettyPrintData;
// =====================================
//TODO: archive data option
//TODO: better viewer (than json and the crappy one I made using only string manipulation.)
process.env.FORCE_COLOR = true;
@ -41,12 +43,14 @@ const SCREEN_REFRESH = arguments.screen_refresh; //ms
const API_REFRESH = arguments.api_refresh; //ms
const API_REFRESH_CYCLES = API_REFRESH/SCREEN_REFRESH;
const NONINTERACTIVE = arguments.dump;
const BLINK = arguments.blink;
const ARCHIVE = arguments.archive;
//
var notified_1h = [];
var notifying_1h = [];
async function formatData(arr) {
function selectData(arr) {
const LUNCHPADS_RESP = arr[0];
const LUNCHES_RESP = arr[1];
const ROCKETS_RESP = arr[2];
@ -95,10 +99,13 @@ async function formatData(arr) {
payload_customers_str = payload_customers_str.substr(0, payload_customers_str.length-3)
var cores_str = "";
var cores_str_ = "";
lunch.cores.forEach(core_ => {
cores_str += CORES[core_["core"]] ? (CORES[core_["core"]]["serial"] + '(' + CORES[core_["core"]]["reuse_count"] + ') ') : STRING.CORE_UNASSIGNED + ' ';
cores_str_ += CORES[core_["core"]] ? (CORES[core_["core"]]["serial"] + '(' + CORES[core_["core"]]["reuse_count"] + ') ') : STRING.CORE_UNASSIGNED_ + ' ';
});
cores_str = cores_str.substr(0, cores_str.length-1)
cores_str = cores_str.substr(0, cores_str.length-1);
cores_str_ = cores_str_.substr(0, cores_str_.length-1);
var time_style = {
hour: { p:"minutes", c: COLOR.TIME.LT1 },
@ -108,82 +115,70 @@ async function formatData(arr) {
half: { p:"days", c: COLOR.TIME.LTH }
}[precision]
const dt_s = t_s - new Date().getTime() / 1000;
var dt_str = secondsHumanReadable(dt_s,time_style.p);
if (dt_s <= CONSTANT_VALUES.TIME_NOTIFY && precision == "hour" && !lunch.tbd && !lunch.net && !notified_1h.includes(lunch.flight_number)) { //Notify user.
const dt_str_ = secondsHumanReadable(dt_s,time_style.p);
var dt_str;
if (dt_s <= CONSTANT_VALUES.TIME_NOTIFY && precision === "hour" && !lunch.tbd && !lunch.net && !notified_1h.includes(lunch.flight_number)) { //Notify user.
notified_1h.push(lunch.flight_number)
notifying_1h.push(lunch.name)
}
if (dt_s <= CONSTANT_VALUES.TIME_BLINK && precision == "hour" && !lunch.tbd && !lunch.net) {
dt_str = COLOR.HUGE_SUCCESS("! "+clc.blink(dt_str))+COLOR.HUGE_SUCCESS(" !");
if (dt_s <= CONSTANT_VALUES.TIME_BLINK && precision === "hour" && !lunch.tbd && !lunch.net) {
dt_str = COLOR.HUGE_SUCCESS("! "+(BLINK ? COLOR.BLINK(dt_str_) : dt_str_))+COLOR.HUGE_SUCCESS(" !");
date_h = COLOR.HUGE_SUCCESS(date_arr.bw.join(''));
} else if (dt_s < 0) {
dt_str = COLOR.INVALID(dt_str);
dt_str = COLOR.INVALID(dt_str_);
date_h = COLOR.INVALID(date_arr.bw.join(''))+'{/}';
} else {
dt_str = time_style.c(dt_str)
dt_str = time_style.c(dt_str_)
date_h = date_arr.col.join('');
}
const precision_ = precision;
precision = time_style.c(precision)
return {
name: lunch.name,
flight_number: lunch.flight_number,
flight_number: String(lunch.flight_number),
date_precision: precision,
date_precision_:precision_,
tbd: lunch.tbd,
net: lunch.net,
date_h: date_h,
date_h_: date_arr.bw.join(''),
rocket: ROCKETS[lunch.rocket],
cores: cores_str,
cores_: cores_str_,
launchpad: lunchpad.name,
launchpad_reg: lunchpad.region,
dt: dt_str,
dt_: dt_str_,
payloads: {
names_str: payload_names_str,
customers_str: payload_customers_str
}
}
});
return LUNCHES;
};
async function formatData(arr) {
return format_tools.tabularizeData(selectData(arr));
};
LUNCHES = LUNCHES.map(lunch => [
COLOR.GENERIC(lunch.flight_number),
COLOR.GENERIC(lunch.name),
lunch.date_h,
lunch.dt,
String(lunch.date_precision),
COLOR.DANGER((lunch.tbd ? "tbd" : "")+(lunch.net && lunch.tbd ? ", " : "")+(lunch.net ? "net" : "")),
COLOR.GENERIC(lunch.rocket),
COLOR.GENERIC(lunch.cores),
COLOR.GENERIC(lunch.launchpad),
// lunch.launchpad_reg
COLOR.GENERIC(lunch.payloads.names_str),
COLOR.GENERIC(lunch.payloads.customers_str)
])
LUNCHES.unshift([
STRING.HEADERS.FLIGHT_NUMBER,
STRING.HEADERS.NAME,
STRING.HEADERS.DATE_H,
STRING.HEADERS.DT,
STRING.HEADERS.PRECISION,
STRING.HEADERS.FLAGS,
STRING.HEADERS.ROCKET,
STRING.HEADERS.CORE,
STRING.HEADERS.LAUNCHPAD,
// STRING.HEADERS.LAUNCHPAD_REG
STRING.HEADERS.PAYLOAD_NAME,
STRING.HEADERS.PAYLOAD_CUSTOMERS,
],[ ' ', ' ', ' ', ' ', ' ', ' ', ' ',' ', ' ', ' ', ' '])
return LUNCHES
}
var diff_table_content;
var diff_json_content;
var newData = false;
var json_view = false;
//Keep these keybind listeners in main // Quit on Escape, q, or Control-C.
screen.key(['escape', 'q', '', 'C-c', 'left'], (ch, key) => {
if (screen.focused.name == "table" && key.name != 'left') {
const name = screen.focused.name;
if (name === "table" && key.name !== 'left') {
console.log('\033[?25h');
return process.exit(0);
} else if (screen.focused.name == "detailed_information") {
} else if (name === "detailed_information" || name === "diff_table" || name === "diff_json") {
if (name === "diff_table" || name === "diff_json") {
newData = false;
};
table_element.setFront();
table_element.focus();
controls_element.setContent(STRING.CONTROLS.TABLE);
@ -191,12 +186,48 @@ screen.key(['escape', 'q', '', 'C-c', 'left'], (ch, key) => {
};
});
screen.key(['j'], (ch, key) => {
const name = screen.focused.name;
if (name === "detailed_information") {
json_view = (json_view ? false : true);
information_element.setData(prettyPrintData(data_cache, idx, json_view));
} else if (name === "diff_table") {
json_view = true;
diff_json_element.setFront();
diff_json_element.focus();
diff_json_element.enableInput();
} else if (name === "diff_json") {
json_view = false;
diff_table_element.setFront();
diff_table_element.focus();
diff_table_element.enableInput();
};
screen.render();
});
screen.key(['d'], (ch, key) => {
const name = screen.focused.name;
if (name !== "diff_table" && name !== "diff_json") {
// console.log(diff_table_content);
// process.kill(process.pid)
controls_element.setContent(STRING.CONTROLS.INFORMATION);
if (json_view) {
diff_json_element.setFront();
diff_json_element.focus();
diff_json_element.enableInput();
} else {
diff_table_element.setFront();
diff_table_element.focus();
diff_table_element.enableInput();
};
} else {
table_element.setFront();
table_element.focus();
controls_element.setContent(STRING.CONTROLS.TABLE);
};
screen.render();
});
screen.key(['enter', 'right'], (ch, key) => {
if (screen.focused.name == "table") {
const name = screen.focused.name;
if (name === "table") {
idx = table_element.getScroll()-2;
if (idx >= 0) {
status_element.setContent(String(data_cache[1][idx].name));
@ -206,7 +237,10 @@ screen.key(['enter', 'right'], (ch, key) => {
information_element.setData(prettyPrintData(data_cache, idx, json_view));
controls_element.setContent(STRING.CONTROLS.INFORMATION);
};
} else if (screen.focused.name == "detailed_information") {
} else if (key.name === 'enter' && (name === "detailed_information" || name === "diff_table" || name === "diff_json")) {
if (name === "diff_table" || name === "diff_json") {
newData = false;
};
table_element.setFront();
table_element.focus();
controls_element.setContent(STRING.CONTROLS.TABLE);
@ -218,6 +252,9 @@ screen.key(['r'], (ch, key) => {
});
var data_cache;
var checkDiff = true;
var firstRun = true;
var lastDelta = '';
var api_counter = 1;
(async function(){
@ -227,13 +264,16 @@ var api_counter = 1;
table_element.select(1);
status_element.setContent(STRING.DOWNLOADING);
countdown_element.setContent(STRING.DOWNLOADING_STAR);
diff_table_element.setData(diff_table_content ? diff_table_content : [['diff_table_content']]);
screen.render();
})()
async function main() {
api_counter--;
if (api_counter == 0) {
if (api_counter === 0) {
checkDiff = true;
status_element.setContent(STRING.DOWNLOADING);
countdown_element.setContent(STRING.DOWNLOADING_STAR);
screen.render();
@ -242,12 +282,13 @@ async function main() {
}
if (data_cache) {
var err_message = await formatErr(await data_cache);
if (err_message == true) {
if (err_message === true) {
const TABLE = await formatData(await data_cache);
const SCROLL = table_element.getScroll();
table_element.setData(TABLE);
table_element.select(SCROLL);
err_message = [( (data_cache[1].length - (table_element.height - 4)) > 0 ? STRING.OK_SCROLL : STRING.OK_GENERIC)];
err_message = [( (data_cache[1].length - (table_element.height - 4)) > 0 ? STRING.OK_SCROLL : STRING.OK_GENERIC) + (newData ? ' | '+COLOR.WARNING(STRING.NEW_DATA) : '')+' '+lastDelta];
};
if (notifying_1h.length > 0) {
notifier.notify({
title: STRING.H_WARNING,
@ -256,8 +297,7 @@ async function main() {
appID: STRING.APPID
});
notifying_1h = [];
}
}
};
if (NONINTERACTIVE) {
screen.remove(status_element);
screen.remove(countdown_element);
@ -272,17 +312,88 @@ async function main() {
}
status_element.setContent(err_message[api_counter%err_message.length]);
countdown_element.setContent("Next update in "+secondsHumanReadable(api_counter*SCREEN_REFRESH/1000));
}
if (checkDiff) {
checkDiff = false;
const path_prev_launches = path.join(CONSTANT_VALUES.DATA_PATH,'previous_data.json');
const path_prev_launches_table = path.join(CONSTANT_VALUES.DATA_PATH,'previous_data_table.json');
const curr_launches = data_cache[1];
var prev_launches = net_tools.readFile(path_prev_launches);
prev_launches = prev_launches !== '' ? JSON.parse(prev_launches) : [];
diff_json_content = [[STRING.HEADERS.JSON],['']];
// jsonDiff.diffString(prev_launches, curr_launches)
// .split('\n')
// .forEach(u => {diff_json_content.push([clc.cyan(u)])});
const json_diff_data = Diff.diffJson(prev_launches, curr_launches)
const changed = json_diff_data.some(chunk => {
return chunk.added || chunk.removed;
});
if (changed || firstRun) {
firstRun = false;
json_diff_data.forEach(chunk => {
if (chunk.added && chunk.removed) {//Not sure if this case is possible.
chunk.value.split('\\n').forEach(line => {[diff_json_content.push([COLOR.WARNING(STRING.DIFF.BOTH_SYMBOL+'│ '+line)])]});
} else if (chunk.added) {
chunk.value.split('\\n').forEach(line => {[diff_json_content.push([COLOR.SUCCESS(STRING.DIFF.CURRENT_SYMBOL+'│ '+line)])]});
} else if (chunk.removed) {
chunk.value.split('\\n').forEach(line => {[diff_json_content.push([COLOR.DANGER(STRING.DIFF.PREVIOUS_SYMBOL+'│ '+line)])]});
} else {
chunk.value.replace(/\n/gm,"_NEWLINE_") //Why the frick doesn't my regexp work when it works in a tester???? Using this workaround.
.replace(/_NEWLINE_\s\s\{(.*?)_NEWLINE_\s\s\},/gm, '_NEWLINE_ [...]')
.replace(/_NEWLINE_\s\s\{(.*?)_NEWLINE_]/m, '_NEWLINE_ [...]_NEWLINE_]')
.split('_NEWLINE_')
.forEach(line => {line !== '' ? [diff_json_content.push([COLOR.GENERIC(STRING.DIFF.UNCHANGED_SYMBOL_+'│ '+line)])] : null}); //Trailing newline results in a empty line - need to filter it out.
};
});
diff_json_content.push([''])
// console.log(json_diff_data);
// process.kill(process.pid)
// JSON.stringify((json_diff_data ? json_diff_data : ''),null,2)
// .split('\n')
// .forEach(u => {diff_json_content.push([clc.cyan(u)])});
var prev_launches_table = net_tools.readFile(path_prev_launches_table);
const curr_launches_table = format_tools.tabularizeDiffData(await selectData(await data_cache));
prev_launches_table = prev_launches_table !== '' ? JSON.parse(prev_launches_table) : Array(curr_launches_table.length).fill(Array(curr_launches_table[0].length).fill(''));
diff_table_content = format_tools.diffTable(prev_launches_table,curr_launches_table)
diff_table_element.setData(diff_table_content ? diff_table_content : [['diff_table_content']]);
diff_json_element.setData(diff_json_content ? diff_json_content : [['diff_json_content']]);
if (changed) {
const dateStr = new Date().toIsoArr().bw.join('');
lastDelta = '| '+STRING.LAST_DELTA+COLOR.GENERIC(dateStr);
newData = true;
notifier.notify({
title: STRING.NEW_DATA,
message: 'Press d to see table diff. Press j to see JSON diff.',
icon: path.join(__dirname, 'spacex.ico'),
appID: STRING.APPID
});
net_tools.writeFile(path_prev_launches, JSON.stringify(curr_launches));
net_tools.writeFile(path_prev_launches_table, JSON.stringify(curr_launches_table));
if (ARCHIVE) {
net_tools.writeFile(
path.join(CONSTANT_VALUES.DATA_PATH,'/archive/'+dateStr.replace(/:/g,'_').replace(' ','_')+'.json'),
JSON.stringify(curr_launches)
);
net_tools.writeFile(
path.join(CONSTANT_VALUES.DATA_PATH,'/archive/'+dateStr.replace(/:/g,'_').replace(' ','_')+'_table.json'),
JSON.stringify(curr_launches_table)
);
};
};
};
};
};
screen.render();
};
console.log('\033[?25l'); //ANSI code - hide cursor
setInterval(main, SCREEN_REFRESH);
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

View File

@ -8,7 +8,7 @@ const COLOR = constants.GETCOLORS(clc);
const STRING = constants.GETSTRING(COLOR);
const STYLES = constants.STYLES;
const control_element_width = STRING.CONTROLS.TABLE.length+8;
const control_element_width = constants.CONTROL_MAX_WIDTH+8;
this.screen = blessed.screen({
smartCSR: true,
@ -32,9 +32,9 @@ this.table_element = blessed.listtable({
style: {
scrollbar: STYLES.scrollbar
},
scrollbar: true,
scrollbar: !arguments.dump,
invertSelected: true,
})
});
this.status_element = blessed.box({
position : {
@ -102,10 +102,47 @@ this.information_element = blessed.listtable({//blessed.box({
name: 'detailed_information'
});
this.diff_json_element = blessed.listtable({//blessed.box({
align: 'left',
position: {
width: '100%',
height: this.screen.height-2,
},
style: {
scrollbar: STYLES.scrollbar
},
tags: true,
scrollbar: true,
scrollable: true,
border: 'line',
keys: true,
parent: this.screen,
invertSelected: true,
name: 'diff_json'
});
this.diff_table_element = blessed.listtable({
parent: this.screen,
border: 'line',
align: 'center',
keys: true,
width: "100%",
height: this.screen.height-2,
name: 'diff_table',
tags: true,
style: {
scrollbar: STYLES.scrollbar
},
scrollbar: true,
invertSelected: true,
});
this.screen.append(this.status_element);
this.screen.append(this.countdown_element);
this.screen.append(this.controls_element);
this.screen.append(this.information_element);
this.screen.append(this.diff_table_element);
this.screen.append(this.table_element);
this.table_element.setFront();
this.table_element.focus();

View File

@ -27,7 +27,7 @@ Date.prototype.toIsoArr = function(precision) {
const color = {
month: (['half','quarter'].includes(precision) ? COLOR.WARNING : ( ['month','day','hour'].includes(precision) ? COLOR.SUCCESS : COLOR.INVALID )),
day: ['day','hour'].includes(precision) ? COLOR.SUCCESS : COLOR.INVALID,
hour: (precision == "hour") ? COLOR.SUCCESS : COLOR.INVALID
hour: (precision === "hour") ? COLOR.SUCCESS : COLOR.INVALID
};
col = [
COLOR.SUCCESS(this.getFullYear()),
@ -51,16 +51,16 @@ Date.prototype.toIsoArr = function(precision) {
this.secondsHumanReadable = ((t, precision) => {
var seconds = parseInt(t, 10); // Convert float to int
var days = Math.floor(seconds / 86400);
if (precision == "days") { return days+" days" };
if (precision === "days") { return days+" days" };
var str = (days != 0 ? days+" days, " : "");
seconds -= days*86400;
var hours = Math.floor(seconds / 3600);
str += (hours+" hours")
if (precision == "hours") { return str };
if (precision === "hours") { return str };
seconds -= hours*3600;
var minutes = Math.floor(seconds / 60);
str += (", "+pad(minutes, " ")+" minutes")
if (precision == "minutes") { return str };
if (precision === "minutes") { return str };
seconds -= minutes*60;
return str+", "+pad(seconds, " ")+" seconds" //Let's not worry about padding the hours.. Doesn't change that much.
});

View File

@ -1,17 +1,21 @@
const clc = require('cli-color');
const constants = require('../constants');
const COLOR = constants.GETCOLORS(clc);
const STRING = constants.GETSTRING(COLOR);
this.prettyPrintData = ((data_cache, idx, json_view) => {
var data = Object.assign({},data_cache[1][idx]);
if (json_view) {
var output = [[clc.underline('Key/Value')]];
var output = [[STRING.HEADERS.JSON]];
JSON.stringify(data,null,2)
.split('\n')
.forEach(u => {output.push([clc.cyan(u)])});
return output
.forEach(u => {output.push([COLOR.GENERIC(u)])});
return output;
}
// data.rocket = data_cache[2].filter((rocket) => rocket.id == data.rocket) //I'm lazy right now - was going to add in some features to view the 'hashes' (other elements such as cores, rocket type, etc.), but holding off on it.
// console.log(JSON.stringify(data,null,2))
var output = [[clc.underline('Property'),clc.underline('Value')]];
var output = [[STRING.HEADERS.KEY,STRING.HEADERS.VALUE]];
JSON.stringify(data,null,2)
.split('\n')
.forEach(u => {
@ -24,7 +28,141 @@ this.prettyPrintData = ((data_cache, idx, json_view) => {
.replace('}','')
.replace('{','')
.split(/:(.+)/);
output.push([clc.yellow(val[0]),clc.cyan(val[1] ? val[1].replace(/,$/, '') : '')]);
output.push([COLOR.WARNING(val[0]),COLOR.GENERIC(val[1] ? val[1].replace(/,$/, '') : '')]);
});
return output;
});
this.deepCompare = ((A,B) => {
if (A.length === B.length) {
for ( var i = 0; i < A.length; i++ ) {
if (Array.isArray(A[i])) {
if (Array.isArray(B[i])) {
if (!this.deepCompare(A[i],B[i])) {
return false
};
} else {
return false;
};
} else {
if (A[i] !== B[i]) {
return false;
};
};
};
return true;
} else {
return false;
};
});
this.diffTable = ((A,B) => {
var diff_table = [
[
'',
STRING.HEADERS.FLIGHT_NUMBER,
STRING.HEADERS.NAME,
STRING.HEADERS.DATE_H,
STRING.HEADERS.PRECISION,
STRING.HEADERS.FLAGS,
STRING.HEADERS.ROCKET,
STRING.HEADERS.CORE,
STRING.HEADERS.LAUNCHPAD,
// STRING.HEADERS.LAUNCHPAD_REG
STRING.HEADERS.PAYLOAD_NAME,
STRING.HEADERS.PAYLOAD_CUSTOMERS,
],
[ ' ', ' ', ' ', ' ', ' ', ' ', ' ',' ', ' ', ' ', ' ']
];
for ( var i = 0; i < B.length; i++ ) {
var row = [[''],[''],Array(B[i].length).fill('')];
var mod = false;
for ( var j = 0; j < B[i].length; j++ ) {
var a = A[i][j];
var b = B[i][j];
if (a !== b) {
a = COLOR.DANGER(a);
b = COLOR.SUCCESS(b);
mod = true;
} else {
a = COLOR.GENERIC(a);
b = COLOR.GENERIC(b);
};
row[0].push(a);
row[1].push(b);
};
if (mod) {
row[0][0] = COLOR.DANGER(STRING.DIFF.PREVIOUS_SYMBOL + STRING.DIFF.PREVIOUS);
row[1][0] = COLOR.SUCCESS(STRING.DIFF.CURRENT_SYMBOL + STRING.DIFF.CURRENT);
} else {
row[0][0] = COLOR.WARNING(STRING.DIFF.UNCHANGED_SYMBOL + STRING.DIFF.PREVIOUS);
row[1][0] = COLOR.WARNING(STRING.DIFF.UNCHANGED_SYMBOL + STRING.DIFF.CURRENT);
};
diff_table = diff_table.concat(row);
};
return diff_table;
});
this.tabularizeDiffData = (LUNCHES => {
LUNCHES = LUNCHES.map(lunch => [
String(lunch.flight_number),
lunch.name,
lunch.date_h_,
lunch.date_precision_,
(lunch.tbd ? "tbd" : "")+(lunch.net && lunch.tbd ? ", " : "")+(lunch.net ? "net" : ""),
lunch.rocket,
lunch.cores_,
lunch.launchpad,
// lunch.launchpad_reg
lunch.payloads.names_str,
lunch.payloads.customers_str
]);
// LUNCHES.unshift([
// STRING.HEADERS.FLIGHT_NUMBER,
// STRING.HEADERS.NAME,
// STRING.HEADERS.DATE_H,
// STRING.HEADERS.DT,
// STRING.HEADERS.PRECISION,
// STRING.HEADERS.FLAGS,
// STRING.HEADERS.ROCKET,
// STRING.HEADERS.CORE,
// STRING.HEADERS.LAUNCHPAD,
// // STRING.HEADERS.LAUNCHPAD_REG
// STRING.HEADERS.PAYLOAD_NAME,
// STRING.HEADERS.PAYLOAD_CUSTOMERS,
// ],[ ' ', ' ', ' ', ' ', ' ', ' ', ' ',' ', ' ', ' ', ' ']);
return LUNCHES;
})
this.tabularizeData = (LUNCHES => {
LUNCHES = LUNCHES.map(lunch => [
COLOR.GENERIC(String(lunch.flight_number)),
COLOR.GENERIC(lunch.name),
lunch.date_h,
lunch.dt,
lunch.date_precision,
COLOR.DANGER((lunch.tbd ? "tbd" : "")+(lunch.net && lunch.tbd ? ", " : "")+(lunch.net ? "net" : "")),
COLOR.GENERIC(lunch.rocket),
COLOR.GENERIC(lunch.cores),
COLOR.GENERIC(lunch.launchpad),
// lunch.launchpad_reg
COLOR.GENERIC(lunch.payloads.names_str),
COLOR.GENERIC(lunch.payloads.customers_str)
]);
LUNCHES.unshift([
STRING.HEADERS.FLIGHT_NUMBER,
STRING.HEADERS.NAME,
STRING.HEADERS.DATE_H,
STRING.HEADERS.DT,
STRING.HEADERS.PRECISION,
STRING.HEADERS.FLAGS,
STRING.HEADERS.ROCKET,
STRING.HEADERS.CORE,
STRING.HEADERS.LAUNCHPAD,
// STRING.HEADERS.LAUNCHPAD_REG
STRING.HEADERS.PAYLOAD_NAME,
STRING.HEADERS.PAYLOAD_CUSTOMERS,
],[ ' ', ' ', ' ', ' ', ' ', ' ', ' ',' ', ' ', ' ', ' ']);
return LUNCHES;
});

View File

@ -1,4 +1,7 @@
const fs = require('fs');
const path = require('path');
const fetch = require('node-fetch');
const { CONSTANT_VALUES } = require('../constants');
async function fetchURLJSON(url) {
var resp;
@ -12,6 +15,36 @@ async function fetchURLJSON(url) {
return resp;
};
this.writeFile = ((path_, data) => {
const dir = path_.substring(0, path_.lastIndexOf(path.sep));
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true }, (e) => {
if (e) {
throw e;
};
});
};
fs.writeFileSync(path_, data, function(e) {
if (e) {
throw e;
};
});
});
this.readFile = (path_ => {
if (fs.existsSync(path_)) {
return fs.readFileSync(path_, { encoding: 'utf-8' }, (e,data) => {
if (e) {
throw e;
} else {
return data;
};
});
} else {
return ''; //Fail silently - for first-time run where the file doesn't exist.
};
});
this.getData = async function() {
const LUNCHPADS_RESP = await fetchURLJSON('https://api.spacexdata.com/v4/launchpads');
const LUNCHES_RESP = await fetchURLJSON('https://api.spacexdata.com/v4/launches/upcoming');

View File

@ -9,19 +9,34 @@ this.arguments = mri(process.argv, {
help: false,
screen_refresh: String(1000),
color: true,
dump: false
dump: false,
blink: false,
archive: false,
path: '~/.spacexcli',
notify_time: String(5400),
highlight_time: String(86400),
},
alias: {
api_refresh: "a",
help: "h",
screen_refresh: "s",
color: "c",
dump: "d"
dump: "d",
blink: "b",
archive: "v",
path: "p",
notify_time: "n",
highlight_time: "g",
}
});
['s', 'screen_refresh', 'a', 'api_refresh'].forEach(idx => {
if (idx == 'a' || idx == 'api_refresh') {
[
's', 'screen_refresh',
'a', 'api_refresh',
'n', 'notify_time',
'g', 'highlight_time',
].forEach(idx => {
if (idx === 'a' || idx === 'api_refresh') {
if (this.arguments[idx] < 30*1000) {
this.arguments[idx] = 30*1000 // No spam pls!
}
@ -33,8 +48,8 @@ if (this.arguments.help) {
console.log(`
Usage:
spacex-cli
spacex-cli [-a <polling interval>] | [-h] | [-s <polling interval>] | [-d]
spacex-cli [--api_refresh=<polling interval>] | [--help] | [--screen_refresh=<polling interval>] | [--dump]
spacex-cli [-a <polling interval>] | [-h] | [-s <polling interval>] | [-d] | [-b]
spacex-cli [--api_refresh=<polling interval>] | [--help] | [--screen_refresh=<polling interval>] | [--dump] | [--blink]
Options:
-h, --help Show this help information.
@ -42,6 +57,11 @@ Options:
-a, --api_refresh API refresh interval in milliseconds. How often we poll the api for new/updated information. Please don't use small values! [default: 600000]
-c, --color Print with color [default: true]
-d, --dump Non-interactive mode - dumps the main launches table [default: false]
-b, --blink Blink for close launches. This argument exists because I know some people hate blink [default: false]
-v, --archive Archive launch data when changed [default: false]
-p, --path Application directory [default: ~/.spacexcli]
-n, --notify_time At this amount of seconds remaining until launch, send a notification [default: 5400]
-g, --highlight_time At this amount of seconds remaining until launch, highlight the row in the table view [default: 86400]
Current configuration:`);
var arguments_ = this.arguments;