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