gm-notepad 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4666e0ae345ed21a19074c884d3d5052287d70f79d61c005598c0176b5305a4c
4
+ data.tar.gz: fbe40603b8d2c7ac16176e04c8dd4c314728891a07cf291fc84522071faca9ef
5
+ SHA512:
6
+ metadata.gz: 29cc37dc451ebb46573cf70c39353d6c73f65962c2956f18ddbd0abedaaa3f9a921c3ce828c10c22f2afda7268fe51099a3255fae938ca0171bfa8ab0ccfde6f
7
+ data.tar.gz: d1d5a709afe55264d2bd694463837b14afaaec529c84f238ca6c6374b706c3dbaea41971d70badd6229a506aed7cdab2dcf4c660c798d22d6d91dae583be594e
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.15.4
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at jeremy.n.friesen@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in gm-notepad.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # GM::Notepad
2
+
3
+ A command-line tool to help with your GM-ing.
4
+
5
+ ## Todo
6
+
7
+ - [ ] Colorize puts to `interactive` buffer
8
+ - [ ] Normalize `WriteToTableHandler` to use a renderer
9
+ - [ ] Normalize `WriteToTableHandler` to deliver on `grep` and `index` behavior
10
+ - [ ] Gracefully handle requesting an entry from a table with an index that does not exist (e.g. with test data try `+name[23]`)
11
+ - [ ] Gracefully handle `+name[]`, where "name" is a registered table
12
+
13
+ ## To install
14
+
15
+ `$ gem install gm-notepad`
16
+
17
+ ## Introduction
18
+
19
+ By default `gm-notepad` interacts with `$stdout` and `$stderr`. There are
20
+ three conceptual buffers:
21
+
22
+ * interactive (defaults to `$stderr`)
23
+ * output (defaults to `$stderr`)
24
+ * filesystem (defaults to your file system)
25
+
26
+ When you type a line, and hit \<enter\>, `gm-notepad` will evaluate the
27
+ line and render it to one or more of the buffers.
28
+
29
+ ## Examples
30
+
31
+ ### Simple Help
32
+
33
+ First, take a look at the help: `$ gm-notepad -h`
34
+
35
+ ```console
36
+ Usage: gm-notepad [options] [files]
37
+ Note taking tool with random table expansion.
38
+
39
+ Examples:
40
+ $ gm-notepad
41
+ $ gm-notepad rolls.txt
42
+ $ echo '{name}' | gm-notepad
43
+
44
+ Specific options:
45
+ --timestamp Append a timestamp to the note (Default: false)
46
+ -c, --config_reporting Dump the configuration data (Default: false)
47
+ -d, --defer_output Defer output until system close (Default: true)
48
+ -p, --path=PATH Path for {table_name}.<config.table_extension> files (Default: ["."])
49
+ -tTABLE_EXTENSION, Path for {table_name}.<config.table_extension> files (Default: ".txt")
50
+ --table_extension
51
+ -l, --list_tables List tables loaded (Default: nil)
52
+ -h, --help You're looking at it!
53
+ ```
54
+
55
+ At it's core, `gm-shell` interacts with named tables. A named table is a file
56
+ found amongst the specified `paths` and has the specified `table_extension`.
57
+ Let's take a look at the defaults. In a new shell, type: `$ gm-notepad -c`
58
+
59
+ Which writes the following to the `interactive` buffer (eg. `$stderr`)::
60
+
61
+ ```console
62
+ => # Configuration Parameters:
63
+ => # config[:config_reporting] = true
64
+ => # config[:defer_output] = true
65
+ => # config[:interactive_buffer] = #<IO:<STDERR>>
66
+ => # config[:output_buffer] = #<IO:<STDOUT>>
67
+ => # config[:paths] = ["."]
68
+ => # config[:table_extension] = ".txt"
69
+ => # config[:with_timestamp] = false
70
+ ```
71
+
72
+ You'll need to exit out (CTRL+D).
73
+
74
+ By default `gm-notepad` will load as tables all files matching the following
75
+ glob: `./**/*.txt`.
76
+
77
+ Included in the gem's test suite are three files:
78
+
79
+ * `./spec/fixtures/name.txt`
80
+ * `./spec/fixtures/first-name.txt`
81
+ * `./spec/fixtures/last-name.txt`
82
+
83
+ When I run `gm-notepad -l`, `gm-notepad` does the following:
84
+
85
+ * load all found tables
86
+ * puts the config (see above) to the `interactive` buffer
87
+ * puts the table_names to the `interactive` buffer
88
+ * exits
89
+
90
+ Below are the table names when you run the `gm-notepad` against the
91
+ repository:
92
+
93
+ ```console
94
+ => first-name
95
+ => last-name
96
+ => name
97
+ ```
98
+
99
+ Now let's load `gm-notepad` for interaction. In the terminal, type:
100
+ `$ gm-notepad`
101
+
102
+ You now have an interactive shell for `gm-notepad`. Type `?` and hit
103
+ \<enter\>.
104
+
105
+ `gm-notepad` will write the following to `interactive` buffer (eg. `$stderr`):
106
+
107
+ ```console
108
+ => Prefixes:
109
+ => ? - Help (this command)
110
+ => + - Query table names and contents
111
+ => <table_name> - Write the results to the given table
112
+ =>
113
+ => Tokens:
114
+ => ! - Skip expansion
115
+ => /search/ - Grep for the given 'search' within the prefix
116
+ => [index] - Target a specific 'index'
117
+ => {table_name} - expand_line the given 'table_name'
118
+ ```
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gm/notepad"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/gm-notepad ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ require 'gm/notepad'
3
+
4
+ require 'optparse'
5
+ config = {
6
+ config_reporting: false,
7
+ defer_output: true,
8
+ interactive_buffer: $stderr,
9
+ output_buffer: $stdout,
10
+ paths: ['.'],
11
+ table_extension: '.txt',
12
+ with_timestamp: false
13
+ }
14
+
15
+ command_name = File.basename(__FILE__)
16
+
17
+ OptionParser.new do |options|
18
+ # This banner is the first line of your help documentation.
19
+ options.set_banner "Usage: #{command_name} [options] [files]\n" \
20
+ "Note taking tool with random table expansion.\n\n" \
21
+ "Examples:\n" \
22
+ "\t$ #{command_name}\n" \
23
+ "\t$ #{command_name} rolls.txt\n" \
24
+ "\t$ echo '{name}' | #{command_name}"
25
+
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
32
+ end
33
+
34
+ options.on("-c", "--config_reporting", "Dump the configuration data (Default: #{config[:config_reporting].inspect})") do |config_reporting|
35
+ config[:config_reporting] = config_reporting
36
+ end
37
+
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
40
+ end
41
+
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
44
+ end
45
+
46
+ options.on("-tTABLE_EXTENSION", "--table_extension=TABLE_EXTENSION", String, "Path for {table_name}.<config.table_extension> files (Default: #{config[:table_extension].inspect})") do |table_extension|
47
+ config[:table_extension] = table_extension
48
+ end
49
+
50
+ options.on("-l", "--list_tables", "List tables loaded (Default: #{config[:list_tables].inspect})") do |list_tables|
51
+ config[:list_tables] = list_tables
52
+ end
53
+
54
+ options.on_tail("-h", "--help", "You're looking at it!") do
55
+ $stderr.puts options
56
+ exit 1
57
+ end
58
+ end.parse!
59
+
60
+ if config[:list_tables]
61
+ notepad = Gm::Notepad.new(config.merge(config_reporting: true))
62
+ notepad.process(input: "+")
63
+ exit(1)
64
+ end
65
+
66
+ begin
67
+ @notepad = Gm::Notepad.new(**config)
68
+ # Keep reading lines of input as long as they're coming.
69
+ while input = ARGF.gets
70
+ input.each_line do |input|
71
+ begin
72
+ @notepad.process(input: input)
73
+ rescue Errno::EPIPE
74
+ # sysexits(3) specifies that exit code 74 represent an IO error,
75
+ # which is the likely situation
76
+ @notepad.close!
77
+ exit(74)
78
+ end
79
+ end
80
+ end
81
+ ensure
82
+ @notepad.close!
83
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "gm/notepad/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gm-notepad"
8
+ spec.version = Gm::Notepad::VERSION
9
+ spec.authors = ["Jeremy Friesen"]
10
+ spec.email = ["jeremy.n.friesen@gmail.com"]
11
+
12
+ spec.summary = %q{A command line tool for GM-ing}
13
+ spec.description = %q{A command line tool for GM-ing}
14
+ spec.homepage = "https://github.com/jeremyf/gm-notepad"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "dice_parser"
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "pry-byebug"
28
+ spec.add_development_dependency "rspec-its"
29
+ end
@@ -0,0 +1,9 @@
1
+ module Gm
2
+ module Notepad
3
+ class DuplicateKeyError < RuntimeError
4
+ def initialize(key:, object:)
5
+ super("Duplicate key for #{key.inspect} in #{object}")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'input_handlers/default_handler'
2
+ module Gm
3
+ module Notepad
4
+ # Responsible for registering the various input handlers
5
+ class InputHandlerRegistry
6
+ def initialize
7
+ @registry = []
8
+ yield(self) if block_given?
9
+ end
10
+
11
+ def handler_for(input:, skip_default: false)
12
+ handler = nil
13
+ @registry.each do |handler_builder|
14
+ if handler = handler_builder.build_if_handled(input: input)
15
+ break
16
+ end
17
+ end
18
+ return handler if handler
19
+ return nil if skip_default
20
+ default_handler_builder.build_if_handled(input: input)
21
+ end
22
+
23
+ def register(handler:)
24
+ @registry << handler
25
+ end
26
+
27
+ def default_handler_builder
28
+ InputHandlers::DefaultHandler
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ module Gm
2
+ module Notepad
3
+ module InputHandlers
4
+ class DefaultHandler
5
+ def self.build_if_handled(input:)
6
+ return false unless handles?(input: input)
7
+ new(input: input)
8
+ end
9
+
10
+ def self.handles?(input:)
11
+ true
12
+ end
13
+
14
+ def initialize(input:, table_registry: nil)
15
+ self.to_interactive = false
16
+ self.to_output = false
17
+ self.to_filesystem = false
18
+ self.expand_line = false
19
+ self.input = input
20
+ self.table_registry = table_registry
21
+ after_initialize!
22
+ end
23
+ attr_accessor :table_registry, :to_interactive, :to_output, :expand_line, :input, :to_filesystem
24
+
25
+ def after_initialize!
26
+ end
27
+
28
+ def lines
29
+ []
30
+ end
31
+
32
+ alias expand_line? expand_line
33
+
34
+ def each_line_with_parameters
35
+ lines.each do |line|
36
+ line = table_registry.evaluate(line: line.to_s.strip) if expand_line?
37
+ yield(line, to_output: to_output, to_interactive: to_interactive)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ require_relative "default_handler"
2
+ module Gm
3
+ module Notepad
4
+ module InputHandlers
5
+ class HelpHandler < DefaultHandler
6
+ HELP_PREFIX = '?'.freeze
7
+
8
+ def self.handles?(input:)
9
+ return false unless input[0] == HELP_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
+ [
21
+ "Prefixes:",
22
+ "\t? - Help (this command)",
23
+ "\t+ - Query table names and contents",
24
+ "\t<table_name> - Write the results to the given table",
25
+ "",
26
+ "Tokens:",
27
+ "\t! - Skip expansion",
28
+ "\t/search/ - Grep for the given 'search' within the prefix",
29
+ "\t[index] - Target a specific 'index'",
30
+ "\t{table_name} - expand_line the given 'table_name'"
31
+ ]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,64 @@
1
+ require_relative "default_handler"
2
+ module Gm
3
+ module Notepad
4
+ module InputHandlers
5
+ class QueryTableHandler < DefaultHandler
6
+ QUERY_TABLE_NAMES_PREFIX = '+'.freeze
7
+ def self.handles?(input:)
8
+ # Does not have the table prefix
9
+ return false unless input[0] == QUERY_TABLE_NAMES_PREFIX
10
+ # It is only the table prefix
11
+ return false if input == QUERY_TABLE_NAMES_PREFIX
12
+ # It is querying all tables by way of grep
13
+ return false if input[0..1] == "#{QUERY_TABLE_NAMES_PREFIX}/"
14
+ true
15
+ end
16
+
17
+ WITH_GREP_REGEXP = %r{(?<declaration>\/(?<grep>[^\/]+)/)}
18
+ WITH_INDEX_REGEXP = %r{(?<declaration>\[(?<index>[^\]]+)\])}
19
+ NON_EXPANDING_CHARATER = '!'.freeze
20
+ def after_initialize!
21
+ self.expand_line = false
22
+ self.to_output = false
23
+ self.to_interactive = true
24
+
25
+ line = input[1..-1].to_s
26
+ self.expand_line = false
27
+ if match = WITH_INDEX_REGEXP.match(line)
28
+ line = line.sub(match[:declaration], '')
29
+ index = match[:index]
30
+ self.index = index
31
+ elsif match = WITH_GREP_REGEXP.match(line)
32
+ line = line.sub(match[:declaration], '')
33
+ grep = match[:grep]
34
+ self.grep = grep
35
+ end
36
+ if line[-1] == NON_EXPANDING_CHARATER
37
+ line = line[0..-2]
38
+ end
39
+ self.table_name = line.downcase
40
+ end
41
+
42
+ attr_accessor :index, :grep, :table_name
43
+
44
+ def lines
45
+ begin
46
+ table = table_registry.fetch_table(name: table_name)
47
+ rescue KeyError
48
+ message = "Unknown table #{table_name.inspect}. Did you mean: "
49
+ message += table_registry.table_names.grep(/\A#{table_name}/).map(&:inspect).join(", ")
50
+ return [message]
51
+ end
52
+ if index
53
+ [table.lookup(index: index)]
54
+ elsif grep
55
+ regexp = %r{#{grep}}i
56
+ table.grep(regexp)
57
+ else
58
+ table.all
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,41 @@
1
+ require_relative "default_handler"
2
+
3
+ module Gm
4
+ module Notepad
5
+ module InputHandlers
6
+ class QueryTableNamesHandler < DefaultHandler
7
+ QUERY_TABLE_NAMES_PREFIX = '+'.freeze
8
+ def self.handles?(input:)
9
+ # Does not have the table prefix
10
+ return false unless input[0] == QUERY_TABLE_NAMES_PREFIX
11
+ # It is only the table prefix
12
+ return true if input == QUERY_TABLE_NAMES_PREFIX
13
+ # It is querying all tables by way of grep
14
+ return true if input[0..1] == "#{QUERY_TABLE_NAMES_PREFIX}/"
15
+ false
16
+ end
17
+
18
+ WITH_GREP_REGEXP = %r{(?<declaration>\/(?<grep>[^\/]+)/)}
19
+ def after_initialize!
20
+ self.expand_line = false
21
+ self.to_output = false
22
+ self.to_interactive = true
23
+
24
+ line = input[1..-1]
25
+ if match = WITH_GREP_REGEXP.match(line)
26
+ line = line.sub(match[:declaration], '')
27
+ self.grep = match[:grep]
28
+ end
29
+ end
30
+
31
+ attr_accessor :grep
32
+
33
+ def lines
34
+ table_names = table_registry.table_names
35
+ return table_names unless grep
36
+ table_names.grep(%r{#{grep}})
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ require_relative "default_handler"
2
+
3
+ module Gm
4
+ module Notepad
5
+ module InputHandlers
6
+ class WriteLineHandler < DefaultHandler
7
+ NON_EXPANDING_CHARATER = '!'.freeze
8
+ def self.handles?(input:)
9
+ true
10
+ end
11
+
12
+ attr_accessor :line
13
+ def after_initialize!
14
+ self.expand_line = false
15
+ self.to_output = true
16
+ self.to_interactive = true
17
+ if input[0] == NON_EXPANDING_CHARATER
18
+ self.line = input[1..-1].strip
19
+ self.expand_line = false
20
+ else
21
+ self.line = input.strip
22
+ self.expand_line = true
23
+ end
24
+ end
25
+
26
+ def lines
27
+ [line]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,56 @@
1
+ require_relative "default_handler"
2
+
3
+ module Gm
4
+ module Notepad
5
+ module InputHandlers
6
+ class WriteToTableHandler < DefaultHandler
7
+ def self.handles?(input:)
8
+ return true if input[0] == "<"
9
+ end
10
+
11
+ attr_accessor :index, :grep, :table_name, :line
12
+ NON_EXPANDING_CHARATER = '!'.freeze
13
+ WITH_INDEX_REGEXP = %r{(?<declaration>\[(?<index>[^\]]+)\])}
14
+ WITH_GREP_REGEXP = %r{(?<declaration>\/(?<grep>[^\/]+)/)}
15
+ WITH_WRITE_TARGET_REGEXP = %r{\A<(?<table_name>[^>]+)>(?<line>.*)}
16
+ def after_initialize!
17
+ self.to_filesystem = true
18
+ self.to_interactive = true
19
+
20
+ if match = WITH_WRITE_TARGET_REGEXP.match(input)
21
+ line = match[:line].strip
22
+ table_name = match[:table_name]
23
+ if index_match = WITH_INDEX_REGEXP.match(table_name)
24
+ table_name = table_name.sub(index_match[:declaration], '')
25
+ self.index = index_match[:index]
26
+ elsif grep_match = WITH_GREP_REGEXP.match(table_name)
27
+ table_name = table_name.sub(grep_match[:declaration], '')
28
+ self.grep = grep_match[:grep]
29
+ end
30
+ self.table_name = table_name.downcase
31
+ else
32
+ raise "I don't know what to do"
33
+ end
34
+ if line[0] == NON_EXPANDING_CHARATER
35
+ self.expand_line = false
36
+ self.line = line[1..-1]
37
+ else
38
+ self.expand_line = true
39
+ end
40
+ self.line = line.strip
41
+ end
42
+
43
+ def lines
44
+ if index
45
+ elsif grep
46
+ end
47
+ if expand_line
48
+ else
49
+ end
50
+ table_registry.append(table_name: table_name, line: line, write: true)
51
+ []
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,46 @@
1
+ module Gm
2
+ module Notepad
3
+ # Responsible for processing the given input into a renderable state
4
+ class InputProcessor
5
+ def initialize(table_registry:, **config)
6
+ self.table_registry = table_registry
7
+ self.input_handler_registry = config.fetch(:input_handler_registry) { default_input_handler_registry }
8
+ end
9
+
10
+ def process(input:)
11
+ processor = build_for(input: input)
12
+ processor.each_line_with_parameters do |*args|
13
+ yield(*args)
14
+ end
15
+ end
16
+
17
+ attr_accessor :table_registry, :input_handler_registry
18
+ private :table_registry=, :input_handler_registry
19
+
20
+ private
21
+
22
+ def build_for(input:)
23
+ input = input.to_s.strip
24
+ handler = input_handler_registry.handler_for(input: input)
25
+ handler.table_registry = table_registry
26
+ handler
27
+ end
28
+
29
+ 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"
36
+ InputHandlerRegistry.new do |registry|
37
+ registry.register(handler: InputHandlers::HelpHandler)
38
+ registry.register(handler: InputHandlers::QueryTableHandler)
39
+ registry.register(handler: InputHandlers::QueryTableNamesHandler)
40
+ registry.register(handler: InputHandlers::WriteToTableHandler)
41
+ registry.register(handler: InputHandlers::WriteLineHandler)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ require 'dice'
2
+ module Gm
3
+ module Notepad
4
+ # Responsible for recording entries and then dumping them accordingly.
5
+ class LineEvaluator
6
+ TABLE_NAME_REGEXP = %r{(?<table_name_container>\{(?<table_name>[^\}]+)\})}
7
+ DICE_REGEXP = %r{(?<dice_container>\[(?<dice>[^\]]+)\])}
8
+ def call(line:, table_lookup_function:, expand_line: true)
9
+ return line unless expand_line
10
+ while match = line.match(TABLE_NAME_REGEXP)
11
+ evaluated_table_name = table_lookup_function.call(table_name: match[:table_name].strip)
12
+ line = line.sub(match[:table_name_container], evaluated_table_name)
13
+ end
14
+ while match = line.match(DICE_REGEXP)
15
+ if parsed_dice = Dice.parse(match[:dice])
16
+ evaluated_dice = "#{parsed_dice.evaluate} (#{match[:dice]})"
17
+ else
18
+ evaluated_dice = "(#{match[:dice]})"
19
+ end
20
+ line = line.sub(match[:dice_container], evaluated_dice)
21
+ end
22
+ line
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,69 @@
1
+ require 'time'
2
+ module Gm
3
+ module Notepad
4
+ # Responsible for rendering lines to the corresponding buffers
5
+ 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
10
+ @defer_output = defer_output
11
+ @lines = []
12
+ yield(self) if block_given?
13
+ end
14
+
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
18
+ end
19
+
20
+ def close!
21
+ if defer_output
22
+ @lines.each do |line|
23
+ output_buffer.puts(line)
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # When true, we defer_output writing until we close the notepad
31
+ # When false, we write immediately to the output buffer
32
+ attr_reader :defer_output
33
+
34
+ # The receiver of interactive messages
35
+ attr_reader :interactive_buffer
36
+
37
+ # The receiver of output
38
+ attr_reader :output_buffer
39
+
40
+ # When writing to output_buffer, should we prefix with a timestamp?
41
+ def with_timestamp?
42
+ @with_timestamp
43
+ end
44
+
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)
53
+ end
54
+ end
55
+
56
+ def render_interactive(line)
57
+ interactive_buffer.puts("=>\t#{line}")
58
+ end
59
+
60
+ def default_output_buffer
61
+ $stdout
62
+ end
63
+
64
+ def default_interactive_buffer
65
+ $stderr
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,73 @@
1
+ module Gm
2
+ module Notepad
3
+ # Responsible for recording entries and then dumping them accordingly.
4
+ class Pad
5
+ attr_reader :config
6
+ def initialize(**config)
7
+ self.config = config
8
+ self.table_registry = config.fetch(:table_registry) { default_table_registry }
9
+ self.renderer = config.fetch(:renderer) { default_renderer }
10
+ self.input_processor = config.fetch(:input_processor) { default_input_processor }
11
+ open!
12
+ end
13
+
14
+ def process(input:)
15
+ input_processor.process(input: input) do |*args|
16
+ renderer.call(*args)
17
+ end
18
+ end
19
+
20
+ def close!
21
+ @renderer.close!
22
+ end
23
+
24
+ attr_reader :table_registry
25
+
26
+ private
27
+
28
+ attr_reader :renderer
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)
35
+ end
36
+ end
37
+
38
+ attr_accessor :renderer, :input_processor
39
+ attr_writer :config, :table_registry
40
+
41
+ def default_input_processor
42
+ require_relative "input_processor"
43
+ InputProcessor.new(table_registry: table_registry)
44
+ end
45
+
46
+ 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
+ )
52
+ end
53
+
54
+ def default_renderer
55
+ require_relative 'line_renderer'
56
+ LineRenderer.new(
57
+ with_timestamp: config.fetch(:with_timestamp, false),
58
+ defer_output: config.fetch(:defer_output, false),
59
+ output_buffer: config.fetch(:output_buffer, default_output_buffer),
60
+ interactive_buffer: config.fetch(:interactive_buffer, default_interactive_buffer)
61
+ )
62
+ end
63
+
64
+ def default_output_buffer
65
+ $stdout
66
+ end
67
+
68
+ def default_interactive_buffer
69
+ $stderr
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,65 @@
1
+ require_relative "exceptions"
2
+ require_relative "table_entry"
3
+
4
+ module Gm
5
+ module Notepad
6
+ class Table
7
+ def initialize(table_name:, lines:, filename: nil)
8
+ self.table_name = table_name
9
+ self.filename = filename
10
+ process(lines: lines)
11
+ end
12
+
13
+ def lookup(index: false)
14
+ if index
15
+ @table.fetch(index)
16
+ else
17
+ @table.values[random_index]
18
+ end
19
+ end
20
+
21
+ def all
22
+ @table.values
23
+ end
24
+
25
+ def grep(expression)
26
+ returning_value = []
27
+ @table.each_value do |entry|
28
+ if expression.match(entry.entry_column)
29
+ returning_value << entry
30
+ end
31
+ end
32
+ returning_value
33
+ end
34
+
35
+ def append(line:, write: false)
36
+ process(lines: [line])
37
+ return unless filename
38
+ return unless write
39
+ File.open(filename, "a") do |file|
40
+ file.puts(line)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ attr_accessor :table_name, :filename
47
+
48
+ def random_index
49
+ rand(@table.size)
50
+ end
51
+
52
+ def process(lines:)
53
+ @table = {}
54
+ lines.each do |line|
55
+ entry = TableEntry.new(line: line)
56
+ entry.lookup_range.each do |i|
57
+ key = i.to_s
58
+ raise DuplicateKeyError.new(key: table_name, object: self) if @table.key?(key)
59
+ @table[key] = entry
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,40 @@
1
+ module Gm
2
+ module Notepad
3
+ TABLE_ENTRY_SEPARATOR = "|".freeze
4
+ TABLE_ENTRY_RANGE_MARKER = "-".freeze
5
+ class TableEntry
6
+ def initialize(line:)
7
+ self.lookup_column, self.entry_column = line.split(TABLE_ENTRY_SEPARATOR)
8
+ end
9
+
10
+ include Comparable
11
+ def <=>(other)
12
+ to_str <=> String(other)
13
+ end
14
+
15
+ NUMBER_RANGE_REGEXP = %r{(?<left>\d+) *- *(?<right>\d+)}
16
+ def lookup_range
17
+ if match = NUMBER_RANGE_REGEXP.match(lookup_column)
18
+ (match[:left].to_i..match[:right].to_i).map(&:to_s)
19
+ else
20
+ [lookup_column]
21
+ end
22
+ end
23
+
24
+ attr_reader :lookup_column, :entry_column
25
+
26
+ alias to_s entry_column
27
+ alias to_str entry_column
28
+
29
+ private
30
+
31
+ def lookup_column=(input)
32
+ @lookup_column = input.strip.freeze
33
+ end
34
+
35
+ def entry_column=(input)
36
+ @entry_column = input.strip.freeze
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,82 @@
1
+ require_relative "table"
2
+ require_relative "exceptions"
3
+
4
+ module Gm
5
+ module Notepad
6
+ # Responsible for loading and registering all of the named tables
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
16
+ table_registry
17
+ end
18
+
19
+ def initialize(line_evaluator: default_line_evaluator, paths: [], table_extension: ".txt")
20
+ self.line_evaluator = line_evaluator
21
+ self.table_extension = table_extension
22
+ self.paths = paths
23
+ @registry = {}
24
+ end
25
+
26
+ private
27
+ attr_accessor :line_evaluator, :paths, :table_extension
28
+ public
29
+
30
+ def table_names
31
+ @registry.keys.sort
32
+ end
33
+
34
+ def fetch_table(name:)
35
+ @registry.fetch(name)
36
+ end
37
+
38
+ def append(table_name:, line:, write:)
39
+ table = nil
40
+ begin
41
+ table = fetch_table(name: table_name)
42
+ rescue KeyError => e
43
+ filename = File.join(paths.first || ".", "#{table_name}#{table_extension}")
44
+ table = register(table_name: table_name, lines: [], filename: filename)
45
+ end
46
+ table.append(line: line, write: write)
47
+ end
48
+
49
+ def register_by_filename(table_name:, filename:)
50
+ content = File.read(filename)
51
+ register(table_name: table_name, lines: content.split("\n"), filename: filename)
52
+ end
53
+
54
+ def register_by_string(table_name:, string:)
55
+ register(table_name: table_name, lines: string.split("\n"))
56
+ end
57
+
58
+ def lookup(table_name:, **kwargs)
59
+ table = @registry.fetch(table_name)
60
+ table.lookup(**kwargs)
61
+ rescue KeyError
62
+ "(undefined #{table_name})"
63
+ end
64
+
65
+ def evaluate(line:)
66
+ line_evaluator.call(line: line, table_lookup_function: method(:lookup))
67
+ end
68
+
69
+ private
70
+
71
+ def register(table_name:, lines:, filename: nil)
72
+ 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)
74
+ end
75
+
76
+ def default_line_evaluator
77
+ require_relative 'line_evaluator'
78
+ LineEvaluator.new
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,5 @@
1
+ module Gm
2
+ module Notepad
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
data/lib/gm/notepad.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "gm/notepad/version"
2
+ require "gm/notepad/pad"
3
+ module Gm
4
+ module Notepad
5
+ def self.new(*args)
6
+ Pad.new(*args)
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gm-notepad
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Friesen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-07-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dice_parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-its
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: A command line tool for GM-ing
98
+ email:
99
+ - jeremy.n.friesen@gmail.com
100
+ executables:
101
+ - gm-notepad
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
+ - CODE_OF_CONDUCT.md
109
+ - Gemfile
110
+ - README.md
111
+ - Rakefile
112
+ - bin/console
113
+ - bin/setup
114
+ - exe/gm-notepad
115
+ - gm-notepad.gemspec
116
+ - lib/gm/notepad.rb
117
+ - lib/gm/notepad/exceptions.rb
118
+ - lib/gm/notepad/input_handler_registry.rb
119
+ - lib/gm/notepad/input_handlers/default_handler.rb
120
+ - lib/gm/notepad/input_handlers/help_handler.rb
121
+ - lib/gm/notepad/input_handlers/query_table_handler.rb
122
+ - lib/gm/notepad/input_handlers/query_table_names_handler.rb
123
+ - lib/gm/notepad/input_handlers/write_line_handler.rb
124
+ - lib/gm/notepad/input_handlers/write_to_table_handler.rb
125
+ - lib/gm/notepad/input_processor.rb
126
+ - lib/gm/notepad/line_evaluator.rb
127
+ - lib/gm/notepad/line_renderer.rb
128
+ - lib/gm/notepad/pad.rb
129
+ - lib/gm/notepad/table.rb
130
+ - lib/gm/notepad/table_entry.rb
131
+ - lib/gm/notepad/table_registry.rb
132
+ - lib/gm/notepad/version.rb
133
+ homepage: https://github.com/jeremyf/gm-notepad
134
+ licenses: []
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubygems_version: 3.0.1
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: A command line tool for GM-ing
155
+ test_files: []