add a helper and update some docs
[view.love.git] / file.lua
blob34be0150383728c3ebab1ca225d9c6f9383bc9ef
1 -- primitives for saving to file and loading from file
2 function file_exists(filename)
3 local infile = App.open_for_reading(filename)
4 if infile then
5 infile:close()
6 return true
7 else
8 return false
9 end
10 end
12 function load_from_disk(State)
13 local infile = App.open_for_reading(State.filename)
14 State.lines = load_from_file(infile)
15 if infile then infile:close() end
16 end
18 function load_from_file(infile)
19 local result = {}
20 if infile then
21 local infile_next_line = infile:lines() -- works with both Lua files and LÖVE Files (https://www.love2d.org/wiki/File)
22 while true do
23 local line = infile_next_line()
24 if line == nil then break end
25 if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
26 table.insert(result, load_drawing(infile_next_line))
27 else
28 table.insert(result, {mode='text', data=line})
29 end
30 end
31 end
32 if #result == 0 then
33 table.insert(result, {mode='text', data=''})
34 end
35 return result
36 end
38 function save_to_disk(State)
39 local outfile = App.open_for_writing(State.filename)
40 if not outfile then
41 error('failed to write to "'..State.filename..'"')
42 end
43 for _,line in ipairs(State.lines) do
44 if line.mode == 'drawing' then
45 store_drawing(outfile, line)
46 else
47 outfile:write(line.data)
48 outfile:write('\n')
49 end
50 end
51 outfile:close()
52 end
54 function load_drawing(infile_next_line)
55 local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
56 while true do
57 local line = infile_next_line()
58 assert(line, 'drawing in file is incomplete')
59 if line == '```' then break end
60 local shape = json.decode(line)
61 if shape.mode == 'freehand' then
62 -- no changes needed
63 elseif shape.mode == 'line' or shape.mode == 'manhattan' then
64 local name = shape.p1.name
65 shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
66 drawing.points[shape.p1].name = name
67 name = shape.p2.name
68 shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
69 drawing.points[shape.p2].name = name
70 elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
71 for i,p in ipairs(shape.vertices) do
72 local name = p.name
73 shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
74 drawing.points[shape.vertices[i]].name = name
75 end
76 elseif shape.mode == 'circle' or shape.mode == 'arc' then
77 local name = shape.center.name
78 shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
79 drawing.points[shape.center].name = name
80 elseif shape.mode == 'deleted' then
81 -- ignore
82 else
83 assert(false, ('unknown drawing mode %s'):format(shape.mode))
84 end
85 table.insert(drawing.shapes, shape)
86 end
87 return drawing
88 end
90 function store_drawing(outfile, drawing)
91 outfile:write('```lines\n')
92 for _,shape in ipairs(drawing.shapes) do
93 if shape.mode == 'freehand' then
94 outfile:write(json.encode(shape))
95 outfile:write('\n')
96 elseif shape.mode == 'line' or shape.mode == 'manhattan' then
97 local line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})
98 outfile:write(line)
99 outfile:write('\n')
100 elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
101 local obj = {mode=shape.mode, vertices={}}
102 for _,p in ipairs(shape.vertices) do
103 table.insert(obj.vertices, drawing.points[p])
105 local line = json.encode(obj)
106 outfile:write(line)
107 outfile:write('\n')
108 elseif shape.mode == 'circle' then
109 outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius}))
110 outfile:write('\n')
111 elseif shape.mode == 'arc' then
112 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}))
113 outfile:write('\n')
114 elseif shape.mode == 'deleted' then
115 -- ignore
116 else
117 assert(false, ('unknown drawing mode %s'):format(shape.mode))
120 outfile:write('```\n')
123 -- for tests
124 function load_array(a)
125 local result = {}
126 local next_line = ipairs(a)
127 local i,line,drawing = 0, ''
128 while true do
129 i,line = next_line(a, i)
130 if i == nil then break end
131 --? print(line)
132 if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
133 --? print('inserting drawing')
134 i, drawing = load_drawing_from_array(next_line, a, i)
135 --? print('i now', i)
136 table.insert(result, drawing)
137 else
138 --? print('inserting text')
139 table.insert(result, {mode='text', data=line})
142 if #result == 0 then
143 table.insert(result, {mode='text', data=''})
145 return result
148 function load_drawing_from_array(iter, a, i)
149 local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
150 local line
151 while true do
152 i, line = iter(a, i)
153 assert(i, 'drawing in array is incomplete')
154 --? print(i)
155 if line == '```' then break end
156 local shape = json.decode(line)
157 if shape.mode == 'freehand' then
158 -- no changes needed
159 elseif shape.mode == 'line' or shape.mode == 'manhattan' then
160 local name = shape.p1.name
161 shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
162 drawing.points[shape.p1].name = name
163 name = shape.p2.name
164 shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
165 drawing.points[shape.p2].name = name
166 elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
167 for i,p in ipairs(shape.vertices) do
168 local name = p.name
169 shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
170 drawing.points[shape.vertices[i]].name = name
172 elseif shape.mode == 'circle' or shape.mode == 'arc' then
173 local name = shape.center.name
174 shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
175 drawing.points[shape.center].name = name
176 elseif shape.mode == 'deleted' then
177 -- ignore
178 else
179 assert(false, ('unknown drawing mode %s'):format(shape.mode))
181 table.insert(drawing.shapes, shape)
183 return i, drawing
186 function is_absolute_path(path)
187 local os_path_separator = package.config:sub(1,1)
188 if os_path_separator == '/' then
189 -- POSIX systems permit backslashes in filenames
190 return path:sub(1,1) == '/'
191 elseif os_path_separator == '\\' then
192 if path:sub(2,2) == ':' then return true end -- DOS drive letter followed by volume separator
193 local f = path:sub(1,1)
194 return f == '/' or f == '\\'
195 else
196 error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator..'"')
200 function is_relative_path(path)
201 return not is_absolute_path(path)