Merge pull request #8 from VagabondAzulien/editor

Editor
This commit is contained in:
Bill Niblock 2017-10-14 15:25:19 -04:00 committed by GitHub
commit 4f0a6a11fb
7 changed files with 331 additions and 235 deletions

110
lib/dialogue.rb Normal file
View file

@ -0,0 +1,110 @@
require_relative './gardner'
# Dialogue is the module for traversing an existing tree.
module Dialogue
# Format and display the trunk
#
# @param trunk [Hash] The trunk hash
# @param debug [Boolean] The status of showing debug information
def self.display_trunk(trunk, debug=false)
40.times { print "-" }
puts "\n[ Trunk ]\n" if debug
puts "\n#{trunk["trunk"]}"
40.times { print "-" }
puts "\n"
end
# Format and display a branch and the options
#
# @param branch [Hash] A branch data set
# @param branch_no [Integer] The branch number
# @param debug [Boolean] Status of showing debug information
def self.display_branch(branch, branch_no, debug=false)
puts "\n[ Branch: #{branch_no} ]" if debug
puts "\n#{branch["desc"]}\n\n"
branch["options"].each_pair do |k,v|
puts "\t#{k}: #{v.keys[0]}"
puts "\t\t[ Goes to branch #{v.values[0]} ]\n" if debug
end
end
# Speaker holds the functionality for going through a dialogue tree.
class Speaker
# The file, which should be a dialogue tree YAML file.
attr_accessor :file
# Status of verbose/debug mode. True = on; false = off.
attr_accessor :debug
def initialize(file="", debug=false)
@file = file
@debug = debug
end
# Conversation handles navigating the tree, until the option to end is
# reached.
def conversation()
tree = Gardner.prune_trunk(@file)
Dialogue.display_trunk(tree[0], false)
branches = Gardner.prune_branches(tree[1])
next_branch = 1
until next_branch == 0 do
next_branch = talk(branches[next_branch], next_branch)
end
puts "\n#{branches[0]["desc"]}"
exit
end
# Talk displays a branch, the options, and prompts for a response.
#
# @param branch [Hash] A branch data set
# @param branch_no [Integer] The branch number
# @return [Integer] The number of the next branch
def talk(branch, branch_no)
# If there are no options on this branch, we assume it's a terminal
# branch. Return 0, and end the program.
if branch["options"].empty?
puts "\n#{branch["desc"]}\n\n"
return 0
end
Dialogue.display_branch(branch, branch_no, @debug)
response = get_response(branch)
unless response == 0
puts "\n"
10.times { print "*" }
puts "\n(Your choice: #{branch["options"][response].keys[0]})"
response = branch["options"][response].values[0].to_i
end
return response
end
# Get a response for the displayed branch
#
# @param branch [Hash] A branch data set
# @return [Integer] the next branch
def get_response(branch)
valid_options = branch["options"].keys.join(", ")
print "\n[#{valid_options}]> "
STDOUT.flush
response = STDIN.gets.chomp.to_i
until branch["options"].keys.include?(response) or response == 0
print "[## Invalid options. "
print "Valid options are #{valid_options}, or 0 to exit."
print "\n[#{valid_options}]> "
response = STDIN.gets.chomp.to_i
end
return response
end
end
end

View file

@ -3,10 +3,10 @@ require 'yaml'
# Gardner is the module for working with a dialogue tree file
module Gardner
# Parse the branch
# Parse the tree array into an array of numbered branches, and ordered leaves.
#
# @param tree [Array] The dialogue tree
# @return [Array] The array of options on the branch.
# @return [Array] An array of numbered branches, with numbered leaves
def self.prune_branches(tree)
branches = { 0 => { "desc" => "Thanks for using Sapling!" } }
tree.each do |b|
@ -16,13 +16,12 @@ module Gardner
end
return branches
end
# Parse the options
# Parse the leaves of a branch into a numbered hash of options.
#
# @param leaves [Array] The option of leaf hashes
# @return [Hash] A has of options
# @return [Hash] A numbered hash of options
def self.prune_leaves(leaves)
x = 1
options = {}
@ -35,34 +34,15 @@ module Gardner
end
return options
end
# Parse the trunk
# The trunk is like the introduction to the tree.
# Parse the trunk of the tree.
#
# @param tree [Hash] The entire tree
# @return [Hash] The tree without the trunk
# @return [Array] The trunk, and the remainder of the tree
def self.prune_trunk(tree)
trunk = tree.shift
40.times { print "-" }
puts "\n#{trunk["trunk"]}"
40.times { print "-" }
puts "\n"
return tree
return [trunk,tree]
end
# The main method for Sapling. From here, the tree is grown.
#
# @param tree [File] The dialogue tree file
# @return [Hash] The final, constructed data set
def self.grow(tree)
trunk = Gardner.prune_trunk(tree)
branches = Gardner.prune_branches(trunk)
return branches
end
end

210
lib/planter.rb Normal file
View file

@ -0,0 +1,210 @@
require_relative './dialogue'
require_relative './gardner'
require_relative './utility'
# Planter is the module for creating or editing a tree.
module Planter
# In-memory tree
class Plot
# The tree, trunk, and branches
attr_accessor :tree, :trunk, :branches
# Edit the trunk of the tree
def edit_trunk
puts "Current Trunk:\n"
Dialogue.display_trunk(@trunk, true)
print "\n[ =EDITING= ](CTRL-C to abort)> "
STDOUT.flush
begin
new_trunk = STDIN.gets.to_s
rescue Interrupt
puts "\n**Aborting edit**\n\n"
new_trunk = @trunk["trunk"]
end
@trunk["trunk"] = new_trunk
end
# Edit a branch on the tree
#
# @param branch [Integer] The number of the branch to be edited.
def edit_branch(branch_no)
puts "Current Branch:\n"
Dialogue.display_branch(@branches[branch_no], branch_no, true)
print "\n[ =EDITING= ](CTRL-C to abort)> "
STDOUT.flush
begin
new_branch = STDIN.gets.to_s
rescue Interrupt
puts "\n**Aborting edit**\n\n"
new_branch = @branches[branch_no]["desc"]
end
@branches[branch_no]["desc"] = new_branch
end
# Edit a leaf on a branch, grasshopper
#
# @param branch [Integer] The number of the branch to be edited.
# @param leaf [Hash] The leaf hash to be edited.
def edit_leaf(branch, leaf)
end
end
# Utilities for editing specific parts of a tree.
class Spade
# The file we parse into a tree
attr_writer :file
def initialize(file)
@file = file
end
# Establish and populate a new Plot (in-memory tree), then control the flow
# of editing the Plot
def plant
@plot = Plot.new
@plot.tree = @file
@plot.trunk = @file.shift
@plot.branches = Gardner.prune_branches(@file)
next_branch = dig(1)
until next_branch == 0 do
next_branch = dig(next_branch)
end
puts "\n#{@plot.branches[0]["desc"]}"
exit
end
# Function for displaying a single branch in debug mode. We also always
# display the trunk, since otherwise it's displayed a single time then gone
# forever (until next time).
#
# @param branch_no [Integer] The number of the branch to be displayed.
def dig(branch_no)
branch = @plot.branches[branch_no]
Dialogue.display_trunk(@plot.trunk, true)
Dialogue.display_branch(branch, branch_no, true)
response = get_response(branch)
to_branch = parse_response(response, branch_no)
return to_branch
end
# Get a response for the displayed branch
#
# @param branch [Hash] A branch data set
# @return [Integer] the next branch
def get_response(branch)
total_branches = @plot.branches.count - 1
valid_options = ["1-#{total_branches}","t","a","b","x","l","s","q"]
print_options = valid_options.join(",")
print "\n[#{print_options}]> "
STDOUT.flush
response = STDIN.gets.chomp.to_s.downcase
until valid_options.include?(response) or response.to_i.between?(1,total_branches)
print "[## Invalid response. "
print "Valid options are #{print_options}"
print "\n[#{print_options}]> "
response = STDIN.gets.chomp.to_s.downcase
end
return response
end
# Parse the response from get_response
#
# @param response [String] The option selected
# @param branch_no [Integer] The currently-displayed branch
# @return [Integer] the branch to display
def parse_response(response, branch_no)
10.times { print "*" }
print "\n(Your choice: "
if response.to_i >= 1
print "Change to branch #{response.to_i})\n\n"
return response.to_i
end
case response.to_s.downcase
when "t"
print "Edit the trunk.)\n\n"
@plot.edit_trunk
return branch_no
when "a"
print "Add a new branch.)\n\n"
return branch_no
when "b"
print "Edit the current branch.)\n\n"
@plot.edit_branch(branch_no)
return branch_no
when "x"
print "Delete the current branch.)\n\n"
return branch_no
when "l"
print "Edit leaves of current branch.)\n\n"
return branch_no
when "s"
print "Save changes.)\n\n"
return branch_no
when "q"
print "Quit without saving.)\n\n"
print "Unsaved changes will be lost. Still quit? [y/n]> "
verify = STDIN.gets.chomp.to_s.downcase
return 0 if verify == "y"
return branch_no
else
print "Unknown option. Returning to current branch.)\n\n"
return branch_no
end
end
end
end
=begin
Process:
- User selects to create/edit a tree
- If the user presented a file, use it as the tree file
- If the user failed to provide a file, create a new tree file in the current directory
- Check if the file is empty.
- If so, assume a new tree
- If not, verify formatting
- If new tree, prompt for trunk
- If existing tree, display trunk and first branch
- At this point, editing is the same
- Prompt provides options:
- #: Go to that branch number
- T: Modify the tree trunk
- A: Add a new branch (append to list of branches)
- B: Modify the current branch description
- X: Delete the current branch (does this renumber branches?)
- L: Modify the current leaves, respond with leaf prompt
- S: Save changes
- Q: Quit
- Example prompt:
[ 0-5,T,A,B,X,L,S,Q ]>
- Leaf prompt:
Details:
- Regardless of the file existing, the user will be editing a Hash, not the actual YAML file
- Use Gardner to build and interact with the tree
- Use Planter to modify the tree "in memory" (aka, the hash)
- Use Dialog to interact with the tree "in memory", to test-run it
- After each edit option, display the current branch with the new changes.
=end

View file

@ -1,79 +0,0 @@
require_relative './gardner'
# Dialogue is the module for traversing an existing tree.
module Dialogue
# Spealer holds the functionality for viewing and going through a dialogue
# tree.
class Speaker
# The file, which should be a dialogue tree YAML file.
attr_accessor :file
# Status of verbose/debug mode. True = on; false = off.
attr_accessor :debug
def initialize
@file = ""
@debug = false
end
# Conversation handles navigating the tree, until the option to end is
# reached.
def conversation()
tree = Gardner.grow(@file)
10.times { print "*" }
puts "\n[ Branch: 1 ]" if @debug
next_branch = talk(tree[1])
until next_branch == 0 do
puts "\n[ Branch: #{next_branch} ]" if @debug
next_branch = talk(tree[next_branch])
end
puts "\n#{tree[0]["desc"]}"
exit
end
# Talk displays a branch, the options, and prompts for a response
#
# @param branch [Hash] A branch data set
# @return [Integer] The number of the next branch
def talk(branch)
# If there are no options on this branch, we assume it's a terminal
# branch. Return 0, and end the program.
if branch["options"].empty?
puts "\n#{branch["desc"]}\n\n"
return 0
end
valid_options = branch["options"].keys.join(", ")
puts "\n#{branch["desc"]}\n\n"
branch["options"].each_pair do |k,v|
puts "\t#{k}: #{v.keys[0]}"
puts "\t\t [ Goes to branch #{v.values[0]} ]" if @debug
end
print "\n[#{valid_options}]> "
STDOUT.flush
response = STDIN.gets.chomp.to_i
until branch["options"].keys.include?(response) or response == 0
print "[## Invalid options. "
print "Valid options are #{valid_options}, or 0 to exit."
print "\n[#{valid_options}]> "
response = STDIN.gets.chomp.to_i
end
puts "\n"
10.times { print "*" }
return 0 if response == 0
puts "\n(Your choice: #{branch["options"][response].keys[0]})"
return branch["options"][response].values[0].to_i
end
end
end

View file

@ -1,117 +0,0 @@
require_relative './dialogue'
require_relative './gardner'
require_relative './utility'
# Planter is the module for creating or editing a tree.
module Planter
class Plot
# The tree, trunk, and branches
attr_accessor :tree, :trunk, :branches
end
# Utilities for editing specific parts of a tree.
class Spade
# The file we parse into a tree
attr_writer :file
# Establish a new Plot, which is basically an object for storing information
# for us. From here, we start gardening.
def plant
@plot = Plot.new
@plot.tree = @file
@plot.trunk = @file.shift.values[0]
@plot.branches = Gardner.prune_branches(@file)
dig(1)
end
# Function for displaying a single branch in debug mode. We also always
# display the trunk, since otherwise it's displayed a single time then gone
# forever (until next time).
#
# @param branch_no [Integer] The number of the branch to be displayed.
def dig(branch_no)
branch = @plot.branches[branch_no]
# Print the trunk
40.times { print "-" }
puts "\n[ Trunk ]\n#{@plot.trunk}"
40.times { print "-" }
puts "\n"
10.times { print "*" }
# Print the branch and options
puts "\n[ Branch: #{branch_no} ]"
puts "\n#{branch["desc"]}\n\n"
unless branch["options"].empty?
branch["options"].each_pair do |k,v|
puts "\t#{k}: #{v.keys[0]}"
puts "\t\t [ Goes to branch #{v.values[0]} ]"
end
end
end
# Edit the trunk of the tree
def edit_trunk
end
# Edit a branch on the tree
#
# @param branch [Integer] The number of the branch to be edited.
def edit_branch(branch)
end
# Edit a leaf on a branch, grasshopper
#
# @param branch [Integer] The number of the branch to be edited.
# @param leaf [Hash] The leaf hash to be edited.
def edit_leaf(branch, leaf)
end
end
end
=begin
Process:
- User selects to create/edit a tree
- If the user presented a file, use it as the tree file
- If the user failed to provide a file, create a new tree file in the current directory
- Check if the file is empty.
- If so, assume a new tree
- If not, verify formatting
- If new tree, prompt for trunk
- If existing tree, display trunk and first branch
- At this point, editing is the same
- Prompt provides options:
- B # - Go to Branch #
- T - Edit Trunk
- D - Edit Current Branch description
- X - Delete Current Branch
- L A - Add a new Leaf
- L # D - Edit Leaf # description
- L # B - Edit Leaf # branch destination
- L # X - Remove Leaf #
- S - Save Changes (write to the file)
- Q - Quit without saving
Details:
- Regardless of the file existing, the user will be editing a Hash, not the actual YAML file
- Use Gardner to build the tree (either existing or skeleton)
- Use Dialog to interact with the tree
- Overwrite prompt with Planter prompt
- On changes, re-build the tree, and restart dialogue from most recent branch
- After each edit option, display the current branch with the new changes.
=end

View file

@ -55,5 +55,4 @@ def verify_tree(file)
end
results.include?(false) ? false : true
end

View file

@ -3,19 +3,14 @@
require 'optparse'
require 'yaml'
require_relative 'sapling/dialogue'
require_relative 'sapling/gardner'
require_relative 'sapling/planter'
require_relative 'sapling/utility'
Dir[File.join(__dir__, 'lib', '*.rb')].each { |file| require file }
# Sapling is the main module for the program. From here, the rest of the world
# starts building.
module Sapling
# CLI is the class for option parsing, and the gateway to the program, on the
# command line
class CLI
# Option parsing, and gateway to either reading and traversing a tree, or
# editing/creating a tree.
def talk(options)
@ -43,8 +38,7 @@ module Sapling
end
puts "Welcome to Sapling, a Dialogue Tree Utility.\n"
speaker = Dialogue::Speaker.new
speaker.file = YAML.load_file(ARGV[0])
speaker = Dialogue::Speaker.new(YAML.load_file(ARGV[0]),false)
speaker.conversation
end
@ -64,14 +58,13 @@ module Sapling
end
puts "Welcome to Sapling, a Dialogue Tree Utility.\n"
gardner = Planter::Spade.new
gardner.file = tree
gardner = Planter::Spade.new(tree)
gardner.plant
end
end
# Hacky way of dealing with bad options
# Handle bad options gracefully
begin
opt_parser.parse!(options)
rescue OptionParser::InvalidOption