source_file.lua 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. -- primitives for saving to file and loading from file
  2. function file_exists(filename)
  3. local infile = App.open_for_reading(App.save_dir..filename)
  4. if not infile then
  5. infile = App.open_for_reading(App.source_dir..filename)
  6. end
  7. if infile then
  8. infile:close()
  9. return true
  10. else
  11. return false
  12. end
  13. end
  14. -- the source editor supports only files in the save dir backed by the source dir
  15. function load_from_disk(State)
  16. local infile = App.open_for_reading(App.save_dir..State.filename)
  17. if not infile then
  18. infile = App.open_for_reading(App.source_dir..State.filename)
  19. end
  20. State.lines = load_from_file(infile)
  21. if infile then infile:close() end
  22. end
  23. function load_from_file(infile)
  24. local result = {}
  25. if infile then
  26. local infile_next_line = infile:lines() -- works with both Lua files and LÖVE Files (https://www.love2d.org/wiki/File)
  27. while true do
  28. local line = infile_next_line()
  29. if line == nil then break end
  30. if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
  31. table.insert(result, load_drawing(infile_next_line))
  32. else
  33. table.insert(result, {mode='text', data=line})
  34. end
  35. end
  36. end
  37. if #result == 0 then
  38. table.insert(result, {mode='text', data=''})
  39. end
  40. return result
  41. end
  42. function save_to_disk(State)
  43. local outfile = App.open_for_writing(App.save_dir..State.filename)
  44. if not outfile then
  45. error('failed to write to "'..State.filename..'"')
  46. end
  47. for _,line in ipairs(State.lines) do
  48. if line.mode == 'drawing' then
  49. store_drawing(outfile, line)
  50. else
  51. outfile:write(line.data)
  52. outfile:write('\n')
  53. end
  54. end
  55. outfile:close()
  56. end
  57. function load_drawing(infile_next_line)
  58. local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
  59. while true do
  60. local line = infile_next_line()
  61. assert(line, 'drawing in file is incomplete')
  62. if line == '```' then break end
  63. local shape = json.decode(line)
  64. if shape.mode == 'freehand' then
  65. -- no changes needed
  66. elseif shape.mode == 'line' or shape.mode == 'manhattan' then
  67. local name = shape.p1.name
  68. shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
  69. drawing.points[shape.p1].name = name
  70. name = shape.p2.name
  71. shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
  72. drawing.points[shape.p2].name = name
  73. elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
  74. for i,p in ipairs(shape.vertices) do
  75. local name = p.name
  76. shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
  77. drawing.points[shape.vertices[i]].name = name
  78. end
  79. elseif shape.mode == 'circle' or shape.mode == 'arc' then
  80. local name = shape.center.name
  81. shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
  82. drawing.points[shape.center].name = name
  83. elseif shape.mode == 'deleted' then
  84. -- ignore
  85. else
  86. assert(false, ('unknown drawing mode %s'):format(shape.mode))
  87. end
  88. table.insert(drawing.shapes, shape)
  89. end
  90. return drawing
  91. end
  92. function store_drawing(outfile, drawing)
  93. outfile:write('```lines\n')
  94. for _,shape in ipairs(drawing.shapes) do
  95. if shape.mode == 'freehand' then
  96. outfile:write(json.encode(shape))
  97. outfile:write('\n')
  98. elseif shape.mode == 'line' or shape.mode == 'manhattan' then
  99. local line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})
  100. outfile:write(line)
  101. outfile:write('\n')
  102. elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
  103. local obj = {mode=shape.mode, vertices={}}
  104. for _,p in ipairs(shape.vertices) do
  105. table.insert(obj.vertices, drawing.points[p])
  106. end
  107. local line = json.encode(obj)
  108. outfile:write(line)
  109. outfile:write('\n')
  110. elseif shape.mode == 'circle' then
  111. outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius}))
  112. outfile:write('\n')
  113. elseif shape.mode == 'arc' then
  114. outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius, start_angle=shape.start_angle, end_angle=shape.end_angle}))
  115. outfile:write('\n')
  116. elseif shape.mode == 'deleted' then
  117. -- ignore
  118. else
  119. assert(false, ('unknown drawing mode %s'):format(shape.mode))
  120. end
  121. end
  122. outfile:write('```\n')
  123. end
  124. -- for tests
  125. function load_array(a)
  126. local result = {}
  127. local next_line = ipairs(a)
  128. local i,line,drawing = 0, ''
  129. while true do
  130. i,line = next_line(a, i)
  131. if i == nil then break end
  132. --? print(line)
  133. if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
  134. --? print('inserting drawing')
  135. i, drawing = load_drawing_from_array(next_line, a, i)
  136. --? print('i now', i)
  137. table.insert(result, drawing)
  138. else
  139. --? print('inserting text')
  140. local line_info = {mode='text'}
  141. line_info.data = line
  142. table.insert(result, line_info)
  143. end
  144. end
  145. if #result == 0 then
  146. table.insert(result, {mode='text', data=''})
  147. end
  148. return result
  149. end
  150. function load_drawing_from_array(iter, a, i)
  151. local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
  152. local line
  153. while true do
  154. i, line = iter(a, i)
  155. assert(i, 'drawing in array is incomplete')
  156. --? print(i)
  157. if line == '```' then break end
  158. local shape = json.decode(line)
  159. if shape.mode == 'freehand' then
  160. -- no changes needed
  161. elseif shape.mode == 'line' or shape.mode == 'manhattan' then
  162. local name = shape.p1.name
  163. shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
  164. drawing.points[shape.p1].name = name
  165. name = shape.p2.name
  166. shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
  167. drawing.points[shape.p2].name = name
  168. elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
  169. for i,p in ipairs(shape.vertices) do
  170. local name = p.name
  171. shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
  172. drawing.points[shape.vertices[i]].name = name
  173. end
  174. elseif shape.mode == 'circle' or shape.mode == 'arc' then
  175. local name = shape.center.name
  176. shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
  177. drawing.points[shape.center].name = name
  178. elseif shape.mode == 'deleted' then
  179. -- ignore
  180. else
  181. assert(false, ('unknown drawing mode %s'):format(shape.mode))
  182. end
  183. table.insert(drawing.shapes, shape)
  184. end
  185. return i, drawing
  186. end
  187. function is_absolute_path(path)
  188. local os_path_separator = package.config:sub(1,1)
  189. if os_path_separator == '/' then
  190. -- POSIX systems permit backslashes in filenames
  191. return path:sub(1,1) == '/'
  192. elseif os_path_separator == '\\' then
  193. if path:sub(2,2) == ':' then return true end -- DOS drive letter followed by volume separator
  194. local f = path:sub(1,1)
  195. return f == '/' or f == '\\'
  196. else
  197. error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator..'"')
  198. end
  199. end
  200. function is_relative_path(path)
  201. return not is_absolute_path(path)
  202. end
  203. function dirname(path)
  204. local os_path_separator = package.config:sub(1,1)
  205. if os_path_separator == '/' then
  206. -- POSIX systems permit backslashes in filenames
  207. return path:match('.*/') or './'
  208. elseif os_path_separator == '\\' then
  209. return path:match('.*[/\\]') or './'
  210. else
  211. error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator..'"')
  212. end
  213. end
  214. function test_dirname()
  215. check_eq(dirname('a/b'), 'a/', 'F - test_dirname')
  216. check_eq(dirname('x'), './', 'F - test_dirname/current')
  217. end
  218. function basename(path)
  219. local os_path_separator = package.config:sub(1,1)
  220. if os_path_separator == '/' then
  221. -- POSIX systems permit backslashes in filenames
  222. return string.gsub(path, ".*/(.*)", "%1")
  223. elseif os_path_separator == '\\' then
  224. return string.gsub(path, ".*[/\\](.*)", "%1")
  225. else
  226. error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator..'"')
  227. end
  228. end
  229. function empty(h)
  230. for _,_ in pairs(h) do
  231. return false
  232. end
  233. return true
  234. end