Compare commits

..

244 Commits
0.5.1 ... 0.8.9

Author SHA1 Message Date
419e7070ee more fixes to god of fixes 2021-12-27 17:02:00 +06:00
612cf40b5f small hotfix 2021-12-27 16:00:07 +06:00
8b39882e83 fixes in DefaultUpdatableStatesMachine 2021-12-27 15:57:55 +06:00
e639ae172b Fixes in uniloadMultipart 2021-12-27 15:55:05 +06:00
d0446850ae start 0.8.9 2021-12-27 15:45:44 +06:00
c48465b90b Merge pull request #119 from InsanusMokrassar/0.8.8
0.8.8
2021-12-26 22:11:04 +06:00
f419fd03d2 small fix of performing state update for UpdatableStatesMachine 2021-12-26 22:00:26 +06:00
494812a660 one more fix 2021-12-26 21:25:12 +06:00
eb78f21eec fix FSMBuilder 2021-12-26 21:21:40 +06:00
4bda70268b small fix of style in DefaultUpdatableStatesMachine 2021-12-26 21:07:40 +06:00
f037ce4371 updates in FSM 2021-12-26 21:06:26 +06:00
3d2196e35d update dependencies (which possible) 2021-12-26 20:07:13 +06:00
a74f061b02 start 0.8.8 2021-12-26 20:02:18 +06:00
11ade14676 Merge pull request #118 from InsanusMokrassar/0.8.7
0.8.7
2021-12-15 01:22:42 +06:00
eb562d8784 add stream using for multipart 2021-12-14 22:03:29 +06:00
1ee5b4bfd4 hotfix for multipart 2021-12-14 21:13:11 +06:00
d97892080b add preview work with multipart 2021-12-14 18:01:41 +06:00
6f37125724 start 0.8.7 and make UnifiedRequester/UnifiedRouter fields are public 2021-12-14 13:25:58 +06:00
ed1baaade7 Merge pull request #117 from InsanusMokrassar/0.8.6
0.8.6
2021-12-08 15:57:43 +06:00
bb9669f8fd fill changelog 2021-12-08 11:45:01 +06:00
bdac715d48 remove crossinline where possible 2021-12-08 11:41:49 +06:00
acf4971298 downgrade ktor 2021-12-08 11:36:24 +06:00
249bc83a8c update ktor 2021-11-27 16:22:13 +06:00
0fbb92f03f start 0.8.6 2021-11-27 16:20:56 +06:00
ca27cb3f82 Merge pull request #116 from InsanusMokrassar/0.8.5
0.8.5
2021-11-27 01:00:18 +06:00
3a5771a0cc Update RepeatOnFailure.kt 2021-11-26 23:07:00 +06:00
527a2a91ac repeatOnFailure 2021-11-26 19:45:59 +06:00
6763e5c4c6 start 0.8.5 2021-11-26 19:32:52 +06:00
06918d8310 add mime types generator python script 2021-11-24 14:31:24 +06:00
89ccaa1b57 Merge pull request #115 from InsanusMokrassar/0.8.4
0.8.4
2021-11-19 14:02:38 +06:00
5d0bdb9bcf fix pagination 2021-11-19 13:58:59 +06:00
31fdcf74a5 ktor server createKtorServer extensions 2021-11-19 13:24:45 +06:00
afca09cc1d start 0.8.4 2021-11-19 13:18:37 +06:00
531d89d9db Merge pull request #114 from InsanusMokrassar/0.8.3
0.8.3
2021-11-19 09:49:35 +06:00
6bbbea0bc3 suppressions in Optional.kt 2021-11-18 23:03:24 +06:00
e337cd98c8 optional workaround 2021-11-17 21:31:35 +06:00
bcbab3b380 add Optional type 2021-11-17 17:38:41 +06:00
fb63de7568 intersect 2021-11-17 14:22:45 +06:00
aa45a4ab13 now Pagination is an ClosedRange 2021-11-17 14:07:11 +06:00
2af7e2f681 start 0.8.3 2021-11-17 14:01:44 +06:00
34fd9edce0 Merge pull request #113 from InsanusMokrassar/0.8.2
0.8.2
2021-11-12 13:23:39 +06:00
2a4cb8c5f9 improvements in FSM 2021-11-12 13:19:15 +06:00
50ea40bc3a add changelog for dependencies update 2021-11-12 13:14:57 +06:00
a77654052d Update gradle.properties 2021-11-11 21:20:17 +06:00
88aafce552 start 0.8.2 2021-11-11 20:31:04 +06:00
4e95d6bfff Merge pull request #112 from InsanusMokrassar/0.8.1
0.8.1
2021-11-09 14:04:11 +06:00
38d0e34fb5 updates in scripts and update core ktx 2021-11-09 14:03:51 +06:00
8fbc6b9041 update changelog 2021-11-09 13:43:46 +06:00
e8219d6cf4 start 0.8.1 2021-11-09 10:21:23 +06:00
6c20fc4ca6 Merge pull request #111 from InsanusMokrassar/0.8.0
0.8.0
2021-11-05 21:56:00 +06:00
85cd975492 preparations for release 2021-11-05 21:45:01 +06:00
1171a717fe optimize imports 2021-11-05 15:49:39 +06:00
bbe5320312 rework of FSM + 0.8.0 2021-11-05 15:43:05 +06:00
00acb9fddd add suppresses to the generated classes 2021-11-03 17:22:32 +06:00
de3d14dc41 solution of #109 2021-11-03 15:36:25 +06:00
67ff9cc9b3 update dependencies 2021-11-03 15:18:41 +06:00
af132103a0 start 0.7.5 and add either serializer 2021-11-02 12:43:59 +06:00
3b1124a804 fixes 2021-10-30 12:17:18 +06:00
f226c2dfd6 Merge pull request #106 from InsanusMokrassar/0.7.4
0.7.4
2021-10-29 13:57:58 +06:00
69d6e63846 getAllBy* 2021-10-29 13:51:59 +06:00
02c3d397ad solution of #104 2021-10-29 13:40:53 +06:00
67a1050646 solution for #105 2021-10-28 18:46:53 +06:00
8cd0775a6c update kdocs of Either 2021-10-28 18:42:05 +06:00
162294d6c6 either 2021-10-28 18:02:02 +06:00
c4dd19dd00 start 0.7.4 2021-10-28 17:50:28 +06:00
d2314422f1 Merge pull request #103 from InsanusMokrassar/0.7.3
0.7.3
2021-10-23 14:26:42 +06:00
6fedd6f859 update dependencies 2021-10-23 14:24:31 +06:00
e52b59665f start 0.7.3 2021-10-23 14:19:30 +06:00
cda9d09689 fixes for kdocs 2021-10-18 22:57:25 +06:00
c9237b3f00 Merge pull request #102 from InsanusMokrassar/0.7.2
0.7.2
2021-10-16 09:51:24 +06:00
18bba66c4a Update CHANGELOG.md 2021-10-16 08:56:38 +06:00
63418c4a8a Update gradle.properties 2021-10-16 08:55:44 +06:00
2e66c6f4e3 Merge pull request #101 from InsanusMokrassar/0.7.1
0.7.1
2021-10-13 15:11:26 +06:00
e9c5df4c13 upfill kdocs 2021-10-13 15:09:05 +06:00
bc7789ad2c add kdocs 2021-10-13 15:06:58 +06:00
e3da761249 Fill changelog 2021-10-13 14:38:50 +06:00
4082f65afa AccumulatorFlow 2021-10-13 13:26:39 +06:00
5d1cab075d Update gradle.properties 2021-10-12 21:03:47 +06:00
bcf67f7e59 Update gradle.properties 2021-10-12 20:56:00 +06:00
7d3b1f8e75 StatesMachine is interface 2021-10-06 13:56:58 +06:00
119a0588cc DefaultStatesManager 2021-10-06 13:30:25 +06:00
fab789d9c0 start rework of FSM states manager 2021-10-06 12:14:02 +06:00
ceba81c08f start 0.7.1 2021-10-06 11:51:55 +06:00
a061af0558 actualize changelog 2021-10-05 13:52:28 +06:00
c7a53846ad Merge pull request #100 from InsanusMokrassar/0.7.0
0.7.0 + migration back to klock
2021-10-05 13:51:41 +06:00
a683cccf0c 0.7.0 + migration back to klock 2021-10-05 13:46:23 +06:00
50d41e35c1 remove deprecations 2021-10-04 16:09:01 +06:00
aa0e831cea Merge pull request #99 from InsanusMokrassar/0.6.0
0.6.0
2021-10-04 15:57:02 +06:00
44e26ccb4f migration onto datetime 2021-10-04 15:54:43 +06:00
2a783f6e2b start 0.6.0 2021-10-03 18:58:15 +06:00
6058d6a724 update ktor 2021-10-01 16:12:26 +06:00
2e9c7eb5fa Merge pull request #98 from InsanusMokrassar/0.5.31
0.5.31
2021-10-01 15:42:24 +06:00
e75465ad10 update dependencies 2021-09-30 11:59:44 +06:00
de01ad54e9 start 0.5.31 2021-09-30 11:57:29 +06:00
eeea7ddbe3 Merge pull request #97 from InsanusMokrassar/0.5.30
0.5.30
2021-09-25 16:22:34 +06:00
e0b18bec05 update dependencies 2021-09-25 14:55:56 +06:00
410e89bba9 start 0.5.30 2021-09-25 14:45:56 +06:00
9ef19dc42b Merge pull request #96 from InsanusMokrassar/0.5.29
0.5.29
2021-09-23 13:45:55 +06:00
0337d1b82d add fix of 31.0.0 for kdocs workflow 2021-09-23 13:23:02 +06:00
f5bd4c5ccb update dependencies 2021-09-23 13:12:03 +06:00
630f9bc0d4 start 0.5.29 2021-09-23 13:10:44 +06:00
18b4ffece1 Update packages_push.yml 2021-09-22 20:18:14 +06:00
f64e1effa3 Delete build.yml 2021-09-22 20:17:04 +06:00
847fcbb488 Merge pull request #95 from InsanusMokrassar/0.5.28
0.5.28
2021-09-19 21:59:25 +06:00
88002ec8e7 set java toolchain version in all projects related to Java 2021-09-19 20:50:24 +06:00
7f8db6a29d update dependencies 2021-09-19 20:39:15 +06:00
b183b82443 start 0.5.28 2021-09-19 20:36:02 +06:00
5dad27de72 Merge pull request #94 from InsanusMokrassar/0.5.27
0.5.27
2021-09-15 21:12:34 +06:00
6b66084d0e update dependencies 2021-09-15 19:07:36 +06:00
50b56a7c39 start 0.5.27 2021-09-15 19:04:02 +06:00
7ab7d14471 Merge pull request #93 from InsanusMokrassar/0.5.26
0.5.26
2021-09-09 12:25:12 +06:00
bdcc179b7b protecteds in map repos instead of privates 2021-09-09 12:16:12 +06:00
55ffd4b46f start 0.5.26 2021-09-09 12:07:56 +06:00
7fc5ee70e1 Merge pull request #92 from InsanusMokrassar/0.5.25
0.5.25
2021-09-08 12:27:31 +06:00
a24a335743 TypedSerializer#plusAssign and TypedSerializer#minusAssign 2021-09-08 12:18:42 +06:00
ef9af71960 clamp deprecation and Iterable#diff 2021-09-08 12:10:32 +06:00
925702d315 MPPFile#withoutSlashAtTheEnd 2021-09-08 12:06:23 +06:00
d50dffec8c update dependencies 2021-09-08 12:01:02 +06:00
cef2081a13 start 0.5.25 2021-09-08 11:57:55 +06:00
06c8bde7c9 Merge pull request #91 from InsanusMokrassar/0.5.24
0.5.24
2021-09-04 14:59:13 +06:00
c9bbfa3820 update gradle config and fix build 2021-09-04 14:58:15 +06:00
eed7cfdc42 CoroutineScope with safely handler parameter 2021-09-04 14:46:12 +06:00
bd9b0d16ab update dependencies 2021-09-04 14:34:27 +06:00
ea6c33b497 start 0.5.24 2021-09-04 14:20:35 +06:00
dc80ade2fb Merge pull request #90 from InsanusMokrassar/0.5.23
0.5.23
2021-09-02 14:35:31 +06:00
f6a06ee8ea small addition to joinTo 2021-09-02 14:33:35 +06:00
2644f27975 small addition to joinTo 2021-09-02 14:16:02 +06:00
3dc68a7b8b small addition to joinTo 2021-09-02 14:09:34 +06:00
97fc1d6239 small addition to joinTo 2021-09-02 14:01:01 +06:00
662f4d22a3 fill changelog 2021-09-02 13:52:00 +06:00
b70aa12be9 add joinTo 2021-09-02 13:50:40 +06:00
71f12f5f19 Update exposed 2021-09-02 10:09:45 +06:00
e10504eeeb Update CHANGELOG.md 2021-09-02 10:09:06 +06:00
2dea9f3bc0 start 0.5.23 2021-09-02 10:08:28 +06:00
35c9dda5bc Merge pull request #89 from InsanusMokrassar/0.5.22
0.5.22 - Update ktor
2021-08-27 08:57:57 +06:00
e831f3949a Update CHANGELOG.md 2021-08-26 14:30:37 +06:00
b0b39cc693 Update gradle.properties 2021-08-26 14:28:21 +06:00
fc03be3f73 Merge pull request #88 from InsanusMokrassar/0.5.22
0.5.22
2021-08-25 16:35:02 +06:00
b61f6b81f1 update dependencies 2021-08-25 15:50:10 +06:00
f5bc1c1fce start 0.5.22 2021-08-25 15:10:55 +06:00
a729f9568c Merge pull request #87 from InsanusMokrassar/0.5.21
0.5.21
2021-08-17 11:01:03 +06:00
5749e00377 update klock 2021-08-17 10:36:15 +06:00
ef73c24a0c fixes in TypedSerializer 2021-08-17 10:35:45 +06:00
94717ee351 start 0.5.21 2021-08-17 10:30:50 +06:00
9a18ded65b Merge pull request #86 from InsanusMokrassar/0.5.20
0.5.20
2021-08-16 19:14:56 +06:00
b23220f491 cursor get float and cursor get*OrNull 2021-08-16 18:42:08 +06:00
6e6bb03246 start 0.5.20 2021-08-16 18:35:46 +06:00
1ae6bae3b8 Merge pull request #85 from InsanusMokrassar/0.5.19
0.5.19#2
2021-08-09 11:24:06 +06:00
1239ca3256 update exposed 2021-08-09 11:23:22 +06:00
57b7797ea4 Merge pull request #84 from InsanusMokrassar/0.5.19
0.5.19
2021-08-08 22:52:09 +06:00
5ee5bfd1d5 updates in IetfLanguageCode 2021-08-08 22:09:33 +06:00
7229a3e198 start 0.5.19 2021-08-08 21:44:06 +06:00
bee083582f Merge pull request #83 from InsanusMokrassar/0.5.18
0.5.18
2021-08-04 11:53:40 +06:00
9d7f99f286 add several functions for string to language code conversation 2021-08-04 11:51:02 +06:00
6ef403853c Merge pull request #82 from InsanusMokrassar/0.5.18
0.5.18
2021-08-04 11:26:06 +06:00
6ae7ccb9a1 add kdocs to language_codes 2021-08-04 11:14:56 +06:00
dafc50c463 small reformat of code for language_codes 2021-08-04 11:11:43 +06:00
e89e2c931d real creating of module language code -.- 2021-08-04 11:04:03 +06:00
43a67b99e4 add language_codes 2021-08-04 11:03:25 +06:00
46c48f4f31 start 0.5.18 2021-08-04 11:03:01 +06:00
bf0fe85aa6 Merge pull request #81 from InsanusMokrassar/0.5.17
0.5.17
2021-07-31 16:36:17 +06:00
42c5bd3a7f update dependencies 2021-07-31 14:05:52 +06:00
d170e86c8a start 0.5.17 2021-07-16 18:42:35 +06:00
e3078169b1 Merge pull request #80 from InsanusMokrassar/0.5.16
0.5.16
2021-07-10 00:44:36 +06:00
a33ad123f6 Update CHANGELOG.md 2021-07-10 00:36:37 +06:00
7e14fa2f5c Update gradle-wrapper.properties 2021-07-10 00:08:17 +06:00
ba698b41e1 Update dependencies 2021-07-10 00:07:51 +06:00
e76215987e start 0.5.16 2021-07-10 00:04:15 +06:00
d1a247af8c Merge pull request #79 from InsanusMokrassar/0.5.15
0.5.15
2021-06-28 01:00:58 +06:00
2b7e9534f3 hotfix 2021-06-28 00:31:46 +06:00
38521558a1 start 0.5.15 2021-06-27 23:18:08 +06:00
100f3d214b Delete .travis.yml 2021-06-26 19:39:37 +06:00
1309867611 Init space CI 2021-06-26 13:38:30 +00:00
611f64f2e1 Merge pull request #78 from InsanusMokrassar/0.5.14
0.5.14
2021-06-26 00:58:02 +06:00
f118ebce6e update kotlin 2021-06-26 00:50:09 +06:00
59fc90e556 add subscribeAsync 2021-06-26 00:46:51 +06:00
fb9e4d57fb start 0.5.14 2021-06-25 17:19:28 +06:00
960c38b696 Merge pull request #77 from InsanusMokrassar/0.5.13
0.5.13
2021-06-23 21:29:56 +06:00
39895e58a6 changelog upsert 2021-06-23 21:29:28 +06:00
b420d85be5 MPPFile 2021-06-22 13:36:23 +06:00
19ea2f340a replace repos common extension for fsm 2021-06-20 17:11:51 +06:00
11b0d059bf start add fsm 2021-06-19 14:41:29 +06:00
c8a25ce544 start 0.5.13 2021-06-18 13:05:45 +06:00
509583ea2e Merge pull request #76 from InsanusMokrassar/0.5.12
0.5.12
2021-06-17 13:54:36 +06:00
1c86f3f4bf wrap with trycatch StateFlowBasedRecyclerViewAdapter listener 2021-06-17 13:46:22 +06:00
6d999be590 small improvement in StateFlowBasedRecyclerViewAdapter 2021-06-17 13:45:19 +06:00
e715772dbf fill changes 2021-06-17 13:39:02 +06:00
63eb7b7ea8 start 0.5.12 2021-06-17 12:54:06 +06:00
b07683b815 Merge pull request #75 from InsanusMokrassar/0.5.11
0.5.11
2021-06-16 13:25:56 +06:00
96e97d1691 ExposedOneToManyKeyValueRepo fixes 2021-06-16 13:22:40 +06:00
261d8827e3 OneToMany fixes 2021-06-16 13:20:05 +06:00
c3156f2e41 strt 0.5.11 2021-06-16 13:15:25 +06:00
8c08801460 Merge pull request #74 from InsanusMokrassar/0.5.10
0.5.10
2021-06-15 14:38:26 +06:00
aaf1299da7 fill changelog 2021-06-15 14:35:11 +06:00
a411355b4f fixes 2021-06-15 14:24:00 +06:00
eba41066b4 several small improvements in OneToManyAndroidRepo 2021-06-15 01:37:12 +06:00
f295dff8a2 update dependencies 2021-06-14 22:10:25 +06:00
a16815143c doForAllWithCurrentPaging and fun interface for elements in ktor server 2021-06-14 22:04:39 +06:00
6ff3f6ae42 start 0.5.10 2021-06-14 22:02:41 +06:00
84071881af Merge pull request #73 from InsanusMokrassar/0.5.9
0.5.9
2021-06-13 11:56:10 +06:00
7cccf7e56e upfix update 2021-06-13 11:50:48 +06:00
2516d5e381 update OneToManyAndroidRepo 2021-06-13 11:48:37 +06:00
cdec8bac75 start 0.5.9 2021-06-13 11:48:00 +06:00
fa30aae194 Merge pull request #72 from InsanusMokrassar/0.5.8
0.5.8
2021-06-11 22:36:40 +06:00
eb959a3135 add regular build workflow 2021-06-11 22:33:08 +06:00
24033e0cac firstNotNull and LinkedSupervisor(Job|Scope) 2021-06-11 17:32:13 +06:00
71f9a505e0 start 0.5.8 2021-06-11 17:03:20 +06:00
979b8f017b Merge pull request #71 from InsanusMokrassar/0.5.7
0.5.7
2021-06-06 10:46:13 +06:00
af78f01682 remove progressbar alert dialog 2021-06-06 10:43:42 +06:00
0b16d5c826 upfix 2021-06-06 02:10:48 +06:00
597e14bc7e fix of alert dialog with progress bar background 2021-06-06 02:04:21 +06:00
04a95867e2 fixes 2021-06-06 01:46:33 +06:00
e0d5eb45b7 separate cache repos to read and read/write 2021-06-05 20:29:59 +06:00
b90cab318e start 0.5.7 2021-06-05 20:12:41 +06:00
3252b61abe Merge pull request #70 from InsanusMokrassar/0.5.6
0.5.6
2021-06-05 15:23:40 +06:00
2a2da21ff3 fillup safelyWithResult 2021-06-05 15:18:48 +06:00
04ef371337 update safelyWithResult 2021-06-05 15:18:09 +06:00
623e0cd369 improve launchSynchronously 2021-06-05 15:16:07 +06:00
1f466747f0 update exposed 2021-06-05 15:08:22 +06:00
2215462f99 start 0.5.6 2021-06-05 15:07:27 +06:00
ac4c0a2e4c Merge pull request #69 from InsanusMokrassar/0.5.5
0.5.5
2021-05-28 23:50:35 +06:00
f7496db5ac Update CHANGELOG.md 2021-05-28 19:34:30 +06:00
3028fe975d Update ktor 2021-05-28 19:33:02 +06:00
23a5034493 start 0.5.5 2021-05-28 19:32:04 +06:00
65e339f811 Merge pull request #68 from InsanusMokrassar/0.5.4
0.5.4
2021-05-26 22:33:53 +06:00
2020e48659 fixes of deprecates 2021-05-26 22:33:08 +06:00
9566d6f81f upfix of android version increasing 2021-05-26 22:22:43 +06:00
a00d734712 Merge pull request #67 from InsanusMokrassar/0.5.4
0.5.4
2021-05-26 22:07:28 +06:00
27a3e8706a update klock 2021-05-26 22:05:34 +06:00
e601efcfc0 Update gradle.properties 2021-05-26 22:03:30 +06:00
2bfad9f885 Merge pull request #66 from InsanusMokrassar/0.5.3
0.5.3
2021-05-26 18:06:39 +06:00
e78e984943 fillup changelog 2021-05-26 18:00:24 +06:00
242f4b02d0 return previous logic of work with safely 2021-05-26 17:59:45 +06:00
041be5a1d1 Update gradle.properties 2021-05-25 16:08:07 +06:00
976ce056c1 runCatching for safely and safelyWithoutExceptions 2021-05-25 00:18:46 +06:00
00c23c73a8 several small improvements 2021-05-24 20:26:48 +06:00
9dd1848337 note about safely logic 2021-05-24 12:31:32 +06:00
9b30efd9a2 optimize imports 2021-05-24 12:29:31 +06:00
5853f7cc49 coroutines updates 2021-05-24 12:27:54 +06:00
7b00a06f3e start 0.5.3 2021-05-23 00:03:43 +06:00
9ef9be0f37 Merge pull request #64 from InsanusMokrassar/0.5.2
0.5.2
2021-05-21 18:08:34 +06:00
13ca419473 fixes in UnifiedRequester 2021-05-21 18:04:04 +06:00
b80f1a0773 start 0.5.2 2021-05-21 17:56:36 +06:00
e85101c74e Merge pull request #63 from InsanusMokrassar/0.5.1
0.5.1
2021-05-20 10:23:40 +06:00
110 changed files with 6379 additions and 277 deletions

View File

@@ -11,6 +11,9 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Fix android 32.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Build
run: ./gradlew dokkaHtml
- name: Publish KDocs

View File

@@ -9,6 +9,9 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Fix android 32.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
@@ -18,6 +21,7 @@ jobs:
- name: Build
run: ./gradlew build
- name: Publish
continue-on-error: true
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
env:
GITHUBPACKAGES_USER: ${{ github.actor }}

8
.space.kts Normal file
View File

@@ -0,0 +1,8 @@
job("Build and run tests") {
container(displayName = "Run gradle build", image = "openjdk:11") {
kotlinScript { api ->
// here can be your complex logic
api.gradlew("build")
}
}
}

View File

@@ -1,27 +0,0 @@
language: android
install: true
os: linux
dist: trusty
jdk: oraclejdk8
android:
components:
- tools
- platform-tools
- build-tools-30.0.2
- android-30
- add-on
- extra
before_script:
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "build-tools;30.0.2"
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "platforms;android-30"
jobs:
include:
- stage: build
script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest
# Tests are temporarily disabled on public travis due to the problems of launching
# - state: test
# script: ./gradlew allTests

View File

@@ -1,5 +1,392 @@
# Changelog
## 0.8.9
* `Ktor`:
* `Server`:
* Fixes in `uniloadMultipart`
* `Client`:
* Fixes in `unimultipart`
* `FSM`:
* Fixes in `DefaultUpdatableStatesMachine`
## 0.8.8
* `Versions`:
* `AppCompat`: `1.3.1` -> `1.4.0`
* Android Compile SDK: `31.0.0` -> `32.0.0`
* `FSM`:
* `DefaultStatesMachine` now is extendable
* New type `UpdatableStatesMachine` with default realization`DefaultUpdatableStatesMachine`
## 0.8.7
* `Ktor`:
* `Client`:
* `UnifiedRequester` now have no private fields
* Add preview work with multipart
* `Server`
* `UnifiedRouter` now have no private fields
* Add preview work with multipart
## 0.8.6
* `Common`:
* `Either` extensions `onFirst` and `onSecond` now accept not `crossinline` callbacks
* All `joinTo` now accept not `crossinline` callbacks
## 0.8.5
* `Common`:
* `repeatOnFailure`
## 0.8.4
* `Ktor`:
* `Server`:
* Several new `createKtorServer`
## 0.8.3
* `Common`:
* Ranges intersection functionality
* New type `Optional`
* `Pagination`:
* `Pagination` now extends `ClosedRange<Int>`
* `Pagination` intersection functionality
## 0.8.2
* `Versions`:
* `Klock`: `2.4.7` -> `2.4.8`
* `Serialization`: `1.3.0` -> `1.3.1`
* `FSM`:
* Now it is possible to pass any `CheckableHandlerHolder` in `FSMBuilder`
* Now `StatesMachine` works with `CheckableHandlerHolder` instead of `CustomizableHandlerHolder`
## 0.8.1
* `Versions`:
* `Exposed`: `0.36.1` -> `0.36.2`
* `Core KTX`: `1.6.0` -> `1.7.0`
## 0.8.0
* `Versions`:
* `Klock`: `2.4.6` -> `2.4.7`
* `Ktor`: `1.6.4` -> `1.6.5`
* `Exposed`: `0.35.3` -> `0.36.1`
* `Common`:
* Type `Either` got its own serializer
* `FSM`:
* `Common`:
* Full rework of FSM:
* Now it is more flexible for checking of handler opportunity to handle state
* Now machine and states managers are type-oriented
* `StateHandlerHolder` has been renamed to `CheckableHandlerHolder`
* Add opportunity for comfortable adding default state handler
## 0.7.4
* `Common`:
* New type `Either`
* `Serialization`:
* `TypedSerializer`
* New factory fun which accept vararg pairs of type and its serializer
* `Repos`:
* `Common` (`Android`):
* `AbstractMutableAndroidCRUDRepo` flows now will have extra buffer capacity instead of reply. It means that
android crud repo _WILL NOT_ send previous events to the
* `Exposed`:
* New parameter `AbstractExposedWriteCRUDRepo#replyCacheInFlows`
* KeyValue realization `ExposedKeyValueRepo` properties `_onNewValue` and `_onValueRemoved` now are available in
inheritors
* `Pagination`:
* `Common`:
* New types `getAllBy*` for current, next and custom paging
## 0.7.3
* `Versions`:
* `Exposed`: `0.35.2` -> `0.35.3`
## 0.7.2
* `Versions`:
* `Klock`: `2.4.5` -> `2.4.6`
## 0.7.1
* `Versions`:
* `Klock`: `2.4.3` -> `2.4.5`
* `Exposed`: `0.35.1` -> `0.35.2`
* `Coroutines`:
* `Common`:
* New `Flow` - `AccumulatorFlow`
* `FSM`:
* `Common`:
* `InMemoryStatesManager` has been replaced
* `StatesMachine` became an interface
* New manager `DefaultStatesManager` with `DefaultStatesManagerRepo` for abstraction of manager and storing of
data info
## 0.7.0
**THIS VERSION HAS MIGRATED FROM KOTLINX DATETIME TO KORLIBS KLOCK. CAREFUL**
* `Versions`
* `kotlinx.datetime` -> `Klock`
## 0.6.0 DO NOT RECOMMENDED
**THIS VERSION HAS MIGRATED FROM KORLIBS KLOCK TO KOTLINX DATETIME. CAREFUL**
**ALL DEPRECATION HAVE BEEN REMOVED**
* `Versions`
* `Klock` -> `kotlinx.datetime`
## 0.5.31
* `Versions`:
* `Klock`: `2.4.2` -> `2.4.3`
* `Ktor`: `1.6.3` -> `1.6.4`
## 0.5.30
* `Versions`:
* `Serialization`: `1.2.2` -> `1.3.0`
## 0.5.29
* `Versions`:
* `Exposed`: `0.34.2` -> `0.35.1`
## 0.5.28
* `Versions`:
* `Kotlin`: `1.5.30` -> `1.5.31`
* `Klock`: `2.4.1` -> `2.4.2`
## 0.5.27
* `Versions`:
* `Exposed`: `0.34.1` -> `0.34.2`
## 0.5.26
* `Repos`:
* `InMemory`:
* `MapCRUDRepo`s and `MapKeyValueRepo`s got `protected` methods and properties instead of private
## 0.5.25
* `Versions`:
* `UUID`: `0.3.0` -> `0.3.1`
* `Common`:
* New property `MPPFile#withoutSlashAtTheEnd`
* Extension `clamp` has been deprecated
* New extension `Iterable#diff`
* `Serialization`:
* New operators `TypedSerializer#plusAssign` and `TypedSerializer#minusAssign`
## 0.5.24
* `Versions`:
* `Coroutines`: `1.5.1` -> `1.5.2`
* `Klock`: `2.3.4` -> `2.4.1`
* `Coroutines`:
* New function `CoroutineScope` with safely exceptions handler as second parameter
## 0.5.23
* `Versions`:
* `Exposed`: `0.33.1` -> `0.34.1`
* `Common`:
* New extensions `Iterable#joinTo` and `Array#joinTo`
## 0.5.22
* `Versions`
* `Kotlin`: `1.5.21` -> `1.5.30`
* `Klock`: `2.3.2` -> `2.3.4`
* `AppCompat`: `1.3.0` -> `1.3.1`
* `Ktor`: `1.6.2` -> `1.6.3`
## 0.5.21
* `Versions`
* `Klock`: `2.3.1` -> `2.3.2`
* `Serialization`
* `Typed Serializer`:
* `TypedSerializer` Descriptor serial name has been fixed
## 0.5.20
* `Repos`:
* `Common`
* `Android`:
* `*OrNull` analogs of `Cursor.get*(String)` extensions have been added
* Extensions `Cursor.getFloat` and `Cursor.getFloatOrNull` have been added
## 0.5.19
* `LanguageCode`:
* `IetfLanguageCode` became as sealed class
* `IetfLanguageCode` now override `toString` and returns its code
## 0.5.18
* `Versions`
* `Kotlin Exposed`: `0.32.1` -> `0.33.1`
* `LanguageCode`:
* Module has been created
## 0.5.17
**SINCE THIS UPDATE JS PARTS WILL BE COMPILED WITH IR COMPILER ONLY**
* `Versions`
* `Kotlin`: `1.5.20` -> `1.5.21`
* `Ktor`: `1.6.1` -> `1.6.2`
* `Klock`: `2.2.0` -> `2.3.1`
* `CryptoJS`: `4.0.0` -> `4.1.1`
## 0.5.16
* `Versions`
* `Coroutines`: `1.5.0` -> `1.5.1`
* `Serialization`: `1.2.1` -> `1.2.2`
* `Ktor`: `1.6.0` -> `1.6.1`
* `Klock`: `2.1.2` -> `2.2.0`
* `Core KTX`: `1.5.0` -> `1.6.0`
## 0.5.15 HOTFIX FOR 0.5.14
* `Coroutines`
* Fixes in `subscribeAsync`
## 0.5.14 NOT RECOMMENDED
* `Versions`
* `Kotlin`: `1.5.10` -> `1.5.20`
* `Coroutines`
* `subscribeSafelyWithoutExceptions` got new parameter `onException` by analogue with `safelyWithoutExceptions`
* New extensions `Flow#subscribeAsync` and subsequent analogs of `subscribe` with opportunity to set up custom marker
## 0.5.13
* `Common`:
* Add functionality for multiplatform working with files:
* Main class for files `MPPFile`
* Inline class for filenames work encapsulation `FileName`
* `FSM`
* Module inited and in preview state
## 0.5.12
* `Common`:
* `Android`
* Extension `View#changeVisibility` has been fixed
* `Android`
* `RecyclerView`
* Default adapter got `dataCountFlow` property
* New subtype of adapter based on `StateFlow`: `StateFlowBasedRecyclerViewAdapter`
## 0.5.11
* `Repos`:
* `Common`:
* Fixes in `WriteOneToManyRepo#add`
* `Exposed`:
* Fixes in `ExposedOneToManyKeyValueRepo#add`
## 0.5.10
* `Versions`
* `Core KTX`: `1.3.2` -> `1.5.0`
* `AndroidX Recycler`: `1.2.0` -> `1.2.1`
* `AppCompat`: `1.2.0` -> `1.3.0`
* `Android`
* `RecyclerView`:
* `data` of `RecyclerViewAdapter` became an abstract field
* New function `RecyclerViewAdapter`
* `Common`:
* New extension `View#changeVisibility`
* `Repos`:
* `Common`:
* `WriteOneToManyRepo` got new function `clearWithValue`
* `Android`:
* New extension `SQLiteDatabase#selectDistinct`
* Fixes in `OneToManyAndroidRepo`
* `Ktor`
* `Server`
* All elements in configurators became a `fun interface`
* `Pagination`
* New function `doForAllWithCurrentPaging`
## 0.5.9
* `Repos`
* `Common`
* `OneToManyAndroidRepo` got new primary constructor
## 0.5.8
* `Common`:
* New extension `Iterable#firstNotNull`
* `Coroutines`
* New extension `Flow#firstNotNull`
* New extensions `CoroutineContext#LinkedSupervisorJob`, `CoroutineScope#LinkedSupervisorJob` and
`CoroutineScope#LinkedSupervisorScope`
## 0.5.7
* `Pagination`
* `Ktor`
* `Server`
* Fixes in extension `extractPagination`
* `Repos`
* `Cache`
* All standard cache repos have been separated to read and read/write repos
## 0.5.6
* `Versions`
* `Exposed`: `0.31.1` -> `0.32.1`
* `Coroutines`
* `JVM`
* `launchSynchronously` and subsequent functions got improved mechanism
* New method `safelyWithResult`
## 0.5.5
* `Versions`
* `Ktor`: `1.5.4` -> `1.6.0`
## 0.5.4
* `Versions`:
* `Klock`: `2.1.0` -> `2.1.2`
## 0.5.3
* `Versions`:
* `Kotlin`: `1.5.0` -> `1.5.10`
* `Coroutines`:
* Extensions `doInUI` and `doInDefault` were replaced in common and available on any supported platform
* Extension `doInIO` replaced into `jvm` and available on any `JVM` platform
* Old extension `safelyWithouException` without `onException` has been replaced by its copy with `onException` and
default value
* New value `defaultSafelyWithoutExceptionHandlerWithNull` which is used in all `*WithoutExceptions` by default
* Analogs of `launch` and `async` for `safely` and `safelyWithoutExceptions` were added
* Analogs of `runCatching` for `safely` and `safelyWithoutExceptions` were added
## 0.5.2
* `Ktor`:
* `Client`:
* Fixes in `UnifiedRequester`
## 0.5.1
* `Versions`:

View File

@@ -35,9 +35,9 @@ class ActionViewHolder(
}
class ActionsRecyclerViewAdapter(
data: List<AlertAction>,
override val data: List<AlertAction>,
private val dialogInterfaceGetter: () -> DialogInterface
) : RecyclerViewAdapter<AlertAction>(data) {
) : RecyclerViewAdapter<AlertAction>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<AlertAction> = ActionViewHolder(
parent, dialogInterfaceGetter
)

View File

@@ -11,6 +11,7 @@ kotlin {
commonMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
api project(":micro_utils.common")
}
}
androidMain {

View File

@@ -1,12 +1,21 @@
package dev.inmo.micro_utils.android.recyclerview
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.flow.*
abstract class RecyclerViewAdapter<T>(
val data: List<T>
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
abstract class RecyclerViewAdapter<T>: RecyclerView.Adapter<AbstractViewHolder<T>>() {
protected abstract val data: List<T>
private val _dataCountState by lazy {
MutableStateFlow<Int>(data.size)
}
val dataCountState: StateFlow<Int> by lazy {
_dataCountState.asStateFlow()
}
var emptyView: View? = null
set(value) {
field = value
@@ -18,31 +27,37 @@ abstract class RecyclerViewAdapter<T>(
object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
super.onItemRangeChanged(positionStart, itemCount)
_dataCountState.value = data.size
checkEmpty()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
super.onItemRangeChanged(positionStart, itemCount, payload)
_dataCountState.value = data.size
checkEmpty()
}
override fun onChanged() {
super.onChanged()
_dataCountState.value = data.size
checkEmpty()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
_dataCountState.value = data.size
checkEmpty()
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
_dataCountState.value = data.size
checkEmpty()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
_dataCountState.value = data.size
checkEmpty()
}
}
@@ -58,7 +73,7 @@ abstract class RecyclerViewAdapter<T>(
private fun checkEmpty() {
emptyView ?. let {
if (data.isEmpty()) {
if (dataCountState.value == 0) {
it.visibility = View.VISIBLE
} else {
it.visibility = View.GONE
@@ -66,3 +81,11 @@ abstract class RecyclerViewAdapter<T>(
}
}
}
fun <T> RecyclerViewAdapter(
data: List<T>,
onCreateViewHolder: (parent: ViewGroup, viewType: Int) -> AbstractViewHolder<T>
) = object : RecyclerViewAdapter<T>() {
override val data: List<T> = data
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<T> = onCreateViewHolder(parent, viewType)
}

View File

@@ -0,0 +1,50 @@
package dev.inmo.micro_utils.android.recyclerview
import dev.inmo.micro_utils.common.Diff
import dev.inmo.micro_utils.common.PreviewFeature
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@PreviewFeature("This feature in preview state and may contains different bugs. " +
"Besides, this feature can be changed in future in non-compatible way")
abstract class StateFlowBasedRecyclerViewAdapter<T>(
listeningScope: CoroutineScope,
dataState: StateFlow<List<T>>
) : RecyclerViewAdapter<T>() {
override var data: List<T> = emptyList()
init {
dataState.onEach {
try {
val diffForRemoves = Diff(data, it)
val removedIndexes = diffForRemoves.removed.map { it.index }
val leftRemove = removedIndexes.toMutableList()
data = data.filterIndexed { i, _ ->
if (i in leftRemove) {
leftRemove.remove(i)
true
} else {
false
}
}
withContext(Dispatchers.Main) {
removedIndexes.sortedDescending().forEach {
notifyItemRemoved(it)
}
}
val diffAddsAndReplaces = Diff(data, it)
data = it
withContext(Dispatchers.Main) {
diffAddsAndReplaces.replaced.forEach { (from, to) ->
notifyItemMoved(from.index, to.index)
}
diffAddsAndReplaces.added.forEach {
notifyItemInserted(it.index)
}
}
} catch (e: Throwable) {
// currently do nothing
}
}.launchIn(listeningScope)
}
}

View File

@@ -1,6 +1,5 @@
buildscript {
repositories {
jcenter()
google()
mavenCentral()
mavenLocal()
@@ -20,10 +19,8 @@ buildscript {
allprojects {
repositories {
mavenLocal()
jcenter()
mavenCentral()
google()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
// temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@@ -5,3 +5,18 @@ plugins {
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
jvmMain {
dependencies {
api project(":micro_utils.coroutines")
}
}
androidMain {
dependencies {
api project(":micro_utils.coroutines")
}
}
}
}

View File

@@ -12,11 +12,9 @@ package dev.inmo.micro_utils.common
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
AnnotationTarget.TYPEALIAS
)
annotation class PreviewFeature
annotation class PreviewFeature(val message: String = "It is possible, that behaviour of this thing will be changed or removed in future releases")
@RequiresOptIn(
"This thing is marked as warned. See message of warn to get more info",
@@ -30,8 +28,6 @@ annotation class PreviewFeature
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
AnnotationTarget.TYPEALIAS
)
annotation class Warning(val message: String)

View File

@@ -1,10 +0,0 @@
package dev.inmo.micro_utils.common
@Suppress("NOTHING_TO_INLINE")
inline fun <T : Comparable<T>> T.clamp(min: T, max: T): T {
return when {
this < min -> min
this > max -> max
else -> this
}
}

View File

@@ -27,8 +27,8 @@ data class Diff<T> internal constructor(
private inline fun <T> performChanges(
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
additionalsInOld: MutableList<T>,
additionalsInNew: MutableList<T>,
additionsInOld: MutableList<T>,
additionsInNew: MutableList<T>,
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
removedList: MutableList<IndexedValue<T>>,
addedList: MutableList<IndexedValue<T>>,
@@ -52,20 +52,20 @@ private inline fun <T> performChanges(
newPotentials.first().second ?.let { addedList.add(it) }
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
addedList.add(newOne!!)
oldOne ?.let { additionalsInOld.add(oldOne.value) }
oldOne ?.let { additionsInOld.add(oldOne.value) }
}
if (newPotentials.size > 1) {
newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) }
newPotentials.last().first ?.value ?.let { additionsInOld.add(it) }
}
}
newOneEqualToOldObject -> {
newPotentials.first().first ?.let { removedList.add(it) }
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
removedList.add(oldOne!!)
newOne ?.let { additionalsInNew.add(newOne.value) }
newOne ?.let { additionsInNew.add(newOne.value) }
}
if (newPotentials.size > 1) {
newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) }
newPotentials.last().second ?.value ?.let { additionsInNew.add(it) }
}
}
}
@@ -139,6 +139,10 @@ fun <T> Iterable<T>.calculateDiff(
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
}
inline fun <T> Iterable<T>.diff(
other: Iterable<T>,
strictComparison: Boolean = false
): Diff<T> = calculateDiff(other, strictComparison)
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)

View File

@@ -0,0 +1,151 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
/**
* Realization of this interface will contains at least one not null - [t1] or [t2]
*
* @see EitherFirst
* @see EitherSecond
* @see Either.Companion.first
* @see Either.Companion.second
* @see Either.onFirst
* @see Either.onSecond
*/
@Serializable(EitherSerializer::class)
sealed interface Either<T1, T2> {
val t1: T1?
val t2: T2?
companion object {
fun <T1, T2> serializer(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
}
}
class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> {
@ExperimentalSerializationApi
@InternalSerializationApi
override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer",
SerialKind.CONTEXTUAL
) {
element("type", String.serializer().descriptor)
element("value", ContextualSerializer(Either::class).descriptor)
}
private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer)
private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer)
@ExperimentalSerializationApi
@InternalSerializationApi
override fun deserialize(decoder: Decoder): Either<T1, T2> {
return decoder.decodeStructure(descriptor) {
var type: String? = null
lateinit var result: Either<T1, T2>
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> type = decodeStringElement(descriptor, 0)
1 -> {
result = when (type) {
"t1" -> decodeSerializableElement(
descriptor,
1,
t1EitherSerializer
)
"t2" -> decodeSerializableElement(
descriptor,
1,
t2EitherSerializer
)
else -> error("Unknown type of either: $type")
}
}
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
result
}
}
@ExperimentalSerializationApi
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: Either<T1, T2>) {
encoder.encodeStructure(descriptor) {
when (value) {
is EitherFirst -> {
encodeStringElement(descriptor, 0, "t1")
encodeSerializableElement(descriptor, 1, t1EitherSerializer, value)
}
is EitherSecond -> {
encodeStringElement(descriptor, 0, "t2")
encodeSerializableElement(descriptor, 1, t2EitherSerializer, value)
}
}
}
}
}
/**
* This type [Either] will always have not nullable [t1]
*/
@Serializable
data class EitherFirst<T1, T2>(
override val t1: T1
) : Either<T1, T2> {
override val t2: T2?
get() = null
}
/**
* This type [Either] will always have not nullable [t2]
*/
@Serializable
data class EitherSecond<T1, T2>(
override val t2: T2
) : Either<T1, T2> {
override val t1: T1?
get() = null
}
/**
* @return New instance of [EitherFirst]
*/
inline fun <T1, T2> Either.Companion.first(t1: T1): Either<T1, T2> = EitherFirst(t1)
/**
* @return New instance of [EitherSecond]
*/
inline fun <T1, T2> Either.Companion.second(t2: T2): Either<T1, T2> = EitherSecond(t2)
/**
* Will call [block] in case when [Either.t1] of [this] is not null
*/
inline fun <T1, T2, E : Either<T1, T2>> E.onFirst(block: (T1) -> Unit): E {
val t1 = t1
t1 ?.let(block)
return this
}
/**
* Will call [block] in case when [Either.t2] of [this] is not null
*/
inline fun <T1, T2, E : Either<T1, T2>> E.onSecond(block: (T2) -> Unit): E {
val t2 = t2
t2 ?.let(block)
return this
}
inline fun <reified T1, reified T2> Any.either() = when (this) {
is T1 -> Either.first<T1, T2>(this)
is T2 -> Either.second<T1, T2>(this)
else -> error("Incorrect type of either argument $this")
}

View File

@@ -7,9 +7,17 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
typealias ByteArrayAllocator = () -> ByteArray
typealias SuspendByteArrayAllocator = suspend () -> ByteArray
val ByteArray.asAllocator: ByteArrayAllocator
get() = { this }
val ByteArray.asSuspendAllocator: SuspendByteArrayAllocator
get() = { this }
val ByteArrayAllocator.asSuspendAllocator: SuspendByteArrayAllocator
get() = { this() }
suspend fun SuspendByteArrayAllocator.asAllocator(): ByteArrayAllocator {
return invoke().asAllocator
}
object ByteArrayAllocatorSerializer : KSerializer<ByteArrayAllocator> {
private val realSerializer = ByteArraySerializer()
@@ -17,7 +25,7 @@ object ByteArrayAllocatorSerializer : KSerializer<ByteArrayAllocator> {
override fun deserialize(decoder: Decoder): ByteArrayAllocator {
val bytes = realSerializer.deserialize(decoder)
return { bytes }
return bytes.asAllocator
}
override fun serialize(encoder: Encoder, value: ByteArrayAllocator) {

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.common
fun <T> Iterable<T?>.firstNotNull() = first { it != null }!!

View File

@@ -0,0 +1,59 @@
package dev.inmo.micro_utils.common
inline fun <I, R> Iterable<I>.joinTo(
separatorFun: (I) -> R?,
prefix: R? = null,
postfix: R? = null,
transform: (I) -> R?
): List<R> {
val result = mutableListOf<R>()
val iterator = iterator()
prefix ?.let(result::add)
while (iterator.hasNext()) {
val element = iterator.next()
result.add(transform(element) ?: continue)
if (iterator.hasNext()) {
result.add(separatorFun(element) ?: continue)
}
}
postfix ?.let(result::add)
return result
}
inline fun <I, R> Iterable<I>.joinTo(
separator: R? = null,
prefix: R? = null,
postfix: R? = null,
transform: (I) -> R?
): List<R> = joinTo({ separator }, prefix, postfix, transform)
inline fun <I> Iterable<I>.joinTo(
separatorFun: (I) -> I?,
prefix: I? = null,
postfix: I? = null
): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it }
inline fun <I> Iterable<I>.joinTo(
separator: I? = null,
prefix: I? = null,
postfix: I? = null
): List<I> = joinTo<I>({ separator }, prefix, postfix)
inline fun <I, reified R> Array<I>.joinTo(
separatorFun: (I) -> R?,
prefix: R? = null,
postfix: R? = null,
transform: (I) -> R?
): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray()
inline fun <I, reified R> Array<I>.joinTo(
separator: R? = null,
prefix: R? = null,
postfix: R? = null,
transform: (I) -> R?
): Array<R> = asIterable().joinTo(separator, prefix, postfix, transform).toTypedArray()

View File

@@ -0,0 +1,34 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
@Serializable
@JvmInline
value class FileName(val string: String) {
val name: String
get() = withoutSlashAtTheEnd.takeLastWhile { it != '/' }
val extension: String
get() = name.takeLastWhile { it != '.' }
val nameWithoutExtension: String
get() {
val filename = name
return filename.indexOfLast { it == '.' }.takeIf { it > -1 } ?.let {
filename.substring(0, it)
} ?: filename
}
val withoutSlashAtTheEnd: String
get() = string.dropLastWhile { it == '/' }
override fun toString(): String = string
}
expect class MPPFile
expect val MPPFile.filename: FileName
expect val MPPFile.filesize: Long
expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator
fun MPPFile.bytesSync() = bytesAllocatorSync()
suspend fun MPPFile.bytes() = bytesAllocator()

View File

@@ -0,0 +1,92 @@
@file:Suppress("unused")
package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
/**
* This type represents [T] as not only potentially nullable data, but also as a data which can not be presented. This
* type will be useful in cases when [T] is nullable and null as valuable data too in time of data absence should be
* presented by some third type.
*
* Let's imagine, you have nullable name in some database. In case when name is not nullable everything is clear - null
* will represent absence of row in the database. In case when name is nullable null will be a little bit dual-meaning,
* cause this null will say nothing about availability of the row (of course, it is exaggerated example)
*
* @see Optional.presented
* @see Optional.absent
* @see Optional.optional
* @see Optional.onPresented
* @see Optional.onAbsent
*/
@Serializable
data class Optional<T> internal constructor(
@Warning("It is unsafe to use this data directly")
val data: T?,
@Warning("It is unsafe to use this data directly")
val dataPresented: Boolean
) {
companion object {
/**
* Will create [Optional] with presented data
*/
fun <T> presented(data: T) = Optional(data, true)
/**
* Will create [Optional] without data
*/
fun <T> absent() = Optional<T>(null, false)
}
}
inline val <T> T.optional
get() = Optional.presented(this)
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
}
/**
* Will call [block] when data absent ([Optional.dataPresented] == false)
*/
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
if (!dataPresented) { block() }
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
}
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
*/
fun <T> Optional<T>.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
*/
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()

View File

@@ -0,0 +1,19 @@
package dev.inmo.micro_utils.common
fun <T : Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): Pair<T, T>? = when {
start == other.start && endInclusive == other.endInclusive -> start to endInclusive
start > other.endInclusive || other.start > endInclusive -> null
else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive)
}
fun IntRange.intersect(
other: IntRange
): IntRange? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
it.first .. it.second
}
fun LongRange.intersect(
other: LongRange
): LongRange? = (this as ClosedRange<Long>).intersect(other as ClosedRange<Long>) ?.let {
it.first .. it.second
}

View File

@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.common
/**
* Executes the given [action] until getting of successful result specified number of [times].
*
* A zero-based index of current iteration is passed as a parameter to [action].
*/
inline fun <R> repeatOnFailure(
times: Int,
onEachFailure: (Throwable) -> Unit = {},
action: (Int) -> R
): Optional<R> {
repeat(times) {
runCatching {
action(it)
}.onFailure(onEachFailure).onSuccess {
return Optional.presented(it)
}
}
return Optional.absent()
}

View File

@@ -11,7 +11,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) {
for ((i, _) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}

View File

@@ -0,0 +1,54 @@
package dev.inmo.micro_utils.common
import org.khronos.webgl.ArrayBuffer
import org.w3c.dom.ErrorEvent
import org.w3c.files.*
import kotlin.js.Promise
/**
* @suppress
*/
actual typealias MPPFile = File
fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
val reader = FileReader()
reader.onload = {
success((reader.result as ArrayBuffer).toByteArray())
Unit
}
reader.onerror = {
failure(Exception((it as ErrorEvent).message))
Unit
}
reader.readAsArrayBuffer(this)
}
fun MPPFile.readBytes(): ByteArray {
val reader = FileReaderSync()
return reader.readAsArrayBuffer(this).toByteArray()
}
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await()
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(name)
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = size.toLong()
/**
* @suppress
*/
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = ::readBytes
/**
* @suppress
*/
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = ::dirtyReadBytes

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.common
import kotlin.coroutines.*
import kotlin.js.Promise
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
then({ cont.resume(it) }, { cont.resumeWithException(it) })
}

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.common
import dev.inmo.micro_utils.coroutines.doInIO
import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine
import java.io.File
/**
* @suppress
*/
actual typealias MPPFile = File
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(name)
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = length()
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = ::readBytes
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
doInIO {
doOutsideOfCoroutine {
readBytes()
}
}
}

View File

@@ -33,3 +33,15 @@ fun View.toggleVisibility(goneOnHide: Boolean = true) {
show()
}
}
fun View.changeVisibility(show: Boolean = !isShown, goneOnHide: Boolean = true) {
if (show) {
show()
} else {
if (goneOnHide) {
gone()
} else {
hide()
}
}
}

View File

@@ -19,4 +19,4 @@ kotlin {
}
}
}
}
}

View File

@@ -0,0 +1,94 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
private sealed interface AccumulatorFlowStep
private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
/**
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
*
* * All unhandled by [FlowCollector] data will not be removed from [AccumulatorFlow] and will be sent to new
* [FlowCollector]s until anybody will handle it
* * Here there are an [activeData] where data [T] will be stored until somebody will handle it
*/
class AccumulatorFlow<T>(
sourceDataFlow: Flow<T>,
scope: CoroutineScope
) : AbstractFlow<T>() {
private val subscope = scope.LinkedSupervisorScope()
private val activeData = ArrayDeque<T>()
private val dataMutex = Mutex()
private val channelsForBroadcast = mutableListOf<Channel<Any>>()
private val channelsMutex = Mutex()
private val steps = subscope.actor<AccumulatorFlowStep> { step ->
when (step) {
is DataRetrievedAccumulatorFlowStep -> {
if (activeData.first() === step.data) {
dataMutex.withLock {
activeData.removeFirst()
}
}
}
is SubscribeAccumulatorFlowStep -> channelsMutex.withLock {
channelsForBroadcast.add(step.channel)
dataMutex.withLock {
val dataToSend = activeData.toList()
safelyWithoutExceptions {
dataToSend.forEach { step.channel.send(it as Any) }
}
}
}
is UnsubscribeAccumulatorFlowStep -> channelsMutex.withLock {
channelsForBroadcast.remove(step.channel)
}
}
}
private val subscriptionJob = sourceDataFlow.subscribeSafelyWithoutExceptions(subscope) {
dataMutex.withLock {
activeData.addLast(it)
}
channelsMutex.withLock {
channelsForBroadcast.forEach { channel ->
safelyWithResult {
channel.send(it as Any)
}
}
}
}
override suspend fun collectSafely(collector: FlowCollector<T>) {
val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
steps.send(SubscribeAccumulatorFlowStep(channel))
for (data in channel) {
try {
collector.emit(data as T)
steps.send(DataRetrievedAccumulatorFlowStep(data))
} finally {
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
}
}
}
}
/**
* Creates [AccumulatorFlow] using [this] as base [Flow]
*/
fun <T> Flow<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> {
return AccumulatorFlow(this, scope)
}
/**
* Creates [AccumulatorFlow] using [this] with [receiveAsFlow] to get
*/
fun <T> Channel<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> {
return receiveAsFlow().accumulatorFlow(scope)
}

View File

@@ -38,7 +38,7 @@ inline fun CoroutineScope.createSafeActionsActor(
suspend fun <T> Channel<suspend () -> Unit>.doWithSuspending(
action: ActorAction<T>
) = suspendCoroutine<T> {
offer {
trySend {
safely({ e -> it.resumeWithException(e) }) {
it.resume(action())
}

View File

@@ -0,0 +1,23 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
inline val UI
get() = Dispatchers.Main
inline val Default
get() = Dispatchers.Default
suspend inline fun <T> doIn(context: CoroutineContext, noinline block: suspend CoroutineScope.() -> T) = withContext(
context,
block
)
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = doIn(
UI,
block
)
suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = doIn(
Default,
block
)

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!!

View File

@@ -4,6 +4,8 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* Shortcut for chain if [Flow.onEach] and [Flow.launchIn]
@@ -29,9 +31,10 @@ inline fun <T> Flow<T>.subscribeSafely(
*/
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
scope: CoroutineScope,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend (T) -> Unit
) = subscribe(scope) {
safelyWithoutExceptions {
safelyWithoutExceptions(onException) {
block(it)
}
}

View File

@@ -0,0 +1,118 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
private class SubscribeAsyncReceiver<T>(
val scope: CoroutineScope,
output: suspend SubscribeAsyncReceiver<T>.(T) -> Unit
) {
private val dataChannel: Channel<T> = Channel(Channel.UNLIMITED)
val channel: SendChannel<T>
get() = dataChannel
init {
scope.launchSafelyWithoutExceptions {
for (data in dataChannel) {
output(data)
}
}
}
fun isEmpty(): Boolean = dataChannel.isEmpty
}
private sealed interface AsyncSubscriptionCommand<T, M> {
suspend operator fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>)
}
private data class AsyncSubscriptionCommandData<T, M>(
val data: T,
val scope: CoroutineScope,
val markerFactory: suspend (T) -> M,
val block: suspend (T) -> Unit,
val onEmpty: suspend (M) -> Unit
) : AsyncSubscriptionCommand<T, M> {
override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) {
val marker = markerFactory(data)
markersMap.getOrPut(marker) {
SubscribeAsyncReceiver(scope.LinkedSupervisorScope()) {
safelyWithoutExceptions { block(it) }
if (isEmpty()) {
onEmpty(marker)
}
}
}.channel.send(data)
}
}
private data class AsyncSubscriptionCommandClearReceiver<T, M>(
val marker: M
) : AsyncSubscriptionCommand<T, M> {
override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) {
val receiver = markersMap[marker]
if (receiver ?.isEmpty() == true) {
markersMap.remove(marker)
receiver.scope.cancel()
}
}
}
fun <T, M> Flow<T>.subscribeAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,
block: suspend (T) -> Unit
): Job {
val subscope = scope.LinkedSupervisorScope()
val markersMap = mutableMapOf<M, SubscribeAsyncReceiver<T>>()
val actor = subscope.actor<AsyncSubscriptionCommand<T, M>>(Channel.UNLIMITED) {
it.invoke(markersMap)
}
val job = subscribeSafelyWithoutExceptions(subscope) { data ->
val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker ->
actor.send(
AsyncSubscriptionCommandClearReceiver(marker)
)
}
actor.send(dataCommand)
}
job.invokeOnCompletion { if (subscope.isActive) subscope.cancel() }
return job
}
inline fun <T, M> Flow<T>.subscribeSafelyAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safely(onException) {
block(it)
}
}
inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions(onException) {
block(it)
}
}
inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions({ /* do nothing */}) {
block(it)
}
}

View File

@@ -86,6 +86,9 @@ suspend fun <T> safelyWithContextExceptionHandler(
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
* * [defaultSafelyExceptionHandler]
*
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
* be called [onException]
*
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
* exception will be available for catching
*
@@ -105,24 +108,49 @@ suspend inline fun <T> safely(
}
}
suspend inline fun <T> runCatchingSafely(
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatching {
safely(onException, block)
}
suspend inline fun <T> safelyWithResult(
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
/**
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
* returning null at one time
*
* @see safelyWithoutExceptions
* @see launchSafelyWithoutExceptions
* @see asyncSafelyWithoutExceptions
*/
val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
defaultSafelyWithoutExceptionHandler.invoke(it)
null
}
/**
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
* result from exception (instead of throwing it)
* result from exception (instead of throwing it, by default always returns null)
*/
suspend inline fun <T> safelyWithoutExceptions(
noinline onException: ExceptionHandler<T?>,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
): T? = safely(onException, block)
/**
* Shortcut for [safely] without exception handler (instead of this you will always receive null as a result)
*/
suspend inline fun <T> safelyWithoutExceptions(
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
): T? = safelyWithoutExceptions(
{
defaultSafelyWithoutExceptionHandler.invoke(it)
null
},
block
): Result<T?> = runCatching {
safelyWithoutExceptions(onException, block)
}
inline fun CoroutineScope(
context: CoroutineContext,
noinline defaultExceptionsHandler: ExceptionHandler<Unit>
) = CoroutineScope(
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
)

View File

@@ -0,0 +1,41 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
inline fun CoroutineScope.launchSafely(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> Unit
) = launch(context, start) {
safely(onException, block)
}
inline fun CoroutineScope.launchSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> Unit
) = launch(context, start) {
safelyWithoutExceptions(onException, block)
}
inline fun <T> CoroutineScope.asyncSafely(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
) = async(context, start) {
safely(onException, block)
}
inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
) = async(context, start) {
safelyWithoutExceptions(onException, block)
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
fun CoroutineContext.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null
) = SupervisorJob(job).let { if (additionalContext != null) it + additionalContext else it }
fun CoroutineScope.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorJob(additionalContext)
fun CoroutineScope.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null
) = CoroutineScope(
coroutineContext + LinkedSupervisorJob(additionalContext)
)

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
val IO
get() = Dispatchers.IO
suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = doIn(
IO,
block
)

View File

@@ -3,27 +3,21 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
val deferred = CompletableDeferred<T>()
val objectToSynchronize = java.lang.Object()
val launchCallback = {
var result: Result<T>? = null
val objectToSynchronize = Object()
synchronized(objectToSynchronize) {
launch {
safely(
{
deferred.completeExceptionally(it)
}
) {
deferred.complete(block())
}
result = safelyWithResult(block)
}.invokeOnCompletion {
synchronized(objectToSynchronize) {
objectToSynchronize.notifyAll()
}
}
while (result == null) {
objectToSynchronize.wait()
}
}
synchronized(objectToSynchronize) {
launchCallback()
objectToSynchronize.wait()
}
return deferred.getCompleted()
return result!!.getOrThrow()
}
fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block)

View File

@@ -1,9 +1,8 @@
package dev.inmo.micro_utils.coroutines
import dev.inmo.micro_utils.coroutines.asDeferred
import dev.inmo.micro_utils.coroutines.launchSynchronously
import kotlinx.coroutines.*
import kotlin.test.*
import kotlin.test.Test
import kotlin.test.assertEquals
class DoWithFirstTests {
@Test

View File

@@ -1,18 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = withContext(
Dispatchers.Main,
block
)
suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = withContext(
Dispatchers.Default,
block
)
suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = withContext(
Dispatchers.IO,
block
)

View File

@@ -1,3 +1,6 @@
package dev.inmo.micro_utils.crypto
/**
* @suppress
*/
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())

View File

@@ -3,6 +3,9 @@ package dev.inmo.micro_utils.crypto
import java.math.BigInteger
import java.security.MessageDigest
/**
* @suppress
*/
actual fun SourceBytes.md5(): MD5 = BigInteger(
1,
MessageDigest.getInstance("MD5").digest(this)

View File

@@ -7,17 +7,16 @@ plugins {
repositories {
mavenLocal()
jcenter()
google()
mavenCentral()
}
kotlin {
jvm()
js(BOTH) {
browser()
nodejs()
}
// js(IR) {
// browser()
// nodejs()
// }
android {}
sourceSets {
@@ -30,7 +29,7 @@ kotlin {
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) {
@@ -39,22 +38,22 @@ kotlin {
}
}
}
jsMain {
dependencies {
implementation kotlin('stdlib')
// jsMain {
// dependencies {
// implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
) {
api it
}
}
}
}
// project.parent.subprojects.forEach {
// if (
// it != project
// && it.hasProperty("kotlin")
// && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
// ) {
// api it
// }
// }
// }
// }
jvmMain {
dependencies {
implementation kotlin('stdlib')
@@ -117,9 +116,9 @@ tasks.dokkaHtml {
sourceRoots.setFrom(findSourcesWithName("commonMain"))
}
named("jsMain") {
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
}
// named("jsMain") {
// sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
// }
named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))

18
fsm/common/build.gradle Normal file
View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.common")
api project(":micro_utils.coroutines")
}
}
}
}

View File

@@ -0,0 +1,81 @@
package dev.inmo.micro_utils.fsm.common
import kotlin.reflect.KClass
/**
* Define checkable holder which can be used to precheck that this handler may handle incoming [State]
*/
interface CheckableHandlerHolder<I : State, O : State> : StatesHandler<I, O> {
suspend fun checkHandleable(state: O): Boolean
}
/**
* Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class
* casting in [handleState]
*/
class CustomizableHandlerHolder<I : O, O : State>(
private val delegateTo: StatesHandler<I, O>,
private val filter: suspend (state: O) -> Boolean
) : CheckableHandlerHolder<I, O> {
/**
* Checks that [state] can be handled by [delegateTo]. Under the hood it will check exact equality of [state]
* [KClass] and use [KClass.isInstance] of [inputKlass] if [strict] == false
*/
override suspend fun checkHandleable(state: O) = filter(state)
/**
* Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable]
* to be sure that this [StatesHandlerHolder] will be able to handle [state]
*/
override suspend fun StatesMachine<in O>.handleState(state: I): O? {
return delegateTo.run { handleState(state) }
}
}
fun <I : O, O : State> CheckableHandlerHolder(
inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CustomizableHandlerHolder(
StatesHandler<O, O> {
delegateTo.run { handleState(it as I) }
},
if (strict) {
{ it::class == inputKlass }
} else {
{ inputKlass.isInstance(it) }
}
)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
fun <I : O, O : State> StateHandlerHolder(
inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(inputKlass, strict, delegateTo)
inline fun <reified I : O, O : State> CheckableHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(I::class, strict, delegateTo)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
inline fun <reified I : O, O : State> StateHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(strict, delegateTo)
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
strict: Boolean = true
) = CheckableHandlerHolder<I, O>(
I::class,
strict,
this
)
inline fun <I : O, O: State> StatesHandler<I, O>.holder(
noinline filter: suspend (state: State) -> Boolean
) = CustomizableHandlerHolder<O, O>(
{ this@holder.run { handleState(it as I) } },
filter
)

View File

@@ -0,0 +1,5 @@
package dev.inmo.micro_utils.fsm.common
interface State {
val context: Any
}

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.fsm.common
/**
* Default realization of states handler
*/
fun interface StatesHandler<I : State, O: State> {
/**
* Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null
* [State] it is assumed that chain is not completed.
*/
suspend fun StatesMachine<in O>.handleState(state: I): O?
}

View File

@@ -0,0 +1,120 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
* [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start
* handling until [start] method will be called
*/
interface StatesMachine<T : State> : StatesHandler<T, T> {
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>
): T? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}
/**
* Starts handling of [State]s
*/
fun start(scope: CoroutineScope): Job
/**
* Start chain of [State]s witn [state]
*/
suspend fun startChain(state: T)
companion object {
/**
* Creates [DefaultStatesMachine]
*/
operator fun <T: State> invoke(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>
) = DefaultStatesMachine(statesManager, handlers)
}
}
/**
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
* resolving, and uses [launchStateHandling] for [State] handling.
*
* This class suppose to be extended in case you wish some custom behaviour inside of [launchStateHandling], for example
*/
open class DefaultStatesMachine <T: State>(
protected val statesManager: StatesManager<T>,
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
) : StatesMachine<T> {
/**
* Will call [launchStateHandling] for state handling
*/
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
/**
* This
*/
protected val statesJobs = mutableMapOf<T, Job>()
protected val statesJobsMutex = Mutex()
protected open suspend fun performUpdate(state: T) {
val newState = launchStateHandling(state, handlers)
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
open suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
statesJobs[actualState] ?.cancel()
statesJobs[actualState] = scope.launch {
performUpdate(actualState)
}.also { job ->
job.invokeOnCompletion { _ ->
scope.launch {
statesJobsMutex.withLock {
if (statesJobs[actualState] == job) {
statesJobs.remove(actualState)
}
}
}
}
}
}
}
/**
* Launch handling of states. On [statesManager] [StatesManager.onStartChain],
* [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
* [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise
* [StatesManager.endChain].
*/
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
}
statesManager.getActiveStates().forEach {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
}
/**
* Just calls [StatesManager.startChain] of [statesManager]
*/
override suspend fun startChain(state: T) {
statesManager.startChain(state)
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.fsm.common
import kotlinx.coroutines.flow.Flow
interface StatesManager<T : State> {
val onChainStateUpdated: Flow<Pair<T, T>>
val onStartChain: Flow<T>
val onEndChain: Flow<T>
/**
* Must set current set using [State.context]
*/
suspend fun update(old: T, new: T)
/**
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
* busy by the other [State]
*/
suspend fun startChain(state: T)
/**
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
* ignored
*/
suspend fun endChain(state: T)
suspend fun getActiveStates(): List<T>
}

View File

@@ -0,0 +1,60 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock
/**
* This extender of [StatesMachine] interface declare one new function [updateChain]. Realizations of this interface
* must be able to perform update of chain in internal [StatesManager]
*/
interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
/**
* Update chain with current state equal to [currentState] with [newState]. Behaviour of this update preforming
* in cases when [currentState] does not exist in [StatesManager] must be declared inside of realization of
* [StatesManager.update] function
*/
suspend fun updateChain(currentState: T, newState: T)
}
open class DefaultUpdatableStatesMachine<T : State>(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
) : DefaultStatesMachine<T>(
statesManager,
handlers
), UpdatableStatesMachine<T> {
protected val jobsStates = mutableMapOf<Job, T>()
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
if (compare(previousState, actualState)) {
statesJobs[actualState] ?.cancel()
}
val job = previousState.mapOnPresented {
statesJobs.remove(it)
} ?.takeIf { it.isActive } ?: scope.launch {
performUpdate(actualState)
}.also { job ->
job.invokeOnCompletion { _ ->
scope.launch {
statesJobsMutex.withLock {
statesJobs.remove(
jobsStates[job] ?: return@withLock
)
}
}
}
}
jobsStates.remove(job)
statesJobs[actualState] = job
jobsStates[job] = actualState
}
}
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState)
}
}

View File

@@ -0,0 +1,61 @@
package dev.inmo.micro_utils.fsm.common.dsl
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryDefaultStatesManagerRepo
import kotlin.reflect.KClass
class FSMBuilder<T : State>(
var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
val fsmBuilder: (statesManager: StatesManager<T>, states: List<CheckableHandlerHolder<T, T>>) -> StatesMachine<T> = { statesManager, states ->
StatesMachine(
statesManager,
states
)
},
var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
) {
private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
fun add(handler: CheckableHandlerHolder<T, T>) {
states.add(handler)
}
fun <I : T> add(kClass: KClass<I>, handler: StatesHandler<I, T>) {
add(CheckableHandlerHolder(kClass, false, handler))
}
fun <I : T> add(filter: suspend (state: State) -> Boolean, handler: StatesHandler<I, T>) {
add(handler.holder(filter))
}
fun <I : T> addStrict(kClass: KClass<I>, handler: StatesHandler<I, T>) {
states.add(CheckableHandlerHolder(kClass, true, handler))
}
inline fun <reified I : T> onStateOrSubstate(handler: StatesHandler<I, T>) {
add(I::class, handler)
}
inline fun <reified I : T> strictlyOn(handler: StatesHandler<I, T>) {
addStrict(I::class, handler)
}
inline fun <reified I : T> doWhen(
noinline filter: suspend (state: State) -> Boolean,
handler: StatesHandler<I, T>
) {
add(filter, handler)
}
fun build() = fsmBuilder(
statesManager,
states.toList().let { list ->
defaultStateHandler ?.let { list + it.holder { true } } ?: list
}
)
}
fun <T : State> buildFSM(
block: FSMBuilder<T>.() -> Unit
): StatesMachine<T> = FSMBuilder<T>().apply(block).build()

View File

@@ -0,0 +1,101 @@
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.StatesManager
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* Implement this repo if you want to use some custom repo for [DefaultStatesManager]
*/
interface DefaultStatesManagerRepo<T : State> {
/**
* Must save [state] as current state of chain with [State.context] of [state]
*/
suspend fun set(state: T)
/**
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
* NOT be removed
*/
suspend fun removeState(state: T)
/**
* @return Current list of available and saved states
*/
suspend fun getStates(): List<T>
/**
* @return Current state by [context]
*/
suspend fun getContextState(context: Any): T?
/**
* @return Current state by [context]
*/
suspend fun contains(context: Any): Boolean = getContextState(context) != null
}
/**
* @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default will be used
* [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
class DefaultStatesManager<T : State>(
private val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
) : StatesManager<T> {
private val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
private val _onStartChain = MutableSharedFlow<T>(0)
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
private val _onEndChain = MutableSharedFlow<T>(0)
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
private val mapMutex = Mutex()
override suspend fun update(old: T, new: T) = mapMutex.withLock {
val stateByOldContext: T? = repo.getContextState(old.context)
when {
stateByOldContext != old -> return@withLock
stateByOldContext == null || old.context == new.context -> {
repo.set(new)
_onChainStateUpdated.emit(old to new)
}
else -> {
val stateOnNewOneContext = repo.getContextState(new.context)
if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) {
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
repo.removeState(old)
repo.set(new)
_onChainStateUpdated.emit(old to new)
}
}
}
}
override suspend fun startChain(state: T) = mapMutex.withLock {
if (!repo.contains(state.context)) {
repo.set(state)
_onStartChain.emit(state)
}
}
private suspend fun endChainWithoutLock(state: T) {
if (repo.getContextState(state.context) == state) {
repo.removeState(state)
_onEndChain.emit(state)
}
}
override suspend fun endChain(state: T) {
mapMutex.withLock {
endChainWithoutLock(state)
}
}
override suspend fun getActiveStates(): List<T> = repo.getStates()
}

View File

@@ -0,0 +1,25 @@
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
/**
* Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive
* functionality
*/
class InMemoryDefaultStatesManagerRepo<T : State>(
private val map: MutableMap<Any, T> = mutableMapOf()
) : DefaultStatesManagerRepo<T> {
override suspend fun set(state: T) {
map[state.context] = state
}
override suspend fun removeState(state: T) {
map.remove(state.context)
}
override suspend fun getStates(): List<T> = map.values.toList()
override suspend fun getContextState(context: Any): T? = map[context]
override suspend fun contains(context: Any): Boolean = map.contains(context)
}

View File

@@ -0,0 +1,16 @@
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
import kotlinx.coroutines.flow.*
/**
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
*
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
fun <T: State> InMemoryStatesManager(
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver)

View File

@@ -0,0 +1,54 @@
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
import kotlinx.coroutines.*
sealed interface TrafficLightState : State {
val trafficLightNumber: Int
override val context: Int
get() = trafficLightNumber
}
data class GreenCommon(override val trafficLightNumber: Int) : TrafficLightState
data class YellowCommon(override val trafficLightNumber: Int) : TrafficLightState
data class RedCommon(override val trafficLightNumber: Int) : TrafficLightState
class PlayableMain {
// @Test
fun test() {
runBlocking {
val countOfTrafficLights = 10
val initialStates = (0 until countOfTrafficLights).map {
when (0/*Random.nextInt(3)*/) {
0 -> GreenCommon(it)
1 -> YellowCommon(it)
else -> RedCommon(it)
}
}
val statesManager = DefaultStatesManager<TrafficLightState>()
val machine = buildFSM<TrafficLightState> {
strictlyOn<GreenCommon> {
delay(1000L)
YellowCommon(it.context).also(::println)
}
strictlyOn<YellowCommon> {
delay(1000L)
RedCommon(it.context).also(::println)
}
strictlyOn<RedCommon> {
delay(1000L)
GreenCommon(it.context).also(::println)
}
this.statesManager = statesManager
}
initialStates.forEach { machine.startChain(it) }
val scope = CoroutineScope(Dispatchers.Default)
machine.start(scope).join()
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.fsm.common"/>

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.fsm.common")
api project(":micro_utils.repos.common")
}
}
}
}

View File

@@ -0,0 +1,25 @@
package dev.inmo.micro_utils.fsm.repos.common
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.pagination.getAll
class KeyValueBasedDefaultStatesManagerRepo<T : State>(
private val keyValueRepo: KeyValueRepo<Any, T>
) : DefaultStatesManagerRepo<T> {
override suspend fun set(state: T) {
keyValueRepo.set(state.context, state)
}
override suspend fun removeState(state: T) {
if (keyValueRepo.get(state.context) == state) {
keyValueRepo.unset(state.context)
}
}
override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second }
override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context)
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.fsm.repos.common"/>

View File

@@ -7,43 +7,43 @@ android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g
kotlin_version=1.5.0
kotlin_coroutines_version=1.5.0
kotlin_serialisation_core_version=1.2.1
kotlin_exposed_version=0.31.1
kotlin_version=1.5.31
kotlin_coroutines_version=1.5.2
kotlin_serialisation_core_version=1.3.1
kotlin_exposed_version=0.36.2
ktor_version=1.5.4
ktor_version=1.6.5
klockVersion=2.1.0
klockVersion=2.4.8
github_release_plugin_version=2.2.12
uuidVersion=0.3.0
uuidVersion=0.3.1
# ANDROID
core_ktx_version=1.3.2
androidx_recycler_version=1.2.0
appcompat_version=1.2.0
core_ktx_version=1.7.0
androidx_recycler_version=1.2.1
appcompat_version=1.4.0
android_minSdkVersion=19
android_compileSdkVersion=30
android_buildToolsVersion=30.0.3
dexcount_version=2.0.0
android_compileSdkVersion=32
android_buildToolsVersion=32.0.0
dexcount_version=3.0.1
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# JS NPM
crypto_js_version=4.0.0
crypto_js_version=4.1.1
# Dokka
dokka_version=1.4.32
dokka_version=1.5.31
# Project data
group=dev.inmo
version=0.5.1
android_code_version=42
version=0.8.9
android_code_version=89

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import io.ktor.client.request.forms.InputProvider
expect suspend fun MPPFile.inputProvider(): InputProvider

View File

@@ -1,44 +1,87 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.serialization.*
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
class UnifiedRequester(
private val client: HttpClient = HttpClient(),
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
val client: HttpClient = HttpClient(),
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) {
suspend fun <ResultType> uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>
): ResultType = client.get<StandardKtorSerialInputData>(
url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
}
): ResultType = client.uniget(url, resultDeserializer, serialFormat)
fun <T> encodeUrlQueryValue(
serializationStrategy: SerializationStrategy<T>,
value: T
) = serialFormat.encodeHex(
serializationStrategy,
value
) = serializationStrategy.encodeUrlQueryValue(
value,
serialFormat
)
suspend fun <BodyType, ResultType> unipost(
url: String,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = client.post<StandardKtorSerialInputData>(url) {
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
serialFormat.decodeDefault(resultDeserializer, it)
}
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
mppFile: MPPFile,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
fun <T> createStandardWebsocketFlow(
url: String,
@@ -51,14 +94,151 @@ val defaultRequester = UnifiedRequester()
suspend fun <ResultType> HttpClient.uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>
) = defaultRequester.uniget(url, resultDeserializer)
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get<StandardKtorSerialInputData>(
url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = defaultRequester.encodeUrlQueryValue(this, value)
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value: T,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = serialFormat.encodeHex(
this,
value
)
suspend fun <BodyType, ResultType> HttpClient.unipost(
url: String,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = defaultRequester.unipost(url, bodyInfo, resultDeserializer)
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post<StandardKtorSerialInputData>(url) {
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
serialFormat.decodeDefault(resultDeserializer, it)
}
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
url,
formData = formData {
append(
"bytes",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, mimetype)
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
}
) {
requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it) }
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
filename: String,
otherData: BodyPair<BodyType>,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
filename,
inputProvider,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile.filename.string,
mppFile.inputProvider(),
resultDeserializer,
mimetype,
additionalParametersBuilder,
dataHeadersBuilder,
requestBuilder,
serialFormat
)
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.*
import io.ktor.client.request.forms.InputProvider
import io.ktor.utils.io.core.ByteReadPacket
actual suspend fun MPPFile.inputProvider(): InputProvider = bytes().let {
InputProvider(it.size.toLong()) {
ByteReadPacket(it)
}
}

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import io.ktor.client.request.forms.InputProvider
import io.ktor.utils.io.streams.asInput
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
inputStream().asInput()
}

View File

@@ -10,6 +10,7 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api internalProject("micro_utils.common")
api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version"
api "com.soywiz.korlibs.klock:klock:$klockVersion"
}

View File

@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.collect
import kotlinx.serialization.SerializationStrategy
private suspend fun DefaultWebSocketSession.checkReceivedAndCloseIfExists() {
if (incoming.poll() != null) {
if (incoming.tryReceive() != null) {
close()
throw CorrectCloseException
}

View File

@@ -1,22 +1,31 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.request.receive
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.response.respondBytes
import io.ktor.routing.Route
import io.ktor.util.asStream
import io.ktor.util.cio.writeChannel
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.*
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.*
import java.io.File
import java.io.File.createTempFile
class UnifiedRouter(
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
private val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) {
fun <T> Route.includeWebsocketHandling(
suburl: String,
@@ -104,6 +113,139 @@ suspend fun <T> ApplicationCall.uniload(
)
}
suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
) = safely {
val multipartData = receiveMultipart()
var resultInput: Input? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> resultInput = it.provider()
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun <T> ApplicationCall.uniloadMultipart(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Pair<Input, T> {
var data: Optional<T>? = null
val resultInput = uniloadMultipart(
onFormItem,
{
if (it.name == "data") {
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
} else {
onCustomFileItem(it)
}
},
onBinaryContent
)
val completeData = data ?: error("Data has not been received")
return resultInput to (completeData.dataOrNull().let { it as T })
}
suspend fun <T> ApplicationCall.uniloadMultipartFile(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
var data: Optional<T>? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.provider().asStream().copyTo(fileStream)
}
}
}
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
val completeData = data ?: error("Data has not been received")
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
}
suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
if (it.name == "bytes") {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.provider().asStream().copyTo(fileStream)
}
}
} else {
onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun ApplicationCall.getParameterOrSendError(
field: String
) = parameters[field].also {

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
import io.ktor.application.Application
import io.ktor.server.cio.CIO
import io.ktor.server.engine.*
@@ -31,3 +32,27 @@ fun createKtorServer(
port: Int = Random.nextInt(1024, 65535),
block: Application.() -> Unit
): ApplicationEngine = createKtorServer(CIO, host, port, block)
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> createKtorServer(
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
host: String = "localhost",
port: Int = Random.nextInt(1024, 65535),
configurators: List<KtorApplicationConfigurator>
): TEngine = createKtorServer(
engine,
host,
port
) {
configurators.forEach { it.apply { configure() } }
}
/**
* Create server with [CIO] server engine without starting of it
*
* @see ApplicationEngine.start
*/
fun createKtorServer(
host: String = "localhost",
port: Int = Random.nextInt(1024, 65535),
configurators: List<KtorApplicationConfigurator>
): ApplicationEngine = createKtorServer(CIO, host, port, configurators)

View File

@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
data class ApplicationCachingHeadersConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
interface Element { operator fun CachingHeaders.Configuration.invoke() }
fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
override fun Application.configure() {
install(CachingHeaders) {

View File

@@ -10,17 +10,18 @@ import kotlinx.serialization.Serializable
class ApplicationRoutingConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
interface Element { operator fun Route.invoke() }
fun interface Element { operator fun Route.invoke() }
private val rootInstaller = Element {
elements.forEach {
it.apply { invoke() }
}
}
override fun Application.configure() {
try {
feature(Routing)
} catch (e: IllegalStateException) {
install(Routing) {
elements.forEach {
it.apply { invoke() }
}
}
featureOrNull(Routing) ?.apply {
rootInstaller.apply { invoke() }
} ?: install(Routing) {
rootInstaller.apply { invoke() }
}
}
}

View File

@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
class ApplicationSessionsConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
interface Element { operator fun Sessions.Configuration.invoke() }
fun interface Element { operator fun Sessions.Configuration.invoke() }
override fun Application.configure() {
install(Sessions) {

View File

@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
class StatusPagesConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
interface Element { operator fun StatusPages.Configuration.invoke() }
fun interface Element { operator fun StatusPages.Configuration.invoke() }
override fun Application.configure() {
install(StatusPages) {

View File

@@ -0,0 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,31 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
plugins {
id 'org.jetbrains.kotlin.jvm'
id "org.jetbrains.kotlin.plugin.serialization"
id "application"
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version"
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-java:$ktor_version"
}
mainClassName="MainKt"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -0,0 +1,214 @@
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import java.io.File
import java.text.Normalizer
private val json = Json {
ignoreUnknownKeys = true
}
private const val baseClassName = "IetfLanguageCode"
private const val unknownBaseClassName = "Unknown$baseClassName"
private const val baseClassSerializerName = "IetfLanguageCodeSerializer"
private const val baseClassSerializerAnnotationName = "@Serializable(${baseClassSerializerName}::class)"
@Serializable
private data class LanguageCode(
@SerialName("alpha2")
val tag: String,
@SerialName("English")
val title: String
)
fun String.adaptAsTitle() = if (first().isDigit()) {
"L$this"
} else {
this
}
fun String.normalized() = Normalizer.normalize(this, Normalizer.Form.NFD).replace(Regex("[^\\p{ASCII}]"), "")
@Serializable
private data class LanguageCodeWithTag(
@SerialName("langType")
val tag: String,
@SerialName("lang")
val withSubtag: String
) {
val partWithoutTag: String
get() {
return withSubtag.substring(
withSubtag.indexOf("-") + 1, withSubtag.length
)
}
val middleTag
get() = if (partWithoutTag.contains("-")) {
partWithoutTag.substring(0, partWithoutTag.indexOf("-"))
} else {
null
}
val middleTagTitle
get() = middleTag ?.adaptAsTitle() ?: partWithoutTag.adaptAsTitle()
val subtag: String
get() = middleTag ?: partWithoutTag
val endTag
get() = if (partWithoutTag.contains("-")) {
partWithoutTag.substring(partWithoutTag.indexOf("-") + 1, partWithoutTag.length)
} else {
null
}
val endTagAsTitle
get() = endTag ?.adaptAsTitle()
}
data class Tag(
val title: String,
val tag: String,
val subtags: List<Tag>
)
private fun printLanguageCodeAndTags(
tag: Tag,
parent: Tag? = null,
indents: String = " "
): String = if (tag.subtags.isEmpty()) {
"""${indents}${baseClassSerializerAnnotationName}
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}" }"""
} else {
"""
${indents}${baseClassSerializerAnnotationName}
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
${indents} override val code: String = "${tag.tag}"
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
${indents} ${baseClassSerializerAnnotationName}
${indents} companion object : ${tag.title}()
${indents}}
"""
}
fun buildKtFileContent(tags: List<Tag>): String = """
import kotlinx.serialization.Serializable
/**
* This class has been automatically generated using
* https://github.com/InsanusMokrassar/MicroUtils/tree/master/language_codes/generator . This generator uses
* https://datahub.io/core/language-codes/ files (base and tags) and create the whole hierarchy using it.
*/
${baseClassSerializerAnnotationName}
sealed class $baseClassName {
abstract val code: String
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
$baseClassSerializerAnnotationName
data class $unknownBaseClassName (override val code: String) : $baseClassName()
override fun toString() = code
}
""".trimIndent()
fun createStringConverterCode(tags: List<Tag>): String {
fun createDeserializeVariantForTag(
tag: Tag,
pretitle: String = baseClassName,
indents: String = " "
): String {
val currentTitle = "$pretitle.${tag.title}"
return """${indents}$currentTitle.code -> $currentTitle${if (tag.subtags.isNotEmpty()) tag.subtags.joinToString("\n", "\n") { createDeserializeVariantForTag(it, currentTitle, indents) } else ""}"""
}
return """fun String.as$baseClassName(): $baseClassName {
return when (this) {
${tags.joinToString("\n") { createDeserializeVariantForTag(it) }}
else -> $baseClassName.${unknownBaseClassName}(this)
}
}
fun convertTo$baseClassName(code: String) = code.as$baseClassName()
fun $baseClassName(code: String) = code.as$baseClassName()
"""
}
fun createSerializerCode(tags: List<Tag>): String {
return """import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object $baseClassSerializerName : KSerializer<$baseClassName> {
override val descriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): $baseClassName {
return $baseClassName(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLanguageCode) {
encoder.encodeString(value.code)
}
}
"""
}
suspend fun main(vararg args: String) {
val outputFolder = args.firstOrNull() ?.let { File(it) }
outputFolder ?.mkdirs()
val ietfLanguageCodesLink = "https://datahub.io/core/language-codes/r/language-codes.json"
val ietfLanguageCodesAdditionalTagsLink = "https://datahub.io/core/language-codes/r/ietf-language-tags.json"
val client = HttpClient()
val ietfLanguageCodes = json.decodeFromString(
ListSerializer(LanguageCode.serializer()),
client.get(ietfLanguageCodesLink)
).map {
it.copy(
title = it.title
.replace(Regex("[;,()-]"), "")
.split(" ")
.joinToString("") { "${it.first().uppercase()}${it.substring(1)}" }
)
}
val ietfLanguageCodesWithTagsMap = json.decodeFromString(
ListSerializer(LanguageCodeWithTag.serializer()),
client.get(ietfLanguageCodesAdditionalTagsLink)
).filter { it.withSubtag != it.tag }.groupBy { it.tag }
val tags = ietfLanguageCodes.map {
val unformattedSubtags = ietfLanguageCodesWithTagsMap[it.tag] ?: emptyList()
val threeLevelTags = unformattedSubtags.filter { it.endTag != null }.groupBy { it.middleTag }
val subtags = unformattedSubtags.mapNotNull {
if (it.endTag == null) {
val currentSubtags = (threeLevelTags[it.subtag] ?: emptyList()).map {
Tag(it.endTagAsTitle!!.normalized(), it.withSubtag, emptyList())
}
Tag(it.middleTagTitle.normalized(), it.withSubtag, currentSubtags)
} else {
null
}
}
Tag(it.title.normalized(), it.tag, subtags)
}
File(outputFolder, "LanguageCodes.kt").apply {
delete()
createNewFile()
writeText(buildKtFileContent(tags))
}
File(outputFolder, "StringToLanguageCodes.kt").apply {
delete()
createNewFile()
writeText(createStringConverterCode(tags))
}
File(outputFolder, "$baseClassSerializerName.kt").apply {
delete()
createNewFile()
writeText(createSerializerCode(tags))
}
}

View File

@@ -0,0 +1,18 @@
package dev.inmo.micro_utils.language_codes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object IetfLanguageCodeSerializer : KSerializer<IetfLanguageCode> {
override val descriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): IetfLanguageCode {
return IetfLanguageCode(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLanguageCode) {
encoder.encodeString(value.code)
}
}

View File

@@ -0,0 +1,671 @@
package dev.inmo.micro_utils.language_codes
fun String.asIetfLanguageCode(): IetfLanguageCode {
return when (this) {
IetfLanguageCode.Afar.code -> IetfLanguageCode.Afar
IetfLanguageCode.Abkhazian.code -> IetfLanguageCode.Abkhazian
IetfLanguageCode.Avestan.code -> IetfLanguageCode.Avestan
IetfLanguageCode.Afrikaans.code -> IetfLanguageCode.Afrikaans
IetfLanguageCode.Afrikaans.NA.code -> IetfLanguageCode.Afrikaans.NA
IetfLanguageCode.Afrikaans.ZA.code -> IetfLanguageCode.Afrikaans.ZA
IetfLanguageCode.Akan.code -> IetfLanguageCode.Akan
IetfLanguageCode.Akan.GH.code -> IetfLanguageCode.Akan.GH
IetfLanguageCode.Amharic.code -> IetfLanguageCode.Amharic
IetfLanguageCode.Amharic.ET.code -> IetfLanguageCode.Amharic.ET
IetfLanguageCode.Aragonese.code -> IetfLanguageCode.Aragonese
IetfLanguageCode.Arabic.code -> IetfLanguageCode.Arabic
IetfLanguageCode.Arabic.L001.code -> IetfLanguageCode.Arabic.L001
IetfLanguageCode.Arabic.AE.code -> IetfLanguageCode.Arabic.AE
IetfLanguageCode.Arabic.BH.code -> IetfLanguageCode.Arabic.BH
IetfLanguageCode.Arabic.DJ.code -> IetfLanguageCode.Arabic.DJ
IetfLanguageCode.Arabic.DZ.code -> IetfLanguageCode.Arabic.DZ
IetfLanguageCode.Arabic.EG.code -> IetfLanguageCode.Arabic.EG
IetfLanguageCode.Arabic.EH.code -> IetfLanguageCode.Arabic.EH
IetfLanguageCode.Arabic.ER.code -> IetfLanguageCode.Arabic.ER
IetfLanguageCode.Arabic.IL.code -> IetfLanguageCode.Arabic.IL
IetfLanguageCode.Arabic.IQ.code -> IetfLanguageCode.Arabic.IQ
IetfLanguageCode.Arabic.JO.code -> IetfLanguageCode.Arabic.JO
IetfLanguageCode.Arabic.KM.code -> IetfLanguageCode.Arabic.KM
IetfLanguageCode.Arabic.KW.code -> IetfLanguageCode.Arabic.KW
IetfLanguageCode.Arabic.LB.code -> IetfLanguageCode.Arabic.LB
IetfLanguageCode.Arabic.LY.code -> IetfLanguageCode.Arabic.LY
IetfLanguageCode.Arabic.MA.code -> IetfLanguageCode.Arabic.MA
IetfLanguageCode.Arabic.MR.code -> IetfLanguageCode.Arabic.MR
IetfLanguageCode.Arabic.OM.code -> IetfLanguageCode.Arabic.OM
IetfLanguageCode.Arabic.PS.code -> IetfLanguageCode.Arabic.PS
IetfLanguageCode.Arabic.QA.code -> IetfLanguageCode.Arabic.QA
IetfLanguageCode.Arabic.SA.code -> IetfLanguageCode.Arabic.SA
IetfLanguageCode.Arabic.SD.code -> IetfLanguageCode.Arabic.SD
IetfLanguageCode.Arabic.SO.code -> IetfLanguageCode.Arabic.SO
IetfLanguageCode.Arabic.SS.code -> IetfLanguageCode.Arabic.SS
IetfLanguageCode.Arabic.SY.code -> IetfLanguageCode.Arabic.SY
IetfLanguageCode.Arabic.TD.code -> IetfLanguageCode.Arabic.TD
IetfLanguageCode.Arabic.TN.code -> IetfLanguageCode.Arabic.TN
IetfLanguageCode.Arabic.YE.code -> IetfLanguageCode.Arabic.YE
IetfLanguageCode.Assamese.code -> IetfLanguageCode.Assamese
IetfLanguageCode.Assamese.IN.code -> IetfLanguageCode.Assamese.IN
IetfLanguageCode.Avaric.code -> IetfLanguageCode.Avaric
IetfLanguageCode.Aymara.code -> IetfLanguageCode.Aymara
IetfLanguageCode.Azerbaijani.code -> IetfLanguageCode.Azerbaijani
IetfLanguageCode.Azerbaijani.Cyrl.code -> IetfLanguageCode.Azerbaijani.Cyrl
IetfLanguageCode.Azerbaijani.Cyrl.AZ.code -> IetfLanguageCode.Azerbaijani.Cyrl.AZ
IetfLanguageCode.Azerbaijani.Latn.code -> IetfLanguageCode.Azerbaijani.Latn
IetfLanguageCode.Azerbaijani.Latn.AZ.code -> IetfLanguageCode.Azerbaijani.Latn.AZ
IetfLanguageCode.Bashkir.code -> IetfLanguageCode.Bashkir
IetfLanguageCode.Belarusian.code -> IetfLanguageCode.Belarusian
IetfLanguageCode.Belarusian.BY.code -> IetfLanguageCode.Belarusian.BY
IetfLanguageCode.Bulgarian.code -> IetfLanguageCode.Bulgarian
IetfLanguageCode.Bulgarian.BG.code -> IetfLanguageCode.Bulgarian.BG
IetfLanguageCode.BihariLanguages.code -> IetfLanguageCode.BihariLanguages
IetfLanguageCode.Bislama.code -> IetfLanguageCode.Bislama
IetfLanguageCode.Bambara.code -> IetfLanguageCode.Bambara
IetfLanguageCode.Bambara.ML.code -> IetfLanguageCode.Bambara.ML
IetfLanguageCode.Bengali.code -> IetfLanguageCode.Bengali
IetfLanguageCode.Bengali.BD.code -> IetfLanguageCode.Bengali.BD
IetfLanguageCode.Bengali.IN.code -> IetfLanguageCode.Bengali.IN
IetfLanguageCode.Tibetan.code -> IetfLanguageCode.Tibetan
IetfLanguageCode.Tibetan.CN.code -> IetfLanguageCode.Tibetan.CN
IetfLanguageCode.Tibetan.IN.code -> IetfLanguageCode.Tibetan.IN
IetfLanguageCode.Breton.code -> IetfLanguageCode.Breton
IetfLanguageCode.Breton.FR.code -> IetfLanguageCode.Breton.FR
IetfLanguageCode.Bosnian.code -> IetfLanguageCode.Bosnian
IetfLanguageCode.Bosnian.Cyrl.code -> IetfLanguageCode.Bosnian.Cyrl
IetfLanguageCode.Bosnian.Cyrl.BA.code -> IetfLanguageCode.Bosnian.Cyrl.BA
IetfLanguageCode.Bosnian.Latn.code -> IetfLanguageCode.Bosnian.Latn
IetfLanguageCode.Bosnian.Latn.BA.code -> IetfLanguageCode.Bosnian.Latn.BA
IetfLanguageCode.CatalanValencian.code -> IetfLanguageCode.CatalanValencian
IetfLanguageCode.CatalanValencian.AD.code -> IetfLanguageCode.CatalanValencian.AD
IetfLanguageCode.CatalanValencian.ES.code -> IetfLanguageCode.CatalanValencian.ES
IetfLanguageCode.CatalanValencian.ES.VALENCIA.code -> IetfLanguageCode.CatalanValencian.ES.VALENCIA
IetfLanguageCode.CatalanValencian.FR.code -> IetfLanguageCode.CatalanValencian.FR
IetfLanguageCode.CatalanValencian.IT.code -> IetfLanguageCode.CatalanValencian.IT
IetfLanguageCode.Chechen.code -> IetfLanguageCode.Chechen
IetfLanguageCode.Chechen.RU.code -> IetfLanguageCode.Chechen.RU
IetfLanguageCode.Chamorro.code -> IetfLanguageCode.Chamorro
IetfLanguageCode.Corsican.code -> IetfLanguageCode.Corsican
IetfLanguageCode.Cree.code -> IetfLanguageCode.Cree
IetfLanguageCode.Czech.code -> IetfLanguageCode.Czech
IetfLanguageCode.Czech.CZ.code -> IetfLanguageCode.Czech.CZ
IetfLanguageCode.ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic.code -> IetfLanguageCode.ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic
IetfLanguageCode.ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic.RU.code -> IetfLanguageCode.ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic.RU
IetfLanguageCode.Chuvash.code -> IetfLanguageCode.Chuvash
IetfLanguageCode.Welsh.code -> IetfLanguageCode.Welsh
IetfLanguageCode.Welsh.GB.code -> IetfLanguageCode.Welsh.GB
IetfLanguageCode.Danish.code -> IetfLanguageCode.Danish
IetfLanguageCode.Danish.DK.code -> IetfLanguageCode.Danish.DK
IetfLanguageCode.Danish.GL.code -> IetfLanguageCode.Danish.GL
IetfLanguageCode.German.code -> IetfLanguageCode.German
IetfLanguageCode.German.AT.code -> IetfLanguageCode.German.AT
IetfLanguageCode.German.BE.code -> IetfLanguageCode.German.BE
IetfLanguageCode.German.CH.code -> IetfLanguageCode.German.CH
IetfLanguageCode.German.DE.code -> IetfLanguageCode.German.DE
IetfLanguageCode.German.IT.code -> IetfLanguageCode.German.IT
IetfLanguageCode.German.LI.code -> IetfLanguageCode.German.LI
IetfLanguageCode.German.LU.code -> IetfLanguageCode.German.LU
IetfLanguageCode.DivehiDhivehiMaldivian.code -> IetfLanguageCode.DivehiDhivehiMaldivian
IetfLanguageCode.Dzongkha.code -> IetfLanguageCode.Dzongkha
IetfLanguageCode.Dzongkha.BT.code -> IetfLanguageCode.Dzongkha.BT
IetfLanguageCode.Ewe.code -> IetfLanguageCode.Ewe
IetfLanguageCode.Ewe.GH.code -> IetfLanguageCode.Ewe.GH
IetfLanguageCode.Ewe.TG.code -> IetfLanguageCode.Ewe.TG
IetfLanguageCode.GreekModern1453.code -> IetfLanguageCode.GreekModern1453
IetfLanguageCode.GreekModern1453.CY.code -> IetfLanguageCode.GreekModern1453.CY
IetfLanguageCode.GreekModern1453.GR.code -> IetfLanguageCode.GreekModern1453.GR
IetfLanguageCode.English.code -> IetfLanguageCode.English
IetfLanguageCode.English.L001.code -> IetfLanguageCode.English.L001
IetfLanguageCode.English.L150.code -> IetfLanguageCode.English.L150
IetfLanguageCode.English.AE.code -> IetfLanguageCode.English.AE
IetfLanguageCode.English.AG.code -> IetfLanguageCode.English.AG
IetfLanguageCode.English.AI.code -> IetfLanguageCode.English.AI
IetfLanguageCode.English.AS.code -> IetfLanguageCode.English.AS
IetfLanguageCode.English.AT.code -> IetfLanguageCode.English.AT
IetfLanguageCode.English.AU.code -> IetfLanguageCode.English.AU
IetfLanguageCode.English.BB.code -> IetfLanguageCode.English.BB
IetfLanguageCode.English.BE.code -> IetfLanguageCode.English.BE
IetfLanguageCode.English.BI.code -> IetfLanguageCode.English.BI
IetfLanguageCode.English.BM.code -> IetfLanguageCode.English.BM
IetfLanguageCode.English.BS.code -> IetfLanguageCode.English.BS
IetfLanguageCode.English.BW.code -> IetfLanguageCode.English.BW
IetfLanguageCode.English.BZ.code -> IetfLanguageCode.English.BZ
IetfLanguageCode.English.CA.code -> IetfLanguageCode.English.CA
IetfLanguageCode.English.CC.code -> IetfLanguageCode.English.CC
IetfLanguageCode.English.CH.code -> IetfLanguageCode.English.CH
IetfLanguageCode.English.CK.code -> IetfLanguageCode.English.CK
IetfLanguageCode.English.CM.code -> IetfLanguageCode.English.CM
IetfLanguageCode.English.CX.code -> IetfLanguageCode.English.CX
IetfLanguageCode.English.CY.code -> IetfLanguageCode.English.CY
IetfLanguageCode.English.DE.code -> IetfLanguageCode.English.DE
IetfLanguageCode.English.DG.code -> IetfLanguageCode.English.DG
IetfLanguageCode.English.DK.code -> IetfLanguageCode.English.DK
IetfLanguageCode.English.DM.code -> IetfLanguageCode.English.DM
IetfLanguageCode.English.ER.code -> IetfLanguageCode.English.ER
IetfLanguageCode.English.FI.code -> IetfLanguageCode.English.FI
IetfLanguageCode.English.FJ.code -> IetfLanguageCode.English.FJ
IetfLanguageCode.English.FK.code -> IetfLanguageCode.English.FK
IetfLanguageCode.English.FM.code -> IetfLanguageCode.English.FM
IetfLanguageCode.English.GB.code -> IetfLanguageCode.English.GB
IetfLanguageCode.English.GD.code -> IetfLanguageCode.English.GD
IetfLanguageCode.English.GG.code -> IetfLanguageCode.English.GG
IetfLanguageCode.English.GH.code -> IetfLanguageCode.English.GH
IetfLanguageCode.English.GI.code -> IetfLanguageCode.English.GI
IetfLanguageCode.English.GM.code -> IetfLanguageCode.English.GM
IetfLanguageCode.English.GU.code -> IetfLanguageCode.English.GU
IetfLanguageCode.English.GY.code -> IetfLanguageCode.English.GY
IetfLanguageCode.English.HK.code -> IetfLanguageCode.English.HK
IetfLanguageCode.English.IE.code -> IetfLanguageCode.English.IE
IetfLanguageCode.English.IL.code -> IetfLanguageCode.English.IL
IetfLanguageCode.English.IM.code -> IetfLanguageCode.English.IM
IetfLanguageCode.English.IN.code -> IetfLanguageCode.English.IN
IetfLanguageCode.English.IO.code -> IetfLanguageCode.English.IO
IetfLanguageCode.English.JE.code -> IetfLanguageCode.English.JE
IetfLanguageCode.English.JM.code -> IetfLanguageCode.English.JM
IetfLanguageCode.English.KE.code -> IetfLanguageCode.English.KE
IetfLanguageCode.English.KI.code -> IetfLanguageCode.English.KI
IetfLanguageCode.English.KN.code -> IetfLanguageCode.English.KN
IetfLanguageCode.English.KY.code -> IetfLanguageCode.English.KY
IetfLanguageCode.English.LC.code -> IetfLanguageCode.English.LC
IetfLanguageCode.English.LR.code -> IetfLanguageCode.English.LR
IetfLanguageCode.English.LS.code -> IetfLanguageCode.English.LS
IetfLanguageCode.English.MG.code -> IetfLanguageCode.English.MG
IetfLanguageCode.English.MH.code -> IetfLanguageCode.English.MH
IetfLanguageCode.English.MO.code -> IetfLanguageCode.English.MO
IetfLanguageCode.English.MP.code -> IetfLanguageCode.English.MP
IetfLanguageCode.English.MS.code -> IetfLanguageCode.English.MS
IetfLanguageCode.English.MT.code -> IetfLanguageCode.English.MT
IetfLanguageCode.English.MU.code -> IetfLanguageCode.English.MU
IetfLanguageCode.English.MW.code -> IetfLanguageCode.English.MW
IetfLanguageCode.English.MY.code -> IetfLanguageCode.English.MY
IetfLanguageCode.English.NA.code -> IetfLanguageCode.English.NA
IetfLanguageCode.English.NF.code -> IetfLanguageCode.English.NF
IetfLanguageCode.English.NG.code -> IetfLanguageCode.English.NG
IetfLanguageCode.English.NL.code -> IetfLanguageCode.English.NL
IetfLanguageCode.English.NR.code -> IetfLanguageCode.English.NR
IetfLanguageCode.English.NU.code -> IetfLanguageCode.English.NU
IetfLanguageCode.English.NZ.code -> IetfLanguageCode.English.NZ
IetfLanguageCode.English.PG.code -> IetfLanguageCode.English.PG
IetfLanguageCode.English.PH.code -> IetfLanguageCode.English.PH
IetfLanguageCode.English.PK.code -> IetfLanguageCode.English.PK
IetfLanguageCode.English.PN.code -> IetfLanguageCode.English.PN
IetfLanguageCode.English.PR.code -> IetfLanguageCode.English.PR
IetfLanguageCode.English.PW.code -> IetfLanguageCode.English.PW
IetfLanguageCode.English.RW.code -> IetfLanguageCode.English.RW
IetfLanguageCode.English.SB.code -> IetfLanguageCode.English.SB
IetfLanguageCode.English.SC.code -> IetfLanguageCode.English.SC
IetfLanguageCode.English.SD.code -> IetfLanguageCode.English.SD
IetfLanguageCode.English.SE.code -> IetfLanguageCode.English.SE
IetfLanguageCode.English.SG.code -> IetfLanguageCode.English.SG
IetfLanguageCode.English.SH.code -> IetfLanguageCode.English.SH
IetfLanguageCode.English.SI.code -> IetfLanguageCode.English.SI
IetfLanguageCode.English.SL.code -> IetfLanguageCode.English.SL
IetfLanguageCode.English.SS.code -> IetfLanguageCode.English.SS
IetfLanguageCode.English.SX.code -> IetfLanguageCode.English.SX
IetfLanguageCode.English.SZ.code -> IetfLanguageCode.English.SZ
IetfLanguageCode.English.TC.code -> IetfLanguageCode.English.TC
IetfLanguageCode.English.TK.code -> IetfLanguageCode.English.TK
IetfLanguageCode.English.TO.code -> IetfLanguageCode.English.TO
IetfLanguageCode.English.TT.code -> IetfLanguageCode.English.TT
IetfLanguageCode.English.TV.code -> IetfLanguageCode.English.TV
IetfLanguageCode.English.TZ.code -> IetfLanguageCode.English.TZ
IetfLanguageCode.English.UG.code -> IetfLanguageCode.English.UG
IetfLanguageCode.English.UM.code -> IetfLanguageCode.English.UM
IetfLanguageCode.English.US.code -> IetfLanguageCode.English.US
IetfLanguageCode.English.US.POSIX.code -> IetfLanguageCode.English.US.POSIX
IetfLanguageCode.English.VC.code -> IetfLanguageCode.English.VC
IetfLanguageCode.English.VG.code -> IetfLanguageCode.English.VG
IetfLanguageCode.English.VI.code -> IetfLanguageCode.English.VI
IetfLanguageCode.English.VU.code -> IetfLanguageCode.English.VU
IetfLanguageCode.English.WS.code -> IetfLanguageCode.English.WS
IetfLanguageCode.English.ZA.code -> IetfLanguageCode.English.ZA
IetfLanguageCode.English.ZM.code -> IetfLanguageCode.English.ZM
IetfLanguageCode.English.ZW.code -> IetfLanguageCode.English.ZW
IetfLanguageCode.Esperanto.code -> IetfLanguageCode.Esperanto
IetfLanguageCode.Esperanto.L001.code -> IetfLanguageCode.Esperanto.L001
IetfLanguageCode.SpanishCastilian.code -> IetfLanguageCode.SpanishCastilian
IetfLanguageCode.SpanishCastilian.L419.code -> IetfLanguageCode.SpanishCastilian.L419
IetfLanguageCode.SpanishCastilian.AR.code -> IetfLanguageCode.SpanishCastilian.AR
IetfLanguageCode.SpanishCastilian.BO.code -> IetfLanguageCode.SpanishCastilian.BO
IetfLanguageCode.SpanishCastilian.BR.code -> IetfLanguageCode.SpanishCastilian.BR
IetfLanguageCode.SpanishCastilian.BZ.code -> IetfLanguageCode.SpanishCastilian.BZ
IetfLanguageCode.SpanishCastilian.CL.code -> IetfLanguageCode.SpanishCastilian.CL
IetfLanguageCode.SpanishCastilian.CO.code -> IetfLanguageCode.SpanishCastilian.CO
IetfLanguageCode.SpanishCastilian.CR.code -> IetfLanguageCode.SpanishCastilian.CR
IetfLanguageCode.SpanishCastilian.CU.code -> IetfLanguageCode.SpanishCastilian.CU
IetfLanguageCode.SpanishCastilian.DO.code -> IetfLanguageCode.SpanishCastilian.DO
IetfLanguageCode.SpanishCastilian.EA.code -> IetfLanguageCode.SpanishCastilian.EA
IetfLanguageCode.SpanishCastilian.EC.code -> IetfLanguageCode.SpanishCastilian.EC
IetfLanguageCode.SpanishCastilian.ES.code -> IetfLanguageCode.SpanishCastilian.ES
IetfLanguageCode.SpanishCastilian.GQ.code -> IetfLanguageCode.SpanishCastilian.GQ
IetfLanguageCode.SpanishCastilian.GT.code -> IetfLanguageCode.SpanishCastilian.GT
IetfLanguageCode.SpanishCastilian.HN.code -> IetfLanguageCode.SpanishCastilian.HN
IetfLanguageCode.SpanishCastilian.IC.code -> IetfLanguageCode.SpanishCastilian.IC
IetfLanguageCode.SpanishCastilian.MX.code -> IetfLanguageCode.SpanishCastilian.MX
IetfLanguageCode.SpanishCastilian.NI.code -> IetfLanguageCode.SpanishCastilian.NI
IetfLanguageCode.SpanishCastilian.PA.code -> IetfLanguageCode.SpanishCastilian.PA
IetfLanguageCode.SpanishCastilian.PE.code -> IetfLanguageCode.SpanishCastilian.PE
IetfLanguageCode.SpanishCastilian.PH.code -> IetfLanguageCode.SpanishCastilian.PH
IetfLanguageCode.SpanishCastilian.PR.code -> IetfLanguageCode.SpanishCastilian.PR
IetfLanguageCode.SpanishCastilian.PY.code -> IetfLanguageCode.SpanishCastilian.PY
IetfLanguageCode.SpanishCastilian.SV.code -> IetfLanguageCode.SpanishCastilian.SV
IetfLanguageCode.SpanishCastilian.US.code -> IetfLanguageCode.SpanishCastilian.US
IetfLanguageCode.SpanishCastilian.UY.code -> IetfLanguageCode.SpanishCastilian.UY
IetfLanguageCode.SpanishCastilian.VE.code -> IetfLanguageCode.SpanishCastilian.VE
IetfLanguageCode.Estonian.code -> IetfLanguageCode.Estonian
IetfLanguageCode.Estonian.EE.code -> IetfLanguageCode.Estonian.EE
IetfLanguageCode.Basque.code -> IetfLanguageCode.Basque
IetfLanguageCode.Basque.ES.code -> IetfLanguageCode.Basque.ES
IetfLanguageCode.Persian.code -> IetfLanguageCode.Persian
IetfLanguageCode.Persian.AF.code -> IetfLanguageCode.Persian.AF
IetfLanguageCode.Persian.IR.code -> IetfLanguageCode.Persian.IR
IetfLanguageCode.Fulah.code -> IetfLanguageCode.Fulah
IetfLanguageCode.Fulah.Adlm.code -> IetfLanguageCode.Fulah.Adlm
IetfLanguageCode.Fulah.Adlm.BF.code -> IetfLanguageCode.Fulah.Adlm.BF
IetfLanguageCode.Fulah.Adlm.CM.code -> IetfLanguageCode.Fulah.Adlm.CM
IetfLanguageCode.Fulah.Adlm.GH.code -> IetfLanguageCode.Fulah.Adlm.GH
IetfLanguageCode.Fulah.Adlm.GM.code -> IetfLanguageCode.Fulah.Adlm.GM
IetfLanguageCode.Fulah.Adlm.GN.code -> IetfLanguageCode.Fulah.Adlm.GN
IetfLanguageCode.Fulah.Adlm.GW.code -> IetfLanguageCode.Fulah.Adlm.GW
IetfLanguageCode.Fulah.Adlm.LR.code -> IetfLanguageCode.Fulah.Adlm.LR
IetfLanguageCode.Fulah.Adlm.MR.code -> IetfLanguageCode.Fulah.Adlm.MR
IetfLanguageCode.Fulah.Adlm.NE.code -> IetfLanguageCode.Fulah.Adlm.NE
IetfLanguageCode.Fulah.Adlm.NG.code -> IetfLanguageCode.Fulah.Adlm.NG
IetfLanguageCode.Fulah.Adlm.SL.code -> IetfLanguageCode.Fulah.Adlm.SL
IetfLanguageCode.Fulah.Adlm.SN.code -> IetfLanguageCode.Fulah.Adlm.SN
IetfLanguageCode.Fulah.Latn.code -> IetfLanguageCode.Fulah.Latn
IetfLanguageCode.Fulah.Latn.BF.code -> IetfLanguageCode.Fulah.Latn.BF
IetfLanguageCode.Fulah.Latn.CM.code -> IetfLanguageCode.Fulah.Latn.CM
IetfLanguageCode.Fulah.Latn.GH.code -> IetfLanguageCode.Fulah.Latn.GH
IetfLanguageCode.Fulah.Latn.GM.code -> IetfLanguageCode.Fulah.Latn.GM
IetfLanguageCode.Fulah.Latn.GN.code -> IetfLanguageCode.Fulah.Latn.GN
IetfLanguageCode.Fulah.Latn.GW.code -> IetfLanguageCode.Fulah.Latn.GW
IetfLanguageCode.Fulah.Latn.LR.code -> IetfLanguageCode.Fulah.Latn.LR
IetfLanguageCode.Fulah.Latn.MR.code -> IetfLanguageCode.Fulah.Latn.MR
IetfLanguageCode.Fulah.Latn.NE.code -> IetfLanguageCode.Fulah.Latn.NE
IetfLanguageCode.Fulah.Latn.NG.code -> IetfLanguageCode.Fulah.Latn.NG
IetfLanguageCode.Fulah.Latn.SL.code -> IetfLanguageCode.Fulah.Latn.SL
IetfLanguageCode.Fulah.Latn.SN.code -> IetfLanguageCode.Fulah.Latn.SN
IetfLanguageCode.Finnish.code -> IetfLanguageCode.Finnish
IetfLanguageCode.Finnish.FI.code -> IetfLanguageCode.Finnish.FI
IetfLanguageCode.Fijian.code -> IetfLanguageCode.Fijian
IetfLanguageCode.Faroese.code -> IetfLanguageCode.Faroese
IetfLanguageCode.Faroese.DK.code -> IetfLanguageCode.Faroese.DK
IetfLanguageCode.Faroese.FO.code -> IetfLanguageCode.Faroese.FO
IetfLanguageCode.French.code -> IetfLanguageCode.French
IetfLanguageCode.French.BE.code -> IetfLanguageCode.French.BE
IetfLanguageCode.French.BF.code -> IetfLanguageCode.French.BF
IetfLanguageCode.French.BI.code -> IetfLanguageCode.French.BI
IetfLanguageCode.French.BJ.code -> IetfLanguageCode.French.BJ
IetfLanguageCode.French.BL.code -> IetfLanguageCode.French.BL
IetfLanguageCode.French.CA.code -> IetfLanguageCode.French.CA
IetfLanguageCode.French.CD.code -> IetfLanguageCode.French.CD
IetfLanguageCode.French.CF.code -> IetfLanguageCode.French.CF
IetfLanguageCode.French.CG.code -> IetfLanguageCode.French.CG
IetfLanguageCode.French.CH.code -> IetfLanguageCode.French.CH
IetfLanguageCode.French.CI.code -> IetfLanguageCode.French.CI
IetfLanguageCode.French.CM.code -> IetfLanguageCode.French.CM
IetfLanguageCode.French.DJ.code -> IetfLanguageCode.French.DJ
IetfLanguageCode.French.DZ.code -> IetfLanguageCode.French.DZ
IetfLanguageCode.French.FR.code -> IetfLanguageCode.French.FR
IetfLanguageCode.French.GA.code -> IetfLanguageCode.French.GA
IetfLanguageCode.French.GF.code -> IetfLanguageCode.French.GF
IetfLanguageCode.French.GN.code -> IetfLanguageCode.French.GN
IetfLanguageCode.French.GP.code -> IetfLanguageCode.French.GP
IetfLanguageCode.French.GQ.code -> IetfLanguageCode.French.GQ
IetfLanguageCode.French.HT.code -> IetfLanguageCode.French.HT
IetfLanguageCode.French.KM.code -> IetfLanguageCode.French.KM
IetfLanguageCode.French.LU.code -> IetfLanguageCode.French.LU
IetfLanguageCode.French.MA.code -> IetfLanguageCode.French.MA
IetfLanguageCode.French.MC.code -> IetfLanguageCode.French.MC
IetfLanguageCode.French.MF.code -> IetfLanguageCode.French.MF
IetfLanguageCode.French.MG.code -> IetfLanguageCode.French.MG
IetfLanguageCode.French.ML.code -> IetfLanguageCode.French.ML
IetfLanguageCode.French.MQ.code -> IetfLanguageCode.French.MQ
IetfLanguageCode.French.MR.code -> IetfLanguageCode.French.MR
IetfLanguageCode.French.MU.code -> IetfLanguageCode.French.MU
IetfLanguageCode.French.NC.code -> IetfLanguageCode.French.NC
IetfLanguageCode.French.NE.code -> IetfLanguageCode.French.NE
IetfLanguageCode.French.PF.code -> IetfLanguageCode.French.PF
IetfLanguageCode.French.PM.code -> IetfLanguageCode.French.PM
IetfLanguageCode.French.RE.code -> IetfLanguageCode.French.RE
IetfLanguageCode.French.RW.code -> IetfLanguageCode.French.RW
IetfLanguageCode.French.SC.code -> IetfLanguageCode.French.SC
IetfLanguageCode.French.SN.code -> IetfLanguageCode.French.SN
IetfLanguageCode.French.SY.code -> IetfLanguageCode.French.SY
IetfLanguageCode.French.TD.code -> IetfLanguageCode.French.TD
IetfLanguageCode.French.TG.code -> IetfLanguageCode.French.TG
IetfLanguageCode.French.TN.code -> IetfLanguageCode.French.TN
IetfLanguageCode.French.VU.code -> IetfLanguageCode.French.VU
IetfLanguageCode.French.WF.code -> IetfLanguageCode.French.WF
IetfLanguageCode.French.YT.code -> IetfLanguageCode.French.YT
IetfLanguageCode.WesternFrisian.code -> IetfLanguageCode.WesternFrisian
IetfLanguageCode.WesternFrisian.NL.code -> IetfLanguageCode.WesternFrisian.NL
IetfLanguageCode.Irish.code -> IetfLanguageCode.Irish
IetfLanguageCode.Irish.GB.code -> IetfLanguageCode.Irish.GB
IetfLanguageCode.Irish.IE.code -> IetfLanguageCode.Irish.IE
IetfLanguageCode.GaelicScottishGaelic.code -> IetfLanguageCode.GaelicScottishGaelic
IetfLanguageCode.GaelicScottishGaelic.GB.code -> IetfLanguageCode.GaelicScottishGaelic.GB
IetfLanguageCode.Galician.code -> IetfLanguageCode.Galician
IetfLanguageCode.Galician.ES.code -> IetfLanguageCode.Galician.ES
IetfLanguageCode.Guarani.code -> IetfLanguageCode.Guarani
IetfLanguageCode.Gujarati.code -> IetfLanguageCode.Gujarati
IetfLanguageCode.Gujarati.IN.code -> IetfLanguageCode.Gujarati.IN
IetfLanguageCode.Manx.code -> IetfLanguageCode.Manx
IetfLanguageCode.Manx.IM.code -> IetfLanguageCode.Manx.IM
IetfLanguageCode.Hausa.code -> IetfLanguageCode.Hausa
IetfLanguageCode.Hausa.GH.code -> IetfLanguageCode.Hausa.GH
IetfLanguageCode.Hausa.NE.code -> IetfLanguageCode.Hausa.NE
IetfLanguageCode.Hausa.NG.code -> IetfLanguageCode.Hausa.NG
IetfLanguageCode.Hebrew.code -> IetfLanguageCode.Hebrew
IetfLanguageCode.Hebrew.IL.code -> IetfLanguageCode.Hebrew.IL
IetfLanguageCode.Hindi.code -> IetfLanguageCode.Hindi
IetfLanguageCode.Hindi.IN.code -> IetfLanguageCode.Hindi.IN
IetfLanguageCode.HiriMotu.code -> IetfLanguageCode.HiriMotu
IetfLanguageCode.Croatian.code -> IetfLanguageCode.Croatian
IetfLanguageCode.Croatian.BA.code -> IetfLanguageCode.Croatian.BA
IetfLanguageCode.Croatian.HR.code -> IetfLanguageCode.Croatian.HR
IetfLanguageCode.HaitianHaitianCreole.code -> IetfLanguageCode.HaitianHaitianCreole
IetfLanguageCode.Hungarian.code -> IetfLanguageCode.Hungarian
IetfLanguageCode.Hungarian.HU.code -> IetfLanguageCode.Hungarian.HU
IetfLanguageCode.Armenian.code -> IetfLanguageCode.Armenian
IetfLanguageCode.Armenian.AM.code -> IetfLanguageCode.Armenian.AM
IetfLanguageCode.Herero.code -> IetfLanguageCode.Herero
IetfLanguageCode.InterlinguaInternationalAuxiliaryLanguageAssociation.code -> IetfLanguageCode.InterlinguaInternationalAuxiliaryLanguageAssociation
IetfLanguageCode.InterlinguaInternationalAuxiliaryLanguageAssociation.L001.code -> IetfLanguageCode.InterlinguaInternationalAuxiliaryLanguageAssociation.L001
IetfLanguageCode.Indonesian.code -> IetfLanguageCode.Indonesian
IetfLanguageCode.Indonesian.ID.code -> IetfLanguageCode.Indonesian.ID
IetfLanguageCode.InterlingueOccidental.code -> IetfLanguageCode.InterlingueOccidental
IetfLanguageCode.Igbo.code -> IetfLanguageCode.Igbo
IetfLanguageCode.Igbo.NG.code -> IetfLanguageCode.Igbo.NG
IetfLanguageCode.SichuanYiNuosu.code -> IetfLanguageCode.SichuanYiNuosu
IetfLanguageCode.SichuanYiNuosu.CN.code -> IetfLanguageCode.SichuanYiNuosu.CN
IetfLanguageCode.Inupiaq.code -> IetfLanguageCode.Inupiaq
IetfLanguageCode.Ido.code -> IetfLanguageCode.Ido
IetfLanguageCode.Icelandic.code -> IetfLanguageCode.Icelandic
IetfLanguageCode.Icelandic.IS.code -> IetfLanguageCode.Icelandic.IS
IetfLanguageCode.Italian.code -> IetfLanguageCode.Italian
IetfLanguageCode.Italian.CH.code -> IetfLanguageCode.Italian.CH
IetfLanguageCode.Italian.IT.code -> IetfLanguageCode.Italian.IT
IetfLanguageCode.Italian.SM.code -> IetfLanguageCode.Italian.SM
IetfLanguageCode.Italian.VA.code -> IetfLanguageCode.Italian.VA
IetfLanguageCode.Inuktitut.code -> IetfLanguageCode.Inuktitut
IetfLanguageCode.Japanese.code -> IetfLanguageCode.Japanese
IetfLanguageCode.Japanese.JP.code -> IetfLanguageCode.Japanese.JP
IetfLanguageCode.Javanese.code -> IetfLanguageCode.Javanese
IetfLanguageCode.Javanese.ID.code -> IetfLanguageCode.Javanese.ID
IetfLanguageCode.Georgian.code -> IetfLanguageCode.Georgian
IetfLanguageCode.Georgian.GE.code -> IetfLanguageCode.Georgian.GE
IetfLanguageCode.Kongo.code -> IetfLanguageCode.Kongo
IetfLanguageCode.KikuyuGikuyu.code -> IetfLanguageCode.KikuyuGikuyu
IetfLanguageCode.KikuyuGikuyu.KE.code -> IetfLanguageCode.KikuyuGikuyu.KE
IetfLanguageCode.KuanyamaKwanyama.code -> IetfLanguageCode.KuanyamaKwanyama
IetfLanguageCode.Kazakh.code -> IetfLanguageCode.Kazakh
IetfLanguageCode.Kazakh.KZ.code -> IetfLanguageCode.Kazakh.KZ
IetfLanguageCode.KalaallisutGreenlandic.code -> IetfLanguageCode.KalaallisutGreenlandic
IetfLanguageCode.KalaallisutGreenlandic.GL.code -> IetfLanguageCode.KalaallisutGreenlandic.GL
IetfLanguageCode.CentralKhmer.code -> IetfLanguageCode.CentralKhmer
IetfLanguageCode.CentralKhmer.KH.code -> IetfLanguageCode.CentralKhmer.KH
IetfLanguageCode.Kannada.code -> IetfLanguageCode.Kannada
IetfLanguageCode.Kannada.IN.code -> IetfLanguageCode.Kannada.IN
IetfLanguageCode.Korean.code -> IetfLanguageCode.Korean
IetfLanguageCode.Korean.KP.code -> IetfLanguageCode.Korean.KP
IetfLanguageCode.Korean.KR.code -> IetfLanguageCode.Korean.KR
IetfLanguageCode.Kanuri.code -> IetfLanguageCode.Kanuri
IetfLanguageCode.Kashmiri.code -> IetfLanguageCode.Kashmiri
IetfLanguageCode.Kashmiri.Arab.code -> IetfLanguageCode.Kashmiri.Arab
IetfLanguageCode.Kashmiri.Arab.IN.code -> IetfLanguageCode.Kashmiri.Arab.IN
IetfLanguageCode.Kurdish.code -> IetfLanguageCode.Kurdish
IetfLanguageCode.Kurdish.TR.code -> IetfLanguageCode.Kurdish.TR
IetfLanguageCode.Komi.code -> IetfLanguageCode.Komi
IetfLanguageCode.Cornish.code -> IetfLanguageCode.Cornish
IetfLanguageCode.Cornish.GB.code -> IetfLanguageCode.Cornish.GB
IetfLanguageCode.KirghizKyrgyz.code -> IetfLanguageCode.KirghizKyrgyz
IetfLanguageCode.KirghizKyrgyz.KG.code -> IetfLanguageCode.KirghizKyrgyz.KG
IetfLanguageCode.Latin.code -> IetfLanguageCode.Latin
IetfLanguageCode.LuxembourgishLetzeburgesch.code -> IetfLanguageCode.LuxembourgishLetzeburgesch
IetfLanguageCode.LuxembourgishLetzeburgesch.LU.code -> IetfLanguageCode.LuxembourgishLetzeburgesch.LU
IetfLanguageCode.Ganda.code -> IetfLanguageCode.Ganda
IetfLanguageCode.Ganda.UG.code -> IetfLanguageCode.Ganda.UG
IetfLanguageCode.LimburganLimburgerLimburgish.code -> IetfLanguageCode.LimburganLimburgerLimburgish
IetfLanguageCode.Lingala.code -> IetfLanguageCode.Lingala
IetfLanguageCode.Lingala.AO.code -> IetfLanguageCode.Lingala.AO
IetfLanguageCode.Lingala.CD.code -> IetfLanguageCode.Lingala.CD
IetfLanguageCode.Lingala.CF.code -> IetfLanguageCode.Lingala.CF
IetfLanguageCode.Lingala.CG.code -> IetfLanguageCode.Lingala.CG
IetfLanguageCode.Lao.code -> IetfLanguageCode.Lao
IetfLanguageCode.Lao.LA.code -> IetfLanguageCode.Lao.LA
IetfLanguageCode.Lithuanian.code -> IetfLanguageCode.Lithuanian
IetfLanguageCode.Lithuanian.LT.code -> IetfLanguageCode.Lithuanian.LT
IetfLanguageCode.LubaKatanga.code -> IetfLanguageCode.LubaKatanga
IetfLanguageCode.LubaKatanga.CD.code -> IetfLanguageCode.LubaKatanga.CD
IetfLanguageCode.Latvian.code -> IetfLanguageCode.Latvian
IetfLanguageCode.Latvian.LV.code -> IetfLanguageCode.Latvian.LV
IetfLanguageCode.Malagasy.code -> IetfLanguageCode.Malagasy
IetfLanguageCode.Malagasy.MG.code -> IetfLanguageCode.Malagasy.MG
IetfLanguageCode.Marshallese.code -> IetfLanguageCode.Marshallese
IetfLanguageCode.Maori.code -> IetfLanguageCode.Maori
IetfLanguageCode.Maori.NZ.code -> IetfLanguageCode.Maori.NZ
IetfLanguageCode.Macedonian.code -> IetfLanguageCode.Macedonian
IetfLanguageCode.Macedonian.MK.code -> IetfLanguageCode.Macedonian.MK
IetfLanguageCode.Malayalam.code -> IetfLanguageCode.Malayalam
IetfLanguageCode.Malayalam.IN.code -> IetfLanguageCode.Malayalam.IN
IetfLanguageCode.Mongolian.code -> IetfLanguageCode.Mongolian
IetfLanguageCode.Mongolian.MN.code -> IetfLanguageCode.Mongolian.MN
IetfLanguageCode.Marathi.code -> IetfLanguageCode.Marathi
IetfLanguageCode.Marathi.IN.code -> IetfLanguageCode.Marathi.IN
IetfLanguageCode.Malay.code -> IetfLanguageCode.Malay
IetfLanguageCode.Malay.BN.code -> IetfLanguageCode.Malay.BN
IetfLanguageCode.Malay.ID.code -> IetfLanguageCode.Malay.ID
IetfLanguageCode.Malay.MY.code -> IetfLanguageCode.Malay.MY
IetfLanguageCode.Malay.SG.code -> IetfLanguageCode.Malay.SG
IetfLanguageCode.Maltese.code -> IetfLanguageCode.Maltese
IetfLanguageCode.Maltese.MT.code -> IetfLanguageCode.Maltese.MT
IetfLanguageCode.Burmese.code -> IetfLanguageCode.Burmese
IetfLanguageCode.Burmese.MM.code -> IetfLanguageCode.Burmese.MM
IetfLanguageCode.Nauru.code -> IetfLanguageCode.Nauru
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.NO.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal.NO
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.SJ.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal.SJ
IetfLanguageCode.NdebeleNorthNorthNdebele.code -> IetfLanguageCode.NdebeleNorthNorthNdebele
IetfLanguageCode.NdebeleNorthNorthNdebele.ZW.code -> IetfLanguageCode.NdebeleNorthNorthNdebele.ZW
IetfLanguageCode.Nepali.code -> IetfLanguageCode.Nepali
IetfLanguageCode.Nepali.IN.code -> IetfLanguageCode.Nepali.IN
IetfLanguageCode.Nepali.NP.code -> IetfLanguageCode.Nepali.NP
IetfLanguageCode.Ndonga.code -> IetfLanguageCode.Ndonga
IetfLanguageCode.DutchFlemish.code -> IetfLanguageCode.DutchFlemish
IetfLanguageCode.DutchFlemish.AW.code -> IetfLanguageCode.DutchFlemish.AW
IetfLanguageCode.DutchFlemish.BE.code -> IetfLanguageCode.DutchFlemish.BE
IetfLanguageCode.DutchFlemish.BQ.code -> IetfLanguageCode.DutchFlemish.BQ
IetfLanguageCode.DutchFlemish.CW.code -> IetfLanguageCode.DutchFlemish.CW
IetfLanguageCode.DutchFlemish.NL.code -> IetfLanguageCode.DutchFlemish.NL
IetfLanguageCode.DutchFlemish.SR.code -> IetfLanguageCode.DutchFlemish.SR
IetfLanguageCode.DutchFlemish.SX.code -> IetfLanguageCode.DutchFlemish.SX
IetfLanguageCode.NorwegianNynorskNynorskNorwegian.code -> IetfLanguageCode.NorwegianNynorskNynorskNorwegian
IetfLanguageCode.NorwegianNynorskNynorskNorwegian.NO.code -> IetfLanguageCode.NorwegianNynorskNynorskNorwegian.NO
IetfLanguageCode.Norwegian.code -> IetfLanguageCode.Norwegian
IetfLanguageCode.NdebeleSouthSouthNdebele.code -> IetfLanguageCode.NdebeleSouthSouthNdebele
IetfLanguageCode.NavajoNavaho.code -> IetfLanguageCode.NavajoNavaho
IetfLanguageCode.ChichewaChewaNyanja.code -> IetfLanguageCode.ChichewaChewaNyanja
IetfLanguageCode.OccitanPost1500.code -> IetfLanguageCode.OccitanPost1500
IetfLanguageCode.Ojibwa.code -> IetfLanguageCode.Ojibwa
IetfLanguageCode.Oromo.code -> IetfLanguageCode.Oromo
IetfLanguageCode.Oromo.ET.code -> IetfLanguageCode.Oromo.ET
IetfLanguageCode.Oromo.KE.code -> IetfLanguageCode.Oromo.KE
IetfLanguageCode.Oriya.code -> IetfLanguageCode.Oriya
IetfLanguageCode.Oriya.IN.code -> IetfLanguageCode.Oriya.IN
IetfLanguageCode.OssetianOssetic.code -> IetfLanguageCode.OssetianOssetic
IetfLanguageCode.OssetianOssetic.GE.code -> IetfLanguageCode.OssetianOssetic.GE
IetfLanguageCode.OssetianOssetic.RU.code -> IetfLanguageCode.OssetianOssetic.RU
IetfLanguageCode.PanjabiPunjabi.code -> IetfLanguageCode.PanjabiPunjabi
IetfLanguageCode.PanjabiPunjabi.Arab.code -> IetfLanguageCode.PanjabiPunjabi.Arab
IetfLanguageCode.PanjabiPunjabi.Arab.PK.code -> IetfLanguageCode.PanjabiPunjabi.Arab.PK
IetfLanguageCode.PanjabiPunjabi.Guru.code -> IetfLanguageCode.PanjabiPunjabi.Guru
IetfLanguageCode.PanjabiPunjabi.Guru.IN.code -> IetfLanguageCode.PanjabiPunjabi.Guru.IN
IetfLanguageCode.Pali.code -> IetfLanguageCode.Pali
IetfLanguageCode.Polish.code -> IetfLanguageCode.Polish
IetfLanguageCode.Polish.PL.code -> IetfLanguageCode.Polish.PL
IetfLanguageCode.PushtoPashto.code -> IetfLanguageCode.PushtoPashto
IetfLanguageCode.PushtoPashto.AF.code -> IetfLanguageCode.PushtoPashto.AF
IetfLanguageCode.PushtoPashto.PK.code -> IetfLanguageCode.PushtoPashto.PK
IetfLanguageCode.Portuguese.code -> IetfLanguageCode.Portuguese
IetfLanguageCode.Portuguese.AO.code -> IetfLanguageCode.Portuguese.AO
IetfLanguageCode.Portuguese.BR.code -> IetfLanguageCode.Portuguese.BR
IetfLanguageCode.Portuguese.CH.code -> IetfLanguageCode.Portuguese.CH
IetfLanguageCode.Portuguese.CV.code -> IetfLanguageCode.Portuguese.CV
IetfLanguageCode.Portuguese.GQ.code -> IetfLanguageCode.Portuguese.GQ
IetfLanguageCode.Portuguese.GW.code -> IetfLanguageCode.Portuguese.GW
IetfLanguageCode.Portuguese.LU.code -> IetfLanguageCode.Portuguese.LU
IetfLanguageCode.Portuguese.MO.code -> IetfLanguageCode.Portuguese.MO
IetfLanguageCode.Portuguese.MZ.code -> IetfLanguageCode.Portuguese.MZ
IetfLanguageCode.Portuguese.PT.code -> IetfLanguageCode.Portuguese.PT
IetfLanguageCode.Portuguese.ST.code -> IetfLanguageCode.Portuguese.ST
IetfLanguageCode.Portuguese.TL.code -> IetfLanguageCode.Portuguese.TL
IetfLanguageCode.Quechua.code -> IetfLanguageCode.Quechua
IetfLanguageCode.Quechua.BO.code -> IetfLanguageCode.Quechua.BO
IetfLanguageCode.Quechua.EC.code -> IetfLanguageCode.Quechua.EC
IetfLanguageCode.Quechua.PE.code -> IetfLanguageCode.Quechua.PE
IetfLanguageCode.Romansh.code -> IetfLanguageCode.Romansh
IetfLanguageCode.Romansh.CH.code -> IetfLanguageCode.Romansh.CH
IetfLanguageCode.Rundi.code -> IetfLanguageCode.Rundi
IetfLanguageCode.Rundi.BI.code -> IetfLanguageCode.Rundi.BI
IetfLanguageCode.RomanianMoldavianMoldovan.code -> IetfLanguageCode.RomanianMoldavianMoldovan
IetfLanguageCode.RomanianMoldavianMoldovan.MD.code -> IetfLanguageCode.RomanianMoldavianMoldovan.MD
IetfLanguageCode.RomanianMoldavianMoldovan.RO.code -> IetfLanguageCode.RomanianMoldavianMoldovan.RO
IetfLanguageCode.Russian.code -> IetfLanguageCode.Russian
IetfLanguageCode.Russian.BY.code -> IetfLanguageCode.Russian.BY
IetfLanguageCode.Russian.KG.code -> IetfLanguageCode.Russian.KG
IetfLanguageCode.Russian.KZ.code -> IetfLanguageCode.Russian.KZ
IetfLanguageCode.Russian.MD.code -> IetfLanguageCode.Russian.MD
IetfLanguageCode.Russian.RU.code -> IetfLanguageCode.Russian.RU
IetfLanguageCode.Russian.UA.code -> IetfLanguageCode.Russian.UA
IetfLanguageCode.Kinyarwanda.code -> IetfLanguageCode.Kinyarwanda
IetfLanguageCode.Kinyarwanda.RW.code -> IetfLanguageCode.Kinyarwanda.RW
IetfLanguageCode.Sanskrit.code -> IetfLanguageCode.Sanskrit
IetfLanguageCode.Sardinian.code -> IetfLanguageCode.Sardinian
IetfLanguageCode.Sindhi.code -> IetfLanguageCode.Sindhi
IetfLanguageCode.Sindhi.Arab.code -> IetfLanguageCode.Sindhi.Arab
IetfLanguageCode.Sindhi.Arab.PK.code -> IetfLanguageCode.Sindhi.Arab.PK
IetfLanguageCode.Sindhi.Deva.code -> IetfLanguageCode.Sindhi.Deva
IetfLanguageCode.Sindhi.Deva.IN.code -> IetfLanguageCode.Sindhi.Deva.IN
IetfLanguageCode.NorthernSami.code -> IetfLanguageCode.NorthernSami
IetfLanguageCode.NorthernSami.FI.code -> IetfLanguageCode.NorthernSami.FI
IetfLanguageCode.NorthernSami.NO.code -> IetfLanguageCode.NorthernSami.NO
IetfLanguageCode.NorthernSami.SE.code -> IetfLanguageCode.NorthernSami.SE
IetfLanguageCode.Sango.code -> IetfLanguageCode.Sango
IetfLanguageCode.Sango.CF.code -> IetfLanguageCode.Sango.CF
IetfLanguageCode.SinhalaSinhalese.code -> IetfLanguageCode.SinhalaSinhalese
IetfLanguageCode.SinhalaSinhalese.LK.code -> IetfLanguageCode.SinhalaSinhalese.LK
IetfLanguageCode.Slovak.code -> IetfLanguageCode.Slovak
IetfLanguageCode.Slovak.SK.code -> IetfLanguageCode.Slovak.SK
IetfLanguageCode.Slovenian.code -> IetfLanguageCode.Slovenian
IetfLanguageCode.Slovenian.SI.code -> IetfLanguageCode.Slovenian.SI
IetfLanguageCode.Samoan.code -> IetfLanguageCode.Samoan
IetfLanguageCode.Shona.code -> IetfLanguageCode.Shona
IetfLanguageCode.Shona.ZW.code -> IetfLanguageCode.Shona.ZW
IetfLanguageCode.Somali.code -> IetfLanguageCode.Somali
IetfLanguageCode.Somali.DJ.code -> IetfLanguageCode.Somali.DJ
IetfLanguageCode.Somali.ET.code -> IetfLanguageCode.Somali.ET
IetfLanguageCode.Somali.KE.code -> IetfLanguageCode.Somali.KE
IetfLanguageCode.Somali.SO.code -> IetfLanguageCode.Somali.SO
IetfLanguageCode.Albanian.code -> IetfLanguageCode.Albanian
IetfLanguageCode.Albanian.AL.code -> IetfLanguageCode.Albanian.AL
IetfLanguageCode.Albanian.MK.code -> IetfLanguageCode.Albanian.MK
IetfLanguageCode.Albanian.XK.code -> IetfLanguageCode.Albanian.XK
IetfLanguageCode.Serbian.code -> IetfLanguageCode.Serbian
IetfLanguageCode.Serbian.Cyrl.code -> IetfLanguageCode.Serbian.Cyrl
IetfLanguageCode.Serbian.Cyrl.BA.code -> IetfLanguageCode.Serbian.Cyrl.BA
IetfLanguageCode.Serbian.Cyrl.ME.code -> IetfLanguageCode.Serbian.Cyrl.ME
IetfLanguageCode.Serbian.Cyrl.RS.code -> IetfLanguageCode.Serbian.Cyrl.RS
IetfLanguageCode.Serbian.Cyrl.XK.code -> IetfLanguageCode.Serbian.Cyrl.XK
IetfLanguageCode.Serbian.Latn.code -> IetfLanguageCode.Serbian.Latn
IetfLanguageCode.Serbian.Latn.BA.code -> IetfLanguageCode.Serbian.Latn.BA
IetfLanguageCode.Serbian.Latn.ME.code -> IetfLanguageCode.Serbian.Latn.ME
IetfLanguageCode.Serbian.Latn.RS.code -> IetfLanguageCode.Serbian.Latn.RS
IetfLanguageCode.Serbian.Latn.XK.code -> IetfLanguageCode.Serbian.Latn.XK
IetfLanguageCode.Swati.code -> IetfLanguageCode.Swati
IetfLanguageCode.SothoSouthern.code -> IetfLanguageCode.SothoSouthern
IetfLanguageCode.Sundanese.code -> IetfLanguageCode.Sundanese
IetfLanguageCode.Sundanese.Latn.code -> IetfLanguageCode.Sundanese.Latn
IetfLanguageCode.Sundanese.Latn.ID.code -> IetfLanguageCode.Sundanese.Latn.ID
IetfLanguageCode.Swedish.code -> IetfLanguageCode.Swedish
IetfLanguageCode.Swedish.AX.code -> IetfLanguageCode.Swedish.AX
IetfLanguageCode.Swedish.FI.code -> IetfLanguageCode.Swedish.FI
IetfLanguageCode.Swedish.SE.code -> IetfLanguageCode.Swedish.SE
IetfLanguageCode.Swahili.code -> IetfLanguageCode.Swahili
IetfLanguageCode.Swahili.CD.code -> IetfLanguageCode.Swahili.CD
IetfLanguageCode.Swahili.KE.code -> IetfLanguageCode.Swahili.KE
IetfLanguageCode.Swahili.TZ.code -> IetfLanguageCode.Swahili.TZ
IetfLanguageCode.Swahili.UG.code -> IetfLanguageCode.Swahili.UG
IetfLanguageCode.Tamil.code -> IetfLanguageCode.Tamil
IetfLanguageCode.Tamil.IN.code -> IetfLanguageCode.Tamil.IN
IetfLanguageCode.Tamil.LK.code -> IetfLanguageCode.Tamil.LK
IetfLanguageCode.Tamil.MY.code -> IetfLanguageCode.Tamil.MY
IetfLanguageCode.Tamil.SG.code -> IetfLanguageCode.Tamil.SG
IetfLanguageCode.Telugu.code -> IetfLanguageCode.Telugu
IetfLanguageCode.Telugu.IN.code -> IetfLanguageCode.Telugu.IN
IetfLanguageCode.Tajik.code -> IetfLanguageCode.Tajik
IetfLanguageCode.Tajik.TJ.code -> IetfLanguageCode.Tajik.TJ
IetfLanguageCode.Thai.code -> IetfLanguageCode.Thai
IetfLanguageCode.Thai.TH.code -> IetfLanguageCode.Thai.TH
IetfLanguageCode.Tigrinya.code -> IetfLanguageCode.Tigrinya
IetfLanguageCode.Tigrinya.ER.code -> IetfLanguageCode.Tigrinya.ER
IetfLanguageCode.Tigrinya.ET.code -> IetfLanguageCode.Tigrinya.ET
IetfLanguageCode.Turkmen.code -> IetfLanguageCode.Turkmen
IetfLanguageCode.Turkmen.TM.code -> IetfLanguageCode.Turkmen.TM
IetfLanguageCode.Tagalog.code -> IetfLanguageCode.Tagalog
IetfLanguageCode.Tswana.code -> IetfLanguageCode.Tswana
IetfLanguageCode.TongaTongaIslands.code -> IetfLanguageCode.TongaTongaIslands
IetfLanguageCode.TongaTongaIslands.TO.code -> IetfLanguageCode.TongaTongaIslands.TO
IetfLanguageCode.Turkish.code -> IetfLanguageCode.Turkish
IetfLanguageCode.Turkish.CY.code -> IetfLanguageCode.Turkish.CY
IetfLanguageCode.Turkish.TR.code -> IetfLanguageCode.Turkish.TR
IetfLanguageCode.Tsonga.code -> IetfLanguageCode.Tsonga
IetfLanguageCode.Tatar.code -> IetfLanguageCode.Tatar
IetfLanguageCode.Tatar.RU.code -> IetfLanguageCode.Tatar.RU
IetfLanguageCode.Twi.code -> IetfLanguageCode.Twi
IetfLanguageCode.Tahitian.code -> IetfLanguageCode.Tahitian
IetfLanguageCode.UighurUyghur.code -> IetfLanguageCode.UighurUyghur
IetfLanguageCode.UighurUyghur.CN.code -> IetfLanguageCode.UighurUyghur.CN
IetfLanguageCode.Ukrainian.code -> IetfLanguageCode.Ukrainian
IetfLanguageCode.Ukrainian.UA.code -> IetfLanguageCode.Ukrainian.UA
IetfLanguageCode.Urdu.code -> IetfLanguageCode.Urdu
IetfLanguageCode.Urdu.IN.code -> IetfLanguageCode.Urdu.IN
IetfLanguageCode.Urdu.PK.code -> IetfLanguageCode.Urdu.PK
IetfLanguageCode.Uzbek.code -> IetfLanguageCode.Uzbek
IetfLanguageCode.Uzbek.Arab.code -> IetfLanguageCode.Uzbek.Arab
IetfLanguageCode.Uzbek.Arab.AF.code -> IetfLanguageCode.Uzbek.Arab.AF
IetfLanguageCode.Uzbek.Cyrl.code -> IetfLanguageCode.Uzbek.Cyrl
IetfLanguageCode.Uzbek.Cyrl.UZ.code -> IetfLanguageCode.Uzbek.Cyrl.UZ
IetfLanguageCode.Uzbek.Latn.code -> IetfLanguageCode.Uzbek.Latn
IetfLanguageCode.Uzbek.Latn.UZ.code -> IetfLanguageCode.Uzbek.Latn.UZ
IetfLanguageCode.Venda.code -> IetfLanguageCode.Venda
IetfLanguageCode.Vietnamese.code -> IetfLanguageCode.Vietnamese
IetfLanguageCode.Vietnamese.VN.code -> IetfLanguageCode.Vietnamese.VN
IetfLanguageCode.Volapuk.code -> IetfLanguageCode.Volapuk
IetfLanguageCode.Volapuk.L001.code -> IetfLanguageCode.Volapuk.L001
IetfLanguageCode.Walloon.code -> IetfLanguageCode.Walloon
IetfLanguageCode.Wolof.code -> IetfLanguageCode.Wolof
IetfLanguageCode.Wolof.SN.code -> IetfLanguageCode.Wolof.SN
IetfLanguageCode.Xhosa.code -> IetfLanguageCode.Xhosa
IetfLanguageCode.Xhosa.ZA.code -> IetfLanguageCode.Xhosa.ZA
IetfLanguageCode.Yiddish.code -> IetfLanguageCode.Yiddish
IetfLanguageCode.Yiddish.L001.code -> IetfLanguageCode.Yiddish.L001
IetfLanguageCode.Yoruba.code -> IetfLanguageCode.Yoruba
IetfLanguageCode.Yoruba.BJ.code -> IetfLanguageCode.Yoruba.BJ
IetfLanguageCode.Yoruba.NG.code -> IetfLanguageCode.Yoruba.NG
IetfLanguageCode.ZhuangChuang.code -> IetfLanguageCode.ZhuangChuang
IetfLanguageCode.Chinese.code -> IetfLanguageCode.Chinese
IetfLanguageCode.Chinese.Hans.code -> IetfLanguageCode.Chinese.Hans
IetfLanguageCode.Chinese.Hans.CN.code -> IetfLanguageCode.Chinese.Hans.CN
IetfLanguageCode.Chinese.Hans.HK.code -> IetfLanguageCode.Chinese.Hans.HK
IetfLanguageCode.Chinese.Hans.MO.code -> IetfLanguageCode.Chinese.Hans.MO
IetfLanguageCode.Chinese.Hans.SG.code -> IetfLanguageCode.Chinese.Hans.SG
IetfLanguageCode.Chinese.Hant.code -> IetfLanguageCode.Chinese.Hant
IetfLanguageCode.Chinese.Hant.HK.code -> IetfLanguageCode.Chinese.Hant.HK
IetfLanguageCode.Chinese.Hant.MO.code -> IetfLanguageCode.Chinese.Hant.MO
IetfLanguageCode.Chinese.Hant.TW.code -> IetfLanguageCode.Chinese.Hant.TW
IetfLanguageCode.Zulu.code -> IetfLanguageCode.Zulu
IetfLanguageCode.Zulu.ZA.code -> IetfLanguageCode.Zulu.ZA
else -> IetfLanguageCode.UnknownIetfLanguageCode(this)
}
}
fun convertToIetfLanguageCode(code: String) = code.asIetfLanguageCode()
fun IetfLanguageCode(code: String) = code.asIetfLanguageCode()

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.language_codes"/>

View File

@@ -0,0 +1,52 @@
import requests
from bs4 import BeautifulSoup
import pandas as pd
import itertools
def fix_name(category, raw_name):
splitted = raw_name.replace('-', '+').replace('.', '+').replace(',', '+').split('+')
out1 = ""
for s in splitted:
out1 += s.capitalize()
result = ""
if out1[0].isdigit():
result += category[0].capitalize()
result += out1
else:
result += out1
return result
if __name__ == '__main__':
df = pd.read_html(open('table.html', 'r'))
mimes = []
for row in df[0].iterrows():
mime = row[1][1]
mime_category = mime.split('/', 1)[0]
mime_name = mime.split('/', 1)[1]
mimes.append({
'mime_category': mime_category,
'mime_name': mime_name,
})
# codegen
mimes.sort(key=lambda x: x['mime_category'])
grouped = itertools.groupby(mimes, lambda x: x['mime_category'])
code = ''
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
code2 += ' KnownMimeTypes.Any,\n'
for key, group in grouped:
group_name = key.capitalize()
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
for mime in group:
name = fix_name(mime['mime_category'], mime['mime_name'])
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name'])
code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name)
code += '}\n\n'
code2 += ')\n'
with open('out1.txt', 'w') as file:
file.write(code)
with open('out2.txt', 'w') as file:
file.write(code2)

View File

@@ -1,3 +1,5 @@
@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.Serializable

View File

@@ -24,3 +24,8 @@ kotlin {
}
apply from: "$defaultAndroidSettingsPresetPath"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -5,7 +5,11 @@ apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main.kotlinOptions.useIR = true
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
sourceSets {
@@ -28,3 +32,8 @@ kotlin {
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -5,9 +5,13 @@ apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main.kotlinOptions.useIR = true
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js (BOTH) {
js (IR) {
browser()
nodejs()
}
@@ -50,3 +54,8 @@ kotlin {
}
apply from: "$defaultAndroidSettingsPresetPath"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -5,3 +5,13 @@ plugins {
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.common")
}
}
}
}

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.pagination
import dev.inmo.micro_utils.common.intersect
import kotlin.math.ceil
import kotlin.math.floor
@@ -9,7 +10,7 @@ import kotlin.math.floor
* If you want to request something, you should use [SimplePagination]. If you need to return some result including
* pagination - [PaginationResult]
*/
interface Pagination {
interface Pagination : ClosedRange<Int> {
/**
* Started with 0.
* Number of page inside of pagination. Offset can be calculated as [page] * [size]
@@ -20,6 +21,17 @@ interface Pagination {
* Size of current page. Offset can be calculated as [page] * [size]
*/
val size: Int
override val start: Int
get() = page * size
override val endInclusive: Int
get() = start + size - 1
}
fun Pagination.intersect(
other: Pagination
): Pagination? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
PaginationByIndexes(it.first, it.second)
}
/**
@@ -32,7 +44,7 @@ inline val Pagination.isFirstPage
* First number in index of objects. It can be used as offset for databases or other data sources
*/
val Pagination.firstIndex: Int
get() = page * size
get() = start
/**
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
@@ -41,7 +53,7 @@ val Pagination.firstIndex: Int
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
*/
val Pagination.lastIndexExclusive: Int
get() = firstIndex + size
get() = endInclusive + 1
/**
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
@@ -50,7 +62,7 @@ val Pagination.lastIndexExclusive: Int
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
*/
val Pagination.lastIndex: Int
get() = lastIndexExclusive - 1
get() = endInclusive
/**
* Calculates pages count for given [datasetSize]

View File

@@ -33,3 +33,8 @@ suspend fun <T> doAllWithCurrentPaging(
block
)
}
suspend fun <T> doForAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
) = doAllWithCurrentPaging(initialPagination, block)

View File

@@ -16,6 +16,16 @@ suspend fun <T> getAll(
return results.toList()
}
suspend fun <T, R> R.getAllBy(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ paginationMapper(it) },
{ block(it) }
)
suspend fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
@@ -25,6 +35,14 @@ suspend fun <T> getAllWithNextPaging(
block
)
suspend fun <T, R> R.getAllByWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging(
initialPagination,
{ block(it) }
)
suspend fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
@@ -33,3 +51,11 @@ suspend fun <T> getAllWithCurrentPaging(
{ it.currentPageIfNotEmpty() },
block
)
suspend fun <T, R> R.getAllByWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithCurrentPaging(
initialPagination,
{ block(it) }
)

View File

@@ -5,8 +5,8 @@ import io.ktor.http.Parameters
val Parameters.extractPagination: Pagination
get() = SimplePagination(
get("page") ?.toIntOrNull() ?: 0,
get("size") ?.toIntOrNull() ?: defaultPaginationPageSize
get(paginationPageKey) ?.toIntOrNull() ?: 0,
get(paginationSizeKey) ?.toIntOrNull() ?: defaultPaginationPageSize
)
val ApplicationCall.extractPagination: Pagination

View File

@@ -6,19 +6,29 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
protected val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
open class ReadCRUDCacheRepo<ObjectType, IdType>(
protected val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
protected val kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
protected val idGetter: (ObjectType) -> IdType
) : CRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo {
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
kvCache.set(id, it)
})
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
}
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType
) : ReadCRUDCacheRepo<ObjectType, IdType>(
parentRepo,
kvCache,
idGetter
), CRUDRepo<ObjectType, IdType, InputValueType>, WriteCRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
}

View File

@@ -7,14 +7,19 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class KeyValueCacheRepo<Key,Value>(
protected val parentRepo: KeyValueRepo<Key, Value>,
open class ReadKeyValueCacheRepo<Key,Value>(
protected val parentRepo: ReadKeyValueRepo<Key, Value>,
protected val kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : KeyValueRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
) : ReadKeyValueRepo<Key,Value> by parentRepo {
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
}
open class KeyValueCacheRepo<Key,Value>(
parentRepo: KeyValueRepo<Key, Value>,
kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
}

View File

@@ -11,15 +11,10 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class KeyValuesCacheRepo<Key,Value>(
protected val parentRepo: KeyValuesRepo<Key, Value>,
protected val kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : KeyValuesRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
open class ReadKeyValuesCacheRepo<Key,Value>(
protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
protected val kvCache: KVCache<Key, List<Value>>
) : ReadKeyValuesRepo<Key,Value> by parentRepo {
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return kvCache.get(k) ?.paginate(
pagination.let { if (reversed) it.reverse(count(k)) else it }
@@ -35,3 +30,13 @@ open class KeyValuesCacheRepo<Key,Value>(
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v)
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
}
open class KeyValuesCacheRepo<Key,Value>(
parentRepo: KeyValuesRepo<Key, Value>,
kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
}

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.flow.Flow
@@ -47,6 +48,7 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
suspend fun remove(toRemove: Map<Key, List<Value>>)
suspend fun clear(k: Key)
suspend fun clearWithValue(v: Value)
suspend fun set(toSet: Map<Key, List<Value>>) {
toSet.keys.forEach { key -> clear(key) }
@@ -87,7 +89,19 @@ suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, vararg v: Value
) = set(k, v.toList())
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
override suspend fun clearWithValue(v: Value) {
doWithPagination {
val keysResult = keys(v, it)
if (keysResult.results.isNotEmpty()) {
remove(keysResult.results.map { it to listOf(v) })
}
keysResult.currentPageIfNotEmpty()
}
}
}
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(

View File

@@ -114,6 +114,7 @@ open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
}
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
override suspend fun clearWithValue(v: FromValue) = to.clearWithValue(v.toOutValue())
}
@Suppress("NOTHING_TO_INLINE")

View File

@@ -2,6 +2,8 @@ package dev.inmo.micro_utils.repos
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import androidx.core.database.*
import dev.inmo.micro_utils.repos.getLongOrNull
fun createTableQuery(
tableName: String,
@@ -32,6 +34,11 @@ fun SQLiteDatabase.createTable(
fun Cursor.getString(columnName: String): String = getString(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getStringOrNull(columnName: String): String? {
return getStringOrNull(
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
)
}
/**
* @throws IllegalArgumentException
@@ -39,6 +46,11 @@ fun Cursor.getString(columnName: String): String = getString(
fun Cursor.getShort(columnName: String): Short = getShort(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getShortOrNull(columnName: String): Short? {
return getShortOrNull(
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
)
}
/**
* @throws IllegalArgumentException
@@ -46,6 +58,11 @@ fun Cursor.getShort(columnName: String): Short = getShort(
fun Cursor.getLong(columnName: String): Long = getLong(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getLongOrNull(columnName: String): Long? {
return getLongOrNull(
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
)
}
/**
* @throws IllegalArgumentException
@@ -53,6 +70,23 @@ fun Cursor.getLong(columnName: String): Long = getLong(
fun Cursor.getInt(columnName: String): Int = getInt(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getIntOrNull(columnName: String): Int? {
return getIntOrNull(
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
)
}
/**
* @throws IllegalArgumentException
*/
fun Cursor.getFloat(columnName: String): Float = getFloat(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getFloatOrNull(columnName: String): Float? {
return getFloatOrNull(
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
)
}
/**
* @throws IllegalArgumentException
@@ -60,6 +94,11 @@ fun Cursor.getInt(columnName: String): Int = getInt(
fun Cursor.getDouble(columnName: String): Double = getDouble(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getDoubleOrNull(columnName: String): Double? {
return getDoubleOrNull(
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
)
}
fun SQLiteDatabase.select(
table: String,
@@ -74,6 +113,19 @@ fun SQLiteDatabase.select(
table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
)
fun SQLiteDatabase.selectDistinct(
table: String,
columns: Array<String>? = null,
selection: String? = null,
selectionArgs: Array<String>? = null,
groupBy: String? = null,
having: String? = null,
orderBy: String? = null,
limit: String? = null
) = query(
true, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
)
fun makePlaceholders(count: Int): String {
return (0 until count).joinToString { "?" }
}

View File

@@ -6,13 +6,15 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.*
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
helper: StandardSQLHelper
helper: StandardSQLHelper,
replyInFlows: Int = 0,
extraBufferCapacityInFlows: Int = 64
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
StandardCRUDRepo<ObjectType, IdType, InputValueType> {
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(64)
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyInFlows, extraBufferCapacityInFlows)
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
@@ -102,4 +104,4 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
}
override suspend fun count(): Long = helper.blockingReadableTransaction { select(tableName).use { it.count.toLong() } }
}
}

View File

@@ -3,10 +3,7 @@ package dev.inmo.micro_utils.repos.onetomany
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.contentValuesOf
import dev.inmo.micro_utils.common.mapNotNullA
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.createPaginationResult
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.reverse
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
@@ -20,10 +17,14 @@ private val internalSerialFormat = Json {
ignoreUnknownKeys = true
}
typealias KeyValuesAndroidRepo<Key, Value> = OneToManyAndroidRepo<Key, Value>
class OneToManyAndroidRepo<Key, Value>(
private val tableName: String,
private val keySerializer: KSerializer<Key>,
private val valueSerializer: KSerializer<Value>,
private val keyAsString: Key.() -> String,
private val valueAsString: Value.() -> String,
private val keyFromString: String.() -> Key,
private val valueFromString: String.() -> Value,
private val helper: SQLiteOpenHelper
) : OneToManyKeyValueRepo<Key, Value> {
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
@@ -34,12 +35,9 @@ class OneToManyAndroidRepo<Key, Value>(
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
private val idColumnName = "id"
private val idColumnArray = arrayOf(idColumnName)
private val valueColumnName = "value"
private fun Key.asId() = internalSerialFormat.encodeToString(keySerializer, this)
private fun Value.asValue() = internalSerialFormat.encodeToString(valueSerializer, this)
private fun String.asValue(): Value = internalSerialFormat.decodeFromString(valueSerializer, this)
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
private val valueColumnArray = arrayOf(valueColumnName)
init {
helper.blockingWritableTransaction {
@@ -57,12 +55,23 @@ class OneToManyAndroidRepo<Key, Value>(
helper.blockingWritableTransaction {
for ((k, values) in toAdd) {
values.forEach { v ->
val kAsString = k.keyAsString()
val vAsString = v.valueAsString()
val isThere = select(tableName,
null,
"$idColumnName=? AND $valueColumnName=?",
arrayOf(kAsString, vAsString),
limit = limitClause(1)
).use { it.moveToFirst() }
if (isThere) {
return@forEach
}
insert(
tableName,
null,
contentValuesOf(
idColumnName to k.asId(),
valueColumnName to v.asValue()
idColumnName to k.keyAsString(),
valueColumnName to v.valueAsString()
)
).also {
if (it != -1L) {
@@ -77,7 +86,7 @@ class OneToManyAndroidRepo<Key, Value>(
override suspend fun clear(k: Key) {
helper.blockingWritableTransaction {
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString()))
}.also {
if (it > 0) {
_onDataCleared.emit(k)
@@ -88,7 +97,7 @@ class OneToManyAndroidRepo<Key, Value>(
override suspend fun set(toSet: Map<Key, List<Value>>) {
val (clearedKeys, inserted) = helper.blockingWritableTransaction {
toSet.mapNotNull { (k, _) ->
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
if (delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString())) > 0) {
k
} else {
null
@@ -98,7 +107,7 @@ class OneToManyAndroidRepo<Key, Value>(
insert(
tableName,
null,
contentValuesOf(idColumnName to k.asId(), valueColumnName to v.asValue())
contentValuesOf(idColumnName to k.keyAsString(), valueColumnName to v.valueAsString())
)
k to v
}
@@ -109,7 +118,7 @@ class OneToManyAndroidRepo<Key, Value>(
}
override suspend fun contains(k: Key): Boolean = helper.blockingReadableTransaction {
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = firstPageWithOneElementPagination.limitClause()).use {
it.count > 0
}
}
@@ -118,14 +127,14 @@ class OneToManyAndroidRepo<Key, Value>(
select(
tableName,
selection = "$idColumnName=? AND $valueColumnName=?",
selectionArgs = arrayOf(k.asId(), v.asValue()),
selectionArgs = arrayOf(k.keyAsString(), v.valueAsString()),
limit = FirstPagePagination(1).limitClause()
).use {
it.count > 0
}
}
override suspend fun count(): Long =helper.blockingReadableTransaction {
override suspend fun count(): Long = helper.blockingReadableTransaction {
select(
tableName
).use {
@@ -134,7 +143,7 @@ class OneToManyAndroidRepo<Key, Value>(
}.toLong()
override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
selectDistinct(tableName, columns = valueColumnArray, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = FirstPagePagination(1).limitClause()).use {
it.count
}
}.toLong()
@@ -144,18 +153,25 @@ class OneToManyAndroidRepo<Key, Value>(
pagination: Pagination,
reversed: Boolean
): PaginationResult<Value> = count(k).let { count ->
if (pagination.firstIndex >= count) {
return@let emptyList<Value>().createPaginationResult(
pagination,
count
)
}
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.blockingReadableTransaction {
select(
tableName,
valueColumnArray,
selection = "$idColumnName=?",
selectionArgs = arrayOf(k.asId()),
selectionArgs = arrayOf(k.keyAsString()),
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Value>().also {
if (c.moveToFirst()) {
do {
it.add(c.getString(valueColumnName).asValue())
it.add(c.getString(valueColumnName).valueFromString())
} while (c.moveToNext())
}
}
@@ -170,16 +186,23 @@ class OneToManyAndroidRepo<Key, Value>(
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = count().let { count ->
if (pagination.firstIndex >= count) {
return@let emptyList<Key>().createPaginationResult(
pagination,
count
)
}
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.blockingReadableTransaction {
select(
selectDistinct(
tableName,
idColumnArray,
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Key>().also {
if (c.moveToFirst()) {
do {
it.add(c.getString(idColumnName).asKey())
it.add(c.getString(idColumnName).keyFromString())
} while (c.moveToNext())
}
}
@@ -197,16 +220,17 @@ class OneToManyAndroidRepo<Key, Value>(
): PaginationResult<Key> = count().let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.blockingReadableTransaction {
select(
selectDistinct(
tableName,
idColumnArray,
selection = "$valueColumnName=?",
selectionArgs = arrayOf(v.asValue()),
selectionArgs = arrayOf(v.valueAsString()),
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Key>().also {
if (c.moveToFirst()) {
do {
it.add(c.getString(idColumnName).asKey())
it.add(c.getString(idColumnName).keyFromString())
} while (c.moveToNext())
}
}
@@ -221,7 +245,7 @@ class OneToManyAndroidRepo<Key, Value>(
helper.blockingWritableTransaction {
toRemove.flatMap { (k, vs) ->
vs.mapNotNullA { v ->
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.keyAsString(), v.valueAsString())) > 0) {
k to v
} else {
null
@@ -233,3 +257,24 @@ class OneToManyAndroidRepo<Key, Value>(
}
}
}
fun <Key, Value> OneToManyAndroidRepo(
tableName: String,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
helper: SQLiteOpenHelper
) = OneToManyAndroidRepo(
tableName,
{ internalSerialFormat.encodeToString(keySerializer, this) },
{ internalSerialFormat.encodeToString(valueSerializer, this) },
{ internalSerialFormat.decodeFromString(keySerializer, this) },
{ internalSerialFormat.decodeFromString(valueSerializer, this) },
helper
)
fun <Key, Value> KeyValuesAndroidRepo(
tableName: String,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
helper: SQLiteOpenHelper
) = OneToManyAndroidRepo(tableName, keySerializer, valueSerializer, helper)

View File

@@ -10,15 +10,16 @@ import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0,
tableName: String = ""
tableName: String = "",
replyCacheInFlows: Int = 0
) :
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
ExposedCRUDRepo<ObjectType, IdType>,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
{
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(flowsChannelsSize)
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize)
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()

Some files were not shown because too many files have changed in this diff Show More