gm-notepad 0.0.2 → 0.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce5817b9b619d272b6b534d4da74e9474fb15a5cbfc1dd21ac1094ec2aed844d
4
- data.tar.gz: a82515600b627a7b0227305cb7430a548d53d0bdf006b633390da5da80c9af8a
3
+ metadata.gz: fea81d64cf4d80ba35966cfb2cf36ed00edf206307f46bad9c7d07dc524da732
4
+ data.tar.gz: b3d9513020a6db543dd6551214aca212c0bc94124fc2a0debbe4e379357606e0
5
5
  SHA512:
6
- metadata.gz: 5c0eb020328774f1e4395f4609fef3da8bd15957f1a97acba14d2d492eee854322d7288ad55ee486ee39774464134f34a3ce9e4150900aa966913a78a4665d00
7
- data.tar.gz: 54da4151c52a779dd42324efe1520e066db65c696e108ccf2780c5f756f9b97f84bb66a3538d43b229d2f8973c818c45e1e390defe69600c45d867470e8db293
6
+ metadata.gz: 2583aa8b65971bd9c8bf7a34246ae710e5ab315ccc937e7721862bb67b331a2b911b2bce9edc15e020034b5f1e3c4725ace64a8daf15bb0b7472ec51cf835e0a
7
+ data.tar.gz: e7008f2fcb80279c398c22fafda4a182346df1d8383c95de181d6489a401d06c587e5f9f5271b39d08e3ce4615c9082cc1ddc9092e91ee00fa0748c3e9d02100
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --format documentation
2
2
  --color
3
+ --order rand
data/LICENSE ADDED
@@ -0,0 +1,201 @@
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 2019 Jeremy Friesen
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.
data/README.md CHANGED
@@ -6,6 +6,29 @@ A command-line tool to help with your GM-ing.
6
6
 
7
7
  `$ gem install gm-notepad`
8
8
 
9
+ ## Background
10
+
11
+ On a commute home from work, while listening to [Judd Karlman's "Daydreaming about Dragons" podcast](https://anchor.fm/daydreaming-about-dragons/)
12
+ he wondered about ways to organize notes for NPCs. And I started thinking. How
13
+ might I organize my content for access while gaming? And what kind of
14
+ content? More importantly, what kind of tasks do I need to complete as a GM.
15
+
16
+ * Remember a character name
17
+ * Lookup their passive perception
18
+ * Lookup a random table
19
+ * Roll on a random table
20
+ * Create a random character name. Maybe based on a culture.
21
+ * Record quick notes about an NPC
22
+
23
+ I thought about `swnt`, and command line tool for Stars without Numbers.
24
+ See the [github project](https://github.com/nboughton/swnt) for more information.
25
+
26
+ And I thought about [Alex Schroeder's tools](https://alexschroeder.ch/wiki/RPG).
27
+
28
+ I have kicked this around for awhile. I made an attempt in [Rollio](https://github.com/jeremyf/rollio).
29
+ But I built that to roll on tables. I needed something more general.
30
+ _I will, however, begin converting the data._
31
+
9
32
  ## Introduction
10
33
 
11
34
  By default `gm-notepad` interacts with `$stdout` and `$stderr`. There are
@@ -30,34 +53,44 @@ Note taking tool with random table expansion.
30
53
 
31
54
  Examples:
32
55
  $ gm-notepad
33
- $ gm-notepad rolls.txt
56
+ $ gm-notepad filename
34
57
  $ echo '{name}' | gm-notepad
35
58
 
36
- Specific options:
37
- --timestamp Append a timestamp to the note (Default: false)
38
- -c, --config_reporting Dump the configuration data (Default: false)
59
+ Options:
60
+ -l, --list_tables List tables loaded and exit (Default: false)
61
+ -r, --report_config Dump the configuration data (Default: false)
62
+ -p, --path=PATH Path(s) for {table_name}.<config.table_extension> files (Default: ["."])
63
+ -f, --filesystem_directory=DIR Path to dump tables (Default: ".")
64
+ -x, --table_extension=EXT Extension to use for selecting tables (Default: ".txt")
65
+ -t, --timestamp Append a timestamp to the note (Default: false)
39
66
  -d, --defer_output Defer output until system close (Default: false)
40
- -p, --path=PATH Path for {table_name}.<config.table_extension> files (Default: ["."])
41
- -t, --table_extension=EXT Path for {table_name}.<config.table_extension> files (Default: ".txt")
42
- -l, --list_tables List tables loaded (Default: nil)
67
+
68
+ Color options:
69
+ -i, --skip-interactive-color Disable color rendering for interactive buffer (Default: false)
70
+ -o, --with-output-color Enable color rendering for output buffer (Default: false)
71
+
43
72
  -h, --help You're looking at it!
44
73
  ```
45
74
 
46
75
  At it's core, `gm-shell` interacts with named tables. A named table is a file
47
76
  found amongst the specified `paths` and has the specified `table_extension`.
48
- Let's take a look at the defaults. In a new shell, type: `$ gm-notepad -c`
77
+ Let's take a look at the defaults. In a new shell, type: `$ gm-notepad -r`
49
78
 
50
79
  Which writes the following to the `interactive` buffer (eg. `$stderr`)::
51
80
 
52
81
  ```console
53
- => # Configuration Parameters:
54
- => # config[:config_reporting] = false
55
- => # config[:defer_output] = false
56
- => # config[:interactive_buffer] = #<IO:<STDERR>>
57
- => # config[:output_buffer] = #<IO:<STDOUT>>
58
- => # config[:paths] = ["."]
59
- => # config[:table_extension] = ".txt"
60
- => # config[:with_timestamp] = false
82
+ # Configuration Parameters:
83
+ # config[:report_config] = true
84
+ # config[:defer_output] = false
85
+ # config[:filesystem_directory] = "."
86
+ # config[:interactive_buffer] = #<IO:<STDERR>>
87
+ # config[:interactive_color] = true
88
+ # config[:output_color] = false
89
+ # config[:list_tables] = false
90
+ # config[:output_buffer] = #<IO:<STDOUT>>
91
+ # config[:paths] = ["."]
92
+ # config[:table_extension] = ".txt"
93
+ # config[:with_timestamp] = false
61
94
  ```
62
95
 
63
96
  You'll need to exit out (CTRL+D).
@@ -65,11 +98,12 @@ You'll need to exit out (CTRL+D).
65
98
  By default `gm-notepad` will load as tables all files matching the following
66
99
  glob: `./**/*.txt`.
67
100
 
68
- Included in the gem's test suite are three files:
101
+ Included in the gem's test suite are four files:
69
102
 
70
103
  * `./spec/fixtures/name.txt`
71
104
  * `./spec/fixtures/first-name.txt`
72
105
  * `./spec/fixtures/last-name.txt`
106
+ * `./spec/fixtures/location.csv`
73
107
 
74
108
  When I run `gm-notepad -l`, `gm-notepad` does the following:
75
109
 
@@ -78,10 +112,11 @@ When I run `gm-notepad -l`, `gm-notepad` does the following:
78
112
  * puts the table_names to the `interactive` buffer
79
113
  * exits
80
114
 
81
- Below are the table names when you run the `gm-notepad` against the
82
- repository:
115
+ Below are the table names when you run the `gm-notepad -l` against the
116
+ repository (note when you run this command you'll get a preamble of the config):
83
117
 
84
118
  ```console
119
+ => character
85
120
  => first-name
86
121
  => last-name
87
122
  => name
@@ -99,7 +134,7 @@ You now have an interactive shell for `gm-notepad`. Type `?` and hit
99
134
  => Prefixes:
100
135
  => ? - Help (this command)
101
136
  => + - Query table names and contents
102
- => <table_name> - Write the results to the given table
137
+ => <table_name: - Write the results to the given table
103
138
  =>
104
139
  => Tokens:
105
140
  => ! - Skip expansion
@@ -114,20 +149,26 @@ the following: `+first-name`
114
149
  `gm-notepad` will write the following to `interactive` buffer (eg. `$stderr`):
115
150
 
116
151
  ```console
117
- => Frodo
118
- => Merry
119
- => Pippin
120
- => Sam
121
- => {first-name}Wise
152
+ => [1] Frodo
153
+ => [2] Merry
154
+ => [3] Pippin
155
+ => [4] Sam
156
+ => [5-6] {first-name}Wise
122
157
  ```
123
158
 
124
- These are the five table entries in the `first-name` table. Notice the fifth
125
- entry: `{first-name}Wise`. The `{first-name}` references a table named
126
- "first-name" (the same on you are looking at). Now type the following in your `gm-notepad`
127
- session: `Hello {first-name}`
159
+ These are the five table entries in the `first-name` table.
160
+ "Frodo" is at index `1`. "Merry", "Pippin", and "Sam" are at indices 2,3,4
161
+ respectively. For the fifth line there are two things happening. First the
162
+ index spans a range. Second, notice the entry: `{first-name}Wise`. The
163
+ `{first-name}` references a table named "first-name" (the same on you are
164
+ looking at). Now type the following in your `gm-notepad` session: `Hello {first-name}`
128
165
 
129
166
  `gm-notepad` will read the line and recursively expand the `{first-name}` and
130
- write the result to the `interactive` buffer and `output` buffer.
167
+ write the result to the `interactive` buffer and `output` buffer. The expander
168
+ randomly picks a name from all entries, with ranges increasing the chance of
169
+ being picked. In the above table "Frodo", "Merry", "Pippin", and "Sam" each
170
+ have a 1 in 6 chance of being picked. And "{first-name}Wise" has a 2 in 6
171
+ chance.
131
172
 
132
173
  In the session you might have something like the below:
133
174
 
@@ -145,19 +186,54 @@ session type the following: `{first-name} owes [2d6]gp to {first-name}`:
145
186
  Frodo owes 3gp to SamWise
146
187
  ```
147
188
 
148
- And there you go.
189
+ Let's take a look at the `+character` table. Your table indices need not be
190
+ numbers. And you can mix numbers and text. _If you expand the table via
191
+ `{table}` each text index counts as 1 entry. For the below table, each
192
+ entry has a 1 in 3 chance of being randomly chosen.
193
+
194
+ ```console
195
+ => [name] Grell
196
+ => [ac] 15
197
+ => [hd] 12D12
198
+ ```
199
+
200
+ ## Testing Locally
201
+
202
+ * Clone the repository
203
+ * Bundle the dependencies (`$ bundle install`)
204
+ * Run the specs (`$ bundle exec rspec`)
205
+ * Run the command from the repository (`$ bundle exec exe/gm-notepad`)
149
206
 
150
207
  ## Todo
151
208
 
152
- - [ ] Colorize puts to `interactive` buffer
209
+ - [X] Colorize puts to `interactive` buffer
210
+ - [X] Disable colors as a configuration option
211
+ - [ ] Write expected interface document
212
+ - [ ] Skip table lines that begin with `#`
213
+ - [X] Skip processing input lines that begin with `#`
214
+ - [ ] Allow configuration to specify colors
215
+ - [X] Allow configuration to specify table delimiter
216
+ - [ ] Allow option to add a table to memory (instead of writing the table)
217
+ - [ ] Add option to dump all tables to the given directory
218
+ - [X] Allow configuration for where to dump data
153
219
  - [ ] Normalize `WriteToTableHandler` to use a renderer
154
220
  - [ ] Normalize `WriteToTableHandler` to deliver on `grep` and `index` behavior
155
- - [ ] Gracefully handle requesting an entry from a table with an index that does not exist (e.g. with test data try `+name[23]`)
156
- - [ ] Gracefully handle `+name[]`, where "name" is a registered table
221
+ - [X] Gracefully handle requesting an entry from a table with an index that does not exist (e.g. with test data try `+name[23]`)
222
+ - [X] Gracefully handle `+name[]`, where "name" is a registered table
157
223
  - [ ] Add time to live for line expansion (to prevent infinite loops)
158
- - [ ] Enable "up" and "down" to scroll through history
224
+ - [X] Enable "up" and "down" to scroll through history
159
225
  - [ ] Add config that expands dice results while including the requested roll
160
226
  - [ ] Determine feasibility of adding dice to the `{}` expansion syntax (instead of the `[]` syntax)
161
- - [ ] Add index name when rendering table entries
227
+ - [X] Add index name when rendering table entries
228
+ - [ ] Gracefully handle loading a malformed data file (maybe?)
162
229
  - [ ] Add force write results to `output`
163
- - [ ] Add concept of history
230
+ - [X] Add concept of history
231
+ - [ ] When expanding tables account for line expansion (via \n and \t)
232
+ - [ ] Separate the InputHandler into pre-amble (e.g. allow overrides to where we are writing, determine what command we are writing)
233
+ - [ ] Handle a `.gm-notepadrc` to inject default configurations
234
+ - [ ] Add auto table expansion for "{}"
235
+ - [ ] Add auto table expansion for "+"
236
+ - [ ] Add auto index expansion for "["
237
+ - [ ] Create a configuration object that captures the initial input (reduce passing around parameters and persisting copies of the config)
238
+ - [ ] Aspiration: Enable `{{monster}[ac]}` to pick a random monster and then fetch that monster's AC
239
+ - [ ] Add concept of "journal entry"; its not a table (perhaps) but something that you could capture notes.
data/Rakefile CHANGED
@@ -1,6 +1,33 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.pattern = "./spec/**/*_spec.rb"
7
+ ENV['COVERAGE'] = 'true'
8
+ end
5
9
 
6
- task :default => :spec
10
+ namespace :commitment do
11
+ task :configure_test_for_code_coverage do
12
+ ENV['COVERAGE'] = 'true'
13
+ end
14
+ task :code_coverage do
15
+ require 'json'
16
+ $stdout.puts "Checking code_coverage"
17
+ lastrun_filename = File.expand_path('../coverage/.last_run.json', __FILE__)
18
+ if File.exist?(lastrun_filename)
19
+ coverage_percentage = JSON.parse(File.read(lastrun_filename)).fetch('result').fetch('covered_percent').to_i
20
+ EXPECTED_COVERAGE_GOAL = 96
21
+ if coverage_percentage < EXPECTED_COVERAGE_GOAL
22
+ abort("ERROR: Code Coverage Goal Not Met:\n\t#{coverage_percentage}%\tExpected\n\t#{EXPECTED_COVERAGE_GOAL}%\tActual")
23
+ else
24
+ $stdout.puts "Code Coverage Goal Met (at least #{EXPECTED_COVERAGE_GOAL}% coverage)"
25
+ end
26
+ else
27
+ abort "Expected #{lastrun_filename} to exist for code coverage"
28
+ end
29
+ end
30
+ end
31
+
32
+ task(default: ['commitment:configure_test_for_code_coverage', :spec, 'commitment:code_coverage'])
33
+ task(release: :default)
@@ -0,0 +1,14 @@
1
+ # Write entry `1|entry one` to in memory table `basic_example`
2
+ <#basic_example:1|entry one
3
+ # Should render to interactive the internal help
4
+ ?
5
+ # Should render to interactive the given tables
6
+ +
7
+ # Should render to interactive the table `basic_example`
8
+ +basic_example
9
+ # Should render `entry one`
10
+ {basic_example}
11
+ # Should append entry `2|entry two` into memory table `basic_example`
12
+ <basic_example:2|entry two
13
+ # Should dump basic_example onto the file system
14
+ +basic_example>
data/exe/gm-notepad CHANGED
@@ -3,11 +3,18 @@ require 'gm/notepad'
3
3
 
4
4
  require 'optparse'
5
5
  config = {
6
- config_reporting: false,
6
+ report_config: false,
7
7
  defer_output: false,
8
+ filesystem_directory: '.',
8
9
  interactive_buffer: $stderr,
10
+ interactive_color: true,
11
+ output_color: false,
12
+ list_tables: false,
9
13
  output_buffer: $stdout,
10
14
  paths: ['.'],
15
+ column_delimiter: Gm::Notepad::DEFAULT_COLUMN_DELIMITER,
16
+ shell_prompt: Gm::Notepad::DEFAULT_SHELL_PROMPT,
17
+ skip_readlines: false,
11
18
  table_extension: '.txt',
12
19
  with_timestamp: false
13
20
  }
@@ -20,37 +27,59 @@ OptionParser.new do |options|
20
27
  "Note taking tool with random table expansion.\n\n" \
21
28
  "Examples:\n" \
22
29
  "\t$ #{command_name}\n" \
23
- "\t$ #{command_name} rolls.txt\n" \
24
- "\t$ echo '{name}' | #{command_name}"
30
+ "\t$ #{command_name} filename \n" \
31
+ "\t$ echo '{name}' | #{command_name}\n\n" \
32
+ "Options:\n"
25
33
 
26
- # Separator just adds a new line with the specified text.
27
- options.separator ""
28
- options.separator "Specific options:"
29
-
30
- options.on("-t", "--timestamp", "Append a timestamp to the note (Default: #{config[:with_timestamp].inspect})") do |timestamp|
31
- config[:with_timestamp] = timestamp
34
+ options.on_head("-l", "--list_tables", "List tables loaded and exit (Default: #{config[:list_tables].inspect})") do |list_tables|
35
+ config[:list_tables] = list_tables
32
36
  end
33
37
 
34
- options.on("-c", "--config_reporting", "Dump the configuration data (Default: #{config[:config_reporting].inspect})") do |config_reporting|
35
- config[:config_reporting] = config_reporting
38
+ options.on("-r", "--report_config", "Dump the configuration data (Default: #{config[:report_config].inspect})") do |report_config|
39
+ config[:report_config] = report_config
36
40
  end
37
41
 
38
- options.on("-d", "--defer_output", "Defer output until system close (Default: #{config[:defer_output].inspect})") do |defer_output|
39
- config[:defer_output] = defer_output
42
+ options.on("-pPATH", "--path=PATH", String, "Path(s) for {table_name}.<config.table_extension> files (Default: #{config[:paths].inspect})") do |path|
43
+ config[:paths] << path
40
44
  end
41
45
 
42
- options.on("-pPATH", "--path=PATH", String, "Path for {table_name}.<config.table_extension> files (Default: #{config[:paths].inspect})") do |path|
43
- config[:paths] << path
46
+ options.on("-fDIR", "--filesystem_directory=DIR", String, "Path to dump tables (Default: #{config[:filesystem_directory].inspect})") do |filesystem_directory|
47
+ # Should we guard that this exists?
48
+ config[:filesystem_directory] = filesystem_directory
44
49
  end
45
50
 
46
- options.on("-tEXT", "--table_extension=EXT", String, "Path for {table_name}.<config.table_extension> files (Default: #{config[:table_extension].inspect})") do |table_extension|
51
+ options.on("-xEXT", "--table_extension=EXT", String, "Extension to use for selecting tables (Default: #{config[:table_extension].inspect})") do |table_extension|
47
52
  config[:table_extension] = table_extension
48
53
  end
49
54
 
50
- options.on("-l", "--list_tables", "List tables loaded (Default: #{config[:list_tables].inspect})") do |list_tables|
51
- config[:list_tables] = list_tables
55
+ options.on("-dDELIM", "--delimiter=DELIM", String, "Default column delimiter for tables (Default: #{config[:column_delimiter].inspect})") do |column_delimiter|
56
+ map = { "t" => "\t" }
57
+ config[:column_delimiter] = map.fetch(column_delimiter) { column_delimiter }
58
+ end
59
+
60
+ options.separator("")
61
+ options.separator("Output options:")
62
+ options.on("-t", "--timestamp", "Append a timestamp to the note (Default: #{config[:with_timestamp].inspect})") do |timestamp|
63
+ config[:with_timestamp] = timestamp
52
64
  end
53
65
 
66
+ options.on("--defer_output", "Defer output until system close (Default: #{config[:defer_output].inspect})") do |defer_output|
67
+ config[:defer_output] = defer_output
68
+ end
69
+
70
+ options.separator("")
71
+ options.separator("Color options:")
72
+
73
+ options.on("-i", "--skip-interactive-color", "Disable color rendering for interactive buffer (Default: #{!config[:interactive_color].inspect})") do |interactive_color|
74
+ config[:interactive_color] = !interactive_color
75
+ end
76
+
77
+ options.on("-o", "--with-output-color", "Enable color rendering for output buffer (Default: #{!config[:output_color].inspect})") do |output_color|
78
+ config[:output_color] = output_color
79
+ end
80
+
81
+ options.separator("")
82
+
54
83
  options.on_tail("-h", "--help", "You're looking at it!") do
55
84
  $stderr.puts options
56
85
  exit 1
@@ -58,26 +87,35 @@ OptionParser.new do |options|
58
87
  end.parse!
59
88
 
60
89
  if config[:list_tables]
61
- notepad = Gm::Notepad.new(config.merge(config_reporting: true))
90
+ notepad = Gm::Notepad.new(config.merge(report_config: true))
62
91
  notepad.process(input: "+")
63
92
  exit(1)
64
93
  end
65
94
 
66
95
  begin
67
96
  @notepad = Gm::Notepad.new(**config)
68
- # Keep reading lines of input as long as they're coming.
69
- while input = ARGF.gets
97
+ if config.fetch(:skip_readlines)
98
+ input_getter = -> { print "#{config.fetch(:shell_prompt)} "; ARGF.gets }
99
+ else
100
+ require 'gm/notepad/readline'
101
+ input_getter = Gm::Notepad::Readline.input_getter(**config)
102
+ end
103
+ while input = input_getter.call
104
+ # # Keep reading lines of input as long as they're coming.
70
105
  input.each_line do |input|
71
106
  begin
72
107
  @notepad.process(input: input)
73
108
  rescue Errno::EPIPE
109
+ @notepad.close!
110
+
74
111
  # sysexits(3) specifies that exit code 74 represent an IO error,
75
112
  # which is the likely situation
76
- @notepad.close!
77
113
  exit(74)
78
114
  end
79
115
  end
80
116
  end
117
+ rescue => e
118
+ $stderr.puts e
81
119
  ensure
82
120
  @notepad.close!
83
121
  end
data/gm-notepad.gemspec CHANGED
@@ -12,6 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = %q{A command line tool for GM-ing}
13
13
  spec.description = %q{A command line tool for GM-ing}
14
14
  spec.homepage = "https://github.com/jeremyf/gm-notepad"
15
+ spec.license = "APACHE2"
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
18
  f.match(%r{^(test|spec|features)/})
@@ -21,9 +22,11 @@ Gem::Specification.new do |spec|
21
22
  spec.require_paths = ["lib"]
22
23
 
23
24
  spec.add_dependency "dice_parser"
25
+ spec.add_dependency "term-ansicolor"
24
26
  spec.add_development_dependency "bundler"
25
27
  spec.add_development_dependency "rake"
26
28
  spec.add_development_dependency "rspec"
27
29
  spec.add_development_dependency "pry-byebug"
28
30
  spec.add_development_dependency "rspec-its"
31
+ spec.add_development_dependency "simplecov"
29
32
  end
@@ -0,0 +1,6 @@
1
+ module Gm
2
+ module Notepad
3
+ DEFAULT_SHELL_PROMPT = "🗒".freeze
4
+ DEFAULT_COLUMN_DELIMITER = "|".freeze
5
+ end
6
+ end
@@ -1,4 +1,4 @@
1
- require_relative 'input_handlers/default_handler'
1
+ require "gm/notepad/input_handlers/default_handler"
2
2
  module Gm
3
3
  module Notepad
4
4
  # Responsible for registering the various input handlers
@@ -0,0 +1,25 @@
1
+ require "gm/notepad/input_handlers/default_handler"
2
+ module Gm
3
+ module Notepad
4
+ module InputHandlers
5
+ class CommentHandler < DefaultHandler
6
+ COMMEND_PREFIX = '#'.freeze
7
+
8
+ def self.handles?(input:)
9
+ return false unless input[0] == COMMEND_PREFIX
10
+ true
11
+ end
12
+
13
+ def after_initialize!
14
+ self.to_interactive = true
15
+ self.to_output = false
16
+ self.expand_line = false
17
+ end
18
+
19
+ def lines
20
+ [input]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,4 @@
1
- require_relative "default_handler"
1
+ require "gm/notepad/input_handlers/default_handler"
2
2
  module Gm
3
3
  module Notepad
4
4
  module InputHandlers
@@ -21,7 +21,7 @@ module Gm
21
21
  "Prefixes:",
22
22
  "\t? - Help (this command)",
23
23
  "\t+ - Query table names and contents",
24
- "\t<table_name> - Write the results to the given table",
24
+ "\t<table_name: - Write the results to the given table",
25
25
  "",
26
26
  "Tokens:",
27
27
  "\t! - Skip expansion",
@@ -1,4 +1,4 @@
1
- require_relative "default_handler"
1
+ require "gm/notepad/input_handlers/default_handler"
2
2
  module Gm
3
3
  module Notepad
4
4
  module InputHandlers
@@ -16,6 +16,8 @@ module Gm
16
16
 
17
17
  WITH_GREP_REGEXP = %r{(?<declaration>\/(?<grep>[^\/]+)/)}
18
18
  WITH_INDEX_REGEXP = %r{(?<declaration>\[(?<index>[^\]]+)\])}
19
+ WITH_EMPTY_INDEX_REGEX = %r{(?<declaration>\[\])}
20
+ WITH_EMPTY_GREP_REGEX = %r{(?<declaration>\/\/)}
19
21
  NON_EXPANDING_CHARATER = '!'.freeze
20
22
  def after_initialize!
21
23
  self.expand_line = false
@@ -24,10 +26,13 @@ module Gm
24
26
 
25
27
  line = input[1..-1].to_s
26
28
  self.expand_line = false
27
- if match = WITH_INDEX_REGEXP.match(line)
29
+ if match = WITH_EMPTY_INDEX_REGEX.match(line)
30
+ line = line.sub(match[:declaration], '')
31
+ elsif match = WITH_INDEX_REGEXP.match(line)
32
+ line = line.sub(match[:declaration], '')
33
+ self.index = match[:index]
34
+ elsif match = WITH_EMPTY_GREP_REGEX.match(line)
28
35
  line = line.sub(match[:declaration], '')
29
- index = match[:index]
30
- self.index = index
31
36
  elsif match = WITH_GREP_REGEXP.match(line)
32
37
  line = line.sub(match[:declaration], '')
33
38
  grep = match[:grep]
@@ -50,7 +55,11 @@ module Gm
50
55
  return [message]
51
56
  end
52
57
  if index
53
- [table.lookup(index: index)]
58
+ begin
59
+ [table.lookup(index: index)]
60
+ rescue KeyError
61
+ [%(Entry with index "#{index}" not found in "#{table_name}" table)]
62
+ end
54
63
  elsif grep
55
64
  regexp = %r{#{grep}}i
56
65
  table.grep(regexp)
@@ -1,4 +1,4 @@
1
- require_relative "default_handler"
1
+ require "gm/notepad/input_handlers/default_handler"
2
2
 
3
3
  module Gm
4
4
  module Notepad
@@ -1,4 +1,4 @@
1
- require_relative "default_handler"
1
+ require "gm/notepad/input_handlers/default_handler"
2
2
 
3
3
  module Gm
4
4
  module Notepad
@@ -1,18 +1,19 @@
1
- require_relative "default_handler"
1
+ require "gm/notepad/input_handlers/default_handler"
2
2
 
3
3
  module Gm
4
4
  module Notepad
5
5
  module InputHandlers
6
6
  class WriteToTableHandler < DefaultHandler
7
+ HANDLED_PREFIX = "<".freeze
7
8
  def self.handles?(input:)
8
- return true if input[0] == "<"
9
+ return true if input[0] == HANDLED_PREFIX
9
10
  end
10
11
 
11
12
  attr_accessor :index, :grep, :table_name, :line
12
13
  NON_EXPANDING_CHARATER = '!'.freeze
13
14
  WITH_INDEX_REGEXP = %r{(?<declaration>\[(?<index>[^\]]+)\])}
14
15
  WITH_GREP_REGEXP = %r{(?<declaration>\/(?<grep>[^\/]+)/)}
15
- WITH_WRITE_TARGET_REGEXP = %r{\A<(?<table_name>[^>]+)>(?<line>.*)}
16
+ WITH_WRITE_TARGET_REGEXP = %r{\A#{HANDLED_PREFIX}(?<table_name>[^:]+):(?<line>.*)}
16
17
  def after_initialize!
17
18
  self.to_filesystem = true
18
19
  self.to_interactive = true
@@ -1,3 +1,4 @@
1
+
1
2
  module Gm
2
3
  module Notepad
3
4
  # Responsible for processing the given input into a renderable state
@@ -27,14 +28,16 @@ module Gm
27
28
  end
28
29
 
29
30
  def default_input_handler_registry
30
- require_relative "input_handler_registry"
31
- require_relative "input_handlers/help_handler"
32
- require_relative "input_handlers/query_table_handler"
33
- require_relative "input_handlers/query_table_names_handler"
34
- require_relative "input_handlers/write_to_table_handler"
35
- require_relative "input_handlers/write_line_handler"
31
+ require "gm/notepad/input_handler_registry"
32
+ require "gm/notepad/input_handlers/help_handler"
33
+ require "gm/notepad/input_handlers/comment_handler"
34
+ require "gm/notepad/input_handlers/query_table_handler"
35
+ require "gm/notepad/input_handlers/query_table_names_handler"
36
+ require "gm/notepad/input_handlers/write_to_table_handler"
37
+ require "gm/notepad/input_handlers/write_line_handler"
36
38
  InputHandlerRegistry.new do |registry|
37
39
  registry.register(handler: InputHandlers::HelpHandler)
40
+ registry.register(handler: InputHandlers::CommentHandler)
38
41
  registry.register(handler: InputHandlers::QueryTableHandler)
39
42
  registry.register(handler: InputHandlers::QueryTableNamesHandler)
40
43
  registry.register(handler: InputHandlers::WriteToTableHandler)
@@ -1,32 +1,32 @@
1
1
  require 'time'
2
+ require 'term/ansicolor'
2
3
  module Gm
3
4
  module Notepad
4
5
  # Responsible for rendering lines to the corresponding buffers
5
6
  class LineRenderer
6
- def initialize(with_timestamp: false, defer_output:, output_buffer: default_output_buffer, interactive_buffer: default_interactive_buffer)
7
- @output_buffer = output_buffer
8
- @interactive_buffer = interactive_buffer
9
- @with_timestamp = with_timestamp
7
+ def initialize(defer_output:, output_buffer: default_output_buffer, interactive_buffer: default_interactive_buffer, **config)
8
+ self.config = config
9
+ self.output_buffer = output_buffer
10
+ self.interactive_buffer = interactive_buffer
10
11
  @defer_output = defer_output
11
12
  @lines = []
12
13
  yield(self) if block_given?
13
14
  end
14
15
 
15
- def call(line, to_output: false, to_interactive: true, as_of: Time.now)
16
- render_output(line, defer_output: defer_output, as_of: as_of) if to_output
17
- render_interactive(line) if to_interactive
16
+ def call(lines, to_output: false, to_interactive: true, as_of: Time.now)
17
+ render_interactive(lines) if to_interactive
18
+ render_output(lines, defer_output: defer_output, as_of: as_of) if to_output
18
19
  end
19
20
 
20
21
  def close!
21
- if defer_output
22
- @lines.each do |line|
23
- output_buffer.puts(line)
24
- end
25
- end
22
+ interactive_buffer.close!
23
+ output_buffer.close!
26
24
  end
27
25
 
28
26
  private
29
27
 
28
+ attr_accessor :config
29
+
30
30
  # When true, we defer_output writing until we close the notepad
31
31
  # When false, we write immediately to the output buffer
32
32
  attr_reader :defer_output
@@ -34,27 +34,48 @@ module Gm
34
34
  # The receiver of interactive messages
35
35
  attr_reader :interactive_buffer
36
36
 
37
+ def interactive_buffer=(buffer)
38
+ if config[:interactive_color]
39
+ @interactive_buffer = BufferWrapper.new(buffer, color: :faint, as: :interactive)
40
+ else
41
+ @interactive_buffer = BufferWrapper.new(buffer, as: :interactive)
42
+ end
43
+ end
44
+
37
45
  # The receiver of output
38
46
  attr_reader :output_buffer
39
47
 
48
+ def output_buffer=(buffer)
49
+ if config[:output_color]
50
+ @output_buffer = BufferWrapper.new(buffer, color: :bold, as: :output)
51
+ else
52
+ @output_buffer = BufferWrapper.new(buffer, as: :output)
53
+ end
54
+ end
55
+
56
+
40
57
  # When writing to output_buffer, should we prefix with a timestamp?
41
58
  def with_timestamp?
42
- @with_timestamp
59
+ config.fetch(:with_timestamp, false)
43
60
  end
44
61
 
45
- def render_output(line, defer_output:, as_of:)
46
- if with_timestamp?
47
- line = "#{as_of}\t#{line}"
48
- end
49
- if defer_output
50
- @lines << line
51
- else
52
- output_buffer.puts(line)
62
+ def render_output(lines, defer_output:, as_of:)
63
+ Array(lines).each do |line|
64
+ if with_timestamp?
65
+ line = "#{as_of}\t#{line}"
66
+ end
67
+ if defer_output
68
+ output_buffer.defer(line)
69
+ else
70
+ output_buffer.puts(line)
71
+ end
53
72
  end
54
73
  end
55
74
 
56
- def render_interactive(line)
57
- interactive_buffer.puts("=>\t#{line}")
75
+ def render_interactive(lines)
76
+ Array(lines).each do |line|
77
+ interactive_buffer.puts("=>\t#{line}")
78
+ end
58
79
  end
59
80
 
60
81
  def default_output_buffer
@@ -65,5 +86,37 @@ module Gm
65
86
  $stderr
66
87
  end
67
88
  end
89
+
90
+ # To provide a means for colorizing the output
91
+ class BufferWrapper
92
+ attr_reader :buffer, :color, :as
93
+ def initialize(buffer, color: false, as:)
94
+ @buffer = buffer
95
+ @buffer.extend(Term::ANSIColor)
96
+ @color = color
97
+ @as = as
98
+ @lines = []
99
+ end
100
+
101
+ def puts(text)
102
+ if color
103
+ buffer.puts("#{buffer.public_send(color)}#{text}#{buffer.reset}")
104
+ else
105
+ buffer.puts("#{text}")
106
+ end
107
+ end
108
+
109
+ def defer(line)
110
+ @lines << line
111
+ end
112
+
113
+ def close!
114
+ buffer.print("\n") if as == :interactive
115
+ @lines.each do |line|
116
+ puts(line)
117
+ end
118
+ end
119
+ end
120
+ private_constant :BufferWrapper
68
121
  end
69
122
  end
@@ -27,37 +27,38 @@ module Gm
27
27
 
28
28
  attr_reader :renderer
29
29
  def open!
30
- return unless config[:config_reporting]
31
- renderer.call("# Configuration Parameters:", to_interactive: true, to_output: true)
32
- config.each do |key, value|
33
- line = "# config[#{key.inspect}] = #{value.inspect}"
34
- renderer.call(line, to_interactive: true, to_output: true)
30
+ return unless config[:report_config]
31
+ lines = ["# Configuration Parameters:"]
32
+ config.each_pair do |key, value|
33
+ lines << "# config[#{key.inspect}] = #{value.inspect}"
35
34
  end
35
+ # When running :list_tables by default I don't want to report
36
+ # that to the output buffer.
37
+ to_output = !config[:list_tables]
38
+ renderer.call(lines, to_interactive: true, to_output: to_output)
36
39
  end
37
40
 
38
41
  attr_accessor :renderer, :input_processor
39
42
  attr_writer :config, :table_registry
40
43
 
41
44
  def default_input_processor
42
- require_relative "input_processor"
45
+ require "gm/notepad/input_processor"
43
46
  InputProcessor.new(table_registry: table_registry)
44
47
  end
45
48
 
46
49
  def default_table_registry
47
- require_relative "table_registry"
48
- TableRegistry.load_for(
49
- paths: config.fetch(:paths, []),
50
- table_extension: config.fetch(:table_extension, ".txt")
51
- )
50
+ require "gm/notepad/table_registry"
51
+ TableRegistry.load_for(**config)
52
52
  end
53
53
 
54
54
  def default_renderer
55
- require_relative 'line_renderer'
55
+ require 'gm/notepad/line_renderer'
56
56
  LineRenderer.new(
57
57
  with_timestamp: config.fetch(:with_timestamp, false),
58
58
  defer_output: config.fetch(:defer_output, false),
59
59
  output_buffer: config.fetch(:output_buffer, default_output_buffer),
60
- interactive_buffer: config.fetch(:interactive_buffer, default_interactive_buffer)
60
+ interactive_buffer: config.fetch(:interactive_buffer, default_interactive_buffer),
61
+ **config
61
62
  )
62
63
  end
63
64
 
@@ -0,0 +1,31 @@
1
+ require 'readline'
2
+ module Gm
3
+ module Notepad
4
+ # A configuration module for the Readline module
5
+ module Readline
6
+
7
+ # Check history for existing matches
8
+ completion_function = proc do |string|
9
+ ::Readline::HISTORY.grep(/^#{Regexp.escape(string)}/)
10
+ end
11
+ if ::Readline.respond_to?("basic_word_break_characters=")
12
+ ::Readline.basic_word_break_characters= " \t\n`><=;|&{("
13
+ end
14
+
15
+ # With a sucessful completion add this to the end
16
+ ::Readline.completion_append_character = " "
17
+
18
+ # Without this, when I had in history the following: ["{name}"]
19
+ # And would type `{\t` into the shell, I would get the following
20
+ # result: "{{name}".
21
+ ::Readline.completer_word_break_characters = ""
22
+
23
+ # Hook-in the above completion function
24
+ ::Readline.completion_proc = completion_function
25
+
26
+ def self.input_getter(**config)
27
+ -> { ::Readline.readline("#{config.fetch(:shell_prompt, ">")} ", true) }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,10 +1,11 @@
1
- require_relative "exceptions"
2
- require_relative "table_entry"
1
+ require "gm/notepad/exceptions"
2
+ require "gm/notepad/table_entry"
3
3
 
4
4
  module Gm
5
5
  module Notepad
6
6
  class Table
7
- def initialize(table_name:, lines:, filename: nil)
7
+ def initialize(table_name:, lines:, filename: nil, **config)
8
+ self.config = config
8
9
  self.table_name = table_name
9
10
  self.filename = filename
10
11
  process(lines: lines)
@@ -19,7 +20,7 @@ module Gm
19
20
  end
20
21
 
21
22
  def all
22
- @table.values
23
+ @table.values.uniq
23
24
  end
24
25
 
25
26
  def grep(expression)
@@ -43,7 +44,7 @@ module Gm
43
44
 
44
45
  private
45
46
 
46
- attr_accessor :table_name, :filename
47
+ attr_accessor :table_name, :filename, :config
47
48
 
48
49
  def random_index
49
50
  rand(@table.size)
@@ -52,7 +53,7 @@ module Gm
52
53
  def process(lines:)
53
54
  @table = {}
54
55
  lines.each do |line|
55
- entry = TableEntry.new(line: line)
56
+ entry = TableEntry.new(line: line, **config)
56
57
  entry.lookup_range.each do |i|
57
58
  key = i.to_s
58
59
  raise DuplicateKeyError.new(key: table_name, object: self) if @table.key?(key)
@@ -1,10 +1,11 @@
1
+ require 'gm/notepad/defaults'
1
2
  module Gm
2
3
  module Notepad
3
- TABLE_ENTRY_SEPARATOR = "|".freeze
4
4
  TABLE_ENTRY_RANGE_MARKER = "-".freeze
5
5
  class TableEntry
6
- def initialize(line:)
7
- self.lookup_column, self.entry_column = line.split(TABLE_ENTRY_SEPARATOR)
6
+ def initialize(line:, column_delimiter: DEFAULT_COLUMN_DELIMITER, **config)
7
+ self.column_delimiter = column_delimiter
8
+ self.lookup_column, self.entry_column = line.split(column_delimiter)
8
9
  end
9
10
 
10
11
  include Comparable
@@ -23,11 +24,15 @@ module Gm
23
24
 
24
25
  attr_reader :lookup_column, :entry_column
25
26
 
26
- alias to_s entry_column
27
+ def to_s
28
+ "[#{lookup_column}]\t#{entry_column}"
29
+ end
27
30
  alias to_str entry_column
28
31
 
29
32
  private
30
33
 
34
+ attr_accessor :column_delimiter
35
+
31
36
  def lookup_column=(input)
32
37
  @lookup_column = input.strip.freeze
33
38
  end
@@ -1,30 +1,36 @@
1
- require_relative "table"
2
- require_relative "exceptions"
1
+ require "gm/notepad/table"
2
+ require "gm/notepad/exceptions"
3
3
 
4
4
  module Gm
5
5
  module Notepad
6
6
  # Responsible for loading and registering all of the named tables
7
7
  class TableRegistry
8
- def self.load_for(paths:, table_extension: ".txt")
9
- table_registry = new(paths: paths, table_extension: table_extension)
10
- paths.each do |path|
11
- Dir.glob(File.join(path, "**/*#{table_extension}")).each do |filename|
12
- table_name = File.basename(filename, table_extension)
13
- table_registry.register_by_filename(table_name: table_name, filename: filename)
14
- end
15
- end
8
+ def self.load_for(**config)
9
+ table_registry = new(**config)
10
+ table_registry.load!
16
11
  table_registry
17
12
  end
18
13
 
19
- def initialize(line_evaluator: default_line_evaluator, paths: [], table_extension: ".txt")
14
+ def initialize(line_evaluator: default_line_evaluator, **config)
15
+ self.config = config
20
16
  self.line_evaluator = line_evaluator
21
- self.table_extension = table_extension
22
- self.paths = paths
17
+ self.paths = config.fetch(:paths) { [] }
18
+ self.table_extension = config.fetch(:table_extension) { ".txt" }
19
+ self.filesystem_directory = config.fetch(:filesystem_directory) { "." }
23
20
  @registry = {}
24
21
  end
25
22
 
23
+ def load!
24
+ paths.each do |path|
25
+ Dir.glob(File.join(path, "**/*#{table_extension}")).each do |filename|
26
+ table_name = File.basename(filename, table_extension)
27
+ register_by_filename(table_name: table_name, filename: filename)
28
+ end
29
+ end
30
+ end
31
+
26
32
  private
27
- attr_accessor :line_evaluator, :paths, :table_extension
33
+ attr_accessor :line_evaluator, :paths, :table_extension, :filesystem_directory, :config
28
34
  public
29
35
 
30
36
  def table_names
@@ -39,8 +45,8 @@ module Gm
39
45
  table = nil
40
46
  begin
41
47
  table = fetch_table(name: table_name)
42
- rescue KeyError => e
43
- filename = File.join(paths.first || ".", "#{table_name}#{table_extension}")
48
+ rescue KeyError
49
+ filename = File.join(filesystem_directory, "#{table_name}#{table_extension}")
44
50
  table = register(table_name: table_name, lines: [], filename: filename)
45
51
  end
46
52
  table.append(line: line, write: write)
@@ -70,7 +76,7 @@ module Gm
70
76
 
71
77
  def register(table_name:, lines:, filename: nil)
72
78
  raise DuplicateKeyError.new(key: table_name, object: self) if @registry.key?(table_name)
73
- @registry[table_name] = Table.new(table_name: table_name, lines: lines, filename: filename)
79
+ @registry[table_name] = Table.new(table_name: table_name, lines: lines, filename: filename, **config)
74
80
  end
75
81
 
76
82
  def default_line_evaluator
@@ -1,5 +1,5 @@
1
1
  module Gm
2
2
  module Notepad
3
- VERSION = "0.0.2"
3
+ VERSION = "0.0.3"
4
4
  end
5
5
  end
data/lib/gm/notepad.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "gm/notepad/version"
2
+ require "gm/notepad/defaults"
2
3
  require "gm/notepad/pad"
3
4
  module Gm
4
5
  module Notepad
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gm-notepad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Friesen
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: term-ansicolor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +108,20 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
97
125
  description: A command line tool for GM-ing
98
126
  email:
99
127
  - jeremy.n.friesen@gmail.com
@@ -107,15 +135,19 @@ files:
107
135
  - ".travis.yml"
108
136
  - CODE_OF_CONDUCT.md
109
137
  - Gemfile
138
+ - LICENSE
110
139
  - README.md
111
140
  - Rakefile
112
141
  - bin/console
113
142
  - bin/setup
143
+ - examples/basic_example_file
114
144
  - exe/gm-notepad
115
145
  - gm-notepad.gemspec
116
146
  - lib/gm/notepad.rb
147
+ - lib/gm/notepad/defaults.rb
117
148
  - lib/gm/notepad/exceptions.rb
118
149
  - lib/gm/notepad/input_handler_registry.rb
150
+ - lib/gm/notepad/input_handlers/comment_handler.rb
119
151
  - lib/gm/notepad/input_handlers/default_handler.rb
120
152
  - lib/gm/notepad/input_handlers/help_handler.rb
121
153
  - lib/gm/notepad/input_handlers/query_table_handler.rb
@@ -126,12 +158,14 @@ files:
126
158
  - lib/gm/notepad/line_evaluator.rb
127
159
  - lib/gm/notepad/line_renderer.rb
128
160
  - lib/gm/notepad/pad.rb
161
+ - lib/gm/notepad/readline.rb
129
162
  - lib/gm/notepad/table.rb
130
163
  - lib/gm/notepad/table_entry.rb
131
164
  - lib/gm/notepad/table_registry.rb
132
165
  - lib/gm/notepad/version.rb
133
166
  homepage: https://github.com/jeremyf/gm-notepad
134
- licenses: []
167
+ licenses:
168
+ - APACHE2
135
169
  metadata: {}
136
170
  post_install_message:
137
171
  rdoc_options: []