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



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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/twee2/story_file.rb', line 23

def initialize(filename)
  raise(StoryFileNotFoundException) if !File::exists?(filename)
  @passages, current_passage = {}, nil
  @child_story_files = []

  # 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 != '')
      child_file = line.strip
      # include a file here because we're in the StoryIncludes section
      if File::exists?(child_file)
        lines.push(*File::read(child_file).split(/\r?\n/)) # add it on to the end
        child_story_files.push(child_file)
      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, pid, @story_js, @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: Twee2::build_config.story_ifid,
                                  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

#child_story_filesObject (readonly)

Returns the value of attribute child_story_files.



12
13
14
# File 'lib/twee2/story_file.rb', line 12

def child_story_files
  @child_story_files
end

#passagesObject

Returns the value of attribute passages.



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

def passages
  @passages
end

Instance Method Details

#run_preprocessorsObject

Runs HAML, Coffeescript etc. preprocessors across each applicable passage



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/twee2/story_file.rb', line 124

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



118
119
120
121
# File 'lib/twee2/story_file.rb', line 118

def xmldata
  data = @story_data.target!
  data.gsub('{{STORY_JS}}', @story_js)
end