sapling-dialogue 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5aaa32f64d5d1e66ff4d8418c48067f2e8a4c283
4
+ data.tar.gz: ddd847cc2912019b12de02680dd35282d7108be3
5
+ SHA512:
6
+ metadata.gz: 798eb921d6a9b8774de8c0353a38b4e7201aebeee83badb67047c3e9f14f2d82886b470f797f1709f977d88c17584299ffbd12f23db74ed1597d5b7434c6c43e
7
+ data.tar.gz: fb7ac1db708b798f8599d758ada08c7176fcd94a0ef94955ff3871793ce1b0d0776e00def90f255a94afd465df4e0f4b333983f43f3a0c110a0d5be61025589d
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sapling'
4
+
5
+ Sapling.start(ARGV)
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'thor'
4
+ require 'yaml'
5
+
6
+ Dir[File.join(__dir__, 'sapling', '*.rb')].each { |file| require file }
7
+
8
+ # The main Sapling interface.
9
+ class Sapling < Thor
10
+ desc 'read TREE', 'Load and traverse the TREE'
11
+ def read(tree)
12
+ exit unless verify_tree(tree)
13
+ puts 'Welcome to Sapling, a Dialogue Tree Utility.'
14
+ speaker = Dialogue::Speaker.new(YAML.load_file(tree), false)
15
+ speaker.conversation
16
+ end
17
+
18
+ desc 'edit TREE', 'Edit a new or existing TREE'
19
+ def edit(tree = '')
20
+ puts 'Welcome to Sapling, a Dialogue Tree Utility.'
21
+ if !tree.empty?
22
+ puts "Loading tree: #{tree}"
23
+ exit unless verify_tree(tree)
24
+ gardner = Planter::Spade.new(YAML.load_file(tree, false))
25
+ else
26
+ puts 'Creating a new tree!'
27
+ gardner = Planter::Spade.new(SKELETON_TREE)
28
+ end
29
+ gardner.plant
30
+ end
31
+
32
+ desc 'serve TREE', 'Load TREE in a web-based interface'
33
+ def serve(tree)
34
+ exit unless verify_tree(tree)
35
+ puts 'Sinatra will be cool.'
36
+ end
37
+
38
+ desc 'export TREE', 'Save a portable HTML version of TREE'
39
+ def export(tree)
40
+ exit unless verify_tree(tree)
41
+ puts 'Cool feature, bro!'
42
+ end
43
+ end
@@ -0,0 +1,110 @@
1
+ require_relative './gardner'
2
+
3
+ # Dialogue is the module for traversing an existing tree.
4
+ module Dialogue
5
+
6
+ # Format and display the trunk
7
+ #
8
+ # @param trunk [Hash] The trunk hash
9
+ # @param debug [Boolean] The status of showing debug information
10
+ def self.display_trunk(trunk, debug=false)
11
+ 40.times { print "-" }
12
+ puts "\n[ Trunk ]\n" if debug
13
+ puts "\n#{trunk["trunk"]}"
14
+ 40.times { print "-" }
15
+ puts "\n"
16
+ end
17
+
18
+ # Format and display a branch and the options
19
+ #
20
+ # @param branch [Hash] A branch data set
21
+ # @param branch_no [Integer] The branch number
22
+ # @param debug [Boolean] Status of showing debug information
23
+ def self.display_branch(branch, branch_no, debug=false)
24
+ puts "\n[ Branch: #{branch_no} ]" if debug
25
+ puts "\n#{branch["desc"]}\n\n"
26
+
27
+ branch["options"].each_pair do |k,v|
28
+ puts "\t#{k}: #{v.keys[0]}"
29
+ puts "\t\t[ Goes to branch #{v.values[0]} ]\n" if debug
30
+ end
31
+ end
32
+
33
+ # Speaker holds the functionality for going through a dialogue tree.
34
+ class Speaker
35
+ # The file, which should be a dialogue tree YAML file.
36
+ attr_accessor :file
37
+ # Status of verbose/debug mode. True = on; false = off.
38
+ attr_accessor :debug
39
+
40
+ def initialize(file="", debug=false)
41
+ @file = file
42
+ @debug = debug
43
+ end
44
+
45
+ # Conversation handles navigating the tree, until the option to end is
46
+ # reached.
47
+ def conversation()
48
+ tree = Gardner.prune_trunk(@file)
49
+
50
+ Dialogue.display_trunk(tree[0], false)
51
+ branches = Gardner.prune_branches(tree[1])
52
+
53
+ next_branch = 1
54
+ until next_branch == 0 do
55
+ next_branch = talk(branches[next_branch], next_branch)
56
+ end
57
+
58
+ puts "\n#{branches[0]["desc"]}"
59
+ exit
60
+ end
61
+
62
+ # Talk displays a branch, the options, and prompts for a response.
63
+ #
64
+ # @param branch [Hash] A branch data set
65
+ # @param branch_no [Integer] The branch number
66
+ # @return [Integer] The number of the next branch
67
+ def talk(branch, branch_no)
68
+ # If there are no options on this branch, we assume it's a terminal
69
+ # branch. Return 0, and end the program.
70
+ if branch["options"].empty?
71
+ puts "\n#{branch["desc"]}\n\n"
72
+ return 0
73
+ end
74
+
75
+ Dialogue.display_branch(branch, branch_no, @debug)
76
+
77
+ response = get_response(branch)
78
+
79
+ unless response == 0
80
+ puts "\n"
81
+ 10.times { print "*" }
82
+ puts "\n(Your choice: #{branch["options"][response].keys[0]})"
83
+ response = branch["options"][response].values[0].to_i
84
+ end
85
+
86
+ return response
87
+ end
88
+
89
+ # Get a response for the displayed branch
90
+ #
91
+ # @param branch [Hash] A branch data set
92
+ # @return [Integer] the next branch
93
+ def get_response(branch)
94
+ valid_options = branch["options"].keys.join(", ")
95
+
96
+ print "\n[#{valid_options}]> "
97
+ STDOUT.flush
98
+ response = STDIN.gets.chomp.to_i
99
+
100
+ until branch["options"].keys.include?(response) or response == 0
101
+ print "[## Invalid options. "
102
+ print "Valid options are #{valid_options}, or 0 to exit."
103
+ print "\n[#{valid_options}]> "
104
+ response = STDIN.gets.chomp.to_i
105
+ end
106
+
107
+ return response
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+
3
+ # Gardner is the module for working with a dialogue tree file
4
+ module Gardner
5
+
6
+ # Parse the tree array into an array of numbered branches, and ordered leaves.
7
+ #
8
+ # @param tree [Array] The dialogue tree
9
+ # @return [Array] An array of numbered branches, with numbered leaves
10
+ def self.prune_branches(tree)
11
+ branches = { 0 => { "desc" => "Thanks for using Sapling!" } }
12
+ tree.each do |b|
13
+ branches[b["branch"]["number"]] = {
14
+ "desc" => b["branch"]["text"],
15
+ "options" => prune_leaves(b["branch"]["leaf"]) }
16
+ end
17
+
18
+ return branches
19
+ end
20
+
21
+ # Parse the leaves of a branch into a numbered hash of options.
22
+ #
23
+ # @param leaves [Array] The option of leaf hashes
24
+ # @return [Hash] A numbered hash of options
25
+ def self.prune_leaves(leaves)
26
+ x = 1
27
+ options = {}
28
+
29
+ return options if leaves.nil?
30
+
31
+ leaves.each do |l|
32
+ options[x] = { l["text"] => l["branch"] }
33
+ x += 1
34
+ end
35
+
36
+ return options
37
+ end
38
+
39
+ # Parse the trunk of the tree.
40
+ #
41
+ # @param tree [Hash] The entire tree
42
+ # @return [Array] The trunk, and the remainder of the tree
43
+ def self.prune_trunk(tree)
44
+ trunk = tree.shift
45
+
46
+ return [trunk,tree]
47
+ end
48
+ end
@@ -0,0 +1,210 @@
1
+ require_relative './dialogue'
2
+ require_relative './gardner'
3
+ require_relative './utility'
4
+
5
+ # Planter is the module for creating or editing a tree.
6
+ module Planter
7
+
8
+ # In-memory tree
9
+ class Plot
10
+
11
+ # The tree, trunk, and branches
12
+ attr_accessor :tree, :trunk, :branches
13
+
14
+ # Edit the trunk of the tree
15
+ def edit_trunk
16
+ puts "Current Trunk:\n"
17
+ Dialogue.display_trunk(@trunk, true)
18
+ print "\n[ =EDITING= ](CTRL-C to abort)> "
19
+ STDOUT.flush
20
+ begin
21
+ new_trunk = STDIN.gets.to_s
22
+ rescue Interrupt
23
+ puts "\n**Aborting edit**\n\n"
24
+ new_trunk = @trunk["trunk"]
25
+ end
26
+ @trunk["trunk"] = new_trunk
27
+ end
28
+
29
+ # Edit a branch on the tree
30
+ #
31
+ # @param branch [Integer] The number of the branch to be edited.
32
+ def edit_branch(branch_no)
33
+ puts "Current Branch:\n"
34
+ Dialogue.display_branch(@branches[branch_no], branch_no, true)
35
+ print "\n[ =EDITING= ](CTRL-C to abort)> "
36
+ STDOUT.flush
37
+ begin
38
+ new_branch = STDIN.gets.to_s
39
+ rescue Interrupt
40
+ puts "\n**Aborting edit**\n\n"
41
+ new_branch = @branches[branch_no]["desc"]
42
+ end
43
+ @branches[branch_no]["desc"] = new_branch
44
+ end
45
+
46
+ # Edit a leaf on a branch, grasshopper
47
+ #
48
+ # @param branch [Integer] The number of the branch to be edited.
49
+ # @param leaf [Hash] The leaf hash to be edited.
50
+ def edit_leaf(branch, leaf)
51
+
52
+ end
53
+ end
54
+
55
+ # Utilities for editing specific parts of a tree.
56
+ class Spade
57
+
58
+ # The file we parse into a tree
59
+ attr_writer :file
60
+
61
+ def initialize(file)
62
+ @file = file
63
+ end
64
+
65
+ # Establish and populate a new Plot (in-memory tree), then control the flow
66
+ # of editing the Plot
67
+ def plant
68
+ @plot = Plot.new
69
+ @plot.tree = @file
70
+ @plot.trunk = @file.shift
71
+ @plot.branches = Gardner.prune_branches(@file)
72
+
73
+ next_branch = dig(1)
74
+ until next_branch == 0 do
75
+ next_branch = dig(next_branch)
76
+ end
77
+
78
+ puts "\n#{@plot.branches[0]["desc"]}"
79
+ exit
80
+ end
81
+
82
+ # Function for displaying a single branch in debug mode. We also always
83
+ # display the trunk, since otherwise it's displayed a single time then gone
84
+ # forever (until next time).
85
+ #
86
+ # @param branch_no [Integer] The number of the branch to be displayed.
87
+ def dig(branch_no)
88
+ branch = @plot.branches[branch_no]
89
+
90
+ Dialogue.display_trunk(@plot.trunk, true)
91
+ Dialogue.display_branch(branch, branch_no, true)
92
+
93
+ response = get_response(branch)
94
+ to_branch = parse_response(response, branch_no)
95
+
96
+ return to_branch
97
+ end
98
+
99
+ # Get a response for the displayed branch
100
+ #
101
+ # @param branch [Hash] A branch data set
102
+ # @return [Integer] the next branch
103
+ def get_response(branch)
104
+ total_branches = @plot.branches.count - 1
105
+ valid_options = ["1-#{total_branches}","t","a","b","x","l","s","q"]
106
+ print_options = valid_options.join(",")
107
+
108
+ print "\n[#{print_options}]> "
109
+ STDOUT.flush
110
+ response = STDIN.gets.chomp.to_s.downcase
111
+
112
+ until valid_options.include?(response) or response.to_i.between?(1,total_branches)
113
+ print "[## Invalid response. "
114
+ print "Valid options are #{print_options}"
115
+ print "\n[#{print_options}]> "
116
+ response = STDIN.gets.chomp.to_s.downcase
117
+ end
118
+
119
+ return response
120
+ end
121
+
122
+ # Parse the response from get_response
123
+ #
124
+ # @param response [String] The option selected
125
+ # @param branch_no [Integer] The currently-displayed branch
126
+ # @return [Integer] the branch to display
127
+ def parse_response(response, branch_no)
128
+ 10.times { print "*" }
129
+ print "\n(Your choice: "
130
+
131
+ if response.to_i >= 1
132
+ print "Change to branch #{response.to_i})\n\n"
133
+ return response.to_i
134
+
135
+ end
136
+
137
+ case response.to_s.downcase
138
+ when "t"
139
+ print "Edit the trunk.)\n\n"
140
+ @plot.edit_trunk
141
+ return branch_no
142
+ when "a"
143
+ print "Add a new branch.)\n\n"
144
+ return branch_no
145
+ when "b"
146
+ print "Edit the current branch.)\n\n"
147
+ @plot.edit_branch(branch_no)
148
+ return branch_no
149
+ when "x"
150
+ print "Delete the current branch.)\n\n"
151
+ return branch_no
152
+ when "l"
153
+ print "Edit leaves of current branch.)\n\n"
154
+ return branch_no
155
+ when "s"
156
+ print "Save changes.)\n\n"
157
+ return branch_no
158
+ when "q"
159
+ print "Quit without saving.)\n\n"
160
+ print "Unsaved changes will be lost. Still quit? [y/n]> "
161
+ verify = STDIN.gets.chomp.to_s.downcase
162
+
163
+ return 0 if verify == "y"
164
+ return branch_no
165
+ else
166
+ print "Unknown option. Returning to current branch.)\n\n"
167
+ return branch_no
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ =begin
174
+
175
+ Process:
176
+ - User selects to create/edit a tree
177
+ - If the user presented a file, use it as the tree file
178
+ - If the user failed to provide a file, create a new tree file in the current directory
179
+
180
+ - Check if the file is empty.
181
+ - If so, assume a new tree
182
+ - If not, verify formatting
183
+
184
+ - If new tree, prompt for trunk
185
+ - If existing tree, display trunk and first branch
186
+
187
+ - At this point, editing is the same
188
+ - Prompt provides options:
189
+ - #: Go to that branch number
190
+ - T: Modify the tree trunk
191
+ - A: Add a new branch (append to list of branches)
192
+ - B: Modify the current branch description
193
+ - X: Delete the current branch (does this renumber branches?)
194
+ - L: Modify the current leaves, respond with leaf prompt
195
+ - S: Save changes
196
+ - Q: Quit
197
+
198
+ - Example prompt:
199
+ [ 0-5,T,A,B,X,L,S,Q ]>
200
+ - Leaf prompt:
201
+
202
+ Details:
203
+
204
+ - Regardless of the file existing, the user will be editing a Hash, not the actual YAML file
205
+ - Use Gardner to build and interact with the tree
206
+ - Use Planter to modify the tree "in memory" (aka, the hash)
207
+ - Use Dialog to interact with the tree "in memory", to test-run it
208
+ - After each edit option, display the current branch with the new changes.
209
+
210
+ =end
@@ -0,0 +1,58 @@
1
+ # Utility functionality for all of Sapling to reference
2
+
3
+ # Constants
4
+
5
+ # A series of constants for handing a brand new tree. The skeleton tree provides
6
+ # a very basic introduction to Sapling. More details can be found in the
7
+ # documentation.
8
+
9
+ # The default trunk text of a new tree
10
+ SKELE_TRUNK_TEXT = "Welcome to the Sapling Editor. For details, please see the
11
+ documentation!"
12
+
13
+ # The default first-branch text of a new tree
14
+ SKELE_BRANCH_TEXT = "The first branch is always shown by default. It should act
15
+ as the introduction to the story. From here, the user enters your world!"
16
+
17
+ # The default first-leaf text of the first branch of a new tree. The leaf points
18
+ # to it's own branch. The only way out of the program is to either force-quit or
19
+ # reply with option 0.
20
+ SKELE_LEAF_TEXT = "Each branch can have any number of leaves, which represent
21
+ the options a user has on that branch. Each leaf points to another branch, or
22
+ can point to branch 0 to immediately exit."
23
+
24
+ # The final tree
25
+ SKELETON_TREE = [
26
+ {"trunk" => "#{SKELE_TRUNK_TEXT}"},
27
+ {"branch" => {
28
+ "number" => 1,
29
+ "text" => "#{SKELE_BRANCH_TEXT}",
30
+ "leaf" => [{
31
+ "text" => "#{SKELE_LEAF_TEXT}",
32
+ "branch" => 1
33
+ }]
34
+ }
35
+ }
36
+ ]
37
+
38
+
39
+ # Verify that a file is a dialogue tree file.
40
+ #
41
+ # @param file [File] The provided file
42
+ # @return [Boolean] True if the file is a tree; false otherwise
43
+ def verify_tree(file)
44
+ results = []
45
+ begin
46
+ tree = YAML.load_file(file)
47
+ results << tree[0].keys.include?("trunk")
48
+ results << tree[1]["branch"].keys.include?("number")
49
+ results << tree[1]["branch"].keys.include?("text")
50
+ results << tree[1]["branch"].keys.include?("leaf")
51
+ rescue
52
+ puts "Sorry chummer, I don't think this is a tree."
53
+ puts "Verify your YAML file is formatted properly."
54
+ results << false
55
+ end
56
+
57
+ results.include?(false) ? false : true
58
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sapling-dialogue
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bill Niblock
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Create, edit, and traverse Dialogue trees
14
+ email: azulien@gmail.com
15
+ executables:
16
+ - sapling
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/sapling
21
+ - lib/sapling.rb
22
+ - lib/sapling/dialogue.rb
23
+ - lib/sapling/gardner.rb
24
+ - lib/sapling/planter.rb
25
+ - lib/sapling/utility.rb
26
+ homepage: http://www.theinternetvagabond.com/sapling/
27
+ licenses:
28
+ - MIT
29
+ metadata: {}
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 2.6.13
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: A Dialogue Tree Utility
50
+ test_files: []