Class: Twee2::StoryFile

Inherits:
Object
  • Object
show all
Defined in:
lib/twee2/story_file.rb

Constant Summary collapse

HAML_OPTIONS =
{
  remove_whitespace: true
}
COFFEESCRIPT_OPTIONS =
{
  bare: true
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename) ⇒ StoryFile

Loads the StoryFile with the given name


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/twee2/story_file.rb', line 21

def initialize(filename)
  raise(StoryFileNotFoundException) if !File::exists?(filename)
  @passages, current_passage = {}, 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}\n"
    end
  end
  @passages.each_key{|k| @passages[k][:content].strip!} # Strip excessive trailing whitespace
  # Run each passage through a preprocessor, if required
  run_preprocessors
  # Extract 'special' passages and mark them as not being included in output
  story_css, story_js, pid, @story_start_pid, @story_start_name = '', '', 0, nil, 'Start'
  @passages.each_key do |k|
    if k == 'StoryTitle'
      Twee2::build_config.story_name = @passages[k][:content]
      @passages[k][:exclude_from_output] = true
    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 @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: 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(' '), position: v[:position] }, v[:content])
      end
    end
  end
end

Instance Attribute Details

#passagesObject

Returns the value of attribute passages


10
11
12
# File 'lib/twee2/story_file.rb', line 10

def passages
  @passages
end

Instance Method Details

#run_preprocessorsObject

Runs HAML, Coffeescript etc. preprocessors across each applicable passage


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/twee2/story_file.rb', line 109

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

#xmldataObject

Returns the rendered XML that represents this story


104
105
106
# File 'lib/twee2/story_file.rb', line 104

def xmldata
  @story_data.target!
end