twee2 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b63cd0490f4dff22a79e5d67a667c6a433d899ba
4
- data.tar.gz: db0132a07a752ac8700e44c2062550808ba74c26
3
+ metadata.gz: 56109f63237dee796f2278bfd81de350dfaa5785
4
+ data.tar.gz: 83c1d18094f0ecfac641f6bb439c4ca430067ae8
5
5
  SHA512:
6
- metadata.gz: 74b26f90671c6f4b7e380ee4fab5f5ee0b8bc716dfaa863d85a5b0611eab6831834ecd3de0ab39ce9db69ba11ea364f7c822cecca08193bcbb946d7bdd283b2c
7
- data.tar.gz: 9eeba4d00780facc6c2c112b7305596853f6771d53cebb51733d3612e1267f7da5b85c7408a36bb9158f4d154a69522f09ef4be4437b86ed371167f4e0ce1efe
6
+ metadata.gz: 12ac1e5acc8b98dfa4fcdf4916537a7a0038a4eec8e8fc145b1ea028d0ad56b3fdeb1ea71ba69d07c9e38610c42c2f13bba771f6ccdcf3d7adb787da91768013
7
+ data.tar.gz: 9287dd8b3fe73f71245823da9d3a73d163d50b2420a529e250fec79a7d2392b25702c1f9d1a8dc21c2428b49ffc03c4ee9551801d7ead50272975def7f40e4af
data/Gemfile CHANGED
@@ -4,4 +4,6 @@ gem 'thor'
4
4
  gem 'builder'
5
5
  gem 'filewatcher'
6
6
  gem 'haml'
7
- gem 'coffee-script'
7
+ gem 'coffee-script'
8
+ gem 'nokogiri'
9
+ gem 'sass'
data/README.md CHANGED
@@ -14,6 +14,10 @@ Right now Twee2 doesn't even support the diversity of original Twee, and it's po
14
14
 
15
15
  I'd love to hear your thoughts about the future of this gem.
16
16
 
17
+ ## Prerequisites
18
+
19
+ You'll need Ruby. Instructions on easily installing it on MacOS, Windows, and Linux can be found at https://www.ruby-lang.org/en/documentation/installation/.
20
+
17
21
  ## Installation
18
22
 
19
23
  Install using gem
@@ -22,6 +26,10 @@ Install using gem
22
26
 
23
27
  ## Usage
24
28
 
29
+ To see usage examples and fuller instructions (documentation on this page is lacking, but as-yet no documentation is complete):
30
+
31
+ twee2
32
+
25
33
  To compile a Twee file into a HTML file using the default format (Harlowe):
26
34
 
27
35
  twee2 build inputfile.twee outputfile.html
@@ -36,6 +44,31 @@ For additional features (e.g. listing known formats, watch-for-changes mode), ru
36
44
 
37
45
  Aside from the regular Twee features, Twee2 provides the following enhancements:
38
46
 
47
+ ### Multi-file inclusion (super-experimental)
48
+
49
+ You can combine multiple input files into one via either (or both!) of two different methods:
50
+
51
+ #### StoryIncludes
52
+
53
+ Borrowed from Twee: simply list the files to include in the special StoryIncludes passage. Each will be appended to the end of your source file on compilation.
54
+
55
+ ```
56
+ ::StoryIncludes
57
+ my-sub-story.tw2
58
+ ```
59
+
60
+ Looking for something more-powerful? Use ::@include-syntax, e.g.:
61
+
62
+ ```
63
+ ::IncompleteRoom [haml]
64
+ %p
65
+ This room is incomplete. Come back later.
66
+ :coffeescript
67
+ ::@include incomplete-room-js.coffee
68
+ ```
69
+
70
+ The requested file will be nested directly in place. Indent levels will be preserved, which makes it safe for use within HAML, Coffeescript, and SASS. This makes it easy to re-use content throughout your story by simply inserting it in multiple places.
71
+
39
72
  ### HAML support
40
73
 
41
74
  You can declare your passages using [HAML markup](http://haml.info/docs/yardoc/file.REFERENCE.html) by applying a 'haml' tag to the passage, e.g.:
@@ -56,6 +89,21 @@ If you'd rather write your Javascript in [Coffeescript](http://coffeescript.org/
56
89
  alert 'Welcome to my game!'
57
90
  ```
58
91
 
92
+ ### SASS/SCSS stylesheets
93
+
94
+ Prefer SASS for your stylesheets? Just use a 'sass' or 'scss' tag alongside your 'stylesheet' tag, or use a :sass or :scss block in HAML:
95
+
96
+ ```
97
+ ::MyStylesheet [scss stylesheet]
98
+ body {
99
+ color: #eee;
100
+ }
101
+ tw-passage {
102
+ tw-link {
103
+ color: red;
104
+ }
105
+ }
106
+
59
107
  ## Notes
60
108
 
61
109
  * This is not a Twee to Harlowe converter. You'll still need to change your macros as described at http://twine2.neocities.org/, and/or rewrite them as Javascript code.
data/bin/twee2 CHANGED
@@ -24,6 +24,11 @@ class Twee2CLI < Thor
24
24
  Twee2::formats
25
25
  end
26
26
 
27
+ desc 'decompile [URL] [OUTPUT]', 'reverse-engineers Twee2/Twine 2 HTML output into a Twee2 source file'
28
+ def decompile(url, output)
29
+ Twee2::decompile(url, output)
30
+ end
31
+
27
32
  desc 'help', 'shows usage instructions'
28
33
  def help
29
34
  Twee2::help
@@ -13,5 +13,9 @@ Usage:
13
13
  twee2 formats
14
14
  Lists the output formats that are understood.
15
15
 
16
+ twee2 decompile [URL] [output.tw2]
17
+ Decompiles a Twee2/Twine 2 HTML output file at a specified URL into
18
+ a Twee2 source file.
19
+
16
20
  twee2 help
17
21
  Displays this message.
@@ -1,8 +1,8 @@
1
1
  Encoding.default_external = Encoding.default_internal = Encoding::UTF_8
2
2
 
3
3
  # Prerequisites (managed by bundler)
4
- %w{rubygems bundler/setup thor json builder filewatcher haml coffee_script
5
- twee2/version twee2/story_format twee2/story_file}.each do |prerequisite|
4
+ %w{rubygems bundler/setup thor json builder filewatcher haml coffee_script nokogiri open-uri singleton sass
5
+ twee2/version twee2/story_format twee2/story_file twee2/decompiler twee2/build_config}.each do |prerequisite|
6
6
  require prerequisite
7
7
  end
8
8
 
@@ -12,12 +12,12 @@ module Twee2
12
12
 
13
13
  def self.build(input, output, options = {})
14
14
  # Read and parse format file
15
- story_format = StoryFormat::new(options[:format])
15
+ build_config.story_format = StoryFormat::new(options[:format])
16
16
  # Read and parse input file
17
- story_file = StoryFile::new(input)
17
+ build_config.story_file = StoryFile::new(input)
18
18
  # Produce output file
19
19
  File::open(output, 'w') do |out|
20
- out.print story_format.compile(story_file)
20
+ out.print build_config.story_format.compile
21
21
  end
22
22
  puts "Done"
23
23
  end
@@ -37,6 +37,14 @@ module Twee2
37
37
  puts StoryFormat.known_names.join("\n")
38
38
  end
39
39
 
40
+ # Reverse-engineers a Twee2/Twine 2 output HTML file into a Twee2 source file
41
+ def self.decompile(url, output)
42
+ File::open(output, 'w') do |out|
43
+ out.print Decompiler::decompile(url)
44
+ end
45
+ puts "Done"
46
+ end
47
+
40
48
  def self.help
41
49
  puts "Twee2 #{Twee2::VERSION}"
42
50
  puts File.read(buildpath('doc/usage.txt'))
@@ -0,0 +1,16 @@
1
+ module Twee2
2
+ class BuildConfig
3
+ include Singleton
4
+
5
+ attr_accessor :story_format, :story_file, :story_name
6
+
7
+ # Set defaults
8
+ def initialize
9
+ @story_name = 'An unnamed story'
10
+ end
11
+ end
12
+
13
+ def self.build_config
14
+ BuildConfig::instance
15
+ end
16
+ end
@@ -0,0 +1,48 @@
1
+ module Twee2
2
+ class DecompilationFailedException < Exception; end
3
+
4
+ class Decompiler
5
+ def self.decompile(url)
6
+ result = ''
7
+ # Load the compiled HTML and sanity-check it
8
+ html = Nokogiri::HTML(open(url))
9
+ raise(DecompilationFailedException, 'tw-storydata not found') unless storydata = html.at_css('tw-storydata')
10
+ # Extract the tw-storydata#name (StoryTitle) and #startnode
11
+ result << "::StoryTitle\n#{storydata[:name].strip}\n\n"
12
+ startnode_pid, startnode_name = storydata[:startnode].strip, nil
13
+ # Extract the custom CSS and Javascript, if applicable
14
+ if (css = storydata.at_css('#twine-user-stylesheet')) && ((css_content = css.content.strip) != '')
15
+ result << "::StoryCSS [stylesheet]\n#{css_content}\n\n"
16
+ end
17
+ if (js = storydata.at_css('#twine-user-script')) && ((js_content = js.content.strip) != '')
18
+ result << "::StoryJS [script]\n#{js.content}\n\n"
19
+ end
20
+ # Extract each passage
21
+ storydata.css('tw-passagedata').each do |passagedata|
22
+ # Check if this is the start passage and record this accordingly
23
+ startnode_name = passagedata[:name] if(startnode_pid == passagedata[:pid])
24
+ # Write the passage out
25
+ result << "::#{passagedata[:name].strip}"
26
+ result << " [#{passagedata[:tags].strip}]" if passagedata[:tags].strip != ''
27
+ result << " <#{passagedata[:position].strip}>" if passagedata[:position].strip != ''
28
+ result << "\n#{tidyup_passagedata(passagedata.content.strip)}\n\n"
29
+ end
30
+ # Write the Twee2 settings out (compatability layer)
31
+ result << "::Twee2Settings [twee2]\n"
32
+ result << "@story_start_name = '#{startnode_name.gsub("'", "\\'")}'\n" if startnode_name
33
+ result << "\n"
34
+ # Return the result
35
+ result
36
+ end
37
+
38
+ protected
39
+
40
+ # Fixes common problems with decompiled passage content
41
+ def self.tidyup_passagedata(passagedata_content)
42
+ passagedata_content.gsub(/\[\[ *(.*?) *\]\]/, '[[\1]]'). # remove excess spacing within links: not suitable for Twee-style source
43
+ gsub(/\[\[ *(.*?) *<- *(.*?) *\]\]/, '[[\1<-\2]]'). # ditto
44
+ gsub(/\[\[ *(.*?) *-> *(.*?) *\]\]/, '[[\1->\2]]'). # ditto
45
+ gsub(/\[\[ *(.*?) *\| *(.*?) *\]\]/, '[[\1|\2]]') # ditto
46
+ end
47
+ end
48
+ end
@@ -2,6 +2,8 @@ module Twee2
2
2
  class StoryFileNotFoundException < Exception; end
3
3
 
4
4
  class StoryFile
5
+ attr_accessor :passages
6
+
5
7
  HAML_OPTIONS = {
6
8
  remove_whitespace: true
7
9
  }
@@ -14,34 +16,55 @@ module Twee2
14
16
  def initialize(filename)
15
17
  raise(StoryFileNotFoundException) if !File::exists?(filename)
16
18
  @passages, current_passage = {}, nil
17
- File::read(filename).each_line do |line| # REFACTOR: switch this to using regular expressions, why not?
18
- if line =~ /^:: *([^\[]*?) *(\[(.*?)\])? *[\r\n]+$/
19
- @passages[current_passage = $1.strip] = { tags: ($3 || '').split(' '), content: '', exclude_from_output: false, pid: nil}
19
+ # Load file into memory to begin with
20
+ lines = File::read(filename).split(/\r?\n/)
21
+ # First pass - go through and perform 'includes'
22
+ i, in_story_includes_section = 0, false
23
+ while i < lines.length
24
+ line = lines[i]
25
+ if line =~ /^:: *StoryIncludes */
26
+ in_story_includes_section = true
27
+ elsif line =~ /^::/
28
+ in_story_includes_section = false
29
+ elsif in_story_includes_section && (line.strip != '')
30
+ # include a file here because we're in the StoryIncludes section
31
+ if File::exists?(line.strip)
32
+ lines.push(*File::read(line.strip).split(/\r?\n/)) # add it on to the end
33
+ else
34
+ puts "WARNING: tried to include file '#{line.strip}' via StoryIncludes but file was not found."
35
+ end
36
+ elsif line =~ /^( *)::@include (.*)$/
37
+ # include a file here because an @include directive was spotted
38
+ prefix, filename = $1, $2.strip
39
+ if File::exists?(filename)
40
+ lines[i,1] = File::read(filename).split(/\r?\n/).map{|l|"#{prefix}#{l}"} # insert in-place, with prefix of appropriate amount of whitespace
41
+ i-=1 # process this line again, in case of ::@include nesting
42
+ else
43
+ puts "WARNING: tried to ::@include file '#{filename}' but file was not found."
44
+ end
45
+ end
46
+ i+=1
47
+ end
48
+ # Second pass - parse the file
49
+ lines.each do |line|
50
+ if line =~ /^:: *([^\[]*?) *(\[(.*?)\])? *(<(.*?)>)? *$/
51
+ @passages[current_passage = $1.strip] = { tags: ($3 || '').split(' '), position: $5, content: '', exclude_from_output: false, pid: nil}
20
52
  elsif current_passage
21
- @passages[current_passage][:content] << line
53
+ @passages[current_passage][:content] << "#{line}\n"
22
54
  end
23
55
  end
24
56
  @passages.each_key{|k| @passages[k][:content].strip!} # Strip excessive trailing whitespace
25
57
  # Run each passage through a preprocessor, if required
26
- @passages.each_key do |k|
27
- # HAML
28
- if @passages[k][:tags].include? 'haml'
29
- @passages[k][:content] = Haml::Engine.new(@passages[k][:content], HAML_OPTIONS).render
30
- @passages[k][:tags].delete 'haml'
31
- end
32
- # Coffeescript
33
- if @passages[k][:tags].include? 'coffee'
34
- @passages[k][:content] = CoffeeScript.compile(@passages[k][:content], COFFEESCRIPT_OPTIONS)
35
- @passages[k][:tags].delete 'coffee'
36
- end
37
- end
58
+ run_preprocessors
38
59
  # Extract 'special' passages and mark them as not being included in output
39
- @story_name, story_css, story_js, pid, story_start_pid = 'An unnamed story', '', '', 0, 1
60
+ story_css, story_js, pid, @story_start_pid, @story_start_name = '', '', 0, nil, 'Start'
40
61
  @passages.each_key do |k|
41
62
  if k == 'StoryTitle'
42
- @story_name = @passages[k][:content]
63
+ Twee2::build_config.story_name = @passages[k][:content]
43
64
  @passages[k][:exclude_from_output] = true
44
- elsif %w{StorySubtitle StoryAuthor StoryMenu StorySettings StoryIncludes}.include? k
65
+ elsif k == 'StoryIncludes'
66
+ @passages[k][:exclude_from_output] = true # includes should already have been handled above
67
+ elsif %w{StorySubtitle StoryAuthor StoryMenu StorySettings}.include? k
45
68
  puts "WARNING: ignoring passage '#{k}'"
46
69
  @passages[k][:exclude_from_output] = true
47
70
  elsif @passages[k][:tags].include? 'stylesheet'
@@ -50,35 +73,54 @@ module Twee2
50
73
  elsif @passages[k][:tags].include? 'script'
51
74
  story_js << "#{@passages[k][:content]}\n"
52
75
  @passages[k][:exclude_from_output] = true
53
- elsif k == 'Start'
54
- @passages[k][:pid] = (pid += 1)
55
- story_start_pid = pid
76
+ elsif @passages[k][:tags].include? 'twee2'
77
+ eval @passages[k][:content]
78
+ @passages[k][:exclude_from_output] = true
56
79
  else
57
80
  @passages[k][:pid] = (pid += 1)
58
81
  end
59
82
  end
83
+ @story_start_pid = (@passages[@story_start_name] || {pid: 1})[:pid]
60
84
  # Generate XML in Twine 2 format
61
85
  @story_data = Builder::XmlMarkup.new
62
86
  # TODO: what is tw-storydata's "options" attribute for?
63
- @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
87
+ @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
64
88
  @story_data.style(story_css, role: 'stylesheet', id: 'twine-user-stylesheet', type: 'text/twine-css')
65
89
  @story_data.script(story_js, role: 'script', id: 'twine-user-script', type: 'text/twine-javascript')
66
90
  @passages.each do |k,v|
67
91
  unless v[:exclude_from_output]
68
- @story_data.tag!('tw-passagedata', { pid: v[:pid], name: k, tags: v[:tags].join(' ') }, v[:content])
92
+ @story_data.tag!('tw-passagedata', { pid: v[:pid], name: k, tags: v[:tags].join(' '), position: v[:position] }, v[:content])
69
93
  end
70
94
  end
71
95
  end
72
96
  end
73
97
 
74
- # Returns the title of this story
75
- def title
76
- @story_name
77
- end
78
-
79
98
  # Returns the rendered XML that represents this story
80
99
  def xmldata
81
100
  @story_data.target!
82
101
  end
102
+
103
+ # Runs HAML, Coffeescript etc. preprocessors across each applicable passage
104
+ def run_preprocessors
105
+ @passages.each_key do |k|
106
+ # HAML
107
+ if @passages[k][:tags].include? 'haml'
108
+ @passages[k][:content] = Haml::Engine.new(@passages[k][:content], HAML_OPTIONS).render
109
+ @passages[k][:tags].delete 'haml'
110
+ end
111
+ # Coffeescript
112
+ if @passages[k][:tags].include? 'coffee'
113
+ @passages[k][:content] = CoffeeScript.compile(@passages[k][:content], COFFEESCRIPT_OPTIONS)
114
+ @passages[k][:tags].delete 'coffee'
115
+ end
116
+ # SASS / SCSS
117
+ if @passages[k][:tags].include? 'sass'
118
+ @passages[k][:content] = Sass::Engine.new(@passages[k][:content], :syntax => :sass).render
119
+ end
120
+ if @passages[k][:tags].include? 'scss'
121
+ @passages[k][:content] = Sass::Engine.new(@passages[k][:content], :syntax => :scss).render
122
+ end
123
+ end
124
+ end
83
125
  end
84
126
  end
@@ -13,8 +13,8 @@ module Twee2
13
13
  end
14
14
 
15
15
  # Given a story file, injects it into the StoryFormat and returns the HTML results
16
- def compile(story_file)
17
- @source.gsub('{{STORY_NAME}}', story_file.title).gsub('{{STORY_DATA}}', story_file.xmldata).gsub('{{STORY_FORMAT}}', @name)
16
+ def compile
17
+ @source.gsub('{{STORY_NAME}}', Twee2::build_config.story_name).gsub('{{STORY_DATA}}', Twee2::build_config.story_file.xmldata).gsub('{{STORY_FORMAT}}', @name)
18
18
  end
19
19
 
20
20
  # Returns an array containing the known StoryFormat names
@@ -1,3 +1,3 @@
1
1
  module Twee2
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -34,6 +34,8 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency 'execjs', '~> 2.6', '>= 2.6.0'
35
35
  spec.add_runtime_dependency 'filewatcher', '~> 0.5', '>= 0.5.2'
36
36
  spec.add_runtime_dependency 'haml', '~> 4.0', '>= 4.0.7'
37
+ spec.add_runtime_dependency 'nokogiri', '~> 1.6', '>= 1.6.6.2'
38
+ spec.add_runtime_dependency 'sass', '~> 3.2', '>= 3.2.19'
37
39
  spec.add_runtime_dependency 'thor', '~> 0.19', '>= 0.19.1'
38
40
  spec.add_runtime_dependency 'tilt', '~> 2.0', '>= 2.0.1'
39
41
  spec.add_runtime_dependency 'trollop', '~> 2.1', '>= 2.1.2'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twee2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Q
@@ -158,6 +158,46 @@ dependencies:
158
158
  - - ">="
159
159
  - !ruby/object:Gem::Version
160
160
  version: 4.0.7
161
+ - !ruby/object:Gem::Dependency
162
+ name: nokogiri
163
+ requirement: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: '1.6'
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: 1.6.6.2
171
+ type: :runtime
172
+ prerelease: false
173
+ version_requirements: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: '1.6'
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 1.6.6.2
181
+ - !ruby/object:Gem::Dependency
182
+ name: sass
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '3.2'
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: 3.2.19
191
+ type: :runtime
192
+ prerelease: false
193
+ version_requirements: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - "~>"
196
+ - !ruby/object:Gem::Version
197
+ version: '3.2'
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: 3.2.19
161
201
  - !ruby/object:Gem::Dependency
162
202
  name: thor
163
203
  requirement: !ruby/object:Gem::Requirement
@@ -238,6 +278,8 @@ files:
238
278
  - bin/twee2
239
279
  - doc/usage.txt
240
280
  - lib/twee2.rb
281
+ - lib/twee2/build_config.rb
282
+ - lib/twee2/decompiler.rb
241
283
  - lib/twee2/story_file.rb
242
284
  - lib/twee2/story_format.rb
243
285
  - lib/twee2/version.rb