rabbit-slide-hasumikin-RubyKaigi2023 2022.05.13.0

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.
@@ -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