scarpe 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/.yardopts +11 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +112 -0
  6. data/README.md +31 -24
  7. data/Rakefile +13 -1
  8. data/docs/yard/catscradle.md +44 -0
  9. data/docs/yard/template/default/fulldoc/html/setup.rb +13 -0
  10. data/docs/yard/template/default/layout/html/setup.rb +9 -0
  11. data/examples/background_with_image.rb +16 -0
  12. data/examples/bloopsaphone/working/bronx_army_knife.rb +66 -0
  13. data/examples/bloopsaphone/working/morning_serenity.rb +21 -0
  14. data/examples/bloopsaphone/working/simpsons_theme_song_by_why.rb +6 -4
  15. data/examples/button_go_away.rb +1 -1
  16. data/examples/check.rb +18 -0
  17. data/examples/clear_and_append.rb +24 -0
  18. data/examples/download_and_show_image.rb +28 -0
  19. data/examples/edit_box.rb +3 -5
  20. data/examples/fonts.rb +2 -2
  21. data/examples/get_headers.rb +10 -0
  22. data/examples/highlander.rb +2 -0
  23. data/examples/link.rb +2 -2
  24. data/examples/local_fonts.rb +4 -0
  25. data/examples/local_images.rb +4 -0
  26. data/examples/motion_events.rb +20 -0
  27. data/examples/parse_xl_funnies.rb +58 -0
  28. data/examples/radio/radio.rb +16 -0
  29. data/examples/radio/radio_groups.rb +18 -0
  30. data/examples/radio/radio_same_slot.rb +6 -0
  31. data/examples/ruby_racer.rb +13 -15
  32. data/examples/selfitude.rb +18 -0
  33. data/examples/shapes/shapes_fill.rb +4 -3
  34. data/examples/shoes_school.rb +2 -4
  35. data/examples/show_hide.rb +6 -0
  36. data/examples/skip_ci/change_my_audio_source.rb +21 -0
  37. data/examples/skip_ci/guitar_fretboard.rb +137 -0
  38. data/examples/video.rb +10 -0
  39. data/exe/scarpe +42 -66
  40. data/fonts/Pacifico.ttf +0 -0
  41. data/lacci/Gemfile +22 -0
  42. data/lacci/Gemfile.lock +72 -0
  43. data/lacci/Rakefile +12 -0
  44. data/lacci/lacci.gemspec +37 -0
  45. data/lacci/lib/lacci/scarpe_cli.rb +70 -0
  46. data/lacci/lib/lacci/scarpe_core.rb +21 -0
  47. data/lacci/lib/lacci/version.rb +13 -0
  48. data/lacci/lib/shoes/app.rb +264 -0
  49. data/{lib/scarpe → lacci/lib/shoes}/background.rb +1 -1
  50. data/{lib/scarpe → lacci/lib/shoes}/border.rb +1 -1
  51. data/{lib/scarpe → lacci/lib/shoes}/colors.rb +1 -1
  52. data/lacci/lib/shoes/constants.rb +29 -0
  53. data/{lib/scarpe → lacci/lib/shoes}/display_service.rb +40 -45
  54. data/lacci/lib/shoes/download.rb +123 -0
  55. data/lacci/lib/shoes/log.rb +71 -0
  56. data/lacci/lib/shoes/spacing.rb +9 -0
  57. data/{lib/scarpe → lacci/lib/shoes}/widget.rb +63 -43
  58. data/{lib/scarpe → lacci/lib/shoes/widgets}/alert.rb +3 -3
  59. data/{lib/scarpe → lacci/lib/shoes/widgets}/arc.rb +7 -5
  60. data/{lib/scarpe → lacci/lib/shoes/widgets}/button.rb +3 -3
  61. data/lacci/lib/shoes/widgets/check.rb +28 -0
  62. data/lacci/lib/shoes/widgets/document_root.rb +20 -0
  63. data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_box.rb +10 -5
  64. data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_line.rb +2 -2
  65. data/lacci/lib/shoes/widgets/flow.rb +22 -0
  66. data/lacci/lib/shoes/widgets/font.rb +14 -0
  67. data/{lib/scarpe → lacci/lib/shoes/widgets}/image.rb +3 -7
  68. data/lacci/lib/shoes/widgets/line.rb +18 -0
  69. data/{lib/scarpe → lacci/lib/shoes/widgets}/link.rb +2 -2
  70. data/{lib/scarpe → lacci/lib/shoes/widgets}/list_box.rb +2 -2
  71. data/{lib/scarpe → lacci/lib/shoes/widgets}/para.rb +4 -26
  72. data/lacci/lib/shoes/widgets/radio.rb +35 -0
  73. data/lacci/lib/shoes/widgets/shape.rb +37 -0
  74. data/lacci/lib/shoes/widgets/slot.rb +75 -0
  75. data/{lib/scarpe → lacci/lib/shoes/widgets}/span.rb +2 -2
  76. data/lacci/lib/shoes/widgets/stack.rb +24 -0
  77. data/{lib/scarpe → lacci/lib/shoes/widgets}/star.rb +6 -9
  78. data/lacci/lib/shoes/widgets/subscription_item.rb +60 -0
  79. data/lacci/lib/shoes/widgets/text_widget.rb +51 -0
  80. data/lacci/lib/shoes/widgets/video.rb +15 -0
  81. data/lacci/lib/shoes/widgets.rb +29 -0
  82. data/lacci/lib/shoes.rb +127 -0
  83. data/lacci/test/test_colors.rb +39 -0
  84. data/lacci/test/test_helper.rb +9 -0
  85. data/lacci/test/test_lacci.rb +9 -0
  86. data/lib/scarpe/cats_cradle.rb +249 -0
  87. data/lib/scarpe/evented_assertions.rb +88 -0
  88. data/lib/scarpe/version.rb +1 -1
  89. data/lib/scarpe/wv/alert.rb +3 -2
  90. data/lib/scarpe/wv/app.rb +30 -8
  91. data/lib/scarpe/wv/arc.rb +5 -6
  92. data/lib/scarpe/wv/background.rb +10 -1
  93. data/lib/scarpe/wv/border.rb +5 -3
  94. data/lib/scarpe/wv/button.rb +11 -9
  95. data/lib/scarpe/wv/check.rb +29 -0
  96. data/lib/scarpe/wv/control_interface.rb +14 -20
  97. data/lib/scarpe/wv/control_interface_test.rb +13 -28
  98. data/lib/scarpe/wv/document_root.rb +3 -45
  99. data/lib/scarpe/wv/edit_box.rb +5 -7
  100. data/lib/scarpe/wv/edit_line.rb +2 -2
  101. data/lib/scarpe/wv/flow.rb +10 -20
  102. data/lib/scarpe/wv/font.rb +36 -0
  103. data/lib/scarpe/wv/html.rb +3 -2
  104. data/lib/scarpe/wv/image.rb +7 -2
  105. data/lib/scarpe/wv/line.rb +4 -7
  106. data/lib/scarpe/wv/link.rb +1 -0
  107. data/lib/scarpe/wv/list_box.rb +3 -3
  108. data/lib/scarpe/wv/para.rb +16 -14
  109. data/lib/scarpe/wv/radio.rb +34 -0
  110. data/lib/scarpe/wv/shape.rb +44 -8
  111. data/lib/scarpe/wv/slot.rb +81 -0
  112. data/lib/scarpe/wv/spacing.rb +1 -1
  113. data/lib/scarpe/wv/span.rb +10 -8
  114. data/lib/scarpe/wv/stack.rb +10 -30
  115. data/lib/scarpe/wv/star.rb +11 -12
  116. data/lib/scarpe/wv/subscription_item.rb +50 -0
  117. data/lib/scarpe/wv/video.rb +34 -0
  118. data/lib/scarpe/wv/web_wrangler.rb +238 -58
  119. data/lib/scarpe/wv/webview_local_display.rb +27 -5
  120. data/lib/scarpe/wv/webview_relay_display.rb +18 -119
  121. data/lib/scarpe/wv/webview_relay_util.rb +143 -0
  122. data/lib/scarpe/wv/widget.rb +80 -11
  123. data/lib/scarpe/wv/wv_display_worker.rb +17 -4
  124. data/lib/scarpe/wv.rb +33 -4
  125. data/lib/scarpe/wv_local.rb +1 -1
  126. data/lib/scarpe/wv_relay.rb +1 -1
  127. data/lib/scarpe.rb +3 -32
  128. data/scarpe-components/.gitignore +1 -0
  129. data/scarpe-components/Gemfile +22 -0
  130. data/scarpe-components/README.md +35 -0
  131. data/scarpe-components/Rakefile +12 -0
  132. data/scarpe-components/lib/scarpe/components/base64.rb +29 -0
  133. data/scarpe-components/lib/scarpe/components/file_helpers.rb +65 -0
  134. data/scarpe-components/lib/scarpe/components/modular_logger.rb +113 -0
  135. data/scarpe-components/lib/scarpe/components/print_logger.rb +43 -0
  136. data/{lib/scarpe → scarpe-components/lib/scarpe/components}/promises.rb +102 -35
  137. data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +170 -0
  138. data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +217 -0
  139. data/scarpe-components/lib/scarpe/components/version.rb +7 -0
  140. data/scarpe-components/scarpe-components.gemspec +38 -0
  141. data/scarpe-components/test/test_components.rb +9 -0
  142. data/scarpe-components/test/test_helper.rb +23 -0
  143. data/scarpe-components/test/test_promises.rb +260 -0
  144. data/scarpe-components/test/test_segmented_app_files.rb +182 -0
  145. data/{lib/scarpe → spikes}/glibui/widget.rb +2 -2
  146. data/{lib/scarpe → spikes}/glibui.rb +1 -1
  147. data/templates/basic_class_template.erb +1 -1
  148. data/templates/class_template_with_event_bind.erb +1 -1
  149. data/templates/class_template_with_shapes.erb +1 -1
  150. data/templates/webview_template.erb +0 -3
  151. metadata +151 -118
  152. data/examples/fill.rb +0 -25
  153. data/examples/legacy/not_checked/shoes-contrib/basic/class-book.yaml +0 -387
  154. data/examples/legacy/not_checked/shoes-contrib/good/good-clock.rb +0 -51
  155. data/examples/legacy/not_checked/shoes-contrib/good/good-follow.rb +0 -26
  156. data/examples/legacy/not_checked/shoes-contrib/good/good-reminder.rb +0 -174
  157. data/examples/legacy/not_checked/shoes-contrib/good/good-vjot.rb +0 -56
  158. data/examples/legacy/not_checked/shoes-contrib/simple/simple-timer.rb +0 -13
  159. data/examples/legacy/not_checked/shoes-dep-samples/good-clock.rb +0 -51
  160. data/examples/legacy/not_checked/shoes-dep-samples/good-follow.rb +0 -26
  161. data/examples/legacy/not_checked/shoes-dep-samples/good-reminder.rb +0 -174
  162. data/examples/legacy/not_checked/shoes-dep-samples/good-vjot.rb +0 -56
  163. data/examples/legacy/not_checked/shoes-dep-samples/simple-accordion.rb +0 -75
  164. data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-shapes.rb +0 -17
  165. data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-text.rb +0 -13
  166. data/examples/legacy/not_checked/shoes-dep-samples/simple-arc.rb +0 -23
  167. data/examples/legacy/not_checked/shoes-dep-samples/simple-bounce.rb +0 -24
  168. data/examples/legacy/not_checked/shoes-dep-samples/simple-calc.rb +0 -70
  169. data/examples/legacy/not_checked/shoes-dep-samples/simple-chipmunk.rb +0 -26
  170. data/examples/legacy/not_checked/shoes-dep-samples/simple-control-sizes.rb +0 -24
  171. data/examples/legacy/not_checked/shoes-dep-samples/simple-curve.rb +0 -26
  172. data/examples/legacy/not_checked/shoes-dep-samples/simple-dialogs.rb +0 -29
  173. data/examples/legacy/not_checked/shoes-dep-samples/simple-draw.rb +0 -13
  174. data/examples/legacy/not_checked/shoes-dep-samples/simple-editor.rb +0 -28
  175. data/examples/legacy/not_checked/shoes-dep-samples/simple-form.rb +0 -28
  176. data/examples/legacy/not_checked/shoes-dep-samples/simple-form.shy +0 -0
  177. data/examples/legacy/not_checked/shoes-dep-samples/simple-mask.rb +0 -21
  178. data/examples/legacy/not_checked/shoes-dep-samples/simple-menu.rb +0 -31
  179. data/examples/legacy/not_checked/shoes-dep-samples/simple-menu1.rb +0 -35
  180. data/examples/legacy/not_checked/shoes-dep-samples/simple-rubygems.rb +0 -29
  181. data/examples/legacy/not_checked/shoes-dep-samples/simple-slide.rb +0 -45
  182. data/examples/legacy/not_checked/shoes-dep-samples/simple-sphere.rb +0 -28
  183. data/examples/legacy/not_checked/shoes-dep-samples/simple-sqlite3.rb +0 -13
  184. data/examples/legacy/not_checked/shoes-dep-samples/simple-timer.rb +0 -13
  185. data/examples/legacy/not_checked/shoes-dep-samples/simple-video.rb +0 -13
  186. data/examples/legacy/not_checked/simple/anim-text.rb +0 -13
  187. data/examples/legacy/not_checked/simple/arc.rb +0 -23
  188. data/examples/legacy/not_checked/simple/bounce.rb +0 -24
  189. data/examples/legacy/not_checked/simple/chipmunk.rb +0 -26
  190. data/examples/legacy/not_checked/simple/curve.rb +0 -26
  191. data/examples/legacy/not_checked/simple/dialogs.rb +0 -29
  192. data/examples/legacy/not_checked/simple/downloader.rb +0 -40
  193. data/examples/legacy/not_checked/simple/draw.rb +0 -13
  194. data/examples/legacy/not_checked/simple/mask.rb +0 -21
  195. data/examples/legacy/not_checked/simple/slide.rb +0 -45
  196. data/examples/legacy/not_checked/simple/sphere.rb +0 -28
  197. data/lib/constants.rb +0 -5
  198. data/lib/scarpe/app.rb +0 -78
  199. data/lib/scarpe/document_root.rb +0 -20
  200. data/lib/scarpe/fill.rb +0 -23
  201. data/lib/scarpe/flow.rb +0 -19
  202. data/lib/scarpe/line.rb +0 -25
  203. data/lib/scarpe/logger.rb +0 -155
  204. data/lib/scarpe/shape.rb +0 -19
  205. data/lib/scarpe/spacing.rb +0 -9
  206. data/lib/scarpe/stack.rb +0 -70
  207. data/lib/scarpe/text_widget.rb +0 -42
  208. data/lib/scarpe/unit_test_helpers.rb +0 -163
  209. data/lib/scarpe/widgets.rb +0 -30
  210. data/lib/scarpe/wv/fill.rb +0 -30
  211. data/lib/scarpe/wv/shape_helper.rb +0 -44
  212. data/scarpe-0.2.0.gem +0 -0
  213. /data/{lib/scarpe → spikes}/glibui/README.md +0 -0
  214. /data/{lib/scarpe → spikes}/glibui/alert.rb +0 -0
  215. /data/{lib/scarpe → spikes}/glibui/app.rb +0 -0
  216. /data/{lib/scarpe → spikes}/glibui/background.rb +0 -0
  217. /data/{lib/scarpe → spikes}/glibui/border.rb +0 -0
  218. /data/{lib/scarpe → spikes}/glibui/button.rb +0 -0
  219. /data/{lib/scarpe → spikes}/glibui/dimensions.rb +0 -0
  220. /data/{lib/scarpe → spikes}/glibui/document_root.rb +0 -0
  221. /data/{lib/scarpe → spikes}/glibui/edit_box.rb +0 -0
  222. /data/{lib/scarpe → spikes}/glibui/edit_line.rb +0 -0
  223. /data/{lib/scarpe → spikes}/glibui/flow.rb +0 -0
  224. /data/{lib/scarpe → spikes}/glibui/html.rb +0 -0
  225. /data/{lib/scarpe → spikes}/glibui/image.rb +0 -0
  226. /data/{lib/scarpe → spikes}/glibui/link.rb +0 -0
  227. /data/{lib/scarpe → spikes}/glibui/local_display.rb +0 -0
  228. /data/{lib/scarpe → spikes}/glibui/para.rb +0 -0
  229. /data/{lib/scarpe → spikes}/glibui/spacing.rb +0 -0
  230. /data/{lib/scarpe → spikes}/glibui/stack.rb +0 -0
  231. /data/{lib/scarpe → spikes}/glibui/text_widget.rb +0 -0
  232. /data/{lib/scarpe → spikes}/libui/alert.rb +0 -0
  233. /data/{lib/scarpe → spikes}/libui/button.rb +0 -0
  234. /data/{lib/scarpe → spikes}/libui/colors.rb +0 -0
  235. /data/{lib/scarpe → spikes}/libui/core.rb +0 -0
  236. /data/{lib/scarpe → spikes}/libui/flow.rb +0 -0
  237. /data/{lib/scarpe → spikes}/libui/libui.rb +0 -0
  238. /data/{lib/scarpe → spikes}/libui/notepad.md +0 -0
  239. /data/{lib/scarpe → spikes}/libui/para.rb +0 -0
  240. /data/{lib/scarpe → spikes}/libui/stack.rb +0 -0
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "scarpe/components/file_helpers"
4
+
5
+ module Scarpe::Components
6
+ class SegmentedFileLoader
7
+ include Scarpe::Components::FileHelpers
8
+
9
+ # Add a new segment type (e.g. "catscradle") with a different
10
+ # file handler.
11
+ #
12
+ # @param type [String] the new name for this segment type
13
+ # @param handler [Object] an object that will be called as obj.call(filename) - often a proc
14
+ # @return <void>
15
+ def add_segment_type(type, handler)
16
+ if segment_type_hash.key?(type)
17
+ raise "Segment type #{type.inspect} already exists!"
18
+ end
19
+
20
+ segment_type_hash[type] = handler
21
+ end
22
+
23
+ # Return an Array of segment type labels, such as "code" and "app_test".
24
+ #
25
+ # @return Array<String> the segment type labels
26
+ def segment_types
27
+ segment_type_hash.keys
28
+ end
29
+
30
+ # Load a .sca file with an optional YAML frontmatter prefix and
31
+ # multiple file sections which can be treated differently.
32
+ #
33
+ # The file loader acts like a proc, being called with .call()
34
+ # and returning true or false for whether it has handled the
35
+ # file load. This allows chaining loaders in order and the
36
+ # first loader to recognise a file will run it.
37
+ #
38
+ # @param path [String] the file or directory to treat as a Scarpe app
39
+ # @return [Boolean] return true if the file is loaded as a segmented Scarpe app file
40
+ def call(path)
41
+ return false unless path.end_with?(".scas")
42
+
43
+ file_load(path)
44
+ true
45
+ end
46
+
47
+ # Segment type handlers can call this to perform an operation after the load
48
+ # has completed. This is important for ordering, and because loading a Shoes
49
+ # app often doesn't return. So to have a later section (e.g. tests, additional
50
+ # data) do something that affects Shoes app loading (e.g. set an env var,
51
+ # affect the display service) it's important that app loading take place later
52
+ # in the sequence.
53
+ def after_load(&block)
54
+ @after_load ||= []
55
+ @after_load << block
56
+ end
57
+
58
+ private
59
+
60
+ def gen_name(segmap)
61
+ ctr = (1..10_000).detect { |i| !segmap.key?("%5d" % i) }
62
+ "%5d" % ctr
63
+ end
64
+
65
+ def tokenize_segments(contents)
66
+ require "yaml" # Only load when needed
67
+ require "English"
68
+
69
+ segments = contents.split(/\n-{5,}/)
70
+ front_matter = {}
71
+
72
+ # The very first segment can start with front matter, or with a divider, or with no divider.
73
+ if segments[0].start_with?("---\n") || segments[0] == "---"
74
+ # We have YAML front matter at the start. All later segments will have a divider.
75
+ front_matter = YAML.load segments[0]
76
+ front_matter ||= {} # If the front matter is just the three dashes it returns nil
77
+ segments = segments[1..-1]
78
+ elsif segments[0].start_with?("-----")
79
+ # We have a divider at the start. Great! We're already well set up for this case.
80
+ elsif segments.size == 1
81
+ # No front matter, no divider, a single unnamed segment. No more parsing needed.
82
+ return [{}, { "" => segments[0] }]
83
+ else
84
+ # No front matter, no divider before the first segment, multiple segments.
85
+ # We'll add an artificial divider to the first segment for uniformity.
86
+ segments = ["-----\n" + segments[0]] + segments[1..-1]
87
+ end
88
+
89
+ segmap = {}
90
+ segments.each do |segment|
91
+ if segment =~ /\A-* +(.*?)\n/
92
+ # named segment with separator
93
+ segmap[::Regexp.last_match(1)] = ::Regexp.last_match.post_match
94
+ elsif segment =~ /\A-* *\n/
95
+ # unnamed segment with separator
96
+ segmap[gen_name(segmap)] = ::Regexp.last_match.post_match
97
+ else
98
+ raise "Internal error when parsing segments in segmented app file! seg: #{segment.inspect}"
99
+ end
100
+ end
101
+
102
+ [front_matter, segmap]
103
+ end
104
+
105
+ def file_load(path)
106
+ contents = File.read(path)
107
+
108
+ front_matter, segmap = tokenize_segments(contents)
109
+
110
+ if segmap.empty?
111
+ raise "Illegal segmented Scarpe file: must have at least one code segment, not just front matter!"
112
+ end
113
+
114
+ if front_matter[:segments]
115
+ if front_matter[:segments].size != segmap.size
116
+ raise "Number of front matter :segments must equal number of file segments!"
117
+ end
118
+ else
119
+ if segmap.size > 2
120
+ raise "Segmented files with more than two segments have to specify what they're for!"
121
+ end
122
+
123
+ # Set to default of shoes code only or shoes code and app test code.
124
+ front_matter[:segments] = segmap.size == 2 ? ["shoes", "app_test"] : ["shoes"]
125
+ end
126
+
127
+ # Match up front_matter[:segments] with the segments, or use the default of shoes and app_test.
128
+
129
+ sth = segment_type_hash
130
+ sv = segmap.values
131
+
132
+ tf_specs = []
133
+ front_matter[:segments].each.with_index do |seg_type, idx|
134
+ unless sth.key?(seg_type)
135
+ raise "Unrecognized segment type #{seg_type.inspect}! No matching segment type available!"
136
+ end
137
+
138
+ tf_specs << ["scarpe_#{seg_type}_segment_contents", sv[idx]]
139
+ end
140
+
141
+ with_tempfiles(tf_specs) do |filenames|
142
+ filenames.each.with_index do |filename, idx|
143
+ seg_name = front_matter[:segments][idx]
144
+ sth[seg_name].call(filename)
145
+ end
146
+
147
+ # Need to call @after_load hooks while tempfiles still exist
148
+ if @after_load && !@after_load.empty?
149
+ @after_load.each(&:call)
150
+ end
151
+ end
152
+ end
153
+
154
+ # The hash of segment type labels mapped to handlers which will be called.
155
+ # Normal client code shouldn't ever call this.
156
+ #
157
+ # @return Hash<String, Object> the name/handler pairs
158
+ def segment_type_hash
159
+ @segment_handlers ||= {
160
+ "shoes" => proc { |seg_file| after_load { load seg_file } },
161
+ "app_test" => proc { |seg_file| ENV["SCARPE_APP_TEST"] = seg_file },
162
+ }
163
+ end
164
+ end
165
+ end
166
+
167
+ # You can add additional segment types to the segmented file loader
168
+ # loader = Scarpe::Components::SegmentedFileLoader.new
169
+ # loader.add_segment_type "capybara", proc { |seg_file| load_file_as_capybara(seg_file) }
170
+ # Shoes.add_file_loader loader
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+ require "json"
5
+ require "fileutils"
6
+
7
+ require "scarpe/components/file_helpers"
8
+
9
+ module Scarpe::Test; end
10
+
11
+ # We want test failures set up once *total*, not per Minitest::Test. So an instance var
12
+ # doesn't do it.
13
+ ALREADY_SET_UP_LOGGED_TEST_FAILURES = { setup: false }
14
+
15
+ # General helpers for general usage.
16
+ # Helpers here should *not* use Webview-specific functionality.
17
+ # The intention is that these are helpers for various Scarpe display
18
+ # services that do *not* necessarily use Webview.
19
+
20
+ module Scarpe::Test::Helpers
21
+ # Very useful for tests
22
+ include Scarpe::Components::FileHelpers
23
+
24
+ # Temporarily set env vars for the block of code inside. The old environment
25
+ # variable values will be restored after the block finishes.
26
+ #
27
+ # @param envs [Hash<String,String>] A hash of environment variable names and values
28
+ def with_env_vars(envs)
29
+ old_env = {}
30
+ envs.each do |k, v|
31
+ old_env[k] = ENV[k]
32
+ ENV[k] = v
33
+ end
34
+ yield
35
+ ensure
36
+ old_env.each { |k, v| ENV[k] = v }
37
+ end
38
+ end
39
+
40
+ # This test will save extensive logs in case of test failure.
41
+ # Note that it defines setup/teardown methods. If you want
42
+ # multiple setup/teardowns from multiple places to happen you
43
+ # may need to explictly call (e.g. with logged_test_setup/teardown)
44
+ # to ensure everything you want happens.
45
+ module Scarpe::Test::LoggedTest
46
+ def self.included(includer)
47
+ class << includer
48
+ attr_accessor :logger_dir
49
+ end
50
+ end
51
+
52
+ def file_id
53
+ "#{self.class.name}_#{self.name}"
54
+ end
55
+
56
+ # This should be called by the test during setup to make sure that
57
+ # failure logs will be saved if this test fails. It makes sure the
58
+ # log config will save all logs from all sources, but keeps a copy
59
+ # of the old log config to restore after the test is finished.
60
+ #
61
+ # @return [void]
62
+ def logged_test_setup
63
+ # Make sure test failures will be saved at the end of the run.
64
+ # Delete stale test failures and logging only the *first* time this is called.
65
+ set_up_test_failures
66
+
67
+ @normal_log_config = Shoes::Log.current_log_config
68
+ Shoes::Log.configure_logger(log_config_for_test)
69
+
70
+ Shoes::Log.logger("LoggedScarpeTest").info("Test: #{self.class.name}##{self.name}")
71
+ end
72
+
73
+ # If you include this module and don't override setup/teardown, everything will
74
+ # work fine. But if you need more setup/teardown steps, you can do that too.
75
+ #
76
+ # The setup method guarantees that just including this module will do setup
77
+ # automatically. If you override it, be sure to call `super` or `logged_test_setup`.
78
+ #
79
+ # @return [void]
80
+ def setup
81
+ logged_test_setup
82
+ end
83
+
84
+ # After the test has finished, this will restore the old log configuration.
85
+ # It will also save the logfiles, but only if the test failed, not if it
86
+ # succeeded or was skipped.
87
+ #
88
+ # @return [void]
89
+ def logged_test_teardown
90
+ # Restore previous log config
91
+ Shoes::Log.configure_logger(@normal_log_config)
92
+
93
+ if self.failure
94
+ save_failure_logs
95
+ else
96
+ remove_unsaved_logs
97
+ end
98
+ end
99
+
100
+ # Make sure that, by default, #logged_test_teardown will be called for teardown.
101
+ # If a class overrides teardown, it should also call `super` or `logged_test_teardown`
102
+ # to make sure this still happens.
103
+ #
104
+ # @return [void]
105
+ def teardown
106
+ logged_test_teardown
107
+ end
108
+
109
+ # Set additional LoggedTest configuration for specific logs to separate or save.
110
+ # This is normally going to be display-service-specific log components.
111
+ # Note that this only really works with the modular logger or another logger
112
+ # that does something useful with the log config. The simple print logger
113
+ # doesn't do a lot with it.
114
+ def extra_log_config=(additional_log_config)
115
+ @additional_log_config = additional_log_config
116
+ end
117
+
118
+ # This is the log config that LoggedTests use. It makes sure all components keep all
119
+ # logs, but also splits the logs into several different files for later ease of scanning.
120
+ #
121
+ # TODO: this shouldn't directly include any Webview entries like WebviewAPI or
122
+ # CatsCradle. Those should be overridden in Webview.
123
+ #
124
+ # @return [Hash] the log config
125
+ def log_config_for_test
126
+ {
127
+ "default" => ["debug", "logger/test_failure_#{file_id}.log"],
128
+ "DisplayService" => ["debug", "logger/test_failure_events_#{file_id}.log"],
129
+ }.merge(@additional_log_config || {})
130
+ end
131
+
132
+ # The list of logfiles that should be saved. Normally this is called internally by the
133
+ # class, not externally from elsewhere.
134
+ #
135
+ # This could be a lot simpler except I want to only update the file list in one place,
136
+ # log_config_for_test(). Having a single spot should (I hope) make it a lot friendlier to
137
+ # add more logfiles for different components, logged API objects, etc.
138
+ def saved_log_files
139
+ lc = log_config_for_test
140
+ log_outfiles = lc.values.map { |_level, loc| loc }
141
+ log_outfiles.select { |s| s.start_with?("logger/") }.map { |s| s.delete_prefix("logger/") }
142
+ end
143
+
144
+ # Make sure that test failure logs will be noticed, and a message will be printed,
145
+ # if any logged tests fail. This needs to be called at least once in any Minitest-enabled
146
+ # process using logged tests.
147
+ #
148
+ # @return [void]
149
+ def set_up_test_failures
150
+ return if ALREADY_SET_UP_LOGGED_TEST_FAILURES[:setup]
151
+
152
+ log_dir = self.class.logger_dir
153
+ raise("Must set logger directory!") unless log_dir
154
+ raise("Can't find logger directory!") unless File.directory?(log_dir)
155
+
156
+ ALREADY_SET_UP_LOGGED_TEST_FAILURES[:setup] = true
157
+ # Delete stale test failures, if any, before starting the first failure-logged test
158
+ Dir["#{log_dir}/test_failure*.log"].each { |fn| File.unlink(fn) }
159
+
160
+ Minitest.after_run do
161
+ # Print test failure notice to console
162
+ unless Dir["#{log_dir}/test_failure*.out.log"].empty?
163
+ puts "Some tests have failed! See #{log_dir}/test_failure*.out.log for test logs!"
164
+ end
165
+
166
+ # Remove un-saved test logs
167
+ Dir["#{log_dir}/test_failure*.log"].each do |f|
168
+ next if f.include?(".out.log")
169
+
170
+ File.unlink(f) if File.exist?(f)
171
+ end
172
+ end
173
+ end
174
+
175
+ # Failure log output location for a given file path. This is normally used internally to this
176
+ # class, not externally.
177
+ #
178
+ # @return [String] the output path
179
+ def logfail_out_loc(filepath)
180
+ # Add a .out prefix before final .log
181
+ out_loc = filepath.gsub(%r{.log\Z}, ".out.log")
182
+
183
+ if out_loc == filepath
184
+ raise "Something is wrong! Could not figure out failure-log output path for #{filepath.inspect}!"
185
+ end
186
+
187
+ if File.exist?(out_loc)
188
+ raise "Duplicate test file #{out_loc.inspect}? This file should *not* already exist!"
189
+ end
190
+
191
+ out_loc
192
+ end
193
+
194
+ # Save the failure logs in the appropriate place(s). This is normally used internally, not externally.
195
+ #
196
+ # @return [void]
197
+ def save_failure_logs
198
+ saved_log_files.each do |log_file|
199
+ full_loc = File.expand_path("#{self.class.logger_dir}/#{log_file}")
200
+ # TODO: we'd like to skip 0-length logfiles. But also Logging doesn't flush. For now, ignore.
201
+ next unless File.exist?(full_loc)
202
+
203
+ FileUtils.mv full_loc, logfail_out_loc(full_loc)
204
+ end
205
+ end
206
+
207
+ # Remove unsaved failure logs. This is normally used internally, not externally.
208
+ #
209
+ # @return [void]
210
+ def remove_unsaved_logs
211
+ Dir["#{self.class.logger_dir}/test_failure*.log"].each do |f|
212
+ next if f.include?(".out.log") # Don't delete saved logs
213
+
214
+ File.unlink(f)
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Scarpe
4
+ module Components
5
+ VERSION = "0.2.2"
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/scarpe/components/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "scarpe-components"
7
+ spec.version = Scarpe::Components::VERSION
8
+ spec.authors = ["Marco Concetto Rudilosso", "Noah Gibbs"]
9
+ spec.email = ["marcoc.r@outlook.com", "the.codefolio.guy@gmail.com"]
10
+
11
+ spec.summary = "Reusable components for Scarpe display libraries"
12
+ spec.homepage = "https://github.com/scarpe-team/scarpe"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.2.0"
15
+
16
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/scarpe-team/scarpe"
20
+ spec.metadata["changelog_uri"] = "https://github.com/scarpe-team/scarpe/blob/main/CHANGELOG.md"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
27
+ end
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ # Scarpe-Components should add *no* runtime dependencies. Since each component
34
+ # is optional, the display library should add dependencies relevant to only
35
+ # the components it directly uses and no others.
36
+
37
+ #spec.add_dependency ""
38
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class TestScarpeComponents < Minitest::Test
6
+ def test_truth
7
+ assert_equal true, true
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+
5
+ require "shoes"
6
+
7
+ require "minitest/autorun"
8
+
9
+ require "minitest/reporters"
10
+ Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
11
+
12
+ # For tests, default to simple print logger
13
+ # TODO: switch to modular logger and failure-logged tests?
14
+ require "shoes/log"
15
+ require "scarpe/components/print_logger"
16
+ Shoes::Log.instance = Scarpe::Components::PrintLogImpl.new
17
+ Shoes::Log.configure_logger(Shoes::Log::DEFAULT_LOG_CONFIG)
18
+
19
+ require "scarpe/components/unit_test_helpers"
20
+
21
+ class Minitest::Test
22
+ include Scarpe::Test::Helpers
23
+ end