rabbit-slide-hasumikin-RubyKaigi2023 2022.05.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,893 @@
1
+ = Build Your
2
+
3
+ : subtitle
4
+ Own SQLite3
5
+ : author
6
+ hasumikin
7
+ : content-source
8
+ RubyKaigi 2023
9
+ : date
10
+ Mar. 13, 2023
11
+ : allotted-time
12
+ 30m
13
+ : theme
14
+ theme
15
+
16
+ = How do you pronounce "SQLite"?
17
+
18
+ * /eskjuːel-aɪt/ エスキューエルアイト
19
+ * /eskjuːelaɪt/ エスキューエライト
20
+ * /eskjuːlaɪt/ エスキューライト
21
+ * /siːkuəl-aɪt/ スィークエルアイト
22
+
23
+ (('tag:center'))
24
+ \n
25
+ Just curious. Answer on\n
26
+ https://twitter.com/hasumikin
27
+
28
+ = SQLite3 - Features
29
+
30
+ * Public domain
31
+ * No limitation for commercial use
32
+ * Single-file database. No client-server style
33
+ * Easy to backup
34
+ * High performance while low resource usage
35
+
36
+ (('tag:large'))(('tag:center'))
37
+ \n
38
+ Good for embedded system
39
+
40
+ = Embedded SQLite3 in
41
+
42
+ * Car navigation system
43
+ * iOS | Android applications
44
+ * Web applications such as Rails
45
+ * Master data such as postal code
46
+ * Browsers like Google Chrome
47
+ * "Local" DB for browser apps
48
+ * ((*One-chip microcontroller*))
49
+
50
+ = Embedded SQLite3 in
51
+
52
+ # image
53
+ # src = images/transparent.png
54
+ # align = right
55
+ # relative-height = 100
56
+ # relative_margin_top = 0
57
+ # relative_margin_left = 50
58
+ # draw0 = [rectangle, false, 0, 0, 0.8, 0.45, {color: red, line_width: 8}]
59
+ # draw1 = [text, with OS, 0.56, 0.15, {color: red, size: 80, font_family: 'Montserrat', weight: bold}]
60
+ # draw2 = [rectangle, false, 0, 0.48, 0.8, 0.35, {color: red, line_width: 8}]
61
+ # draw3 = [text, without OS, 0.49, 0.58, {color: red, size: 80, font_family: 'Montserrat', weight: bold}]
62
+
63
+ * Car navigation system
64
+ * iOS | Android applications
65
+ * Web applications such as Rails
66
+ * Master data such as postal code
67
+ * Browsers like Google Chrome
68
+ * "Local" DB for browser apps
69
+ * ((*One-chip microcontroller*))
70
+
71
+ = Target device and OS
72
+
73
+ * RP2040
74
+ * RAM: 264 KB
75
+ * CPU: 32 bit Cortex-M0+ (dual)
76
+ * Flash ROM: 2 MB (Raspberry Pi Pico)
77
+ * Bare metal (No OS)
78
+ * Instead, ((*PicoRuby*)) takes care of\neverything
79
+
80
+ # image
81
+ # src = images/rpi_pico.jpg
82
+ # align = right
83
+ # relative-height = 90
84
+ # relative_margin_left = 5
85
+
86
+ = self.inspect
87
+
88
+ * hasumikin | ハスミキン(Twitter, GitHub)
89
+ * Creator of PicoRuby and PRK Firmware
90
+ * Committer of mruby/c
91
+ * Maintainer of IRB and Reline
92
+ * First prize of Fukuoka Ruby Award\n(2020 and 2022✌️)
93
+ * A final nominee of Ruby Prize 2021
94
+ * Monstarlab Shimane Dev. branch
95
+
96
+ # image
97
+ # src = images/hasumi.jpg
98
+ # align = right
99
+ # relative-height = 60
100
+ # relative_margin_top = 10
101
+ # relative_margin_left = 20
102
+
103
+ = Technical advisor of Monstarlab
104
+
105
+ * From May 1st, 2023
106
+ * Kosaki-san
107
+ * CRuby committer
108
+ * Linux Kernel committer
109
+ * The man of "Malloc"
110
+
111
+ # image
112
+ # src = images/kosaki.png
113
+ # align = right
114
+ # relative-height = 100
115
+ # relative_margin_top = 0
116
+ # relative_margin_left = 20
117
+
118
+ = Demo
119
+
120
+ # image
121
+ # src = images/R2P2.jpg
122
+ # relative-height = 100
123
+ # draw0 = [text, SD card, 0.21, 0.48, {color: white, size: 40, font_family: 'Courier Prime', weight: bold}]
124
+ # draw1 = [text, RTC, 0.45, 0.60, {color: white, size: 40, font_family: 'Courier Prime', weight: bold}]
125
+ # draw2 = [text, RP2040, 0.58, 0.47, {color: white, size: 40, font_family: 'Courier Prime', weight: bold}]
126
+
127
+ = Demo (review or fallback)
128
+
129
+ * R2P2 (Unix-like shell of PicoRuby)
130
+ * PicoIRB
131
+ * require 'sqilte3'
132
+ * db = SQLite3::database.new 'test.db'
133
+ * Interoperable database file between the laptop and the microcontroller
134
+
135
+ = Building an SQLite3 shared library
136
+
137
+ #enscript bash
138
+ # Download and unzip sqlite3.c and sqlite3.h
139
+ # Then,
140
+ # Typical build on Unix systems
141
+ gcc -shared -fPIC sqlite3.c -o libsqlite3.so
142
+
143
+ # On Windows (Microsoft Visual C++)
144
+ cl sqlite3.c -link -dll -out:sqlite3.dll
145
+
146
+ (('tag:center'))
147
+ (('tag:x-large'))
148
+ Easy-peasy🍋
149
+
150
+ = Compile-time options
151
+
152
+ # enscript console
153
+ # Platform Configuration SQLITE_MAX_LENGTH SQLITE_ENABLE_SESSION SQLITE_OMIT_FOREIGN_KEY
154
+ _HAVE_SQLITE_CONFIG_H SQLITE_MAX_LIKE_PATTERN_LENGTH SQLITE_ENABLE_SNAPSHOT SQLITE_OMIT_GENERATED_COLUMNS
155
+ HAVE_FDATASYNC SQLITE_MAX_PAGE_COUNT SQLITE_ENABLE_SORTER_REFERENCES SQLITE_OMIT_GET_TABLE
156
+ HAVE_GMTIME_R SQLITE_MAX_SQL_LENGTH SQLITE_ENABLE_STMT_SCANSTATUS SQLITE_OMIT_HEX_INTEGER
157
+ HAVE_ISNAN SQLITE_MAX_VARIABLE_NUMBER SQLITE_ENABLE_STMTVTAB SQLITE_OMIT_INCRBLOB
158
+ HAVE_LOCALTIME_R SQLITE_RTREE_INT_ONLY SQLITE_OMIT_INTEGRITY_CHECK
159
+ HAVE_LOCALTIME_S # Options To Control Operating Characteristics SQLITE_ENABLE_SQLLOG SQLITE_OMIT_INTROSPECTION_PRAGMAS
160
+ HAVE_MALLOC_USABLE_SIZE SQLITE_4_BYTE_ALIGNED_MALLOC SQLITE_ENABLE_STAT2 SQLITE_OMIT_JSON
161
+ HAVE_STRCHRNUL SQLITE_CASE_SENSITIVE_LIKE SQLITE_ENABLE_STAT3 SQLITE_OMIT_LIKE_OPTIMIZATION
162
+ HAVE_UTIME SQLITE_DIRECT_OVERFLOW_READ SQLITE_ENABLE_STAT4 SQLITE_OMIT_LOAD_EXTENSION
163
+ SQLITE_BYTEORDER=(0|1234|4321) SQLITE_HAVE_ISNAN SQLITE_ENABLE_TREE_EXPLAIN SQLITE_OMIT_LOCALTIME
164
+ SQLITE_MAX_ALLOCATION_SIZE=N SQLITE_ENABLE_UPDATE_DELETE_LIMIT SQLITE_OMIT_LOOKASIDE
165
+ # Options To Set Default Parameter Values SQLITE_OS_OTHER=<0 or 1> SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION SQLITE_OMIT_MEMORYDB
166
+ SQLITE_DEFAULT_AUTOMATIC_INDEX=<0 or 1> SQLITE_SECURE_DELETE SQLITE_ENABLE_UNLOCK_NOTIFY SQLITE_OMIT_OR_OPTIMIZATION
167
+ See also: SQLITE_OMIT_AUTOMATIC_INDEX SQLITE_THREADSAFE=<0 or 1 or 2> SQLITE_INTROSPECTION_PRAGMAS SQLITE_OMIT_PAGER_PRAGMAS
168
+ SQLITE_DEFAULT_AUTOVACUUM=<0 or 1 or 2> SQLITE_CONFIG_SINGLETHREAD SQLITE_SOUNDEX SQLITE_OMIT_PRAGMA
169
+ SQLITE_DEFAULT_CACHE_SIZE=<N> SQLITE_CONFIG_MULTITHREAD SQLITE_USE_ALLOCA SQLITE_OMIT_PROGRESS_CALLBACK
170
+ SQLITE_DEFAULT_FILE_FORMAT=<1 or 4> SQLITE_CONFIG_SERIALIZED SQLITE_USE_FCNTL_TRACE SQLITE_OMIT_QUICKBALANCE
171
+ SQLITE_DEFAULT_FILE_PERMISSIONS=N SQLITE_TEMP_STORE=<0 through 3> SQLITE_HAVE_ZLIB SQLITE_OMIT_REINDEX
172
+ SQLITE_DEFAULT_FOREIGN_KEYS=<0 or 1> SQLITE_TRACE_SIZE_LIMIT=N YYTRACKMAXSTACKDEPTH SQLITE_OMIT_SCHEMA_PRAGMAS
173
+ SQLITE_DEFAULT_MMAP_SIZE=N SQLITE_TRUSTED_SCHEMA=<0 or 1> SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
174
+ SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=<bytes> SQLITE_USE_URI # Options To Disable Features Normally Turned On SQLITE_OMIT_SHARED_CACHE
175
+ SQLITE_DEFAULT_LOCKING_MODE=<1 or 0> SQLITE_DISABLE_LFS SQLITE_OMIT_SUBQUERY
176
+ SQLITE_DEFAULT_LOOKASIDE=SZ,N # Options To Enable Features Normally Turned Off SQLITE_DISABLE_DIRSYNC SQLITE_OMIT_TCL_VARIABLE
177
+ SQLITE_DEFAULT_MEMSTATUS=<1 or 0> SQLITE_ALLOW_URI_AUTHORITY SQLITE_DISABLE_FTS3_UNICODE SQLITE_OMIT_TEMPDB
178
+ SQLITE_DEFAULT_PCACHE_INITSZ=N SQLITE_ALLOW_COVERING_INDEX_SCAN=<0 or 1> SQLITE_DISABLE_FTS4_DEFERRED SQLITE_OMIT_TRACE
179
+ SQLITE_DEFAULT_PAGE_SIZE=<bytes> SQLITE_ENABLE_8_3_NAMES=<1 or 2> SQLITE_DISABLE_INTRINSIC SQLITE_OMIT_TRIGGER
180
+ SQLITE_DEFAULT_SYNCHRONOUS=<0-3> SQLITE_ENABLE_API_ARMOR SQLITE_OMIT_TRUNCATE_OPTIMIZATION
181
+ SQLITE_DEFAULT_WAL_SYNCHRONOUS=<0-3> SQLITE_ENABLE_ATOMIC_WRITE # Options To Omit Features SQLITE_OMIT_UTF16
182
+ SQLITE_DEFAULT_WAL_AUTOCHECKPOINT=<pages> SQLITE_ENABLE_BATCH_ATOMIC_WRITE SQLITE_OMIT_ALTERTABLE SQLITE_OMIT_VACUUM
183
+ SQLITE_DEFAULT_WORKER_THREADS=N SQLITE_ENABLE_BYTECODE_VTAB SQLITE_OMIT_ANALYZE SQLITE_OMIT_VIEW
184
+ SQLITE_DQS=N SQLITE_ENABLE_COLUMN_METADATA SQLITE_OMIT_ATTACH SQLITE_OMIT_VIRTUALTABLE
185
+ SQLITE_EXTRA_DURABLE SQLITE_ENABLE_DBPAGE_VTAB SQLITE_OMIT_AUTHORIZATION SQLITE_OMIT_WAL
186
+ SQLITE_FTS3_MAX_EXPR_DEPTH=N SQLITE_ENABLE_DBSTAT_VTAB SQLITE_OMIT_AUTOINCREMENT SQLITE_OMIT_WINDOWFUNC
187
+ SQLITE_LIKE_DOESNT_MATCH_BLOBS SQLITE_ENABLE_DESERIALIZE SQLITE_OMIT_AUTOINIT SQLITE_OMIT_WSD
188
+ SQLITE_MAX_MEMORY=N SQLITE_ENABLE_EXPLAIN_COMMENTS SQLITE_OMIT_AUTOMATIC_INDEX SQLITE_OMIT_XFER_OPT
189
+ SQLITE_MAX_MMAP_SIZE=N SQLITE_ENABLE_FTS3 SQLITE_OMIT_AUTORESET SQLITE_UNTESTABLE
190
+ SQLITE_MAX_SCHEMA_RETRY=N SQLITE_ENABLE_FTS3_PARENTHESIS SQLITE_OMIT_AUTOVACUUM SQLITE_ZERO_MALLOC
191
+ SQLITE_MAX_WORKER_THREADS=N SQLITE_ENABLE_FTS3_TOKENIZER SQLITE_OMIT_BETWEEN_OPTIMIZATION
192
+ SQLITE_MEMDB_DEFAULT_MAXSIZE=N SQLITE_ENABLE_FTS4 SQLITE_OMIT_BLOB_LITERAL # Analysis and Debugging Options
193
+ SQLITE_MINIMUM_FILE_DESCRIPTOR=N SQLITE_ENABLE_FTS5 SQLITE_OMIT_BTREECOUNT SQLITE_DEBUG
194
+ SQLITE_POWERSAFE_OVERWRITE=<0 or 1> SQLITE_ENABLE_GEOPOLY SQLITE_OMIT_BUILTIN_TEST SQLITE_MEMDEBUG
195
+ SQLITE_PRINTF_PRECISION_LIMIT=N SQLITE_ENABLE_ICU SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA
196
+ SQLITE_QUERY_PLANNER_LIMIT=N SQLITE_ENABLE_IOTRACE SQLITE_OMIT_CAST # Windows-Specific Options
197
+ SQLITE_QUERY_PLANNER_LIMIT_INCR=N SQLITE_ENABLE_MATH_FUNCTIONS SQLITE_OMIT_CHECK SQLITE_WIN32_HEAP_CREATE
198
+ SQLITE_REVERSE_UNORDERED_SELECTS SQLITE_ENABLE_JSON1 SQLITE_OMIT_COMPILEOPTION_DIAGS SQLITE_WIN32_MALLOC_VALIDATE
199
+ SQLITE_SORTER_PMASZ=N SQLITE_ENABLE_LOCKING_STYLE SQLITE_OMIT_COMPLETE
200
+ SQLITE_STMTJRNL_SPILL=N SQLITE_ENABLE_MEMORY_MANAGEMENT SQLITE_OMIT_COMPOUND_SELECT # Compiler Linkage and Calling Convention Control
201
+ SQLITE_WIN32_MALLOC SQLITE_ENABLE_MEMSYS3 SQLITE_OMIT_CTE SQLITE_API
202
+ YYSTACKDEPTH=<max_depth> SQLITE_ENABLE_MEMSYS5 SQLITE_OMIT_DATETIME_FUNCS SQLITE_APICALL
203
+ SQLITE_ENABLE_NORMALIZE SQLITE_OMIT_DECLTYPE SQLITE_CALLBACK
204
+ # Options To Set Size Limits SQLITE_ENABLE_NULL_TRIM SQLITE_OMIT_DEPRECATED SQLITE_CDECL
205
+ SQLITE_MAX_ATTACHED SQLITE_ENABLE_OFFSET_SQL_FUNC SQLITE_OMIT_DESERIALIZE SQLITE_EXTERN
206
+ SQLITE_MAX_COLUMN SQLITE_ENABLE_PREUPDATE_HOOK SQLITE_OMIT_DISKIO SQLITE_STDCALL
207
+ SQLITE_MAX_COMPOUND_SELECT SQLITE_ENABLE_QPSG SQLITE_OMIT_EXPLAIN SQLITE_SYSAPI
208
+ SQLITE_MAX_EXPR_DEPTH SQLITE_ENABLE_RBU SQLITE_OMIT_FLAG_PRAGMAS SQLITE_TCLAPI
209
+ SQLITE_MAX_FUNCTION_ARG SQLITE_ENABLE_RTREE SQLITE_OMIT_FLOATING_POINT
210
+
211
+ (('tag:center'))
212
+ (('tag:xx-small:https://sqlite.org/compile.html'))
213
+
214
+ = Compile-time options
215
+
216
+ SQLITE_OS_OTHER=<0 or 1> (default: 0)
217
+
218
+ * If the OS is other than Unix, Windows or OS/2,
219
+ * You'll specify `SQLITE_OS_OTHER=1`,
220
+ * Then you must provide:
221
+ * int sqlite3_os_init(void);
222
+ * int sqlite3_os_end(void);
223
+
224
+
225
+ = int sqlite3_os_init(void);
226
+
227
+ # enscript c
228
+ sqlite3_mem_methods my_mem_methods = {...}
229
+ sqlite3_vfs my_vfs = {...}
230
+ /*
231
+ * Typical implementation
232
+ */
233
+ int
234
+ sqlite3_os_init(void)
235
+ {
236
+ sqlite3_config(SQLITE_CONFIG_MALLOC, &my_mem_methods);
237
+ sqlite3_initialize();
238
+ return sqlite3_vfs_register(&my_vfs, 1);
239
+ }
240
+
241
+ = When a Ruby app opens a DB
242
+
243
+ # image
244
+ # src = images/transparent.png
245
+ # relative-height = 94
246
+ # relative_margin_top = 2
247
+ # draw97 = [rectangle, true, 0.08, 0.0, 0.4, 0.16, {color: red}]
248
+ # draw98 = [text, SQLite3::Database.open, 0.1, 0.04, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
249
+ # draw00 = [rectangle, true, 0.08, 0.0, 0.4, 0.40, {color: black}]
250
+ # draw01 = [rectangle, true, 0.08, 0.40, 0.4, 0.32, {color: blue}]
251
+ # draw03 = [rectangle, true, 0.08, 0.64, 0.4, 0.12, {color: purple}]
252
+ # draw04 = [rectangle, true, 0.08, 0.76, 0.4, 0.12, {color: gray}]
253
+ # draw05 = [rectangle, true, 0.08, 0.88, 0.4, 0.12, {color: brown}]
254
+ # draw10 = [text, CRuby SQLite3 module, 0.09, 0.18, {color: white, size: 45, font_family: 'Courier Prime', weight: normal}]
255
+ # draw115= [text, sqlite3_open_v2(3), 0.10, 0.29, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
256
+ # draw12 = [text, libsqlite3, 0.18, 0.44, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
257
+ # draw13 = [text, open(2), 0.22, 0.54, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
258
+ # draw14 = [text, System call interface, 0.11, 0.67, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
259
+ # draw15 = [text, Device Driver, 0.17, 0.78, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
260
+ # draw16 = [text, Hardware(HDD/SDD), 0.11, 0.89, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
261
+ # draw94 = [rectangle, true, 0.52, 0.0, 0.4, 0.16, {color: red}]
262
+ # draw95 = [text, SQLite3::Database.open, 0.53, 0.04, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
263
+ # draw20 = [rectangle, true, 0.52, 0.0, 0.4, 0.8, {color: black}]
264
+ # draw24 = [rectangle, true, 0.52, 0.8, 0.4, 0.20, {color: brown}]
265
+ # draw30 = [text, PicoRuby SQLite3 class, 0.53, 0.18, {color: white, size: 42, font_family: 'Courier Prime', weight: normal}]
266
+ # draw31 = [text, sqlite3_open_v2(3), 0.54, 0.29, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
267
+ # draw34 = [text, (Ruby)File.open, 0.56, 0.54, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
268
+ # draw35 = [text, PicoRuby File class, 0.53, 0.66, {color: white, size: 47, font_family: 'Courier Prime', weight: normal}]
269
+ # draw36 = [text, Hardware(SD card), 0.55, 0.85, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
270
+
271
+ = When a Ruby app opens a DB
272
+
273
+ # image
274
+ # src = images/transparent.png
275
+ # relative-height = 94
276
+ # relative_margin_top = 2
277
+ # draw97 = [rectangle, true, 0.08, 0.0, 0.4, 0.16, {color: red}]
278
+ # draw98 = [text, SQLite3::Database.open, 0.1, 0.04, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
279
+ # draw00 = [rectangle, true, 0.08, 0.0, 0.4, 0.40, {color: black}]
280
+ # draw01 = [rectangle, true, 0.08, 0.40, 0.4, 0.32, {color: blue}]
281
+ # draw03 = [rectangle, true, 0.08, 0.64, 0.4, 0.12, {color: purple}]
282
+ # draw04 = [rectangle, true, 0.08, 0.76, 0.4, 0.12, {color: gray}]
283
+ # draw05 = [rectangle, true, 0.08, 0.88, 0.4, 0.12, {color: brown}]
284
+ # draw10 = [text, CRuby SQLite3 module, 0.09, 0.18, {color: white, size: 45, font_family: 'Courier Prime', weight: normal}]
285
+ # draw115= [text, sqlite3_open_v2(3), 0.10, 0.29, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
286
+ # draw12 = [text, libsqlite3, 0.18, 0.44, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
287
+ # draw13 = [text, open(2), 0.22, 0.54, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
288
+ # draw14 = [text, System call interface, 0.11, 0.67, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
289
+ # draw15 = [text, Device Driver, 0.17, 0.78, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
290
+ # draw16 = [text, Hardware(HDD/SDD), 0.11, 0.89, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
291
+ # draw94 = [rectangle, true, 0.52, 0.0, 0.4, 0.16, {color: red}]
292
+ # draw95 = [text, SQLite3::Database.open, 0.53, 0.04, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
293
+ # draw20 = [rectangle, true, 0.52, 0.0, 0.4, 0.8, {color: black}]
294
+ # draw24 = [rectangle, true, 0.52, 0.8, 0.4, 0.20, {color: brown}]
295
+ # draw30 = [text, PicoRuby SQLite3 class, 0.53, 0.18, {color: white, size: 42, font_family: 'Courier Prime', weight: normal}]
296
+ # draw31 = [text, sqlite3_open_v2(3), 0.54, 0.29, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
297
+ # draw34 = [text, (Ruby)File.open, 0.56, 0.54, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
298
+ # draw35 = [text, PicoRuby File class, 0.53, 0.66, {color: white, size: 47, font_family: 'Courier Prime', weight: normal}]
299
+ # draw36 = [text, Hardware(SD card), 0.55, 0.85, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
300
+ # draw86 = [text, "WHAT HAPPENS HERE?", 0.49, 0.40, {color: red, size: 55, font_family: 'Montserrat', weight: bold}]
301
+ # draw88= [rectangle, false, 0.485, 0.28, 0.49, 0.36, {color: red, line_width: 10}]
302
+
303
+ = SQLite3 needs VFS
304
+
305
+ # image
306
+ # src = images/transparent.png
307
+ # relative-height = 94
308
+ # relative_margin_top = 2
309
+ # draw97 = [rectangle, true, 0.08, 0.0, 0.4, 0.16, {color: red}]
310
+ # draw98 = [text, SQLite3::Database.open, 0.1, 0.04, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
311
+ # draw00 = [rectangle, true, 0.08, 0.0, 0.4, 0.40, {color: black}]
312
+ # draw01 = [rectangle, true, 0.08, 0.40, 0.4, 0.32, {color: blue}]
313
+ # draw03 = [rectangle, true, 0.08, 0.64, 0.4, 0.12, {color: purple}]
314
+ # draw04 = [rectangle, true, 0.08, 0.76, 0.4, 0.12, {color: gray}]
315
+ # draw05 = [rectangle, true, 0.08, 0.88, 0.4, 0.12, {color: brown}]
316
+ # draw10 = [text, CRuby SQLite3 module, 0.09, 0.18, {color: white, size: 45, font_family: 'Courier Prime', weight: normal}]
317
+ # draw115= [text, sqlite3_open_v2(3), 0.10, 0.29, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
318
+ # draw12 = [text, libsqlite3, 0.18, 0.44, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
319
+ # draw13 = [text, open(2), 0.22, 0.54, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
320
+ # draw14 = [text, System call interface, 0.11, 0.67, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
321
+ # draw15 = [text, Device Driver, 0.17, 0.78, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
322
+ # draw16 = [text, Hardware(HDD/SDD), 0.11, 0.89, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
323
+ # draw94 = [rectangle, true, 0.52, 0.0, 0.4, 0.16, {color: red}]
324
+ # draw95 = [text, SQLite3::Database.open, 0.53, 0.04, {color: white, size: 40, font_family: 'Courier Prime', weight: normal}]
325
+ # draw20 = [rectangle, true, 0.52, 0.0, 0.4, 0.8, {color: black}]
326
+ # draw24 = [rectangle, true, 0.52, 0.8, 0.4, 0.20, {color: brown}]
327
+ # draw30 = [text, PicoRuby SQLite3 class, 0.53, 0.18, {color: white, size: 42, font_family: 'Courier Prime', weight: normal}]
328
+ # draw31 = [text, sqlite3_open_v2(3), 0.54, 0.29, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
329
+ # draw34 = [text, (Ruby)File.open, 0.56, 0.54, {color: yellow, size: 50, font_family: 'Courier Prime', weight: bold}]
330
+ # draw35 = [text, PicoRuby File class, 0.53, 0.66, {color: white, size: 47, font_family: 'Courier Prime', weight: normal}]
331
+ # draw36 = [text, Hardware(SD card), 0.55, 0.85, {color: white, size: 50, font_family: 'Courier Prime', weight: normal}]
332
+ # draw76 = [text, "VFS", 0.45, 0.54, {color: red, size: 75, font_family: 'Montserrat', weight: bold}]
333
+ # draw78= [rectangle, false, 0.05, 0.51, 0.9, 0.25, {color: red, line_width: 10}]
334
+
335
+ = VFS (Virtual FileSystem) in SQLite3
336
+
337
+ # image
338
+ # src = images/vfs1.gif
339
+ # relative-height = 94
340
+ # relative_margin_top = 2
341
+ # relative_margin_left = 10
342
+ # align = right
343
+
344
+ * “The module at the bottom\nof the SQLite implementation\nstack that provides portability\nacross operating systems.”
345
+ * “ (...) Hence, porting SQLite to\na new operating system is\nsimply a matter of writing a\nnew OS interface layer or\n"VFS".”
346
+
347
+ (('tag:xx-small'))(('tag:center'))
348
+ https://www.sqlite.org/vfs.html
349
+
350
+ = Structs to be prepared
351
+
352
+ * sqlite3_mem_methods
353
+ * Pointers to functions to manage ((*memory*))
354
+ * sqlite3_vfs
355
+ * Pointers to functions of ((*filesystem*)) management,\n((*utility*)) functions and ((*global app data*))
356
+ * sqlite3_io_methods
357
+ * Pointers to functions to handle ((*an open file*))
358
+ * sqlite3_file (mentioned later)
359
+
360
+ = struct sqlite3_mem_methods
361
+
362
+ # enscript c
363
+ struct sqlite3_mem_methods {
364
+ void *(*xMalloc)(int); /* Memory allocation function */
365
+ void (*xFree)(void*); /* Free a prior allocation */
366
+ void *(*xRealloc)(void*,int); /* Resize an allocation */
367
+ int (*xSize)(void*); /* Return the size of an allocation */
368
+ int (*xRoundup)(int); /* Round up request size to allocation size */
369
+ int (*xInit)(void*); /* Initialize the memory allocator */
370
+ void (*xShutdown)(void*); /* Deinitialize the memory allocator */
371
+ void *pAppData; /* Argument to xInit() and xShutdown() */
372
+ };
373
+
374
+ = struct sqlite3_vfs
375
+
376
+ # enscript c
377
+ struct sqlite3_vfs {
378
+ int iVersion; /* Structure version number (currently 3) */
379
+ int szOsFile; /* Size of subclassed sqlite3_file */
380
+ int mxPathname; /* Maximum file pathname length */
381
+ sqlite3_vfs *pNext; /* Next registered VFS */
382
+ const char *zName; /* Name of this virtual file system */
383
+ void *pAppData; /* Pointer to application-specific data */
384
+ int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*,
385
+ int flags, int *pOutFlags);
386
+ int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
387
+ int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
388
+ int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
389
+ void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
390
+ void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
391
+ void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void);
392
+ void (*xDlClose)(sqlite3_vfs*, void*);
393
+ int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
394
+ int (*xSleep)(sqlite3_vfs*, int microseconds);
395
+ int (*xCurrentTime)(sqlite3_vfs*, double*);
396
+ int (*xGetLastError)(sqlite3_vfs*, int, char *);
397
+ int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
398
+ int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
399
+ sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName);
400
+ const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
401
+ };
402
+
403
+ = struct sqlite3_io_methods
404
+
405
+ # enscript c
406
+ struct sqlite3_io_methods {
407
+ int iVersion;
408
+ int (*xClose)(sqlite3_file*);
409
+ int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
410
+ int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
411
+ int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
412
+ int (*xSync)(sqlite3_file*, int flags);
413
+ int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
414
+ int (*xLock)(sqlite3_file*, int);
415
+ int (*xUnlock)(sqlite3_file*, int);
416
+ int (*xCheckReservedLock)(sqlite3_file*, int *pResOut);
417
+ int (*xFileControl)(sqlite3_file*, int op, void *pArg);
418
+ int (*xSectorSize)(sqlite3_file*);
419
+ int (*xDeviceCharacteristics)(sqlite3_file*);
420
+ int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
421
+ int (*xShmLock)(sqlite3_file*, int offset, int n, int flags);
422
+ void (*xShmBarrier)(sqlite3_file*);
423
+ int (*xShmUnmap)(sqlite3_file*, int deleteFlag);
424
+ int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
425
+ int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void *p);
426
+ };
427
+
428
+ = struct sqlite3_file
429
+
430
+ # enscript c
431
+ struct sqlite3_file { /* Defined in sqlite3.h */
432
+ const struct sqlite3_io_methods *pMethods; /* Methods for an open file */
433
+ };
434
+
435
+ struct PRBFile { /* Defined in PicoRuby */
436
+ sqlite3_file base;
437
+ mrbc_vm *vm;
438
+ mrbc_value *file;
439
+ char pathname[PATHNAME_MAX_LEN];
440
+ int sector_size;
441
+ };
442
+
443
+ PRBFile *prbfile = (PRBFile *)pFile;
444
+
445
+ (('tag:center'))A trick to let SQLite3 carry Ruby
446
+
447
+ = prbVFSOpen is called to open DB
448
+
449
+ # enscript c
450
+ sqlite3_vfs prb_vfs = {
451
+ ...
452
+ sizeof(PRBFile), /* szOsFile. SQLite3 knows the size of PRBFile👀 */
453
+ ...
454
+ vm, /* pAppData -> PicoRuby's VM */
455
+ prbVFSOpen, /* xOpen */
456
+ ...
457
+ }
458
+
459
+ int prbVFSOpen(sqlite3_vfs *pVfs, const char *zName,
460
+ sqlite3_file *pFile, int flags, int *pOutFlags) {
461
+ PRBFile *prbfile = (PRBFile *)pFile; /* Cast sqlite3_file to PRBFile */
462
+ prbfile->vm = prb_vfs.pAppData; /* prb_vfs.pAppData points to VM */
463
+ pFile->pMethods = &prb_io_methods; /* Attach IO mehtods to the file */
464
+ prb_file_new(prbfile, zName, flags); /* Wrapper of Ruby's File.new */
465
+ return SQLITE_OK;
466
+ }
467
+
468
+ = prb_file_new == File.new
469
+
470
+ # enscript c
471
+ int prb_file_new(PRBFile *prbfile, const char *zName, int flags) {
472
+ mrbc_vm *vm = (mrbc_vm *)prbfile->vm;
473
+ mrbc_value v[3];
474
+ v[0] = mrbc_nil_value();
475
+ v[1] = mrbc_string_new_cstr(vm, zName);
476
+ char *mode = ... /* "r", "w", "w+", etc. from flags */
477
+ v[2] = mrbc_string_new_cstr(vm, mode);
478
+ prb_funcall(vfs_methods.file_new, &v[0], 2);
479
+ mrbc_decref(&v[1]);
480
+ mrbc_decref(&v[2]);
481
+ prbfile->file = mrbc_alloc(vm, sizeof(mrbc_value));
482
+ memcpy(prbfile->file, &v[0], sizeof(mrbc_value)); /* File Object */
483
+ return OK;
484
+ }
485
+
486
+ (('tag:center'))
487
+ ((*prb_funcall(vfs_methods.file_new, &v[0], 2)*))🤔
488
+
489
+ = prbIORead is called to read DB
490
+
491
+ # enscript c
492
+ sqlite3_io_methods prb_io_methods = {
493
+ ...
494
+ prbIORead, /* xRead */
495
+ ...
496
+ }
497
+
498
+ int prbIORead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite3_int64 iOfst) {
499
+ PRBFile *prbfile = (PRBFile *)pFile;
500
+ prb_file_read(prbfile, zBuf, iAmt); /* Wrapper of Ruby's File#read */
501
+ return SQLITE_OK;
502
+ }
503
+
504
+ = prb_file_read == File#read
505
+
506
+ # enscript c
507
+ int prb_file_read(PRBFile *prbfile, void *zBuf, size_t nBuf) {
508
+ mrbc_value v[2];
509
+ v[0] = *prbfile->file;
510
+ v[1] = mrbc_integer_value(nBuf);
511
+ prb_funcall(vfs_methods.file_read, &v[0], 1);
512
+ size_t retSize = v[0].string->size;
513
+ memcpy(zBuf, v[0].string->data, retSize);
514
+ mrbc_decref(&v[0]);
515
+ return retSize;
516
+ }
517
+
518
+ (('tag:center'))
519
+ ((*prb_funcall(vfs_methods.file_read, &v[0], 1)*))🤔
520
+
521
+ = prb_funcall
522
+
523
+ # enscript c
524
+ void prb_funcall(
525
+ void (*func)(mrbc_vm *, mrbc_value *, int),
526
+ mrbc_value *v, int argc
527
+ )
528
+ {
529
+ mrbc_incref(&v[0]);
530
+ func(prbvfs.pAppData, &v[0], argc);
531
+ }
532
+
533
+ (('tag:center'))
534
+ Takes a pointer to a C function representing a Ruby method.
535
+ \n
536
+ But how does SQLite3 class know File methods?
537
+
538
+ = File::VFS class exposes methods😈
539
+
540
+ # enscript c
541
+ void FileVFS_vfs_methods(mrbc_vm *vm, mrbc_value v[], int argc)
542
+ {
543
+ prb_vfs_methods vfs_methods = {
544
+ File_new,
545
+ File_close,
546
+ File_read,
547
+ ...
548
+ };
549
+ mrbc_value methods = mrbc_instance_new(
550
+ vm, class_FAT_VFSMethods, sizeof(prb_vfs_methods)
551
+ );
552
+ memcpy(methods.instance->data, &vfs_methods, sizeof(prb_vfs_methods));
553
+ SET_RETURN(methods);
554
+ }
555
+ mrbc_define_method(0, class_FAT, "vfs_methods", FileVFS_vfs_methods);
556
+
557
+ (('tag:center'))
558
+ ((*SQLite3.vfs_methods = File::VFS.vfs_methods*))
559
+
560
+ = From the point of Ruby's view
561
+
562
+ # enscript ruby
563
+
564
+ SQLite3.vfs_methods = File::VFS.vfs_methods
565
+ # Pointers to File methods are told to SQLite3
566
+
567
+ db = SQLite3::Database.new("database.db")
568
+ # sqlite3_os_init() -> sqlite3_vfs_register()
569
+ # sqlite3_open_v2() -> prbVFSOpen() -> prb_file_new() -> File.new
570
+
571
+ db.prepare "SELECT * FROM table;"
572
+ # sqlite3_prepare_v2() -> prbIORead() -> prb_file_read() -> File#read
573
+
574
+ (('tag:center'))
575
+ SQLite3 (Ruby) -> SQLite3 (C) -> File (Ruby)
576
+ \n
577
+ In bare-metal (no-OS) PicoRuby,
578
+ \n
579
+ methods of File class work as system call
580
+
581
+ = chapter
582
+ (('tag:xx-large:We made it🎉'))
583
+ == prop
584
+ : hide-title
585
+ true
586
+
587
+ = SQLite3 in Micon for what? Guess...
588
+
589
+ * IoT, General electrical appliance
590
+ * Store structured sensor data (in case no network)
591
+ * Log firmware update history
592
+ * Store, backup and share configuration
593
+ * Embedded in-memory-database
594
+ * (CRuby(Ractor[PicoRuby(in-memory-SQLite3) * n]))
595
+
596
+ = App for PRK Firmware
597
+
598
+ # image
599
+ # src = images/GPK60-46W_top.jpg
600
+ # relative-height = 100
601
+
602
+ = App for PRK Firmware
603
+
604
+ # image
605
+ # src = images/GPK60-46W.jpg
606
+ # relative-height = 100
607
+ # draw0 = [text, SD card, 0.61, 0.62, {color: white, size: 40, font_family: 'Courier Prime', weight: bold}]
608
+ # draw1 = [text, RTC, 0.18, 0.70, {color: white, size: 40, font_family: 'Courier Prime', weight: bold}]
609
+ # draw2 = [text, RP2040, 0.45, 0.49, {color: white, size: 40, font_family: 'Courier Prime', weight: bold}]
610
+ # draw3 = [text, Trackball,0.43, 0.15, {color: white, size: 40, font_family: 'Courier Prime', weight: bold}]
611
+
612
+ = chapter
613
+ (('tag:xx-large:Make a better keymap'))
614
+ == prop
615
+ : hide-title
616
+ true
617
+
618
+ = Make a better keymap
619
+
620
+ * On the keyboard,
621
+ * Log every press of the alphabet key with a timestamp
622
+ * Exclude consecutive same key
623
+ * On the laptop/desktop,
624
+ * Analyze the data, then make a better keymap
625
+
626
+ = keymap.rb (1/2)
627
+
628
+ # enscript Ruby
629
+ begin
630
+ dbfile = "/keylog.db"
631
+ jounalfile = "/keylog.db-journal"
632
+ if File.exist?(dbfile)
633
+ File.rename(dbfile, "/keylog-#{Time.now.to_i}.db")
634
+ File.unlink(jounalfile) if File.exist?(jounalfile)
635
+ end
636
+ sqlite3 = SQLite3::Database.new(dbfile)
637
+ sql = "CREATE TABLE IF NOT EXISTS keylog (
638
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
639
+ key TEXT, unixtime REAL);"
640
+ sqlite3.execute(sql)
641
+ sqlite3_stmt = sqlite3.prepare(
642
+ "INSERT INTO keylog (key, unixtime) VALUES (?, ?);")
643
+ rescue => e
644
+ puts "Not available"
645
+ puts "#{e.message} (#{e.class})"
646
+ end
647
+
648
+ = keymap.rb (2/2)
649
+
650
+ # enscript Ruby
651
+
652
+ kbd = Keyboard.new
653
+ last_keycode = nil
654
+ kbd.before_report do |keyboard|
655
+ # KC_A..KC_Z == 4..29
656
+ if (keycode = keyboard.keycodes[0]) && (keycode < 30)
657
+ next if last_keycode == keycode
658
+ last_keycode = keycode
659
+ key = (keycode + 61).chr # (4 + 61).chr => "A"
660
+ puts "key: #{key}"
661
+ begin
662
+ sqlite3_stmt&.execute(key, Time.now.to_f)
663
+ rescue => e
664
+ puts "SQLite3 error: #{e.message} (#{e.class})"
665
+ end
666
+ end
667
+ end
668
+
669
+ = keylog.db looks like
670
+
671
+ # enscript ruby
672
+ [[ 1, "H", 1683536977.633],
673
+ [ 2, "E", 1683536977.8],
674
+ [ 3, "L", 1683536977.968],
675
+ [ 4, "O", 1683536978.696],
676
+ [ 5, "W", 1683536979.16],
677
+ [ 6, "O", 1683536979.24],
678
+ [ 7, "R", 1683536979.456,
679
+ [ 8, "L", 1683536980.008,
680
+ [ 9, "D", 1683536980.984],
681
+ [10, "R", 1683536981.616],
682
+ [11, "U", 1683536983.480],
683
+ [12, "B", 1683536983.607],
684
+ [13, "Y", 1683536984.18],
685
+ (...array goes...)
686
+
687
+ = Analysis strategy
688
+
689
+ * Order by "most hit key"
690
+ * Order by "key combination most frequently hit in succession"
691
+ * Place each character of the combination on opposite sides of the keyboard
692
+ \nEg: If "RUBY" is the most hit word,
693
+ \n"RU" -> "R" for right, "U" for left
694
+ \n"BY" -> "B" for right, "Y" for left
695
+
696
+ = Copy keylog.db to laptop, then
697
+
698
+ # enscript ruby
699
+
700
+ db = SQLite3::Database.new('keylog.db')
701
+ db.results_as_hash = true
702
+ db.execute "CREATE TABLE IF NOT EXISTS key_interval
703
+ (id INTEGER PRIMARY KEY, prev_key TEXT, next_key TEXT, interval REAL);"
704
+ db.execute "DELETE FROM key_interval;"
705
+
706
+ stmt = db.prepare("INSERT INTO key_interval
707
+ (prev_key, next_key, interval) VALUES (?, ?, ?)")
708
+ prev_row = nil
709
+ db.execute("SELECT id, key, unixtime FROM keylog order by id") do |next_row|
710
+ if prev_row
711
+ interval = next_row['unixtime'] - prev_row['unixtime']
712
+ stmt.execute(prev_row['key'], next_row['key'], interval)
713
+ end
714
+ prev_row = next_row
715
+ end
716
+
717
+ (('tag:center'))
718
+ Make another table that consists of the time difference between adjoining keystrokes
719
+
720
+ = Prepare objects for result
721
+
722
+ # enscript ruby
723
+
724
+ ALPHABET = Hash.new.tap do |h|
725
+ ('A'..'Z').each { |c| h[c] = false }
726
+ end
727
+
728
+ COLUMN_SIZE = 5
729
+ ROW_SIZE = 3
730
+ KEYMAP_RIGHT = Array.new(COLUMN_SIZE).map do |colomn|
731
+ colum = Array.new(ROW_SIZE)
732
+ end
733
+ KEYMAP_LEFT = Array.new(COLUMN_SIZE).map do |colomn|
734
+ colum = Array.new(ROW_SIZE)
735
+ end
736
+
737
+ COLUMN_PRIORITY = [1, 2, 0, 3, 4]
738
+ ROW_PRIORITY = [1, 2, 0]
739
+
740
+ ASSIGN_ORDER = Array.new.tap do |a|
741
+ COLUMN_PRIORITY.each do |column|
742
+ ROW_PRIORITY.each do |row|
743
+ a << [column, row]
744
+ end
745
+ end
746
+ end
747
+
748
+ = Calculate a better keymap
749
+
750
+ # enscript ruby
751
+
752
+ right_num, left_num = 0, 0
753
+ db.execute("SELECT key, COUNT(*) AS count
754
+ FROM keylog GROUP BY key ORDER BY count DESC;") do |row|
755
+ key = row["key"]
756
+ next if ALPHABET[key]
757
+ db.execute("SELECT next_key, COUNT(*) AS count FROM key_interval
758
+ WHERE prev_key = '#{key}' AND interval < 1
759
+ GROUP BY next_key ORDER BY count DESC;") do |row|
760
+ right_x, right_y = ASSIGN_ORDER[right_num]
761
+ break if right_x.nil? || right_y.nil?
762
+ KEYMAP_RIGHT[right_x][right_y] = key
763
+ ALPHABET[key] = true
764
+ next_key = row["next_key"]
765
+ next if ALPHABET[next_key]
766
+ left_x, left_y = ASSIGN_ORDER[left_num]
767
+ left_num += 1
768
+ KEYMAP_LEFT[left_x][left_y] = next_key
769
+ ALPHABET[next_key] = true
770
+ break
771
+ end
772
+ right_num += 1
773
+ end
774
+ db.close
775
+
776
+ = Show the result on terminal
777
+
778
+ # enscript ruby
779
+ puts " LEFT HAND RIGHT HAND"
780
+ 0.upto(ROW_SIZE - 1) do |y|
781
+ (COLUMN_SIZE - 1).downto(0) do |x|
782
+ print "[#{KEYMAP_LEFT[x][y] || ' '}] "
783
+ end
784
+ print " "
785
+ 0.upto(COLUMN_SIZE - 1) do |x|
786
+ print "[#{KEYMAP_RIGHT[x][y] || ' '}] "
787
+ end
788
+ puts
789
+ end
790
+ FINGERS = %w(index middle ring pinky)
791
+ 0.upto(4) do |c|
792
+ FINGERS.reverse.each do |finger|
793
+ print " #{finger[c] || ' '} "
794
+ end
795
+ print " "
796
+ FINGERS.each do |finger|
797
+ print " #{finger[c] || ' '} "
798
+ end
799
+ puts
800
+ end
801
+
802
+ = The result🎊
803
+
804
+ # enscript bash
805
+ LEFT HAND RIGHT HAND
806
+ [ ] [ ] [V] [Y] [O] [D] [B] [E] [A] [F]
807
+ [ ] [M] [T] [K] [R] [P] [L] [I] [C] [N]
808
+ [ ] [Z] [W] [H] [U] [S] [J] [X] [G] [Q]
809
+ p r m i i m r p
810
+ i i i n n i i i
811
+ n n d d d d n n
812
+ k g d e e d g k
813
+ y l x x l y
814
+ e e
815
+
816
+ = The result🎊
817
+
818
+ # enscript bash
819
+ LEFT HAND RIGHT HAND
820
+ [ ] [ ] [V] [Y] [O] [D] [B] [E] [A] [F]
821
+ [ ] [M] [T] [K] [R] [P] [L] [I] [C] [N]
822
+ [ ] [Z] [W] [H] [U] [S] [J] [X] [G] [Q]
823
+ p r m ☝ ☝ m r p
824
+ i i i n H,J,K,L n i i i
825
+ n n d d 🤔 d d n n
826
+ k g d e e d g k
827
+ y l x x l y
828
+ e e
829
+
830
+ = ((* *))
831
+
832
+ # image
833
+ # src = images/initial-V.png
834
+ # relative-width = 100
835
+ # relative_margin_top = 5
836
+
837
+ (('tag:xx-small'))(('tag:center'))
838
+ https://github.com/tenderlove/initial-v
839
+
840
+ == prop
841
+ : hide-title
842
+ true
843
+
844
+ = chapter
845
+ (('tag:x-large'))
846
+ The cursor keys of Vim
847
+ \n
848
+ ((*H*)), ((*J*)), ((*K*)), ((*L*))
849
+ \n
850
+ occupy a prime location😂
851
+
852
+ == prop
853
+ : hide-title
854
+ true
855
+
856
+ = Database's ready, what's next?
857
+
858
+ * Improve shell🐚
859
+ * Network, Socket, TCP
860
+ * Bluetooth Low Energy🦷
861
+ * API documentation🔍
862
+ * Picoるりま(Ruby reference manual)
863
+ * Write a doujinshi (thin book)📖
864
+ * In English🇬🇧 🤔
865
+
866
+ # image
867
+ # src = images/picoruby.png
868
+ # align = right
869
+ # relative-height = 80
870
+ # relative_margin_top = -5
871
+ # relative_margin_left = 18
872
+
873
+ = Visit repos and stargaze🌟
874
+
875
+ (('tag:small'))
876
+ \n\n\n\n
877
+ github.com/picoruby/picoruby
878
+ \n\n
879
+ github.com/picoruby/prk_firmware
880
+
881
+ # image
882
+ # src = images/QR_github-com-picoruby.png
883
+ # align = right
884
+ # relative-height = 80
885
+ # relative_margin_top = 0
886
+ # relative_margin_left = 18
887
+
888
+ = chapter
889
+ (('tag:xx-large'))
890
+ ((*Thank you!!!!q*))
891
+ == prop
892
+ : hide-title
893
+ true