123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- task default: %w(develop)
- begin
- require 'colorize'
- String.disable_colorization = false
- rescue LoadError
- end
- require 'set'
- STORY_DIR=File.join(ENV['USERPROFILE'], 'Documents\\Twine\\Stories')
- STORY_TITLE = 'Volleyball'
- STORY_FORMAT = 'SugarCube'
- STORY_FORMAT_PATH = "story_formats/sugarcube-2.28.2-for-twine-2.1-local/sugarcube-2"
- STORY_FORMAT_VERSION = '2.28.2'
- STORY_RELEASE_TITLE = 'Volleyball Chapter 1 v0.5'
- FILES = {
- '01_intro' => '01 Intro',
- '02_transform' => '02 Transform',
- '03_custom_girl' => '03 Custom Girl',
- '04_money_game' => '04 Money Games',
- '05_beaches' => '05 Beaches',
- '06_public_beach' => '06 Public Beach',
- }
- def files
- FILES.map do |short_name, long_name|
- OpenStruct.new({
- short_name: short_name,
- long_name: long_name,
- source_file: "story/#{short_name}.tw2",
- dest_file: File.join(STORY_DIR, "#{STORY_TITLE} - #{long_name}.html"),
- wrapper_file: "#{short_name}_wrapper.tw2",
- })
- end
- end
- task develop: %w(bundle_install storyjs story.tw2 story_title.tw2) do
- sh "bundle exec twee2 build '--format=#{STORY_FORMAT_PATH}' main.tw2 develop.html"
- end
- task watch: %w(bundle_install storyjs story.tw2 story_title.tw2) do
- sh 'start watch.html' # FIXME non-windows
- sh "bundle exec twee2 watch '--format=#{STORY_FORMAT_PATH}' main.tw2 watch.html"
- end
- task release: %w(bundle_install storyjs story.tw2 story_title.tw2) do
- sh "bundle exec twee2 build '--format=#{STORY_FORMAT_PATH}' main.tw2 release.html"
- end
- task :storyjs do
- File.write(
- 'storyjs.tw2',
- File.read('storyjs.tw2')\
- .sub(/\$STORY_FORMAT_VERSION = '[^']*'/, "$STORY_FORMAT_VERSION = '#{STORY_FORMAT_VERSION}'")
- )
- end
- file 'story.tw2' do
- File.open('story.tw2', 'w') do |f|
- f.puts('::StoryIncludes')
- files.each do |file|
- f.puts(file.source_file)
- end
- end
- end
- file 'story_title.tw2' do
- File.open('story_title.tw2', 'w') do |f|
- f.puts('::StoryTitle')
- f.puts("#{STORY_RELEASE_TITLE}")
- end
- end
- desc 'export sub files to Twine 2'
- task export: %w(bundle_install storyjs story.tw2 story_title.tw2) do
- files.each do |file|
- IO.write file.wrapper_file, <<~EOF
- ::StoryTitle
- Volleyball - #{file.long_name}
- ::Twee2Settings [twee2]
- @story_start_name = 'Start #{file.long_name}'
- Twee2::build_config.story_format = '#{STORY_FORMAT}'
- Twee2::build_config.story_format_version = '#{STORY_FORMAT_VERSION}'
- Twee2::build_config.story_ifid = ''
-
- ::StoryIncludes
- images.tw2
- stylesheet.tw2
- storyjs.tw2
- #{file.source_file}
- EOF
- sh(
- 'bundle', 'exec', 'twee2', 'export',
- "--format=#{STORY_FORMAT}", "--format-version=#{STORY_FORMAT_VERSION}",
- file.wrapper_file, file.dest_file,
- )
- end
- sh(
- 'bundle', 'exec', 'twee2', 'export',
- "--format=#{STORY_FORMAT}", "--format-version=#{STORY_FORMAT_VERSION}",
- 'main.tw2', STORY_DIR,
- )
- end
- desc 'import sub files from Twine 2'
- task import: %w(bundle_install) do
- files.each do |file|
- sh(
- 'bundle', 'exec', 'twee2', 'decompile',
- file.dest_file, file.source_file,
- '--exclude=StoryJS,StoryCSS,StoryTitle,Twee2Settings',
- '--exclude-from=../stylesheet.tw2,../storyjs.tw2,../stylesheet.tw2,../images.tw2',
- )
- end
- end
- task import_main: %w(bundle_install) do
- file_set = Set.new(files)
- base_excludes=%w[../stylesheet.tw2 ../storyjs.tw2 ../stylesheet.tw2 ../images.tw2]
- files.each do |file|
- other_files = (file_set - Set.new([file])).map { |f| File.basename(f.source_file) }
- excludes = other_files + base_excludes
- sh(
- 'bundle', 'exec', 'twee2', 'decompile',
- File.join(STORY_DIR, STORY_RELEASE_TITLE + '.html'),
- './' + file.source_file,
- '--exclude=StoryJS,StoryCSS,StoryTitle,Twee2Settings',
- "--exclude-from=#{excludes.join(',')}",
- )
- end
- end
- task :bundle_install do
- sh 'bundle check || bundle install'
- end
- task :clean do
- # TODO
- end
- task :autofix do
- #check links
- files.each do |file|
- twee_source = File.read(file.source_file)
- twee_source = twee_source.lines.each_with_index.map do |line, lineno|
- line.chomp!
-
- line.gsub(%r{\[\[(?<link>[^\]]*?)\](?<link_mods>\[[^\]]*\])?\]}) do |m|
- link = $~[:link]
- link_mods = $~[:link_mods]
- case link.scan('->').length
- when 0
- # this style of link doesn't need to be quoted, but it also shouldn't contain special chars
- "[[#{link}]#{link_mods}]"
- when 1
- text, dest = link.split('->')
- if text.start_with?("'")
- text = text.end_with?("'") ? text[1..-2] : text[1..-1]
- text.gsub!(/(?<!\\)'/) { "\\'" }
- text.gsub!(/(?<!\\)\\(?!')/) { "\\\\" }
- text = "'#{text}'"
- elsif link.start_with?('"')
- text = text.end_with?('"') ? text[1..-2] : text[1..-1]
- text.gsub!(/(?<!\\)"/) { "\\\"" }
- text.gsub!(/(?<!\\)\\(?!")/) { "\\\\" }
- text = "\"#{text}\""
- elsif link =~ /\A[\[\]'"\\]*\z/
- text.gsub!(/(?<!\\)'/) { "\\'" }
- text.gsub!(/(?<!\\)\\(?!')/) { "\\\\" }
- text = "'" + text + "'"
- end
- "[[#{text}->#{dest}]#{link_mods}]"
- else
- # TODO this doesn't really happen very much in practice, so don't worry about an autofix, just passthru
- warn('multiple -> in link')
- "[[#{link}]#{link_mods}]"
- end
- end
- end.join("\n") + "\n"
- File.write(file.source_file, twee_source)
- end
- end
- task :lint do
- titles = Set.new
- passages = {}
- title = nil
- files.each do |file|
- twee_source = IO.read(file.source_file)
- twee_source.lines.each_with_index do |line, lineno|
- lineno += 1
- # check for duplicate
- if line.start_with?('::') && !line.start_with?('::StoryIncludes')
- title = line.match(/^:: *([^\[]*?) *(\[(.*?)\])? *(<(.*?)>)? *$/)[1]
- #puts title
- if title != 'Twee2Settings' && titles.include?(title)
- puts "#{file.source_file}:#{lineno} duplicate title #{title}".red
- end
- titles.add(title)
- passages[title] = {outbound_links: [], inbound_links: [], source_line: "#{file.source_file}:#{lineno}"}
- end
-
- # check links
- line
- .scan(%r{\[\[(.+?)\](\[(.+?)\])?\]})
- .map { |x| x[0] }
- .each do |link|
- case link.scan('->').length
- when 0
- #link contains no ->, probably not a problem
- text = dest = link
- passages[title][:outbound_links] << \
- { passage: dest, source_line: "#{file.source_file}:#{lineno}" }
- when 1
- # no problem
- text, dest = link.split('->')
- passages[title][:outbound_links] << \
- { passage: dest, source_line: "#{file.source_file}:#{lineno}" }
- else
- puts "#{file.source_file}:#{lineno} link contains multiple -> definitely a problem".colorize(:red)
- next
- end
- if text.start_with?('\'')
- if !text.end_with?('\'')
- puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) does not end with single quote despite starting with one".colorize(:red)
- next
- end
- text = text[1..-2]
- text
- .to_enum(:scan, '\'')
- .map {Regexp.last_match.offset(0)[0]}
- .each do |offset|
- if text[offset - 1] != '\\'
- puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) contains unquoted single quote in body".colorize(:red)
- end
- end
- elsif text.start_with?('"')
- if !text.end_with?('"')
- puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) does not end with double quote despite starting with one".colorize(:red)
- next
- end
- text = text[1..-2]
- text
- .to_enum(:scan, '"')
- .map {Regexp.last_match.offset(0)[0]}
- .each do |offset|
- if text[offset - 1] != '\\'
- puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) contains unquoted double quote in body".colorize(:red)
- end
- end
- elsif text =~ /\A[\[\]'"\\]*\z/
- puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) contains special characters but is not quoted with single or double quotes".colorize(:red)
- end
- end
- # check for default text
- if line.strip == 'Double-click this passage to edit it.'
- puts "#{file.source_file}:#{lineno} has the default text \"Double-click this passage to edit it.\""
- end
- # check for TODO/FIXME
- if line.include?('TODO') || line.include?('FIXME')
- puts "#{file.source_file}:#{lineno} has TODO/FIXME text: #{line}"
- end
- # check for <<include>> as string
- if line.include?('<<include') && !line.include?('[[')
- puts "#{file.source_file}:#{lineno} has an <<include>>, but no link"
- end
- end
- end
- passages.each do |title, passage|
- passage[:outbound_links].each do |link|
- if passages[link[:passage]]
- passages[link[:passage]][:inbound_links] << {passage: title, source_file: passage[:source_line]}
- else
- puts "#{link[:source_line]} (title=#{title}) links to non-existent passage #{link[:passage]}".red
- end
- end
- end
- passages.each do |title, passage|
- if passage[:inbound_links].empty? && title != 'Twee2Settings'
- puts "#{passage[:source_line]} (title=#{title}) has no inbound links"
- end
- end
- end
|