viewworkbook 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -0,0 +1,126 @@
1
+ ========================
2
+ Viewworkbook
3
+ ========================
4
+ ------------------------------------------------
5
+ view spreadsheet files in a terminal window
6
+ ------------------------------------------------
7
+
8
+ SYNOPSIS
9
+ =============
10
+ viewworkbook <spreadsheet>
11
+
12
+ DESCRIPTION
13
+ =============
14
+ Viewworkbook lets you read spreadsheet-files in text-mode, in a terminal
15
+ window. It reproduces the tables which are contained in a spreadsheet file and
16
+ lets you navigate through the sheets. The visible part of a sheet is limited
17
+ laterally and horizontally by the terminal-size but can be scrolled. You can
18
+ adapt the width of table-columns, to render the table more readable and
19
+ finally, if you wish, save tables to a text-file. To the author of the
20
+ program, the utility serves to take a quick glance at spreadsheets that are
21
+ received via email, in the Mutt mail-client, which is itself a terminal
22
+ application.
23
+
24
+ The supported spreadsheet formats are those which are handled by the Ruby-gem
25
+ "**roo**" (February 2017):
26
+
27
+ - Microsoft™'s **xls** and **xlsx**,
28
+
29
+ - the OpenDocument spreadsheet **ods**
30
+
31
+ - and the SoftMaker™ spreadsheet formats **pmd** and **pmdx**.
32
+
33
+ Options
34
+ =============
35
+ Other than the path to the spreadsheet file, viewworkbook does currently not
36
+ interpret any command line arguments.
37
+
38
+ Menu commands
39
+ -------------------
40
+ A list of commands and their associated hotkeys (in parenthesis) is shown
41
+ below the current table:
42
+
43
+ +--+--------+--------+--------+--------+--------+--------+--------+
44
+ |11| 12.2 | July | 34000 | 5% | prior | 1957 | true |
45
+ +--+--------+--------+--------+--------+--------+--------+--------+
46
+ |12| 2.33 | July | 1234 | 12.2% | prior | 1966 | true |
47
+ +--+--------+--------+--------+--------+--------+--------+--------+
48
+ |13| 50.0 | August | 334566 | 12% | in | 1966 | false |
49
+ +==+========+========+========+========+========+========+========+
50
+ | | A| B| C| D| E| F| G|
51
+ +--+--------+--------+--------+--------+--------+--------+--------+
52
+
53
+ **value (v) * save to file (f) * sheet (s) * column (c) * up(i) * down(k) * left(j) * right(l) * quit (q) ***
54
+
55
+ When you enter one of the hotkeys, a command can be executed directly (like "q"
56
+ to terminate the program), a sub-menu may be shown (like with "c" which shall
57
+ give you control on column properties) or you are confronted with an input
58
+ invitation (like with "v", where you have to enter a cell-reference). Read on
59
+ for an explanation of each menu command.
60
+
61
+ value (v)
62
+ display the value from a cell. This is useful, when columns are not wide
63
+ enough to show complete values. After entering "**v**", you are
64
+ immediately invited to enter a cell reference. Cells are referenced in
65
+ the format "Column:Row", e.g. D:12 for the cell in column "D" and row
66
+ "12".
67
+
68
+ save to file (f)
69
+ Save the current or all tables to a text file. The command does initially
70
+ show two alternative sub-commands:
71
+
72
+ Current sheet (c) * all sheets (a)
73
+ Independently of your choice, in the next step you will have to enter the
74
+ path to the output file. You will be warned, if the file already exists
75
+ and may choose to either overwrite the existing file or name a different
76
+ one.
77
+
78
+ sheet (s)
79
+ Navigate to a different sheet in the current workbook. You may choose
80
+ between a sheet-number and the name of a sheet. If you want to indicate
81
+ the next sheet **by name**, the list of all available names is first
82
+ shown. You just type right away the one that you want.
83
+
84
+ column (c)
85
+ Alter the properties of the table-columns. At the time of this writing
86
+ (February 2017) the only property that can be changed, is the width of all
87
+ columns. When you enter "**c**" the subcommand column width (w) is shown.
88
+ After entering "**w**" you can enter the desired width in characters.
89
+
90
+ Arrows(i, j, k, l)
91
+ Navigate in a sheet that is too big to be displayed entirely at once.
92
+ These hotkeys correspond to the default navigation keys in the vi editor.
93
+
94
+ quit (q)
95
+ Enter "**q**" to quit the program at any moment except when an input
96
+ invitation is shown.
97
+
98
+ Hidden (menu-)commands
99
+ ------------------------
100
+ There is currently only one such command available:
101
+ The **Escape-key** will interrupt an unfinished action and refresh the display.
102
+
103
+ Other Information
104
+ =================
105
+
106
+ Development and source code
107
+ Viewworkbook has been written in Ruby. As Ruby is an interpreted programming
108
+ language, the executable file and all those that it may refer to at one
109
+ point in time, are themselves the source-files of the current
110
+ program-version. You can open them in any text-editor to scrutinize the
111
+ source-code. If you have received the program as a Ruby-gem, you can also
112
+ decompress a copy of the gem-file with **tar -x**, then **tar -xzf**.
113
+
114
+ Bugs
115
+ Negative values are not always displayed, when very long (like 15 ciphers).
116
+ Enlarging the column-width further does not have an effect.
117
+
118
+ License
119
+ Viewworkbook is distributed under the conditions of the GNU General Public
120
+ License, version 3.
121
+
122
+ Author
123
+ Viewworkbook has been developed by Michael Uplawski
124
+ <michael.uplawski@uplawski.eu>
125
+
126
+ **Ω**
@@ -0,0 +1,57 @@
1
+ #encoding: UTF-8
2
+
3
+ =begin
4
+ /***************************************************************************
5
+ * Copyright © 2014, Michael Uplawski <michael.uplawski@uplawski.eu> *
6
+ * *
7
+ * This program is free software; you can redistribute it and/or modify *
8
+ * it under the terms of the GNU General Public License as published by *
9
+ * the Free Software Foundation; either version 3 of the License, or *
10
+ * (at your option) any later version. *
11
+ * *
12
+ * This program is distributed in the hope that it will be useful, *
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15
+ * GNU General Public License for more details. *
16
+ * *
17
+ * You should have received a copy of the GNU General Public License *
18
+ * along with this program; if not, write to the *
19
+ * Free Software Foundation, Inc., *
20
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21
+ ***************************************************************************/
22
+ =end
23
+
24
+ require_relative 'logging'
25
+
26
+ # An Action is executed on a user's request.
27
+ # It has a name, an associated closure and hotkey.
28
+ class Action
29
+ include Logging
30
+
31
+ class ActionError < StandardError
32
+ end
33
+
34
+ attr_accessor :name, :key, :proc, :global, :hidden
35
+
36
+ def to_s
37
+ "[#<" << classname << ':' << hash << '@name="' << name << '", @key="' << key << '">'
38
+ end
39
+
40
+
41
+ def initialize(options = {}, &b)
42
+ init_logger(STDOUT, Logger::INFO)
43
+
44
+ @name = options[:name]
45
+ @key = options[:key]
46
+ @proc = b if b
47
+ @global = options[:global]
48
+ @hidden = options[:hidden]
49
+ end
50
+
51
+ def call(*args)
52
+ unless @proc
53
+ raise ActionError.new((@name ? '' : 'Unnamed ') << 'action' << (@name ? (' ' << @name) : '') << ' called before a command was defined')
54
+ end
55
+ @proc.call(*args)
56
+ end
57
+ end
@@ -0,0 +1,56 @@
1
+ #encoding: UTF-8
2
+ =begin
3
+ /***************************************************************************
4
+ * Copyright (c) 2011, Michael Uplawski <michael.uplawski@uplawski.eu> *
5
+ * *
6
+ * This program is free software; you can redistribute it and/or modify *
7
+ * it under the terms of the GNU General Public License as published by *
8
+ * the Free Software Foundation; either version 3 of the License, or *
9
+ * (at your option) any later version. *
10
+ * *
11
+ * This program is distributed in the hope that it will be useful, *
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
+ * GNU General Public License for more details. *
15
+ * *
16
+ * You should have received a copy of the GNU General Public License *
17
+ * along with this program; if not, write to the *
18
+ * Free Software Foundation, Inc., *
19
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
20
+ ***************************************************************************/
21
+
22
+ A test and demo for the BusyIndicator.
23
+ This does not use the final BusyIndicator but directly creates
24
+ a Thread.
25
+ =end
26
+ require './color_output'
27
+
28
+ def clean_busy_indicator(thr, width, comment)
29
+ thr.terminate
30
+ thr.join
31
+ print ("\b" * width)
32
+ print ("%+#{width}s\n" %comment)
33
+ end
34
+
35
+ def busy_indicator(width)
36
+ tobj = Thread.new() do
37
+ loop do
38
+ %w"OOO ooo ___ ooo".each do |s|
39
+ print "%+#{width}s" %s
40
+ sleep 0.1
41
+ print ("\b" * width)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ # =======================
47
+ if($0 == __FILE__)
48
+ width = 20
49
+ 2.times do
50
+ puts "I am busy, pse wait..."
51
+ thr = busy_indicator(width)
52
+ sleep 3
53
+ clean_busy_indicator(thr, width, "Okay")
54
+ end
55
+ end
56
+
@@ -0,0 +1,95 @@
1
+ #encoding: UTF-8
2
+ =begin
3
+ /***************************************************************************
4
+ * Copyright (c) 2011, Michael Uplawski <michael.uplawski@uplawski.eu> *
5
+ * *
6
+ * This program is free software; you can redistribute it and/or modify *
7
+ * it under the terms of the GNU General Public License as published by *
8
+ * the Free Software Foundation; either version 3 of the License, or *
9
+ * (at your option) any later version. *
10
+ * *
11
+ * This program is distributed in the hope that it will be useful, *
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
+ * GNU General Public License for more details. *
15
+ * *
16
+ * You should have received a copy of the GNU General Public License *
17
+ * along with this program; if not, write to the *
18
+ * Free Software Foundation, Inc., *
19
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
20
+ ***************************************************************************/
21
+
22
+ =end
23
+ require_relative '../color_output'
24
+
25
+ #The BusyIndicator will show an "Animation" for the time of its execution.
26
+ #The main program will continue to run in its own thread and should stop
27
+ #the BusyIndicator, once that a process of longer duration has terminated.
28
+ class BusyIndicator
29
+ attr_writer :width
30
+ # Defines a busy_indicator of a width of 'width' characters.
31
+ # If 'start' is true, the text-animation is run immediately.
32
+ def initialize(start = true, width = nil)
33
+ @width = width && width >= 3 ? width : 3
34
+ @thr = busy_indicator(@width) if start
35
+ end
36
+
37
+ # Starts the text-animation, returns the thread.
38
+ def run()
39
+ @thr = busy_indicator(@width)
40
+ end
41
+
42
+ # Stops the text-animation, terminates the thread.
43
+ # If comment is not null, it will be displayed in the end.
44
+ def stop(comment)
45
+ @thr.terminate
46
+ @thr.join
47
+ print ("\b" * @width)
48
+ print ("%+#{@width}s\n" %comment)
49
+ end
50
+ private
51
+ def busy_indicator(width)
52
+ tobj = Thread.new() do
53
+ print "working ... "
54
+ loop do
55
+ # %w"OOO ooo ___ ooo".each do |s|
56
+ # %w"000 OOO UUU VVV YYY TTT 777 >>> === --- ooo".each do |s|
57
+ %w"0OU OUV UVY VYT YT7 T7> 7>= >=- =-o -o0 o0O".each do |s|
58
+ print "%+#{width}s" %s
59
+ sleep 0.1
60
+ print ("\b" * width)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ # =======================
67
+ # Example
68
+ # You can run this file directly from a command-line,
69
+ # like this
70
+ # user@mchine$: ruby busy_indicator.rb
71
+ #
72
+ # The program below will then start four (4)
73
+ # BusyIndicators and stop them again, one by one.
74
+ # The width of the "animation" is variated.
75
+ # In the loop, the BusyIndicator is started upon
76
+ # its creation, the other two instances are first
77
+ # created then 'run'.
78
+ if($0 == __FILE__)
79
+ width = 20
80
+ 2.times do
81
+ puts "I am busy, pse wait..."
82
+ thr = BusyIndicator.new(true, width)
83
+ sleep 3
84
+ thr.stop("Okay")
85
+ end
86
+ bi = BusyIndicator.new(false)
87
+ bi.run
88
+ sleep 3
89
+ bi.stop("Stopped")
90
+ bi.width = 8
91
+ bi.run
92
+ sleep 3
93
+ bi.stop("stopped again")
94
+ end
95
+
@@ -0,0 +1,122 @@
1
+ #encoding: UTF-8
2
+
3
+ =begin
4
+ /***************************************************************************
5
+ * Copyright ©2016-2016, Michael Uplawski <michael.uplawski@uplawski.eu> *
6
+ * *
7
+ * This program is free software; you can redistribute it and/or modify *
8
+ * it under the terms of the GNU General Public License as published by *
9
+ * the Free Software Foundation; either version 3 of the License, or *
10
+ * (at your option) any later version. *
11
+ * *
12
+ * This program is distributed in the hope that it will be useful, *
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15
+ * GNU General Public License for more details. *
16
+ * *
17
+ * You should have received a copy of the GNU General Public License *
18
+ * along with this program; if not, write to the *
19
+ * Free Software Foundation, Inc., *
20
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21
+ ***************************************************************************/
22
+ =end
23
+
24
+ require_relative 'logging'
25
+ require_relative 'row'
26
+ require_relative 'column'
27
+
28
+ # Objects of this class represent cells in a spreadsheet table
29
+
30
+ class Cell
31
+ @@DEF_HEIGHT=1
32
+ @@DEF_TYPE=Integer
33
+ @@split_pattern = nil
34
+
35
+ self.extend(Logging)
36
+ @@log = self.init_logger
37
+
38
+ def initialize(row = nil, col = nil, value = nil)
39
+ @log = @@log
40
+ @row = row if row
41
+ @col = col if col
42
+ @type_class = @@DEF_TYPE
43
+ @value = value
44
+ split_value
45
+ set_limits
46
+ @row.resize
47
+ @log.debug("cell.initialize, lines is #{@lines}, value is #{value}, ideal_width is #{@ideal_width}")
48
+ end
49
+
50
+ def to_cell()
51
+ self
52
+ end
53
+
54
+ def line(num = nil)
55
+ num && num < @lines.length ? @lines[num].to_s : ' ' if @lines && !@lines.empty?
56
+ end
57
+
58
+ def to_s
59
+ object_id.to_s << "{" << @row.number.to_s << ":" << @col.number.to_s << ", " << @lines.to_s << ", " << @ideal_height.to_s << ", " << @ideal_width.to_s << " }"
60
+ end
61
+
62
+ def resize
63
+ @@split_pattern = nil
64
+ split_value
65
+ set_limits
66
+ @row.resize
67
+ end
68
+
69
+ def value=(value)
70
+ @@split_pattern = nil
71
+ @value = value
72
+ split_value
73
+ @log.debug('after split_value, lines is ' << @lines.to_s)
74
+ end
75
+
76
+ def col
77
+ @col.number
78
+ end
79
+
80
+ def row
81
+ @row.number
82
+ end
83
+
84
+ attr_reader :ideal_height, :ideal_width, :value
85
+
86
+ private
87
+
88
+ def split_value()
89
+ # The regex may be used in many cells. Define on class-level.
90
+ # And...
91
+ # This looks complicated because it is.
92
+ #
93
+ # until 18 february 2017
94
+ # @@split_pattern = Regexp.new('[\p{P}\p{M}]?\b.{1,%i}\b[\p{P}\p{M}]?' %(@col.width)) if !@@split_pattern
95
+ # since 18 february 2017
96
+ @@split_pattern = Regexp.new('\b(?:.{1,%i}(?:[\b\s\p{P}]?))' %(@col.width - 1)) if !@@split_pattern
97
+
98
+ @lines = @value.to_s.scan(@@split_pattern) # .collect {|match| match.strip}
99
+ @lines.reject! {|l| l.empty?}
100
+ set_limits
101
+ if @value.to_s.strip.length > @lines.join.strip.length
102
+ # @lines.insert(0, '...' )
103
+ if(! @lines.empty?)
104
+ @lines[@lines.length - 1] = '...'
105
+ else
106
+ @lines.insert(0, '...' )
107
+ end
108
+ end
109
+ @row.resize
110
+ end
111
+
112
+ def set_limits
113
+ if @lines && !@lines.empty?
114
+ @ideal_height = @lines.length
115
+ @ideal_width = @lines.max {|l1, l2 | l1.length <=> l2.length}.length if @lines && !@lines.empty?
116
+ else
117
+ @ideal_width ||= @col.width
118
+ @ideal_height ||= @@DEF_HEIGHT
119
+ @height ||= @ideal_height
120
+ end
121
+ end
122
+ end