lib/twee2/story_file.rb in twee2-0.2.2 vs lib/twee2/story_file.rb in twee2-0.3.0

- old
+ new

@@ -1,9 +1,11 @@ module Twee2 class StoryFileNotFoundException < Exception; end class StoryFile + attr_accessor :passages + HAML_OPTIONS = { remove_whitespace: true } Tilt::CoffeeScriptTemplate.default_bare = true # bare mode for HAML :coffeescript blocks COFFEESCRIPT_OPTIONS = { @@ -12,73 +14,113 @@ # Loads the StoryFile with the given name def initialize(filename) raise(StoryFileNotFoundException) if !File::exists?(filename) @passages, current_passage = {}, nil - File::read(filename).each_line do |line| # REFACTOR: switch this to using regular expressions, why not? - if line =~ /^:: *([^\[]*?) *(\[(.*?)\])? *[\r\n]+$/ - @passages[current_passage = $1.strip] = { tags: ($3 || '').split(' '), content: '', exclude_from_output: false, pid: nil} + # Load file into memory to begin with + lines = File::read(filename).split(/\r?\n/) + # First pass - go through and perform 'includes' + i, in_story_includes_section = 0, false + while i < lines.length + line = lines[i] + if line =~ /^:: *StoryIncludes */ + in_story_includes_section = true + elsif line =~ /^::/ + in_story_includes_section = false + elsif in_story_includes_section && (line.strip != '') + # include a file here because we're in the StoryIncludes section + if File::exists?(line.strip) + lines.push(*File::read(line.strip).split(/\r?\n/)) # add it on to the end + else + puts "WARNING: tried to include file '#{line.strip}' via StoryIncludes but file was not found." + end + elsif line =~ /^( *)::@include (.*)$/ + # include a file here because an @include directive was spotted + prefix, filename = $1, $2.strip + if File::exists?(filename) + lines[i,1] = File::read(filename).split(/\r?\n/).map{|l|"#{prefix}#{l}"} # insert in-place, with prefix of appropriate amount of whitespace + i-=1 # process this line again, in case of ::@include nesting + else + puts "WARNING: tried to ::@include file '#{filename}' but file was not found." + end + end + i+=1 + end + # Second pass - parse the file + lines.each do |line| + if line =~ /^:: *([^\[]*?) *(\[(.*?)\])? *(<(.*?)>)? *$/ + @passages[current_passage = $1.strip] = { tags: ($3 || '').split(' '), position: $5, content: '', exclude_from_output: false, pid: nil} elsif current_passage - @passages[current_passage][:content] << line + @passages[current_passage][:content] << "#{line}\n" end end @passages.each_key{|k| @passages[k][:content].strip!} # Strip excessive trailing whitespace # Run each passage through a preprocessor, if required - @passages.each_key do |k| - # HAML - if @passages[k][:tags].include? 'haml' - @passages[k][:content] = Haml::Engine.new(@passages[k][:content], HAML_OPTIONS).render - @passages[k][:tags].delete 'haml' - end - # Coffeescript - if @passages[k][:tags].include? 'coffee' - @passages[k][:content] = CoffeeScript.compile(@passages[k][:content], COFFEESCRIPT_OPTIONS) - @passages[k][:tags].delete 'coffee' - end - end + run_preprocessors # Extract 'special' passages and mark them as not being included in output - @story_name, story_css, story_js, pid, story_start_pid = 'An unnamed story', '', '', 0, 1 + story_css, story_js, pid, @story_start_pid, @story_start_name = '', '', 0, nil, 'Start' @passages.each_key do |k| if k == 'StoryTitle' - @story_name = @passages[k][:content] + Twee2::build_config.story_name = @passages[k][:content] @passages[k][:exclude_from_output] = true - elsif %w{StorySubtitle StoryAuthor StoryMenu StorySettings StoryIncludes}.include? k + elsif k == 'StoryIncludes' + @passages[k][:exclude_from_output] = true # includes should already have been handled above + elsif %w{StorySubtitle StoryAuthor StoryMenu StorySettings}.include? k puts "WARNING: ignoring passage '#{k}'" @passages[k][:exclude_from_output] = true elsif @passages[k][:tags].include? 'stylesheet' story_css << "#{@passages[k][:content]}\n" @passages[k][:exclude_from_output] = true elsif @passages[k][:tags].include? 'script' story_js << "#{@passages[k][:content]}\n" @passages[k][:exclude_from_output] = true - elsif k == 'Start' - @passages[k][:pid] = (pid += 1) - story_start_pid = pid + elsif @passages[k][:tags].include? 'twee2' + eval @passages[k][:content] + @passages[k][:exclude_from_output] = true else @passages[k][:pid] = (pid += 1) end end + @story_start_pid = (@passages[@story_start_name] || {pid: 1})[:pid] # Generate XML in Twine 2 format @story_data = Builder::XmlMarkup.new # TODO: what is tw-storydata's "options" attribute for? - @story_data.tag!('tw-storydata', { name: @story_name, startnode: story_start_pid, creator: 'Twee2', 'creator-version' => Twee2::VERSION, ifid: 'TODO', format: '{{STORY_FORMAT}}', options: '' }) do + @story_data.tag!('tw-storydata', { name: Twee2::build_config.story_name, startnode: @story_start_pid, creator: 'Twee2', 'creator-version' => Twee2::VERSION, ifid: 'TODO', format: '{{STORY_FORMAT}}', options: '' }) do @story_data.style(story_css, role: 'stylesheet', id: 'twine-user-stylesheet', type: 'text/twine-css') @story_data.script(story_js, role: 'script', id: 'twine-user-script', type: 'text/twine-javascript') @passages.each do |k,v| unless v[:exclude_from_output] - @story_data.tag!('tw-passagedata', { pid: v[:pid], name: k, tags: v[:tags].join(' ') }, v[:content]) + @story_data.tag!('tw-passagedata', { pid: v[:pid], name: k, tags: v[:tags].join(' '), position: v[:position] }, v[:content]) end end end end - # Returns the title of this story - def title - @story_name - end - # Returns the rendered XML that represents this story def xmldata @story_data.target! + end + + # Runs HAML, Coffeescript etc. preprocessors across each applicable passage + def run_preprocessors + @passages.each_key do |k| + # HAML + if @passages[k][:tags].include? 'haml' + @passages[k][:content] = Haml::Engine.new(@passages[k][:content], HAML_OPTIONS).render + @passages[k][:tags].delete 'haml' + end + # Coffeescript + if @passages[k][:tags].include? 'coffee' + @passages[k][:content] = CoffeeScript.compile(@passages[k][:content], COFFEESCRIPT_OPTIONS) + @passages[k][:tags].delete 'coffee' + end + # SASS / SCSS + if @passages[k][:tags].include? 'sass' + @passages[k][:content] = Sass::Engine.new(@passages[k][:content], :syntax => :sass).render + end + if @passages[k][:tags].include? 'scss' + @passages[k][:content] = Sass::Engine.new(@passages[k][:content], :syntax => :scss).render + end + end end end end \ No newline at end of file