├── .gitignore ├── .npmignore ├── LICENSE ├── Makefile ├── README.md ├── bg.js ├── ex.png ├── help.png ├── index.js ├── index.ts ├── package.json ├── src ├── help.js ├── help.ts ├── index.js ├── index.ts ├── util.js └── util.ts ├── tsconfig.json ├── tslint.json ├── wk.js └── ws.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.tgz 3 | *.[xX][lL][sSwWcCaAtTmM] 4 | *.[xX][lL][sSaAtT][xXmMbB] 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test_files/ 2 | tests/files/ 3 | demos/ 4 | index.html 5 | misc/ 6 | node_modules 7 | *.tgz 8 | _book 9 | book.json 10 | tmp 11 | *.[tT][xX][tT] 12 | *.[cC][sS][vV] 13 | *.[dD][iIbB][fF] 14 | *.[pP][rR][nN] 15 | *.[sS][lL][kK] 16 | *.socialcalc 17 | *.[xX][lL][sSwWcC] 18 | *.[xX][lL][sS][xXmMbB] 19 | *.[oO][dD][sS] 20 | *.[fF][oO][dD][sS] 21 | *.[xX][mM][lL] 22 | *.[uU][oO][sS] 23 | *.[wW][kKqQbB][S1234567890] 24 | *.[qQ][pP][wW] 25 | *.123 26 | *.htm 27 | *.html 28 | *.sheetjs 29 | *.exe 30 | .gitignore 31 | .eslintrc 32 | .jshintrc 33 | CONTRIBUTING.md 34 | Makefile 35 | make.cmd 36 | *.lst 37 | .npmignore 38 | xlsworker.js 39 | shim.js 40 | test.js 41 | .jscs.json 42 | .gitmodules 43 | .travis.yml 44 | .flowconfig 45 | *.flow.js 46 | bits/ 47 | docbits/ 48 | tests/ 49 | *.ts 50 | *.nts 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (C) 2017-present SheetJS LLC 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | index.js: index.ts 2 | tsc $< 3 | 4 | .PHONY: lint 5 | lint: index.ts 6 | tslint $^ 7 | 8 | .PHONY: init 9 | init: 10 | @make index.js 11 | @make lint 12 | 13 | .PHONY: clean 14 | clean: 15 | @rm -f index.js src/*.js 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wk 2 | 3 | Preview spreadsheets in your terminal! 4 | 5 | ![example screenshot](ex.png) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | $ npm install -g wk 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```bash 16 | $ wk test.xlsx 17 | ``` 18 | 19 | - `CTRL+C` quits the viewer. 20 | 21 | - `?` displays the help message: 22 | 23 | ![help screenshot](help.png) 24 | 25 | - `~` shows the worksheet selection screen: 26 | 27 | ![worksheet screenshot](ws.png) 28 | 29 | ## License 30 | 31 | Please consult the attached LICENSE file for details. All rights not explicitly 32 | granted by the Apache 2.0 license are reserved by the Original Author. 33 | 34 | [![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/wk?pixel)](https://github.com/SheetJS/wk) 35 | -------------------------------------------------------------------------------- /bg.js: -------------------------------------------------------------------------------- 1 | /* wk.js (C) 2017-present SheetJS -- http://sheetjs.com */ 2 | /* vim: set ts=2 ft=typescript: */ 3 | var XLSX = require('xlsx'); 4 | process.on('message', (filename) => { 5 | try { 6 | const wb = XLSX.readFile(filename, {cellStyles: true, cellDates: true, cellFormula: true}); 7 | process.send([wb, null]); 8 | } catch(e) { 9 | process.send([null, e]); 10 | } 11 | }); -------------------------------------------------------------------------------- /ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheetJS/wk/f2ef501ad3ad240c8c08f8b6a74b5dfbf4f61030/ex.png -------------------------------------------------------------------------------- /help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheetJS/wk/f2ef501ad3ad240c8c08f8b6a74b5dfbf4f61030/help.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | exports.__esModule = true; 4 | var child_process_1 = require("child_process"); 5 | var blessed = require("blessed"); 6 | var src_1 = require("./src/"); 7 | var filename = process.argv[2]; 8 | /* init screen */ 9 | var screen = blessed.screen({ title: "SheetJS spreadsheet viewer - " + filename }); 10 | var loader = blessed.loading({ 11 | align: 'center', 12 | border: 'line', 13 | height: 5, 14 | hidden: true, 15 | left: 'center', 16 | parent: screen, 17 | tags: true, 18 | top: 'center', 19 | width: '50%' 20 | }); 21 | loader.load("Loading " + filename + " ..."); 22 | var n = child_process_1.fork(__dirname + '/bg.js', [], { silent: true }); 23 | n.send(filename); 24 | n.on('message', function (wb) { 25 | loader.stop(); 26 | if (wb[1] && wb[1].message) 27 | throw wb[1]; 28 | n.disconnect(); 29 | src_1["default"](wb[0], filename, screen); 30 | }); 31 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* wk.js (C) 2017-present SheetJS -- http://sheetjs.com */ 3 | /* vim: set ts=2 ft=typescript: */ 4 | 5 | import { fork } from 'child_process'; 6 | 7 | import * as blessed from 'blessed'; 8 | import { WorkBook } from 'xlsx'; 9 | 10 | import initialize from './src/'; 11 | 12 | const filename = process.argv[2]; 13 | 14 | /* init screen */ 15 | const screen: blessed.Widgets.Screen = blessed.screen({ title: "SheetJS spreadsheet viewer - " + filename }); 16 | 17 | const loader = blessed.loading({ 18 | align: 'center', 19 | border: 'line', 20 | height: 5, 21 | hidden: true, 22 | left: 'center', 23 | parent: screen, 24 | tags: true, 25 | top: 'center', 26 | width: '50%' 27 | }); 28 | loader.load("Loading " + filename + " ..."); 29 | 30 | const n = fork(__dirname + '/bg.js', [], { silent: true }); 31 | n.send(filename); 32 | 33 | n.on('message', (wb: [WorkBook, Error]) => { 34 | loader.stop(); 35 | if(wb[1] && wb[1].message) throw wb[1]; 36 | n.disconnect(); 37 | initialize(wb[0], filename, screen); 38 | }); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wk", 3 | "version": "0.2.0", 4 | "author": "sheetjs", 5 | "description": "Spreadsheet Preview", 6 | "keywords": [ "spreadsheet", "excel", "xlsx", "xls", "office", "editor", "tui" ], 7 | "bin": { 8 | "wk": "./index.js" 9 | }, 10 | "main": "./wk", 11 | "dependencies": { 12 | "blessed": "^0.1.81", 13 | "exit-on-epipe": "~1.0.1", 14 | "printj": "~1.2.2", 15 | "xlsx": "~0.15.5" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "" 19 | }, 20 | "repository": { "type":"git", "url":"git://github.com/SheetJS/wk.git" }, 21 | "homepage": "http://sheetjs.com/opensource", 22 | "bugs": { "url": "https://github.com/SheetJS/wk/issues" }, 23 | "license": "Apache-2.0", 24 | "engines": { "node": ">=0.8" } 25 | } 26 | -------------------------------------------------------------------------------- /src/help.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* wk.js (C) 2017-present SheetJS -- http://sheetjs.com */ 3 | /* vim: set ts=2 ft=typescript: */ 4 | exports.__esModule = true; 5 | var blessed = require("blessed"); 6 | function make_help(screen) { 7 | var help = blessed.box({ mouse: true, keys: true, top: 'center', left: 'center', width: '50%', height: '50%', content: "" }); 8 | var helpstr = "(press any key to close this help)\n\n CTRL+C Exit viewer\n\n Click cell Jump to selected cell\n UP/DOWN Jump up/down 1 line\n Mouse scrl Jump up/down 3 lines\n PGUP/PGDN Jump up/down 1 page\n\n \u00AB/\u00BB Shrink/expand col width\n ~ (tilde) Select Worksheet"; 9 | help.content = helpstr; 10 | return help; 11 | } 12 | exports["default"] = make_help; 13 | -------------------------------------------------------------------------------- /src/help.ts: -------------------------------------------------------------------------------- 1 | /* wk.js (C) 2017-present SheetJS -- http://sheetjs.com */ 2 | /* vim: set ts=2 ft=typescript: */ 3 | 4 | import * as blessed from 'blessed'; 5 | 6 | export default function make_help(screen: blessed.Widgets.Screen): blessed.Widgets.BoxElement { 7 | const help: blessed.Widgets.BoxElement = blessed.box({ mouse:true, keys:true, top: 'center', left: 'center', width: '50%', height: '50%', content: "" }); 8 | const helpstr = `\ 9 | (press any key to close this help) 10 | 11 | CTRL+C Exit viewer 12 | 13 | Click cell Jump to selected cell 14 | UP/DOWN Jump up/down 1 line 15 | Mouse scrl Jump up/down 3 lines 16 | PGUP/PGDN Jump up/down 1 page 17 | 18 | «/» Shrink/expand col width 19 | ~ (tilde) Select Worksheet\ 20 | `; 21 | help.content = helpstr; 22 | return help; 23 | } 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* wk.js (C) 2017-present SheetJS -- http://sheetjs.com */ 3 | /* vim: set ts=2 ft=typescript: */ 4 | exports.__esModule = true; 5 | var blessed = require("blessed"); 6 | var printj_1 = require("printj"); 7 | var XLSX = require("xlsx"); 8 | var util_1 = require("./util"); 9 | var help_1 = require("./help"); 10 | var FG = '#00FF00'; 11 | var BG = 'black'; 12 | var FS = 'blue'; 13 | var BS = 'grey'; 14 | var colwidth = 8; 15 | colwidth = 9; 16 | var base_cell = { r: 0, c: 0 }; 17 | /* worksheet info */ 18 | var wsidx = 0; 19 | var ws; 20 | var range; 21 | /* number of columns for row labels */ 22 | var colhdr = 3; 23 | var ncol = 123; 24 | var nrow = 123; 25 | var DW = ""; 26 | function update_wsidx(wb, screen) { 27 | ws = wb.Sheets[wb.SheetNames[wsidx]]; 28 | range = XLSX.utils.decode_range(ws['!ref']); 29 | colhdr = 3; 30 | if (range.e.r >= 1000) 31 | colhdr = (1 + Math.log(range.e.r) * Math.LOG10E) | 0; 32 | ncol = ((screen.cols - colhdr) / colwidth) | 0; 33 | nrow = screen.rows - 4; 34 | DW = printj_1.sprintf('100%%-%d', colhdr); 35 | } 36 | function initialize(wb, filename, screen) { 37 | update_wsidx(wb, screen); 38 | var body = blessed.box({ height: '100%', width: '100%', bg: BG, fg: FG }); 39 | var H1 = blessed.box({ top: 0, height: 1, width: '100%', bg: FG, fg: BG, parent: body }); 40 | var H1r = blessed.text({ top: 0, right: 0, width: 2, bg: FG, fg: BG, parent: H1 }); 41 | var H1l = blessed.text({ top: 0, left: 0, width: '100%-2', bg: FG, fg: BG, parent: H1 }); 42 | var H2 = blessed.box({ top: 1, height: 1, width: '100%', bg: FG, fg: BG, parent: body }); 43 | var H2r = blessed.text({ top: 0, right: 0, width: 2, bg: BG, fg: FG, parent: H2 }); 44 | var H2l = blessed.text({ top: 0, left: 0, width: '100%-2', bg: FG, fg: BG, parent: H2 }); 45 | var H3 = blessed.box({ top: 2, height: 1, width: '100%', bg: BG, fg: FG, tags: true, parent: body }); 46 | var H4 = blessed.box({ top: 3, height: 1, width: '100%', bg: BG, fg: FG, parent: body }); 47 | var H4l = blessed.box({ top: 0, height: 1, width: '50%', bg: FG, fg: BG, parent: H4 }); 48 | var H5 = blessed.box({ top: 4, height: '100%-4', width: colhdr, bg: FG, fg: BG, parent: body }); 49 | H2r.setContent('JS'); 50 | var D = []; 51 | function rebuild_screen() { 52 | var O = ""; 53 | ncol = ((screen.cols - colhdr) / colwidth) | 0; 54 | nrow = screen.rows - 4; 55 | /* row labels */ 56 | for (var i = 0; i < nrow; ++i) 57 | O += printj_1.sprintf("%*s\n", colhdr, XLSX.utils.encode_row(base_cell.r + i)); 58 | H5.setContent(O); 59 | /* column labels */ 60 | H4l.width = (ncol * colwidth + colhdr); 61 | O = util_1.center_str("", colhdr); 62 | for (var i = 0; i < ncol; ++i) 63 | O += util_1.center_str(XLSX.utils.encode_col(base_cell.c + i), colwidth); 64 | H4l.setContent(O); 65 | for (var i = D.length; i < nrow; ++i) { 66 | D[i] = blessed.box({ top: 4 + i, left: colhdr, height: 1, width: DW, bg: BG, fg: FG }); 67 | body.append(D[i]); 68 | D[i].setContent(printj_1.sprintf("haha %d", i)); 69 | } 70 | var fmt = "%2$ *1$.*1$s "; 71 | for (var i = 0; i < nrow; ++i) { 72 | O = ""; 73 | for (var j = 0; j < ncol; ++j) { 74 | var cell = ws[XLSX.utils.encode_cell({ r: base_cell.r + i, c: base_cell.c + j })]; 75 | var o = ""; 76 | if (cell) { 77 | /* TODO: cell alignment */ 78 | o = cell.w ? cell.w : String(cell.v); 79 | switch (cell.t) { 80 | case 'n': 81 | if (!cell.w) 82 | o = printj_1.sprintf("%2$*1$g", colwidth - 1, cell.v); 83 | /* falls through */ 84 | case 'd': 85 | o = util_1.right_str(o, colwidth - 1); 86 | break; 87 | case 's': 88 | o = util_1.left_str(o, colwidth + 1); 89 | break; 90 | case 'b': 91 | case 'e': 92 | o = util_1.center_str(o, colwidth - 1); 93 | break; 94 | case 'z': 95 | o = ""; 96 | break; 97 | } 98 | } 99 | O += printj_1.sprintf(fmt, colwidth - 1, o); 100 | } 101 | D[i].setContent(O); 102 | } 103 | } 104 | rebuild_screen(); 105 | screen.append(body); 106 | /* form to select worksheet */ 107 | var form = (function () { 108 | var _form = blessed.form({ mouse: true, keys: true, top: 'center', left: 'center', width: '50%', height: '50%', content: "" }); 109 | _form.setLabel('Select a Worksheet \n(hit backspace to cancel)'); 110 | var radios = []; 111 | var radioset = blessed.radioset({ top: 3, parent: _form }); 112 | wb.SheetNames.forEach(function (wsname, i) { 113 | var radio = blessed.radiobutton({ mouse: true, keys: true, top: i, left: 0, width: '100%', height: 1, content: wsname, parent: radioset, checked: i === wsidx }); 114 | radio.on('check', function () { return set_worksheet(i); }); 115 | radios.push(radio); 116 | }); 117 | return _form; 118 | })(); 119 | screen.append(form); 120 | form.hide(); 121 | /* help screen */ 122 | var help = help_1["default"](screen); 123 | screen.append(help); 124 | help.hide(); 125 | function set_worksheet(_wsidx) { 126 | if (_wsidx !== -1) { 127 | wsidx = _wsidx; 128 | update_wsidx(wb, screen); 129 | selcell.r = selcell.c = 0; 130 | move_sel_to_cell(selcell); 131 | } 132 | form.hide(); 133 | body.focus(); 134 | screen.render(); 135 | rebuild_screen(); 136 | } 137 | /* selection */ 138 | var sel = blessed.box({ top: 0, left: 0, height: 1, width: colwidth, style: { bg: FS, fg: BS, transparent: true } }); 139 | screen.append(sel); 140 | var selcell = { r: 0, c: 0 }; 141 | function show_version(arg) { 142 | H2l.setContent(arg && arg[0] ? arg[0] : '(C) SheetJS http://sheetjs.com Party like it\'s 1979'); 143 | H3.setContent(arg && arg[1] ? arg[1] : 'Press ? for help, CTRL+C to quit'); 144 | } 145 | /* determine whether a recentering is needed */ 146 | function recenter_screen(cell) { 147 | var dirty = false; 148 | if (cell.r < base_cell.r) { 149 | base_cell.r = cell.r; 150 | dirty = true; 151 | } 152 | if (cell.c < base_cell.c) { 153 | base_cell.c = cell.c; 154 | dirty = true; 155 | } 156 | if (cell.r >= base_cell.r + nrow) { 157 | base_cell.r = cell.r - nrow + 1; 158 | dirty = true; 159 | } 160 | if (cell.c >= base_cell.c + ncol) { 161 | base_cell.c = cell.c - ncol + 1; 162 | dirty = true; 163 | } 164 | return dirty; 165 | } 166 | function move_sel_to_cell(cell) { 167 | if (recenter_screen(cell)) 168 | rebuild_screen(); 169 | selcell.c = cell.c; 170 | selcell.r = cell.r; 171 | sel.top = 4 + cell.r - base_cell.r; 172 | if (sel.top < 4) 173 | sel.top = -1; 174 | sel.left = colhdr + (cell.c - base_cell.c) * colwidth; 175 | if (sel.left < colhdr) 176 | sel.left = -colwidth; 177 | var addr = XLSX.utils.encode_cell(cell); 178 | var text = addr; 179 | if (ws[addr]) { 180 | text += printj_1.sprintf(" (%c) |%s|", ws[addr].t, ws[addr].w || ws[addr].v); 181 | if (ws[addr].t === 'n' || ws[addr].t === 'd') 182 | text += printj_1.sprintf(" raw %s", ws[addr].v); 183 | if (ws[addr].f) { 184 | show_version([(ws[addr].F || addr) + "=" + ws[addr].f]); 185 | } 186 | else if (ws[addr].F) { 187 | var base_c = XLSX.utils.encode_cell(XLSX.utils.decode_range(ws[addr].F).s); 188 | show_version([ws[addr].F + "=" + ws[base_c].f]); 189 | } 190 | else 191 | show_version(); 192 | } 193 | else { 194 | text += printj_1.sprintf(" EMPTY"); 195 | show_version(); 196 | } 197 | H1l.setText(text); 198 | } 199 | function find_coord(r, c) { 200 | if (r < 4 || c < colhdr || c >= colhdr + ncol * colwidth) 201 | return null; 202 | return { r: base_cell.r + r - 4, c: base_cell.c + ((c - colhdr) / colwidth) | 0 }; 203 | } 204 | function init() { 205 | H1r.setText('C'); 206 | base_cell.r = base_cell.c = 0; 207 | H1l.setText('??'); 208 | show_version(); 209 | move_sel_to_cell(base_cell); 210 | screen.render(); 211 | } 212 | body.on('mouse', function (mouse) { 213 | if (help.visible) { 214 | if (mouse.action === 'mousemove') 215 | return; 216 | help.hide(); 217 | screen.render(); 218 | return; 219 | } 220 | if (form.visible) 221 | return; 222 | var cell = { r: selcell.r, c: selcell.c }; 223 | switch (mouse.action) { 224 | case 'wheeldown': 225 | if (mouse.ctrl) { 226 | // pass 227 | } 228 | else { 229 | cell.r += 3; 230 | if (cell.r > range.e.r) 231 | cell.r = range.e.r; 232 | move_sel_to_cell(cell); 233 | } 234 | break; 235 | case 'wheelup': 236 | if (mouse.ctrl) { 237 | // pass 238 | } 239 | else { 240 | cell.r -= 3; 241 | if (cell.r < 0) 242 | cell.r = 0; 243 | move_sel_to_cell(cell); 244 | } 245 | break; 246 | case 'mousedown': 247 | case 'mouseup': 248 | var cc = find_coord(mouse.y, mouse.x); 249 | if (cc) 250 | move_sel_to_cell(cc); 251 | break; 252 | case 'mousemove': break; 253 | default: throw new Error("Unsupported action: " + mouse.action); 254 | } 255 | screen.render(); 256 | }); 257 | screen.on('keypress', function (ch, key) { 258 | var movesel = false; 259 | if (help.visible) { 260 | help.hide(); 261 | screen.render(); 262 | return; 263 | } 264 | if (form.visible) { 265 | if (key.name === "backspace") 266 | set_worksheet(-1); 267 | else if (key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) { 268 | if (key.sequence.charCodeAt(0) === 0x1D) 269 | set_worksheet(-1); 270 | } 271 | screen.render(); 272 | return; 273 | } 274 | if (key.name === "pageup") { 275 | if (selcell.r === 0) 276 | return; 277 | selcell.r -= screen.rows - 4; 278 | if (selcell.r < 0) 279 | selcell.r = 0; 280 | move_sel_to_cell(selcell); 281 | screen.render(); 282 | } 283 | else if (key.name === "pagedown") { 284 | if (selcell.r === range.e.r) 285 | return; 286 | selcell.r += screen.rows - 4; 287 | if (selcell.r > range.e.r) 288 | selcell.r = range.e.r; 289 | move_sel_to_cell(selcell); 290 | screen.render(); 291 | } 292 | else if (ch === "»" || ch === "«") { 293 | colwidth += (ch === "»") ? 1 : -1; 294 | if (colwidth > 20) 295 | colwidth = 20; 296 | if (colwidth < 6) 297 | colwidth = 6; 298 | sel.width = colwidth; 299 | move_sel_to_cell(selcell); 300 | rebuild_screen(); 301 | screen.render(); 302 | } 303 | else if (key.sequence) { 304 | if (key.sequence.length === 1 && !key.ctrl && !key.meta) { 305 | switch (key.sequence.charCodeAt(0)) { 306 | case 0x1D: /* escape */ 307 | screen.render(); 308 | break; 309 | case 0x3F: /* ? */ 310 | help.show(); 311 | help.setFront(); 312 | help.focus(); 313 | screen.render(); 314 | break; 315 | } 316 | } 317 | else if (key.sequence.length === 3 && key.sequence.substr(1, 1) === "O") { 318 | switch (key.sequence.substr(2, 1)) { 319 | case "A": /* up arrow */ 320 | if (selcell.r > 0) { 321 | movesel = true; 322 | --selcell.r; 323 | } 324 | break; 325 | case "B": /* down arrow */ 326 | if (selcell.r < range.e.r) { 327 | movesel = true; 328 | ++selcell.r; 329 | } 330 | break; 331 | case "C": /* right arrow */ 332 | if (selcell.c < range.e.c) { 333 | movesel = true; 334 | ++selcell.c; 335 | } 336 | break; 337 | case "D": /* left arrow */ 338 | if (selcell.c > 0) { 339 | movesel = true; 340 | --selcell.c; 341 | } 342 | break; 343 | case "H": /* home */ 344 | movesel = true; 345 | selcell.r = selcell.c = 0; 346 | break; 347 | case "F": /* end */ 348 | movesel = true; 349 | selcell.r = range.e.r; 350 | selcell.c = range.e.c; 351 | break; 352 | } 353 | if (movesel) { 354 | move_sel_to_cell(selcell); 355 | screen.render(); 356 | } 357 | } 358 | } 359 | else if (key.ch) { 360 | switch (key.ch.charCodeAt(0)) { 361 | case 0x7E: /* ~ */ 362 | form.show(); 363 | form.setFront(); 364 | form.focus(); 365 | screen.render(); 366 | break; 367 | case 0x3F: /* ? */ 368 | help.show(); 369 | help.setFront(); 370 | help.focus(); 371 | screen.render(); 372 | break; 373 | } 374 | } 375 | }); 376 | screen.key(['C-c'], function (ch, key) { screen.destroy(); }); 377 | process.on('SIGWINCH', function () { return rebuild_screen(); }); 378 | init(); 379 | show_version(); 380 | body.focus(); 381 | screen.render(); 382 | } 383 | exports["default"] = initialize; 384 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* wk.js (C) 2017-present SheetJS -- http://sheetjs.com */ 2 | /* vim: set ts=2 ft=typescript: */ 3 | 4 | import * as blessed from 'blessed'; 5 | import { sprintf } from 'printj'; 6 | import * as XLSX from 'xlsx'; 7 | 8 | import { center_str, left_str, right_str } from './util'; 9 | 10 | import make_help from './help'; 11 | 12 | const FG = '#00FF00'; 13 | const BG = 'black'; 14 | const FS = 'blue'; 15 | const BS = 'grey'; 16 | 17 | let colwidth = 8; 18 | colwidth = 9; 19 | 20 | const base_cell: XLSX.CellAddress = {r:0, c:0}; 21 | 22 | /* worksheet info */ 23 | let wsidx = 0; 24 | let ws: XLSX.WorkSheet; 25 | let range: XLSX.Range; 26 | 27 | /* number of columns for row labels */ 28 | let colhdr = 3; 29 | let ncol = 123; 30 | let nrow = 123; 31 | 32 | let DW = ""; 33 | function update_wsidx(wb: XLSX.WorkBook, screen: blessed.Widgets.Screen) { 34 | ws = wb.Sheets[wb.SheetNames[wsidx]]; 35 | range = XLSX.utils.decode_range(ws['!ref']); 36 | colhdr = 3; 37 | if(range.e.r >= 1000) colhdr = (1 + Math.log(range.e.r) * Math.LOG10E) | 0; 38 | ncol = ((screen.cols - colhdr)/colwidth)|0; 39 | nrow = screen.rows - 4; 40 | DW = sprintf('100%%-%d', colhdr); 41 | } 42 | 43 | export default function initialize(wb: XLSX.WorkBook, filename: string, screen: blessed.Widgets.Screen): void { 44 | update_wsidx(wb, screen); 45 | 46 | const body = blessed.box({ height:'100%', width:'100%', bg:BG, fg:FG }); 47 | 48 | const H1 = blessed.box({ top:0, height:1, width:'100%', bg:FG, fg:BG, parent:body }); 49 | const H1r = blessed.text({ top:0, right:0, width:2, bg:FG, fg:BG, parent: H1 }); 50 | const H1l = blessed.text({ top:0, left:0, width:'100%-2', bg:FG, fg:BG, parent: H1 }); 51 | 52 | const H2 = blessed.box({ top:1, height:1, width:'100%', bg:FG, fg:BG, parent:body }); 53 | const H2r = blessed.text({ top:0, right:0, width:2, bg:BG, fg:FG, parent:H2 }); 54 | const H2l = blessed.text({ top:0, left:0, width:'100%-2', bg:FG, fg:BG, parent:H2 }); 55 | 56 | const H3 = blessed.box({ top:2, height:1, width:'100%', bg:BG, fg:FG, tags:true, parent:body }); 57 | 58 | const H4 = blessed.box({ top:3, height:1, width:'100%', bg:BG, fg:FG, parent:body }); 59 | const H4l = blessed.box({ top:0, height:1, width:'50%', bg:FG, fg:BG, parent:H4 }); 60 | 61 | const H5 = blessed.box({ top:4, height:'100%-4', width:colhdr, bg:FG, fg:BG, parent:body }); 62 | 63 | H2r.setContent('JS'); 64 | 65 | const D: blessed.Widgets.BoxElement[] = []; 66 | 67 | function rebuild_screen(): void { 68 | let O = ""; 69 | ncol = ((screen.cols - colhdr)/colwidth)|0; 70 | nrow = screen.rows - 4; 71 | 72 | /* row labels */ 73 | for(let i = 0; i < nrow; ++i) O += sprintf("%*s\n", colhdr, XLSX.utils.encode_row(base_cell.r + i)); 74 | H5.setContent(O); 75 | 76 | /* column labels */ 77 | H4l.width = (ncol * colwidth + colhdr); 78 | O = center_str("", colhdr); 79 | for(let i = 0; i < ncol; ++i) O += center_str(XLSX.utils.encode_col(base_cell.c + i), colwidth); 80 | H4l.setContent(O); 81 | 82 | for(let i = D.length; i < nrow; ++i) { 83 | D[i] = blessed.box({ top:4+i, left:colhdr, height:1, width:DW, bg:BG, fg:FG }); 84 | body.append(D[i]); 85 | D[i].setContent(sprintf("haha %d", i)); 86 | } 87 | 88 | const fmt = "%2$ *1$.*1$s "; 89 | for(let i = 0; i < nrow; ++i) { 90 | O = ""; 91 | for(let j = 0; j < ncol; ++j) { 92 | const cell: XLSX.CellObject = ws[XLSX.utils.encode_cell({r:base_cell.r+i, c:base_cell.c+j})]; 93 | let o = ""; 94 | if(cell) { 95 | /* TODO: cell alignment */ 96 | o = cell.w ? cell.w : String(cell.v); 97 | switch(cell.t) { 98 | case 'n': 99 | if(!cell.w) o = sprintf("%2$*1$g", colwidth-1, cell.v); 100 | /* falls through */ 101 | case 'd': 102 | o = right_str(o, colwidth - 1); break; 103 | case 's': o = left_str(o, colwidth + 1); break; 104 | case 'b': case 'e': o = center_str(o, colwidth - 1); break; 105 | case 'z': o = ""; break; 106 | } 107 | } 108 | O += sprintf(fmt,colwidth-1,o); 109 | } 110 | D[i].setContent(O); 111 | } 112 | } 113 | 114 | rebuild_screen(); 115 | screen.append(body); 116 | 117 | /* form to select worksheet */ 118 | const form: blessed.Widgets.FormElement = (() => { 119 | const _form = blessed.form({ mouse:true, keys:true, top: 'center', left: 'center', width: '50%', height: '50%', content: "" }); 120 | _form.setLabel('Select a Worksheet \n(hit backspace to cancel)'); 121 | const radios: blessed.Widgets.RadioButtonElement[] = []; 122 | const radioset: blessed.Widgets.RadioSetElement = blessed.radioset({ top:3, parent:_form }); 123 | wb.SheetNames.forEach((wsname, i) => { 124 | const radio: blessed.Widgets.RadioButtonElement = blessed.radiobutton({mouse:true, keys:true, top:i, left:0, width:'100%', height:1, content:wsname, parent:radioset, checked:i === wsidx}); 125 | radio.on('check', () => set_worksheet(i) ); 126 | radios.push(radio); 127 | }); 128 | return _form; 129 | })(); 130 | screen.append(form); 131 | form.hide(); 132 | 133 | /* help screen */ 134 | const help = make_help(screen); 135 | screen.append(help); 136 | help.hide(); 137 | 138 | function set_worksheet(_wsidx: number): void { 139 | if(_wsidx !== -1) { 140 | wsidx = _wsidx; 141 | update_wsidx(wb, screen); 142 | selcell.r = selcell.c = 0; 143 | move_sel_to_cell(selcell); 144 | } 145 | form.hide(); 146 | body.focus(); 147 | screen.render(); 148 | rebuild_screen(); 149 | } 150 | 151 | /* selection */ 152 | const sel = blessed.box({ top:0, left:0, height:1, width:colwidth, style: {bg:FS, fg:BS, transparent:true} }); 153 | screen.append(sel); 154 | const selcell: XLSX.CellAddress = {r:0, c:0}; 155 | 156 | function show_version(arg?: any[]) { 157 | H2l.setContent(arg && arg[0] ? arg[0] : '(C) SheetJS http://sheetjs.com Party like it\'s 1979'); 158 | H3.setContent(arg && arg[1] ? arg[1] : 'Press ? for help, CTRL+C to quit'); 159 | } 160 | 161 | /* determine whether a recentering is needed */ 162 | function recenter_screen(cell: XLSX.CellAddress): boolean { 163 | let dirty = false; 164 | if(cell.r < base_cell.r) { base_cell.r = cell.r; dirty = true; } 165 | if(cell.c < base_cell.c) { base_cell.c = cell.c; dirty = true; } 166 | if(cell.r >= base_cell.r + nrow) { base_cell.r = cell.r - nrow + 1; dirty = true; } 167 | if(cell.c >= base_cell.c + ncol) { base_cell.c = cell.c - ncol + 1; dirty = true; } 168 | return dirty; 169 | } 170 | 171 | function move_sel_to_cell(cell: XLSX.CellAddress): void { 172 | if(recenter_screen(cell)) rebuild_screen(); 173 | selcell.c = cell.c; selcell.r = cell.r; 174 | sel.top = 4 + cell.r - base_cell.r; if(sel.top < 4) sel.top = -1; 175 | sel.left = colhdr + (cell.c - base_cell.c) * colwidth; if(sel.left < colhdr) sel.left = -colwidth; 176 | const addr = XLSX.utils.encode_cell(cell); 177 | let text = addr; 178 | if(ws[addr]) { 179 | text += sprintf(" (%c) |%s|", ws[addr].t, ws[addr].w||ws[addr].v); 180 | if(ws[addr].t === 'n' || ws[addr].t === 'd') text += sprintf(" raw %s", ws[addr].v); 181 | if(ws[addr].f) { 182 | show_version([(ws[addr].F || addr) + "=" + ws[addr].f ]); 183 | } else if(ws[addr].F) { 184 | const base_c = XLSX.utils.encode_cell(XLSX.utils.decode_range(ws[addr].F).s); 185 | show_version([ws[addr].F + "=" + ws[base_c].f ]); 186 | } else show_version(); 187 | } else { text += sprintf(" EMPTY"); show_version(); } 188 | H1l.setText(text); 189 | } 190 | 191 | function find_coord(r: number, c: number): XLSX.CellAddress { 192 | if(r < 4 || c < colhdr || c >= colhdr + ncol * colwidth) return null; 193 | return {r:base_cell.r + r - 4, c:base_cell.c + ((c - colhdr) / colwidth)|0 }; 194 | } 195 | 196 | function init() { 197 | H1r.setText('C'); 198 | base_cell.r = base_cell.c = 0; 199 | H1l.setText('??'); 200 | show_version(); 201 | move_sel_to_cell(base_cell); 202 | screen.render(); 203 | } 204 | 205 | body.on('mouse', (mouse) => { 206 | if(help.visible) { 207 | if(mouse.action === 'mousemove') return; 208 | help.hide(); screen.render(); return; 209 | } 210 | if(form.visible) return; 211 | const cell: XLSX.CellAddress = {r:selcell.r, c:selcell.c}; 212 | switch(mouse.action) { 213 | case 'wheeldown': 214 | if(mouse.ctrl) { 215 | // pass 216 | } else { 217 | cell.r += 3; 218 | if(cell.r > range.e.r) cell.r = range.e.r; 219 | move_sel_to_cell(cell); 220 | } 221 | break; 222 | case 'wheelup': 223 | if(mouse.ctrl) { 224 | // pass 225 | } else { 226 | cell.r -= 3; 227 | if(cell.r < 0) cell.r = 0; 228 | move_sel_to_cell(cell); 229 | } 230 | break; 231 | case 'mousedown': 232 | case 'mouseup': 233 | const cc = find_coord(mouse.y, mouse.x); 234 | if(cc) move_sel_to_cell(cc); 235 | break; 236 | case 'mousemove': break; 237 | default: throw new Error("Unsupported action: " + mouse.action); 238 | } 239 | screen.render(); 240 | }); 241 | 242 | screen.on('keypress', (ch: string, key) => { 243 | let movesel = false; 244 | 245 | if(help.visible) { help.hide(); screen.render(); return; } 246 | 247 | if(form.visible) { 248 | if(key.name === "backspace") set_worksheet(-1); 249 | else if(key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) { 250 | if(key.sequence.charCodeAt(0) === 0x1D) set_worksheet(-1); 251 | } 252 | screen.render(); return; 253 | } 254 | if(key.name === "pageup") { 255 | if(selcell.r === 0) return; 256 | selcell.r -= screen.rows - 4; 257 | if(selcell.r < 0) selcell.r = 0; 258 | move_sel_to_cell(selcell); 259 | screen.render(); 260 | } else if(key.name === "pagedown") { 261 | if(selcell.r === range.e.r) return; 262 | selcell.r += screen.rows - 4; 263 | if(selcell.r > range.e.r) selcell.r = range.e.r; 264 | move_sel_to_cell(selcell); 265 | screen.render(); 266 | } else if(ch === "»" || ch === "«") { 267 | colwidth += (ch === "»") ? 1 : -1; 268 | if(colwidth > 20) colwidth = 20; 269 | if(colwidth < 6) colwidth = 6; 270 | sel.width = colwidth; 271 | move_sel_to_cell(selcell); 272 | rebuild_screen(); screen.render(); 273 | } else if(key.sequence) { 274 | if(key.sequence.length === 1 && !key.ctrl && !key.meta) { 275 | switch(key.sequence.charCodeAt(0)) { 276 | case 0x1D: /* escape */ screen.render(); break; 277 | case 0x3F: /* ? */ help.show(); help.setFront(); help.focus(); screen.render(); break; 278 | } 279 | } else if(key.sequence.length === 3 && key.sequence.substr(1,1) === "O") { 280 | switch(key.sequence.substr(2,1)) { 281 | case "A": /* up arrow */ 282 | if(selcell.r > 0) { movesel = true; --selcell.r; } break; 283 | case "B": /* down arrow */ 284 | if(selcell.r < range.e.r) { movesel = true; ++selcell.r; } break; 285 | case "C": /* right arrow */ 286 | if(selcell.c < range.e.c) { movesel = true; ++selcell.c; } break; 287 | case "D": /* left arrow */ 288 | if(selcell.c > 0) { movesel = true; --selcell.c; } break; 289 | case "H": /* home */ 290 | movesel = true; selcell.r = selcell.c = 0; break; 291 | case "F": /* end */ 292 | movesel = true; selcell.r = range.e.r; selcell.c = range.e.c; break; 293 | } 294 | if(movesel) { move_sel_to_cell(selcell); screen.render(); } 295 | } 296 | } else if((key as any).ch) { 297 | switch((key as any).ch.charCodeAt(0)) { 298 | case 0x7E: /* ~ */ form.show(); form.setFront(); form.focus(); screen.render(); break; 299 | case 0x3F: /* ? */ help.show(); help.setFront(); help.focus(); screen.render(); break; 300 | } 301 | } 302 | }); 303 | 304 | screen.key(['C-c'], (ch, key) => { screen.destroy(); }); 305 | 306 | process.on('SIGWINCH', () => rebuild_screen() ); 307 | 308 | init(); 309 | show_version(); 310 | body.focus(); 311 | screen.render(); 312 | } 313 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | function center_str(s, w) { 4 | if (s.length >= w) 5 | return s.substr(0, w); 6 | var pl = (w - s.length) >> 1; 7 | return new Array(pl + 1).join(" ") + s + new Array(w - s.length - pl + 1).join(" "); 8 | } 9 | exports.center_str = center_str; 10 | function right_str(s, w) { 11 | if (s.length >= w) 12 | return s.substr(0, w); 13 | var l = (w - s.length); 14 | return new Array(l + 1).join(" ") + s; 15 | } 16 | exports.right_str = right_str; 17 | function left_str(s, w) { 18 | if (s.length >= w) 19 | return s.substr(0, w); 20 | var l = (w - s.length); 21 | return s + new Array(l + 1).join(" "); 22 | } 23 | exports.left_str = left_str; 24 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export function center_str(s: string, w: number): string { 2 | if(s.length >= w) return s.substr(0,w); 3 | const pl = (w - s.length) >> 1; 4 | return new Array(pl+1).join(" ") + s + new Array(w - s.length - pl + 1).join(" "); 5 | } 6 | 7 | export function right_str(s: string, w: number): string { 8 | if(s.length >= w) return s.substr(0, w); 9 | const l = (w - s.length); 10 | return new Array(l+1).join(" ") + s; 11 | } 12 | 13 | export function left_str(s: string, w: number): string { 14 | if(s.length >= w) return s.substr(0, w); 15 | const l = (w - s.length); 16 | return s + new Array(l+1).join(" "); 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "noImplicitAny": true, 5 | "noImplicitThis": true, 6 | "strictNullChecks": false, 7 | "strictFunctionTypes": true, 8 | "forceConsistentCasingInFileNames": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "whitespace": false, 9 | "no-consecutive-blank-lines": false, 10 | "trailing-comma": false, 11 | "indent": false, 12 | "max-line-length": false, 13 | "curly": false, 14 | "quotemark": false, 15 | "variable-name": false, 16 | "no-console": false, 17 | "no-bitwise": false 18 | }, 19 | "rulesDirectory": [] 20 | } 21 | -------------------------------------------------------------------------------- /wk.js: -------------------------------------------------------------------------------- 1 | module.exports = require('xlsx'); 2 | -------------------------------------------------------------------------------- /ws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheetJS/wk/f2ef501ad3ad240c8c08f8b6a74b5dfbf4f61030/ws.png --------------------------------------------------------------------------------