Rakefile 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. task default: %w(develop)
  2. begin
  3. require 'colorize'
  4. String.disable_colorization = false
  5. rescue LoadError
  6. end
  7. require 'set'
  8. STORY_DIR=File.join(ENV['USERPROFILE'], 'Documents\\Twine\\Stories')
  9. STORY_TITLE = 'Volleyball'
  10. STORY_FORMAT = 'SugarCube'
  11. STORY_FORMAT_PATH = "story_formats/sugarcube-2.28.2-for-twine-2.1-local/sugarcube-2"
  12. STORY_FORMAT_VERSION = '2.28.2'
  13. FILES = {
  14. '01_intro' => '01 Intro',
  15. '02_transform' => '02 Transform',
  16. '03_custom_girl' => '03 Custom Girl',
  17. '04_money_game' => '04 Money Games',
  18. '05_beaches' => '05 Beaches',
  19. }
  20. def files
  21. FILES.map do |short_name, long_name|
  22. OpenStruct.new({
  23. short_name: short_name,
  24. long_name: long_name,
  25. source_file: "story/#{short_name}.tw2",
  26. dest_file: File.join(STORY_DIR, "#{STORY_TITLE} - #{long_name}.html"),
  27. wrapper_file: "#{short_name}_wrapper.tw2",
  28. })
  29. end
  30. end
  31. task develop: %w(bundle_install storyjs) do
  32. sh "bundle exec twee2 build '--format=#{STORY_FORMAT_PATH}' main.tw2 develop.html"
  33. end
  34. task watch: %w(bundle_install storyjs) do
  35. sh 'start watch.html' # FIXME non-windows
  36. sh "bundle exec twee2 watch '--format=#{STORY_FORMAT_PATH}' main.tw2 watch.html"
  37. end
  38. task release: %w(bundle_install storyjs) do
  39. sh "bundle exec twee2 build '--format=#{STORY_FORMAT_PATH}' main.tw2 release.html"
  40. end
  41. task :storyjs do
  42. File.write(
  43. 'storyjs.tw2',
  44. File.read('storyjs.tw2')\
  45. .sub(/\$STORY_FORMAT_VERSION = '[^']*'/, "$STORY_FORMAT_VERSION = '#{STORY_FORMAT_VERSION}'")
  46. )
  47. end
  48. desc 'export sub files to Twine 2'
  49. task export: %w(bundle_install storyjs) do
  50. files.each do |file|
  51. IO.write file.wrapper_file, <<~EOF
  52. ::StoryTitle
  53. Volleyball - #{file.long_name}
  54. ::Twee2Settings [twee2]
  55. @story_start_name = 'Start #{file.long_name}'
  56. Twee2::build_config.story_format = '#{STORY_FORMAT}'
  57. Twee2::build_config.story_format_version = '#{STORY_FORMAT_VERSION}'
  58. ::StoryIncludes
  59. images.tw2
  60. stylesheet.tw2
  61. storyjs.tw2
  62. #{file.source_file}
  63. EOF
  64. sh(
  65. 'bundle', 'exec', 'twee2', 'export',
  66. "--format=#{STORY_FORMAT}", "--format-version=#{STORY_FORMAT_VERSION}",
  67. file.wrapper_file, file.dest_file,
  68. )
  69. end
  70. sh(
  71. 'bundle', 'exec', 'twee2', 'export',
  72. "--format=#{STORY_FORMAT}", "--format-version=#{STORY_FORMAT_VERSION}",
  73. 'main.tw2', STORY_DIR,
  74. )
  75. end
  76. desc 'import sub files from Twine 2'
  77. task import: %w(bundle_install) do
  78. files.each do |file|
  79. sh(
  80. 'bundle', 'exec', 'twee2', 'decompile',
  81. file.dest_file, file.source_file,
  82. '--exclude=StoryJS,StoryCSS,StoryTitle',
  83. '--exclude-from=../stylesheet.tw2,../storyjs.tw2,../stylesheet.tw2,../images.tw2',
  84. )
  85. end
  86. end
  87. task :bundle_install do
  88. sh 'bundle check || bundle install'
  89. end
  90. task :clean do
  91. # TODO
  92. end
  93. task :lint do
  94. titles = Set.new
  95. passages = {}
  96. title = nil
  97. files.each do |file|
  98. twee_source = IO.read(file.source_file)
  99. twee_source.lines.each_with_index do |line, lineno|
  100. lineno += 1
  101. # check for duplicate
  102. if line.start_with?('::') && !line.start_with?('::StoryIncludes')
  103. title = line.match(/^:: *([^\[]*?) *(\[(.*?)\])? *(<(.*?)>)? *$/)[1]
  104. #puts title
  105. if title != 'Twee2Settings' && titles.include?(title)
  106. puts "#{file.source_file}:#{lineno} duplicate title #{title}".red
  107. end
  108. titles.add(title)
  109. passages[title] = {outbound_links: [], inbound_links: [], source_line: "#{file.source_file}:#{lineno}"}
  110. end
  111. # check links
  112. line
  113. .scan(%r{\[\[(.+?)\](\[(.+?)\])?\]})
  114. .map { |x| x[0] }
  115. .each do |link|
  116. case link.scan('->').length
  117. when 0
  118. #link contains no ->, probably not a problem
  119. text = dest = link
  120. passages[title][:outbound_links] << \
  121. { passage: dest, source_line: "#{file.source_file}:#{lineno}" }
  122. when 1
  123. # no problem
  124. text, dest = link.split('->')
  125. passages[title][:outbound_links] << \
  126. { passage: dest, source_line: "#{file.source_file}:#{lineno}" }
  127. else
  128. puts "#{file.source_file}:#{lineno} link contains multiple -> definitely a problem".colorize(:red)
  129. next
  130. end
  131. if text.start_with?('\'')
  132. if !text.end_with?('\'')
  133. puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) does not end with single quote despite starting with one".colorize(:red)
  134. next
  135. end
  136. text = text[1..-2]
  137. text
  138. .to_enum(:scan, '\'')
  139. .map {Regexp.last_match.offset(0)[0]}
  140. .each do |offset|
  141. if text[offset - 1] != '\\'
  142. puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) contains unquoted single quote in body".colorize(:red)
  143. end
  144. end
  145. elsif text.start_with?('"')
  146. if !text.end_with?('"')
  147. puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) does not end with double quote despite starting with one".colorize(:red)
  148. next
  149. end
  150. text = text[1..-2]
  151. text
  152. .to_enum(:scan, '"')
  153. .map {Regexp.last_match.offset(0)[0]}
  154. .each do |offset|
  155. if text[offset - 1] != '\\'
  156. puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) contains unquoted double quote in body".colorize(:red)
  157. end
  158. end
  159. elsif text =~ /\A[\[\]'"\\]*\z/
  160. puts "#{file.source_file}:#{lineno} link text (#{text.inspect}) contains special characters but is not quoted with single or double quotes".colorize(:red)
  161. end
  162. end
  163. # check for default text
  164. if line.strip == 'Double-click this passage to edit it.'
  165. #puts "#{file.source_file}:#{lineno} has the default text \"Double-click this passage to edit it.\""
  166. end
  167. # check for TODO/FIXME
  168. if line.include?('TODO') || line.include?('FIXME')
  169. #puts "#{file.source_file}:#{lineno} has TODO/FIXME text: #{line}"
  170. end
  171. # check for <<include>> as string
  172. if line.include?('<<include') && !line.include?('[[')
  173. puts "#{file.source_file}:#{lineno} has an <<include>>, but no link"
  174. end
  175. end
  176. end
  177. passages.each do |title, passage|
  178. passage[:outbound_links].each do |link|
  179. if passages[link[:passage]]
  180. passages[link[:passage]][:inbound_links] << {passage: title, source_file: passage[:source_line]}
  181. else
  182. puts "#{link[:source_line]} (title=#{title}) links to non-existent passage #{link[:passage]}".red
  183. end
  184. end
  185. end
  186. passages.each do |title, passage|
  187. if passage[:inbound_links].empty? && title != 'Twee2Settings'
  188. puts "#{passage[:source_line]} (title=#{title}) has no inbound links"
  189. end
  190. end
  191. end