Add Built-in Patch Support (#3)
|
@ -35,7 +35,7 @@ jobs:
|
|||
|
||||
- name: Pull Assets
|
||||
run: |
|
||||
git clone https://${{ secrets.ACCESS_TOKEN_GITHUB }}@github.com/imas-tools/gakumas-raw-txts.git app/src/main/assets/gakumas-local/gakumas-raw-txts
|
||||
git clone https://${{ secrets.ACCESS_TOKEN_GITHUB }}@github.com/DreamGallery/Campus-adv-txts.git app/src/main/assets/gakumas-local/gakumas-raw-txts
|
||||
mv app/src/main/assets/gakumas-local/gakumas-raw-txts/Resource app/src/main/assets/gakumas-local/raw
|
||||
rm -rf app/src/main/assets/gakumas-local/gakumas-raw-txts
|
||||
continue-on-error: true
|
||||
|
|
|
@ -3,6 +3,7 @@ plugins {
|
|||
alias(libs.plugins.kotlinAndroid)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.serialization)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -14,8 +15,8 @@ android {
|
|||
applicationId "io.github.chinosk.gakumas.localify"
|
||||
minSdk 29
|
||||
targetSdk 34
|
||||
versionCode 2
|
||||
versionName "v1.2"
|
||||
versionCode 4
|
||||
versionName "v1.6"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
@ -75,6 +76,7 @@ android {
|
|||
mergeAssetsTask.doLast {
|
||||
delete(fileTree(dir: mergeAssetsTask.outputDir, includes: ['gakumas-local/gakuen-adapted-translation-data/**',
|
||||
'gakumas-local/GakumasPreTranslation/**',
|
||||
'gakumas-local/gakumas-generic-strings-translation/**',
|
||||
'gakumas-local/raw/**']))
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +84,9 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
// implementation files('libs/lspatch_cleaned.jar')
|
||||
// api 'com.google.guava:guava:32.0.1-jre'
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.material3)
|
||||
|
@ -90,6 +95,14 @@ dependencies {
|
|||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation files('libs/lspatch_cleaned.jar')
|
||||
|
||||
implementation(libs.zip4j)
|
||||
implementation(libs.shizukuApi)
|
||||
implementation(libs.rikka.shizuku.provider)
|
||||
implementation(libs.rikka.refine)
|
||||
implementation(libs.rikka.hidden.stub)
|
||||
implementation(libs.hiddenapibypass)
|
||||
|
||||
def composeBom = platform(libs.androidx.compose.bom)
|
||||
implementation(composeBom)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
jar xf lspatch.jar
|
||||
cp -r assets/lspatch/so ../../src/main/assets/lspatch/so
|
||||
rm -rf com/google/common/util/concurrent
|
||||
rm -rf com/google/errorprone/annotations
|
||||
python rm_duplicate.py
|
||||
jar cf lspatch_cleaned.jar .
|
|
@ -0,0 +1,45 @@
|
|||
import os
|
||||
|
||||
|
||||
logs = """
|
||||
Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules listenablefuture-1.0.jar -> listenablefuture-1.0 (com.google.guava:listenablefuture:1.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.CanIgnoreReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.CheckReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.CompatibleWith found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.CompileTimeConstant found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.DoNotCall found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.DoNotMock found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.ForOverride found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.FormatMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.FormatString found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.Immutable found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.IncompatibleModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.InlineMe found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.InlineMeValidationDisabled found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.Keep found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.Modifier found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.MustBeClosed found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.NoAllocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.RequiredModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.RestrictedApi found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.SuppressPackageLocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.Var found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.concurrent.GuardedBy found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.concurrent.LazyInit found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.concurrent.LockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
Duplicate class com.google.errorprone.annotations.concurrent.UnlockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar)
|
||||
"""
|
||||
|
||||
for i in logs.split("\n"):
|
||||
if i.startswith("Duplicate class "):
|
||||
flag_pos = i.find(" found in modules")
|
||||
if flag_pos == -1:
|
||||
continue
|
||||
class_name = i[len("Duplicate class "):flag_pos]
|
||||
file_name = class_name.replace(".", "/") + ".class"
|
||||
if not os.path.isfile(file_name):
|
||||
print(file_name, "not found!!!")
|
||||
else:
|
||||
os.remove(file_name)
|
||||
print(file_name, "removed.")
|
|
@ -3,6 +3,9 @@
|
|||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
@ -41,6 +44,15 @@
|
|||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.INSTALL_PACKAGE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="application/vnd.android.package-archive" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
@ -50,6 +62,22 @@
|
|||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".PatchActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.GakumasLocalify">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.INSTALL_PACKAGE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="application/vnd.android.package-archive" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
@ -59,6 +87,15 @@
|
|||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="rikka.shizuku.ShizukuProvider"
|
||||
android:authorities="${applicationId}.shizuku"
|
||||
android:multiprocess="false"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -24,6 +24,10 @@
|
|||
],
|
||||
"contrib_img": {
|
||||
"plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
|
||||
"translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
|
||||
"translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData",
|
||||
"translations": [
|
||||
"https://contrib.rocks/image?repo=imas-tools/gakuen-adapted-translation-data",
|
||||
"https://contrib.rocks/image?repo=imas-tools/gakumas-generic-strings-translation"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
Subproject commit a60a171b40b22b04d567ab39a8fd7f571c7921f5
|
||||
Subproject commit ee4fb90f37cf2bbeb5744a4735071473f2c5635d
|
|
@ -45,6 +45,7 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
|
|||
GakumasLocalify/camera/baseCamera.cpp
|
||||
GakumasLocalify/camera/camera.cpp
|
||||
GakumasLocalify/config/Config.cpp
|
||||
GakumasLocalify/string_parser/StringParser.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} xdl::xdl)
|
||||
|
@ -53,12 +54,17 @@ target_link_libraries(${CMAKE_PROJECT_NAME} shadowhook::shadowhook)
|
|||
include_directories(GakumasLocalify)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps)
|
||||
|
||||
set(FMT_DIR "${CMAKE_SOURCE_DIR}/deps/fmt-11.0.2")
|
||||
include_directories(${FMT_DIR}/include)
|
||||
add_library(fmt STATIC ${FMT_DIR}/src/format.cc)
|
||||
|
||||
# Specifies libraries CMake should link to your target library. You
|
||||
# can link libraries from various origins, such as libraries defined in this
|
||||
# build script, prebuilt third-party libraries, or Android system libraries.
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||
# List libraries link to the target library
|
||||
android
|
||||
log)
|
||||
log
|
||||
fmt)
|
||||
|
||||
target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_23)
|
||||
|
|
|
@ -333,6 +333,13 @@ namespace GakumasLocal::HookMain {
|
|||
"TMPro", "TMP_Text", "get_font");
|
||||
static auto set_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
|
||||
"TMPro", "TMP_Text", "set_font");
|
||||
// static auto set_fontMaterial = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
|
||||
// "TMPro", "TMP_Text", "set_fontMaterial");
|
||||
// static auto ForceMeshUpdate = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
|
||||
// "TMPro", "TMP_Text", "ForceMeshUpdate");
|
||||
//
|
||||
// static auto get_material = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
|
||||
// "TMPro", "TMP_Asset", "get_material");
|
||||
|
||||
static auto set_sourceFontFile = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_FontAsset", "set_sourceFontFile");
|
||||
|
@ -351,6 +358,10 @@ namespace GakumasLocal::HookMain {
|
|||
if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear();
|
||||
}
|
||||
set_font->Invoke<void>(TMP_Textself, fontAsset);
|
||||
|
||||
// auto fontMaterial = get_material->Invoke<void*>(fontAsset);
|
||||
// set_fontMaterial->Invoke<void>(TMP_Textself, fontMaterial);
|
||||
// ForceMeshUpdate->Invoke<void>(TMP_Textself, false, false);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* self, UnityResolve::UnityType::String* text, int start, int length)) {
|
||||
|
@ -365,6 +376,7 @@ namespace GakumasLocal::HookMain {
|
|||
std::string transText;
|
||||
if (Local::GetGenericText(origText, &transText)) {
|
||||
const auto newText = UnityResolve::UnityType::String::New(transText);
|
||||
UpdateFont(self);
|
||||
return TMP_Text_PopulateTextBackingArray_Orig(self, newText, 0, newText->length);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <cctype>
|
||||
#include <algorithm>
|
||||
#include "BaseDefine.h"
|
||||
#include "string_parser/StringParser.hpp"
|
||||
|
||||
|
||||
namespace GakumasLocal::Local {
|
||||
|
@ -22,9 +23,12 @@ namespace GakumasLocal::Local {
|
|||
std::unordered_map<std::string, std::string> i18nDumpData{};
|
||||
std::unordered_map<std::string, std::string> genericText{};
|
||||
std::unordered_map<std::string, std::string> genericSplitText{};
|
||||
std::unordered_map<std::string, std::string> genericFmtText{};
|
||||
std::vector<std::string> genericTextDumpData{};
|
||||
std::vector<std::string> genericSplittedDumpData{};
|
||||
std::vector<std::string> genericOrigTextDumpData{};
|
||||
std::vector<std::string> genericFmtTextDumpData{};
|
||||
|
||||
std::unordered_set<std::string> translatedText{};
|
||||
int genericDumpFileIndex = 0;
|
||||
const std::string splitTextPrefix = "[__split__]";
|
||||
|
@ -64,7 +68,8 @@ namespace GakumasLocal::Local {
|
|||
enum class DumpStrStat {
|
||||
DEFAULT = 0,
|
||||
SPLITTABLE_ORIG = 1,
|
||||
SPLITTED = 2
|
||||
SPLITTED = 2,
|
||||
FMT = 3
|
||||
};
|
||||
|
||||
enum class SplitTagsTranslationStat {
|
||||
|
@ -262,6 +267,15 @@ namespace GakumasLocal::Local {
|
|||
return ret;
|
||||
}
|
||||
|
||||
void ReplaceNumberComma(std::string* orig) {
|
||||
if (!orig->contains(",")) return;
|
||||
std::string newStr = *orig;
|
||||
ReplaceString(&newStr, ",", ",");
|
||||
if (IsPureStringValue(newStr)) {
|
||||
*orig = newStr;
|
||||
}
|
||||
}
|
||||
|
||||
SplitTagsTranslationStat GetSplitTagsTranslationFull(const std::string& origTextIn, std::string* newText, std::vector<std::string>& unTransResultRet) {
|
||||
// static const std::u16string splitFlags = u"0123456789++--%%【】.";
|
||||
static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
|
||||
|
@ -306,7 +320,9 @@ namespace GakumasLocal::Local {
|
|||
return SplitTagsTranslationStat::NO_SPLIT_AND_EMPTY;
|
||||
}
|
||||
else {
|
||||
return SplitTagsTranslationStat::NO_SPLIT;
|
||||
if (!(!origText.empty() && splitFlags.contains(origText[0]))) { // 开头为特殊符号或数字
|
||||
return SplitTagsTranslationStat::NO_SPLIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
checkCurrentWaitingReplaceTextAndClear()
|
||||
|
@ -317,8 +333,9 @@ namespace GakumasLocal::Local {
|
|||
bool hasNotTrans = false;
|
||||
if (!waitingReplaceTexts.empty()) {
|
||||
for (const auto& i : waitingReplaceTexts) {
|
||||
const auto searchResult = findInMapIgnoreSpace(i, genericSplitText);
|
||||
std::string searchResult = findInMapIgnoreSpace(i, genericSplitText);
|
||||
if (!searchResult.empty()) {
|
||||
ReplaceNumberComma(&searchResult);
|
||||
ReplaceString(newText, i, searchResult);
|
||||
hasTrans = true;
|
||||
}
|
||||
|
@ -358,6 +375,7 @@ namespace GakumasLocal::Local {
|
|||
|
||||
LoadJsonDataToMap(genericFile, genericText, true, true, true);
|
||||
genericSplitText.clear();
|
||||
genericFmtText.clear();
|
||||
LoadJsonDataToMap(genericSplitFile, genericSplitText, true, true, true);
|
||||
if (std::filesystem::exists(genericDir) || std::filesystem::is_directory(genericDir)) {
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(genericDir)) {
|
||||
|
@ -367,6 +385,9 @@ namespace GakumasLocal::Local {
|
|||
if (currFile.filename().string().ends_with(".split.json")) { // split text file
|
||||
LoadJsonDataToMap(currFile, genericSplitText, true, false, true);
|
||||
}
|
||||
if (currFile.filename().string().ends_with(".fmt.json")) { // fmt text file
|
||||
LoadJsonDataToMap(currFile, genericFmtText, true, false, false);
|
||||
}
|
||||
else {
|
||||
LoadJsonDataToMap(currFile, genericText, true, false, true);
|
||||
}
|
||||
|
@ -442,6 +463,10 @@ namespace GakumasLocal::Local {
|
|||
if (genericDumpFileIndex == 0) return "generic_orig.json";
|
||||
return Log::StringFormat("generic_orig_%d.json", genericDumpFileIndex);
|
||||
}
|
||||
else if (stat == DumpStrStat::FMT) {
|
||||
if (genericDumpFileIndex == 0) return "generic.fmt.json";
|
||||
return Log::StringFormat("generic_%d.fmt.json", genericDumpFileIndex);
|
||||
}
|
||||
else {
|
||||
if (genericDumpFileIndex == 0) return "generic.json";
|
||||
return Log::StringFormat("generic_%d.json", genericDumpFileIndex);
|
||||
|
@ -452,10 +477,11 @@ namespace GakumasLocal::Local {
|
|||
void DumpGenericText(const std::string& origText, DumpStrStat stat = DumpStrStat::DEFAULT) {
|
||||
if (translatedText.contains(origText)) return;
|
||||
|
||||
std::array<std::reference_wrapper<std::vector<std::string>>, 3> targets = {
|
||||
std::array<std::reference_wrapper<std::vector<std::string>>, 4> targets = {
|
||||
genericTextDumpData,
|
||||
genericOrigTextDumpData,
|
||||
genericSplittedDumpData
|
||||
genericSplittedDumpData,
|
||||
genericFmtTextDumpData
|
||||
};
|
||||
|
||||
auto& appendTarget = targets[static_cast<int>(stat)].get();
|
||||
|
@ -475,25 +501,50 @@ namespace GakumasLocal::Local {
|
|||
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::DEFAULT), genericTextDumpData);
|
||||
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTABLE_ORIG), genericOrigTextDumpData);
|
||||
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTED), genericSplittedDumpData, splitTextPrefix);
|
||||
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::FMT), genericFmtTextDumpData);
|
||||
genericTextDumpData.clear();
|
||||
genericSplittedDumpData.clear();
|
||||
genericOrigTextDumpData.clear();
|
||||
genericFmtTextDumpData.clear();
|
||||
inDumpGeneric = false;
|
||||
}).detach();
|
||||
}
|
||||
|
||||
bool GetGenericText(const std::string& origText, std::string* newStr) {
|
||||
// 完全匹配
|
||||
if (const auto iter = genericText.find(origText); iter != genericText.end()) {
|
||||
*newStr = iter->second;
|
||||
return true;
|
||||
}
|
||||
// 不翻译翻译过的文本
|
||||
if (translatedText.contains(origText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// fmt 文本
|
||||
auto fmtText = StringParser::ParseItems::parse(origText, false);
|
||||
if (fmtText.isValid) {
|
||||
const auto fmtStr = fmtText.ToFmtString();
|
||||
if (auto it = genericFmtText.find(fmtStr); it != genericFmtText.end()) {
|
||||
auto newRet = fmtText.MergeText(it->second);
|
||||
if (!newRet.empty()) {
|
||||
*newStr = newRet;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (Config::dumpText) {
|
||||
DumpGenericText(fmtStr, DumpStrStat::FMT);
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = false;
|
||||
|
||||
// 分割匹配
|
||||
std::vector<std::string> unTransResultRet;
|
||||
const auto splitTransStat = GetSplitTagsTranslationFull(origText, newStr, unTransResultRet);
|
||||
switch (splitTransStat) {
|
||||
case SplitTagsTranslationStat::FULL_TRANS: {
|
||||
DumpGenericText(origText, DumpStrStat::SPLITTABLE_ORIG);
|
||||
return true;
|
||||
} break;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <jni.h>
|
||||
#include "fmt/core.h"
|
||||
|
||||
|
||||
extern JavaVM* g_javaVM;
|
||||
|
@ -88,11 +89,85 @@ namespace GakumasLocal::Misc {
|
|||
|
||||
int CSEnum::GetValueByName(const std::string &name) {
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
if (names[i].compare(name) == 0) {
|
||||
if (names[i] == name) {
|
||||
return values[i];
|
||||
}
|
||||
}
|
||||
return values[0];
|
||||
}
|
||||
|
||||
|
||||
namespace StringFormat {
|
||||
template<typename... Args>
|
||||
std::string string_format(const std::string& fmt, Args&&... args) {
|
||||
// return std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
|
||||
return fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
template <std::size_t N, std::size_t... Indices, typename T>
|
||||
auto vectorToTupleImpl(const std::vector<T>& vec, std::index_sequence<Indices...>) {
|
||||
if (vec.size() != N) {
|
||||
// printf("vec.size: %zu, N: %zu\n", vec.size(), N);
|
||||
throw std::out_of_range("Vector size does not match tuple size.");
|
||||
}
|
||||
return std::make_tuple(vec[Indices]...);
|
||||
}
|
||||
|
||||
template <std::size_t N, typename T>
|
||||
auto vectorToTuple(const std::vector<T>& vec) {
|
||||
return vectorToTupleImpl<N>(vec, std::make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
std::string stringFormat(const std::string& fmt, const std::vector<T>& vec) {
|
||||
std::string ret = fmt;
|
||||
|
||||
#define CASE_ARG_COUNT(N) \
|
||||
case N: {\
|
||||
auto tp = vectorToTuple<N>(vec); \
|
||||
std::apply([&](auto&&... args) { \
|
||||
ret = string_format(fmt, args...); \
|
||||
}, tp); } break;
|
||||
|
||||
switch (vec.size()) {
|
||||
CASE_ARG_COUNT(1)
|
||||
CASE_ARG_COUNT(2)
|
||||
CASE_ARG_COUNT(3)
|
||||
CASE_ARG_COUNT(4)
|
||||
CASE_ARG_COUNT(5)
|
||||
CASE_ARG_COUNT(6)
|
||||
CASE_ARG_COUNT(7)
|
||||
CASE_ARG_COUNT(8)
|
||||
CASE_ARG_COUNT(9)
|
||||
CASE_ARG_COUNT(10)
|
||||
CASE_ARG_COUNT(11)
|
||||
CASE_ARG_COUNT(12)
|
||||
CASE_ARG_COUNT(13)
|
||||
CASE_ARG_COUNT(14)
|
||||
CASE_ARG_COUNT(15)
|
||||
CASE_ARG_COUNT(16)
|
||||
CASE_ARG_COUNT(17)
|
||||
CASE_ARG_COUNT(18)
|
||||
CASE_ARG_COUNT(19)
|
||||
CASE_ARG_COUNT(20)
|
||||
CASE_ARG_COUNT(21)
|
||||
CASE_ARG_COUNT(22)
|
||||
CASE_ARG_COUNT(23)
|
||||
CASE_ARG_COUNT(24)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec) {
|
||||
try {
|
||||
return stringFormat(fmt, vec);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
return fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -73,5 +73,9 @@ namespace GakumasLocal {
|
|||
size_t maxSize;
|
||||
T sum;
|
||||
};
|
||||
|
||||
namespace StringFormat {
|
||||
std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
#include <unordered_set>
|
||||
#include "StringParser.hpp"
|
||||
#include "fmt/core.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "../Misc.hpp"
|
||||
|
||||
namespace StringParser {
|
||||
|
||||
std::string ParseItems::ToFmtString() {
|
||||
std::vector<std::string> ret{};
|
||||
int currFlagIndex = 0;
|
||||
for (const auto& i : items) {
|
||||
if (i.type == ParseItemType::FLAG) {
|
||||
ret.push_back(fmt::format("{{{}}}", currFlagIndex));
|
||||
currFlagIndex++;
|
||||
}
|
||||
else {
|
||||
ret.push_back(i.content);
|
||||
}
|
||||
}
|
||||
return fmt::format("{}", fmt::join(ret, ""));
|
||||
}
|
||||
|
||||
std::vector<std::string> ParseItems::GetFlagValues() {
|
||||
std::vector<std::string> ret{};
|
||||
for (const auto& i : items) {
|
||||
if (i.type == ParseItemType::FLAG) {
|
||||
ret.push_back(i.content);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ParseItems ParseItems::parse(const std::string &str, bool parseTags) {
|
||||
static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
|
||||
u'6', u'7', u'8', u'9', u'+', u'+',
|
||||
u'-', u'-', u'%', u'%',
|
||||
u'.', u'×', u',', u','};
|
||||
|
||||
ParseItems result;
|
||||
if (str.contains("{")) {
|
||||
result.isValid = false;
|
||||
return result;
|
||||
}
|
||||
std::u16string origText = GakumasLocal::Misc::ToUTF16(str);
|
||||
bool isInTag = false;
|
||||
bool isInFlagSequence = false;
|
||||
std::u16string currentCacheText;
|
||||
|
||||
for (char16_t currChar : origText) {
|
||||
if (parseTags && currChar == u'<') {
|
||||
if (!currentCacheText.empty()) {
|
||||
result.items.push_back({isInFlagSequence ? ParseItemType::FLAG : ParseItemType::TEXT,
|
||||
GakumasLocal::Misc::ToUTF8(currentCacheText)});
|
||||
currentCacheText.clear();
|
||||
isInFlagSequence = false;
|
||||
}
|
||||
isInTag = true;
|
||||
currentCacheText.push_back(currChar);
|
||||
} else if (parseTags && currChar == u'>') {
|
||||
isInTag = false;
|
||||
currentCacheText.push_back(currChar);
|
||||
result.items.push_back({ParseItemType::FLAG, GakumasLocal::Misc::ToUTF8(currentCacheText)});
|
||||
currentCacheText.clear();
|
||||
} else if (isInTag) {
|
||||
currentCacheText.push_back(currChar);
|
||||
} else if (splitFlags.contains(currChar)) {
|
||||
if (!isInFlagSequence && !currentCacheText.empty()) {
|
||||
result.items.push_back({ParseItemType::TEXT, GakumasLocal::Misc::ToUTF8(currentCacheText)});
|
||||
currentCacheText.clear();
|
||||
}
|
||||
isInFlagSequence = true;
|
||||
currentCacheText.push_back(currChar);
|
||||
} else {
|
||||
if (isInFlagSequence && !currentCacheText.empty()) {
|
||||
result.items.push_back({ParseItemType::FLAG, GakumasLocal::Misc::ToUTF8(currentCacheText)});
|
||||
currentCacheText.clear();
|
||||
isInFlagSequence = false;
|
||||
}
|
||||
currentCacheText.push_back(currChar);
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentCacheText.empty()) {
|
||||
result.items.push_back({isInFlagSequence ? ParseItemType::FLAG : ParseItemType::TEXT,
|
||||
GakumasLocal::Misc::ToUTF8(currentCacheText)});
|
||||
}
|
||||
|
||||
for (auto& i : result.items) {
|
||||
if (i.type == ParseItemType::FLAG) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
result.isValid = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ParseItems::MergeText(ParseItems &textTarget, ParseItems &valueTarget) {
|
||||
if (!textTarget.isValid) return "";
|
||||
if (!valueTarget.isValid) return "";
|
||||
const auto fmtText = textTarget.ToFmtString();
|
||||
const auto values = valueTarget.GetFlagValues();
|
||||
const std::string ret = GakumasLocal::Misc::StringFormat::stringFormatString(fmtText, values);
|
||||
return {ret.begin(), ret.end()};
|
||||
}
|
||||
|
||||
std::string ParseItems::MergeText(const std::string &newStr) {
|
||||
if (!isValid) return "";
|
||||
const auto values = GetFlagValues();
|
||||
return GakumasLocal::Misc::StringFormat::stringFormatString(newStr, values);
|
||||
}
|
||||
|
||||
int ParseItems::GetFlagCount() {
|
||||
int ret = 0;
|
||||
for (auto& i : items) {
|
||||
if (i.type == ParseItemType::FLAG) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace StringParser {
|
||||
enum class ParseItemType {
|
||||
FLAG,
|
||||
TEXT
|
||||
};
|
||||
|
||||
struct ParseItem {
|
||||
ParseItemType type;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
struct ParseItems {
|
||||
bool isValid = true;
|
||||
std::vector<ParseItem> items;
|
||||
|
||||
std::string ToFmtString();
|
||||
std::vector<std::string> GetFlagValues();
|
||||
std::string MergeText(const std::string& newStr);
|
||||
int GetFlagCount();
|
||||
|
||||
static ParseItems parse(const std::string& str, bool parseTags);
|
||||
static std::string MergeText(ParseItems& textTarget, ParseItems& valueTarget);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
// Formatting library for C++ - dynamic argument lists
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_ARGS_H_
|
||||
#define FMT_ARGS_H_
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <functional> // std::reference_wrapper
|
||||
# include <memory> // std::unique_ptr
|
||||
# include <vector>
|
||||
#endif
|
||||
|
||||
#include "format.h" // std_string_view
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T> struct is_reference_wrapper : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
|
||||
|
||||
template <typename T> auto unwrap(const T& v) -> const T& { return v; }
|
||||
template <typename T>
|
||||
auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
|
||||
return static_cast<const T&>(v);
|
||||
}
|
||||
|
||||
// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC
|
||||
// 2022 (v17.10.0).
|
||||
//
|
||||
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
|
||||
// templates it doesn't complain about inability to deduce single translation
|
||||
// unit for placing vtable. So node is made a fake template.
|
||||
template <typename = void> struct node {
|
||||
virtual ~node() = default;
|
||||
std::unique_ptr<node<>> next;
|
||||
};
|
||||
|
||||
class dynamic_arg_list {
|
||||
template <typename T> struct typed_node : node<> {
|
||||
T value;
|
||||
|
||||
template <typename Arg>
|
||||
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
|
||||
: value(arg.data(), arg.size()) {}
|
||||
};
|
||||
|
||||
std::unique_ptr<node<>> head_;
|
||||
|
||||
public:
|
||||
template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
|
||||
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
|
||||
auto& value = new_node->value;
|
||||
new_node->next = std::move(head_);
|
||||
head_ = std::move(new_node);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* A dynamic list of formatting arguments with storage.
|
||||
*
|
||||
* It can be implicitly converted into `fmt::basic_format_args` for passing
|
||||
* into type-erased formatting functions such as `fmt::vformat`.
|
||||
*/
|
||||
template <typename Context>
|
||||
class dynamic_format_arg_store
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround a GCC template argument substitution bug.
|
||||
: public basic_format_args<Context>
|
||||
#endif
|
||||
{
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
template <typename T> struct need_copy {
|
||||
static constexpr detail::type mapped_type =
|
||||
detail::mapped_type_constant<T, Context>::value;
|
||||
|
||||
enum {
|
||||
value = !(detail::is_reference_wrapper<T>::value ||
|
||||
std::is_same<T, basic_string_view<char_type>>::value ||
|
||||
std::is_same<T, detail::std_string_view<char_type>>::value ||
|
||||
(mapped_type != detail::type::cstring_type &&
|
||||
mapped_type != detail::type::string_type &&
|
||||
mapped_type != detail::type::custom_type))
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using stored_type = conditional_t<
|
||||
std::is_convertible<T, std::basic_string<char_type>>::value &&
|
||||
!detail::is_reference_wrapper<T>::value,
|
||||
std::basic_string<char_type>, T>;
|
||||
|
||||
// Storage of basic_format_arg must be contiguous.
|
||||
std::vector<basic_format_arg<Context>> data_;
|
||||
std::vector<detail::named_arg_info<char_type>> named_info_;
|
||||
|
||||
// Storage of arguments not fitting into basic_format_arg must grow
|
||||
// without relocation because items in data_ refer to it.
|
||||
detail::dynamic_arg_list dynamic_args_;
|
||||
|
||||
friend class basic_format_args<Context>;
|
||||
|
||||
auto get_types() const -> unsigned long long {
|
||||
return detail::is_unpacked_bit | data_.size() |
|
||||
(named_info_.empty()
|
||||
? 0ULL
|
||||
: static_cast<unsigned long long>(detail::has_named_args_bit));
|
||||
}
|
||||
|
||||
auto data() const -> const basic_format_arg<Context>* {
|
||||
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
||||
}
|
||||
|
||||
template <typename T> void emplace_arg(const T& arg) {
|
||||
data_.emplace_back(detail::make_arg<Context>(arg));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
|
||||
if (named_info_.empty()) {
|
||||
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
|
||||
data_.insert(data_.begin(), {zero_ptr, 0});
|
||||
}
|
||||
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
|
||||
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
|
||||
data->pop_back();
|
||||
};
|
||||
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
|
||||
guard{&data_, pop_one};
|
||||
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
|
||||
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
|
||||
guard.release();
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr dynamic_format_arg_store() = default;
|
||||
|
||||
/**
|
||||
* Adds an argument into the dynamic store for later passing to a formatting
|
||||
* function.
|
||||
*
|
||||
* Note that custom types and string types (but not string views) are copied
|
||||
* into the store dynamically allocating memory if necessary.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
* store.push_back(42);
|
||||
* store.push_back("abc");
|
||||
* store.push_back(1.5f);
|
||||
* std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
*/
|
||||
template <typename T> void push_back(const T& arg) {
|
||||
if (detail::const_check(need_copy<T>::value))
|
||||
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
|
||||
else
|
||||
emplace_arg(detail::unwrap(arg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a reference to the argument into the dynamic store for later passing
|
||||
* to a formatting function.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
* char band[] = "Rolling Stones";
|
||||
* store.push_back(std::cref(band));
|
||||
* band[9] = 'c'; // Changing str affects the output.
|
||||
* std::string result = fmt::vformat("{}", store);
|
||||
* // result == "Rolling Scones"
|
||||
*/
|
||||
template <typename T> void push_back(std::reference_wrapper<T> arg) {
|
||||
static_assert(
|
||||
need_copy<T>::value,
|
||||
"objects of built-in types and string views are always copied");
|
||||
emplace_arg(arg.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds named argument into the dynamic store for later passing to a
|
||||
* formatting function. `std::reference_wrapper` is supported to avoid
|
||||
* copying of the argument. The name is always copied into the store.
|
||||
*/
|
||||
template <typename T>
|
||||
void push_back(const detail::named_arg<char_type, T>& arg) {
|
||||
const char_type* arg_name =
|
||||
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
|
||||
if (detail::const_check(need_copy<T>::value)) {
|
||||
emplace_arg(
|
||||
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
|
||||
} else {
|
||||
emplace_arg(fmt::arg(arg_name, arg.value));
|
||||
}
|
||||
}
|
||||
|
||||
/// Erase all elements from the store.
|
||||
void clear() {
|
||||
data_.clear();
|
||||
named_info_.clear();
|
||||
dynamic_args_ = detail::dynamic_arg_list();
|
||||
}
|
||||
|
||||
/// Reserves space to store at least `new_cap` arguments including
|
||||
/// `new_cap_named` named arguments.
|
||||
void reserve(size_t new_cap, size_t new_cap_named) {
|
||||
FMT_ASSERT(new_cap >= new_cap_named,
|
||||
"Set of arguments includes set of named arguments");
|
||||
data_.reserve(new_cap);
|
||||
named_info_.reserve(new_cap_named);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_ARGS_H_
|
|
@ -0,0 +1,612 @@
|
|||
// Formatting library for C++ - color support
|
||||
//
|
||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COLOR_H_
|
||||
#define FMT_COLOR_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
antique_white = 0xFAEBD7, // rgb(250,235,215)
|
||||
aqua = 0x00FFFF, // rgb(0,255,255)
|
||||
aquamarine = 0x7FFFD4, // rgb(127,255,212)
|
||||
azure = 0xF0FFFF, // rgb(240,255,255)
|
||||
beige = 0xF5F5DC, // rgb(245,245,220)
|
||||
bisque = 0xFFE4C4, // rgb(255,228,196)
|
||||
black = 0x000000, // rgb(0,0,0)
|
||||
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
|
||||
blue = 0x0000FF, // rgb(0,0,255)
|
||||
blue_violet = 0x8A2BE2, // rgb(138,43,226)
|
||||
brown = 0xA52A2A, // rgb(165,42,42)
|
||||
burly_wood = 0xDEB887, // rgb(222,184,135)
|
||||
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
|
||||
chartreuse = 0x7FFF00, // rgb(127,255,0)
|
||||
chocolate = 0xD2691E, // rgb(210,105,30)
|
||||
coral = 0xFF7F50, // rgb(255,127,80)
|
||||
cornflower_blue = 0x6495ED, // rgb(100,149,237)
|
||||
cornsilk = 0xFFF8DC, // rgb(255,248,220)
|
||||
crimson = 0xDC143C, // rgb(220,20,60)
|
||||
cyan = 0x00FFFF, // rgb(0,255,255)
|
||||
dark_blue = 0x00008B, // rgb(0,0,139)
|
||||
dark_cyan = 0x008B8B, // rgb(0,139,139)
|
||||
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
|
||||
dark_gray = 0xA9A9A9, // rgb(169,169,169)
|
||||
dark_green = 0x006400, // rgb(0,100,0)
|
||||
dark_khaki = 0xBDB76B, // rgb(189,183,107)
|
||||
dark_magenta = 0x8B008B, // rgb(139,0,139)
|
||||
dark_olive_green = 0x556B2F, // rgb(85,107,47)
|
||||
dark_orange = 0xFF8C00, // rgb(255,140,0)
|
||||
dark_orchid = 0x9932CC, // rgb(153,50,204)
|
||||
dark_red = 0x8B0000, // rgb(139,0,0)
|
||||
dark_salmon = 0xE9967A, // rgb(233,150,122)
|
||||
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
|
||||
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
|
||||
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
|
||||
dark_turquoise = 0x00CED1, // rgb(0,206,209)
|
||||
dark_violet = 0x9400D3, // rgb(148,0,211)
|
||||
deep_pink = 0xFF1493, // rgb(255,20,147)
|
||||
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
|
||||
dim_gray = 0x696969, // rgb(105,105,105)
|
||||
dodger_blue = 0x1E90FF, // rgb(30,144,255)
|
||||
fire_brick = 0xB22222, // rgb(178,34,34)
|
||||
floral_white = 0xFFFAF0, // rgb(255,250,240)
|
||||
forest_green = 0x228B22, // rgb(34,139,34)
|
||||
fuchsia = 0xFF00FF, // rgb(255,0,255)
|
||||
gainsboro = 0xDCDCDC, // rgb(220,220,220)
|
||||
ghost_white = 0xF8F8FF, // rgb(248,248,255)
|
||||
gold = 0xFFD700, // rgb(255,215,0)
|
||||
golden_rod = 0xDAA520, // rgb(218,165,32)
|
||||
gray = 0x808080, // rgb(128,128,128)
|
||||
green = 0x008000, // rgb(0,128,0)
|
||||
green_yellow = 0xADFF2F, // rgb(173,255,47)
|
||||
honey_dew = 0xF0FFF0, // rgb(240,255,240)
|
||||
hot_pink = 0xFF69B4, // rgb(255,105,180)
|
||||
indian_red = 0xCD5C5C, // rgb(205,92,92)
|
||||
indigo = 0x4B0082, // rgb(75,0,130)
|
||||
ivory = 0xFFFFF0, // rgb(255,255,240)
|
||||
khaki = 0xF0E68C, // rgb(240,230,140)
|
||||
lavender = 0xE6E6FA, // rgb(230,230,250)
|
||||
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
|
||||
lawn_green = 0x7CFC00, // rgb(124,252,0)
|
||||
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
|
||||
light_blue = 0xADD8E6, // rgb(173,216,230)
|
||||
light_coral = 0xF08080, // rgb(240,128,128)
|
||||
light_cyan = 0xE0FFFF, // rgb(224,255,255)
|
||||
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
|
||||
light_gray = 0xD3D3D3, // rgb(211,211,211)
|
||||
light_green = 0x90EE90, // rgb(144,238,144)
|
||||
light_pink = 0xFFB6C1, // rgb(255,182,193)
|
||||
light_salmon = 0xFFA07A, // rgb(255,160,122)
|
||||
light_sea_green = 0x20B2AA, // rgb(32,178,170)
|
||||
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
|
||||
light_slate_gray = 0x778899, // rgb(119,136,153)
|
||||
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
|
||||
light_yellow = 0xFFFFE0, // rgb(255,255,224)
|
||||
lime = 0x00FF00, // rgb(0,255,0)
|
||||
lime_green = 0x32CD32, // rgb(50,205,50)
|
||||
linen = 0xFAF0E6, // rgb(250,240,230)
|
||||
magenta = 0xFF00FF, // rgb(255,0,255)
|
||||
maroon = 0x800000, // rgb(128,0,0)
|
||||
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
|
||||
medium_blue = 0x0000CD, // rgb(0,0,205)
|
||||
medium_orchid = 0xBA55D3, // rgb(186,85,211)
|
||||
medium_purple = 0x9370DB, // rgb(147,112,219)
|
||||
medium_sea_green = 0x3CB371, // rgb(60,179,113)
|
||||
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
|
||||
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
|
||||
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
|
||||
medium_violet_red = 0xC71585, // rgb(199,21,133)
|
||||
midnight_blue = 0x191970, // rgb(25,25,112)
|
||||
mint_cream = 0xF5FFFA, // rgb(245,255,250)
|
||||
misty_rose = 0xFFE4E1, // rgb(255,228,225)
|
||||
moccasin = 0xFFE4B5, // rgb(255,228,181)
|
||||
navajo_white = 0xFFDEAD, // rgb(255,222,173)
|
||||
navy = 0x000080, // rgb(0,0,128)
|
||||
old_lace = 0xFDF5E6, // rgb(253,245,230)
|
||||
olive = 0x808000, // rgb(128,128,0)
|
||||
olive_drab = 0x6B8E23, // rgb(107,142,35)
|
||||
orange = 0xFFA500, // rgb(255,165,0)
|
||||
orange_red = 0xFF4500, // rgb(255,69,0)
|
||||
orchid = 0xDA70D6, // rgb(218,112,214)
|
||||
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
|
||||
pale_green = 0x98FB98, // rgb(152,251,152)
|
||||
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
|
||||
pale_violet_red = 0xDB7093, // rgb(219,112,147)
|
||||
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
|
||||
peach_puff = 0xFFDAB9, // rgb(255,218,185)
|
||||
peru = 0xCD853F, // rgb(205,133,63)
|
||||
pink = 0xFFC0CB, // rgb(255,192,203)
|
||||
plum = 0xDDA0DD, // rgb(221,160,221)
|
||||
powder_blue = 0xB0E0E6, // rgb(176,224,230)
|
||||
purple = 0x800080, // rgb(128,0,128)
|
||||
rebecca_purple = 0x663399, // rgb(102,51,153)
|
||||
red = 0xFF0000, // rgb(255,0,0)
|
||||
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
|
||||
royal_blue = 0x4169E1, // rgb(65,105,225)
|
||||
saddle_brown = 0x8B4513, // rgb(139,69,19)
|
||||
salmon = 0xFA8072, // rgb(250,128,114)
|
||||
sandy_brown = 0xF4A460, // rgb(244,164,96)
|
||||
sea_green = 0x2E8B57, // rgb(46,139,87)
|
||||
sea_shell = 0xFFF5EE, // rgb(255,245,238)
|
||||
sienna = 0xA0522D, // rgb(160,82,45)
|
||||
silver = 0xC0C0C0, // rgb(192,192,192)
|
||||
sky_blue = 0x87CEEB, // rgb(135,206,235)
|
||||
slate_blue = 0x6A5ACD, // rgb(106,90,205)
|
||||
slate_gray = 0x708090, // rgb(112,128,144)
|
||||
snow = 0xFFFAFA, // rgb(255,250,250)
|
||||
spring_green = 0x00FF7F, // rgb(0,255,127)
|
||||
steel_blue = 0x4682B4, // rgb(70,130,180)
|
||||
tan = 0xD2B48C, // rgb(210,180,140)
|
||||
teal = 0x008080, // rgb(0,128,128)
|
||||
thistle = 0xD8BFD8, // rgb(216,191,216)
|
||||
tomato = 0xFF6347, // rgb(255,99,71)
|
||||
turquoise = 0x40E0D0, // rgb(64,224,208)
|
||||
violet = 0xEE82EE, // rgb(238,130,238)
|
||||
wheat = 0xF5DEB3, // rgb(245,222,179)
|
||||
white = 0xFFFFFF, // rgb(255,255,255)
|
||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||
yellow_green = 0x9ACD32 // rgb(154,205,50)
|
||||
}; // enum class color
|
||||
|
||||
enum class terminal_color : uint8_t {
|
||||
black = 30,
|
||||
red,
|
||||
green,
|
||||
yellow,
|
||||
blue,
|
||||
magenta,
|
||||
cyan,
|
||||
white,
|
||||
bright_black = 90,
|
||||
bright_red,
|
||||
bright_green,
|
||||
bright_yellow,
|
||||
bright_blue,
|
||||
bright_magenta,
|
||||
bright_cyan,
|
||||
bright_white
|
||||
};
|
||||
|
||||
enum class emphasis : uint8_t {
|
||||
bold = 1,
|
||||
faint = 1 << 1,
|
||||
italic = 1 << 2,
|
||||
underline = 1 << 3,
|
||||
blink = 1 << 4,
|
||||
reverse = 1 << 5,
|
||||
conceal = 1 << 6,
|
||||
strikethrough = 1 << 7,
|
||||
};
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// Using the name "rgb" makes some editors show the color in a tooltip.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
||||
FMT_CONSTEXPR rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF),
|
||||
g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
|
||||
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
|
||||
value.rgb_color = static_cast<uint32_t>(rgb_color);
|
||||
}
|
||||
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
|
||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
|
||||
: is_rgb(), value{} {
|
||||
value.term_color = static_cast<uint8_t>(term_color);
|
||||
}
|
||||
bool is_rgb;
|
||||
union color_union {
|
||||
uint8_t term_color;
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// A text style consisting of foreground and background colors and emphasis.
|
||||
class text_style {
|
||||
public:
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
|
||||
: set_foreground_color(), set_background_color(), ems(em) {}
|
||||
|
||||
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
report_error("can't OR a terminal color");
|
||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
report_error("can't OR a terminal color");
|
||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
|
||||
-> text_style {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
|
||||
return set_foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
|
||||
return set_background_color;
|
||||
}
|
||||
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
}
|
||||
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
}
|
||||
|
||||
private:
|
||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||
detail::color_type text_color) noexcept
|
||||
: set_foreground_color(), set_background_color(), ems() {
|
||||
if (is_foreground) {
|
||||
foreground_color = text_color;
|
||||
set_foreground_color = true;
|
||||
} else {
|
||||
background_color = text_color;
|
||||
set_background_color = true;
|
||||
}
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
|
||||
-> text_style;
|
||||
|
||||
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
|
||||
-> text_style;
|
||||
|
||||
detail::color_type foreground_color;
|
||||
detail::color_type background_color;
|
||||
bool set_foreground_color;
|
||||
bool set_background_color;
|
||||
emphasis ems;
|
||||
};
|
||||
|
||||
/// Creates a text style from the foreground (text) color.
|
||||
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
|
||||
-> text_style {
|
||||
return text_style(true, foreground);
|
||||
}
|
||||
|
||||
/// Creates a text style from the background color.
|
||||
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
|
||||
-> text_style {
|
||||
return text_style(false, background);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
|
||||
-> text_style {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
const char* esc) noexcept {
|
||||
// If we have a terminal color, we need to output another escape code
|
||||
// sequence.
|
||||
if (!text_color.is_rgb) {
|
||||
bool is_background = esc == string_view("\x1b[48;2;");
|
||||
uint32_t value = text_color.value.term_color;
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
if (is_background) value += 10u;
|
||||
|
||||
size_t index = 0;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
|
||||
if (value >= 100u) {
|
||||
buffer[index++] = static_cast<Char>('1');
|
||||
value %= 100u;
|
||||
}
|
||||
buffer[index++] = static_cast<Char>('0' + value / 10u);
|
||||
buffer[index++] = static_cast<Char>('0' + value % 10u);
|
||||
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
buffer[index++] = static_cast<Char>('\0');
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
buffer[i] = static_cast<Char>(esc[i]);
|
||||
}
|
||||
rgb color(text_color.value.rgb_color);
|
||||
to_esc(color.r, buffer + 7, ';');
|
||||
to_esc(color.g, buffer + 11, ';');
|
||||
to_esc(color.b, buffer + 15, 'm');
|
||||
buffer[19] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
|
||||
uint8_t em_codes[num_emphases] = {};
|
||||
if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
|
||||
if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
|
||||
if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
|
||||
if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
|
||||
if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
|
||||
if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
|
||||
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
|
||||
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
|
||||
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < num_emphases; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
}
|
||||
buffer[index++] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
|
||||
|
||||
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
|
||||
FMT_CONSTEXPR20 auto end() const noexcept -> const Char* {
|
||||
return buffer + basic_string_view<Char>(buffer).size();
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t num_emphases = 8;
|
||||
Char buffer[7u + 3u * num_emphases + 1u];
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
char delimiter) noexcept {
|
||||
out[0] = static_cast<Char>('0' + c / 100);
|
||||
out[1] = static_cast<Char>('0' + c / 10 % 10);
|
||||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
|
||||
-> bool {
|
||||
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(em);
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
|
||||
auto reset_color = string_view("\x1b[0m");
|
||||
buffer.append(reset_color.begin(), reset_color.end());
|
||||
}
|
||||
|
||||
template <typename T> struct styled_arg : detail::view {
|
||||
const T& value;
|
||||
text_style style;
|
||||
styled_arg(const T& v, text_style s) : value(v), style(s) {}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(
|
||||
buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffered_context<type_identity_t<Char>>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background = detail::make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
detail::vformat_to(buf, format_str, args, {});
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline void vprint(FILE* f, const text_style& ts, string_view fmt,
|
||||
format_args args) {
|
||||
auto buf = memory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a string and prints it to the specified file stream using ANSI
|
||||
* escape sequences to specify text formatting.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
* "Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename... T>
|
||||
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
|
||||
T&&... args) {
|
||||
vprint(f, ts, fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
* specify text formatting.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
* "Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename... T>
|
||||
void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
|
||||
return print(stdout, ts, fmt, std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
inline auto vformat(const text_style& ts, string_view fmt, format_args args)
|
||||
-> std::string {
|
||||
auto buf = memory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats arguments and returns the result as a string using ANSI escape
|
||||
* sequences to specify text formatting.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* ```
|
||||
* #include <fmt/color.h>
|
||||
* std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
* "The answer is {}", 42);
|
||||
* ```
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
|
||||
-> std::string {
|
||||
return fmt::vformat(ts, fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
/// Formats a string with the given text_style and writes the output to `out`.
|
||||
template <typename OutputIt,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
||||
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
|
||||
format_args args) -> OutputIt {
|
||||
auto&& buf = detail::get_buffer<char>(out);
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats arguments with the given text style, writes the result to the output
|
||||
* iterator `out` and returns the iterator past the end of the output range.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* std::vector<char> out;
|
||||
* fmt::format_to(std::back_inserter(out),
|
||||
* fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||
*/
|
||||
template <typename OutputIt, typename... T,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
||||
inline auto format_to(OutputIt out, const text_style& ts,
|
||||
format_string<T...> fmt, T&&... args) -> OutputIt {
|
||||
return vformat_to(out, ts, fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
const auto& ts = arg.style;
|
||||
const auto& value = arg.value;
|
||||
auto out = ctx.out();
|
||||
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||
out = std::copy(emphasis.begin(), emphasis.end(), out);
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground =
|
||||
detail::make_foreground_color<Char>(ts.get_foreground());
|
||||
out = std::copy(foreground.begin(), foreground.end(), out);
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background =
|
||||
detail::make_background_color<Char>(ts.get_background());
|
||||
out = std::copy(background.begin(), background.end(), out);
|
||||
}
|
||||
out = formatter<T, Char>::format(value, ctx);
|
||||
if (has_style) {
|
||||
auto reset_color = string_view("\x1b[0m");
|
||||
out = std::copy(reset_color.begin(), reset_color.end(), out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an argument that will be formatted using ANSI escape sequences,
|
||||
* to be used in a formatting function.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print("Elapsed time: {0:.2f} seconds",
|
||||
* fmt::styled(1.23, fmt::fg(fmt::color::green) |
|
||||
* fmt::bg(fmt::color::blue)));
|
||||
*/
|
||||
template <typename T>
|
||||
FMT_CONSTEXPR auto styled(const T& value, text_style ts)
|
||||
-> detail::styled_arg<remove_cvref_t<T>> {
|
||||
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
|
||||
}
|
||||
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
|
@ -0,0 +1,529 @@
|
|||
// Formatting library for C++ - experimental format string compilation
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COMPILE_H_
|
||||
#define FMT_COMPILE_H_
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <iterator> // std::back_inserter
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
FMT_EXPORT class compiled_string {};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename InputIt>
|
||||
FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it)
|
||||
-> counting_iterator {
|
||||
return it + (end - begin);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||
|
||||
/**
|
||||
* Converts a string literal `s` into a format string that will be parsed at
|
||||
* compile time and converted into efficient formatting code. Requires C++17
|
||||
* `constexpr if` compiler support.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* // Converts 42 into std::string using the most efficient method and no
|
||||
* // runtime format string processing.
|
||||
* std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||
*/
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit)
|
||||
#else
|
||||
# define FMT_COMPILE(s) FMT_STRING(s)
|
||||
#endif
|
||||
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
template <typename Char, size_t N,
|
||||
fmt::detail_exported::fixed_string<Char, N> Str>
|
||||
struct udl_compiled_string : compiled_string {
|
||||
using char_type = Char;
|
||||
explicit constexpr operator basic_string_view<char_type>() const {
|
||||
return {Str.data, N - 1};
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
auto first(const T& value, const Tail&...) -> const T& {
|
||||
return value;
|
||||
}
|
||||
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get([[maybe_unused]] const T& first,
|
||||
[[maybe_unused]] const Args&... rest) {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return detail::get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
template <typename Char, typename... Args>
|
||||
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
|
||||
type_list<Args...>) {
|
||||
return get_arg_index_by_name<Args...>(name);
|
||||
}
|
||||
|
||||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type =
|
||||
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
using get_type = typename get_type_impl<N, T>::type;
|
||||
|
||||
template <typename T> struct is_compiled_format : std::false_type {};
|
||||
|
||||
template <typename Char> struct text {
|
||||
basic_string_view<Char> data;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, data);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<text<Char>> : std::true_type {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
size_t size) {
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
template <typename Char> struct code_unit {
|
||||
Char value;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||
*out++ = value;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
// This ensures that the argument type is convertible to `const T&`.
|
||||
template <typename T, int N, typename... Args>
|
||||
constexpr const T& get_arg_checked(const Args&... args) {
|
||||
const auto& arg = detail::get<N>(args...);
|
||||
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
|
||||
return arg.value;
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<code_unit<Char>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
const T& arg = get_arg_checked<T, N>(args...);
|
||||
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) {
|
||||
auto s = basic_string_view<Char>(arg);
|
||||
return copy<Char>(s.begin(), s.end(), out);
|
||||
}
|
||||
return write<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument with name.
|
||||
template <typename Char> struct runtime_named_field {
|
||||
using char_type = Char;
|
||||
basic_string_view<Char> name;
|
||||
|
||||
template <typename OutputIt, typename T>
|
||||
constexpr static bool try_format_argument(
|
||||
OutputIt& out,
|
||||
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
|
||||
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
|
||||
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
|
||||
if (arg_name == arg.name) {
|
||||
out = write<Char>(out, arg.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
bool found = (try_format_argument(out, name, args) || ...);
|
||||
if (!found) {
|
||||
FMT_THROW(format_error("argument with specified name is not found"));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N and has format specifiers.
|
||||
template <typename Char, typename T, int N> struct spec_field {
|
||||
using char_type = Char;
|
||||
formatter<T, Char> fmt;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr FMT_INLINE OutputIt format(OutputIt out,
|
||||
const Args&... args) const {
|
||||
const auto& vargs =
|
||||
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
|
||||
basic_format_context<OutputIt, Char> ctx(out, vargs);
|
||||
return fmt.format(get_arg_checked<T, N>(args...), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R> struct concat {
|
||||
L lhs;
|
||||
R rhs;
|
||||
using char_type = typename L::char_type;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
out = lhs.format(out, args...);
|
||||
return rhs.format(out, args...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R>
|
||||
struct is_compiled_format<concat<L, R>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
struct unknown_format {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
for (size_t size = str.size(); pos != size; ++pos) {
|
||||
if (str[pos] == '{' || str[pos] == '}') break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S fmt);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S fmt) {
|
||||
if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
return tail;
|
||||
else
|
||||
return make_concat(head, tail);
|
||||
} else {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Char> struct parse_specs_result {
|
||||
formatter<T, Char> fmt;
|
||||
size_t end;
|
||||
int next_arg_id;
|
||||
};
|
||||
|
||||
enum { manual_indexing_id = -1 };
|
||||
|
||||
template <typename T, typename Char>
|
||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
size_t pos, int next_arg_id) {
|
||||
str.remove_prefix(pos);
|
||||
auto ctx =
|
||||
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
|
||||
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
|
||||
}
|
||||
|
||||
template <typename Char> struct arg_id_handler {
|
||||
arg_ref<Char> arg_id;
|
||||
|
||||
constexpr int on_auto() {
|
||||
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
||||
return 0;
|
||||
}
|
||||
constexpr int on_index(int id) {
|
||||
arg_id = arg_ref<Char>(id);
|
||||
return 0;
|
||||
}
|
||||
constexpr int on_name(basic_string_view<Char> id) {
|
||||
arg_id = arg_ref<Char>(id);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct parse_arg_id_result {
|
||||
arg_ref<Char> arg_id;
|
||||
const Char* arg_id_end;
|
||||
};
|
||||
|
||||
template <int ID, typename Char>
|
||||
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
|
||||
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
|
||||
auto arg_id_end = parse_arg_id(begin, end, handler);
|
||||
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void> struct field_type {
|
||||
using type = remove_cvref_t<T>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
|
||||
using type = remove_cvref_t<decltype(T::value)>;
|
||||
};
|
||||
|
||||
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
|
||||
typename S>
|
||||
constexpr auto parse_replacement_field_then_tail(S fmt) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr auto str = basic_string_view<char_type>(fmt);
|
||||
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
|
||||
if constexpr (c == '}') {
|
||||
return parse_tail<Args, END_POS + 1, NEXT_ID>(
|
||||
field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);
|
||||
} else if constexpr (c != ':') {
|
||||
FMT_THROW(format_error("expected ':'"));
|
||||
} else {
|
||||
constexpr auto result = parse_specs<typename field_type<T>::type>(
|
||||
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
|
||||
if constexpr (result.end >= str.size() || str[result.end] != '}') {
|
||||
FMT_THROW(format_error("expected '}'"));
|
||||
return 0;
|
||||
} else {
|
||||
return parse_tail<Args, result.end + 1, result.next_arg_id>(
|
||||
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||
result.fmt},
|
||||
fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S fmt) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr auto str = basic_string_view<char_type>(fmt);
|
||||
if constexpr (str[POS] == '{') {
|
||||
if constexpr (POS + 1 == str.size())
|
||||
FMT_THROW(format_error("unmatched '{' in format string"));
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
|
||||
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
|
||||
static_assert(ID != manual_indexing_id,
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
constexpr auto next_id =
|
||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
|
||||
POS + 1, ID, next_id>(fmt);
|
||||
} else {
|
||||
constexpr auto arg_id_result =
|
||||
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
|
||||
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
|
||||
constexpr char_type c =
|
||||
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
|
||||
static_assert(c == '}' || c == ':', "missing '}' in format string");
|
||||
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
|
||||
static_assert(
|
||||
ID == manual_indexing_id || ID == 0,
|
||||
"cannot switch from automatic to manual argument indexing");
|
||||
constexpr auto arg_index = arg_id_result.arg_id.val.index;
|
||||
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
|
||||
Args, arg_id_end_pos,
|
||||
arg_index, manual_indexing_id>(
|
||||
fmt);
|
||||
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
||||
constexpr auto arg_index =
|
||||
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
||||
if constexpr (arg_index >= 0) {
|
||||
constexpr auto next_id =
|
||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||
return parse_replacement_field_then_tail<
|
||||
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
||||
arg_index, next_id>(fmt);
|
||||
} else if constexpr (c == '}') {
|
||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||
fmt);
|
||||
} else if constexpr (c == ':') {
|
||||
return unknown_format(); // no type info for specs parsing
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if constexpr (POS + 1 == str.size())
|
||||
FMT_THROW(format_error("unmatched '}' in format string"));
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
if constexpr (end - POS > 1) {
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);
|
||||
} else {
|
||||
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
constexpr auto compile(S fmt) {
|
||||
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
|
||||
if constexpr (str.size() == 0) {
|
||||
return detail::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
} // namespace detail
|
||||
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
auto s = std::basic_string<Char>();
|
||||
cf.format(std::back_inserter(s), args...);
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
Args&&... args) {
|
||||
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
||||
constexpr auto str = basic_string_view<typename S::char_type>(S());
|
||||
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
|
||||
const auto& first = detail::first(args...);
|
||||
if constexpr (detail::is_named_arg<
|
||||
remove_cvref_t<decltype(first)>>::value) {
|
||||
return fmt::to_string(first.value);
|
||||
} else {
|
||||
return fmt::to_string(first);
|
||||
}
|
||||
}
|
||||
}
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||
detail::unknown_format>()) {
|
||||
return fmt::format(
|
||||
static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||
std::forward<Args>(args)...);
|
||||
} else {
|
||||
return fmt::format(compiled, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||
detail::unknown_format>()) {
|
||||
return fmt::format_to(
|
||||
out, static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||
std::forward<Args>(args)...);
|
||||
} else {
|
||||
return fmt::format_to(out, compiled, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
|
||||
fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
|
||||
-> size_t {
|
||||
return fmt::format_to(detail::counting_iterator(), fmt, args...).count();
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
void print(std::FILE* f, const S& fmt, const Args&... args) {
|
||||
memory_buffer buffer;
|
||||
fmt::format_to(std::back_inserter(buffer), fmt, args...);
|
||||
detail::print(f, {buffer.data(), buffer.size()});
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
void print(const S& fmt, const Args&... args) {
|
||||
print(stdout, fmt, args...);
|
||||
}
|
||||
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
inline namespace literals {
|
||||
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
|
||||
using char_t = remove_cvref_t<decltype(Str.data[0])>;
|
||||
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
|
||||
Str>();
|
||||
}
|
||||
} // namespace literals
|
||||
#endif
|
||||
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COMPILE_H_
|
|
@ -0,0 +1,5 @@
|
|||
// This file is only provided for compatibility and may be removed in future
|
||||
// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h
|
||||
// otherwise.
|
||||
|
||||
#include "format.h"
|
|
@ -0,0 +1,439 @@
|
|||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OS_H_
|
||||
#define FMT_OS_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <cerrno>
|
||||
# include <cstddef>
|
||||
# include <cstdio>
|
||||
# include <system_error> // std::system_error
|
||||
|
||||
# if FMT_HAS_INCLUDE(<xlocale.h>)
|
||||
# include <xlocale.h> // LC_NUMERIC_MASK on macOS
|
||||
# endif
|
||||
#endif // FMT_MODULE
|
||||
|
||||
#ifndef FMT_USE_FCNTL
|
||||
// UWP doesn't provide _pipe.
|
||||
# if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
# endif
|
||||
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || \
|
||||
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
# else
|
||||
# define FMT_USE_FCNTL 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_HAS_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) ::call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
(result) = (expression); \
|
||||
} while ((result) == (error_result) && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
/**
|
||||
* A reference to a null-terminated string. It can be constructed from a C
|
||||
* string or `std::string`.
|
||||
*
|
||||
* You can use one of the following type aliases for common character types:
|
||||
*
|
||||
* +---------------+-----------------------------+
|
||||
* | Type | Definition |
|
||||
* +===============+=============================+
|
||||
* | cstring_view | basic_cstring_view<char> |
|
||||
* +---------------+-----------------------------+
|
||||
* | wcstring_view | basic_cstring_view<wchar_t> |
|
||||
* +---------------+-----------------------------+
|
||||
*
|
||||
* This class is most useful as a parameter type for functions that wrap C APIs.
|
||||
*/
|
||||
template <typename Char> class basic_cstring_view {
|
||||
private:
|
||||
const Char* data_;
|
||||
|
||||
public:
|
||||
/// Constructs a string reference object from a C string.
|
||||
basic_cstring_view(const Char* s) : data_(s) {}
|
||||
|
||||
/// Constructs a string reference from an `std::string` object.
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
|
||||
/// Returns the pointer to a C string.
|
||||
auto c_str() const -> const Char* { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
#ifdef _WIN32
|
||||
FMT_API const std::error_category& system_category() noexcept;
|
||||
|
||||
namespace detail {
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
const char* message) noexcept;
|
||||
}
|
||||
|
||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||
format_args args);
|
||||
|
||||
/**
|
||||
* Constructs a `std::system_error` object with the description of the form
|
||||
*
|
||||
* <message>: <system-message>
|
||||
*
|
||||
* where `<message>` is the formatted message and `<system-message>` is the
|
||||
* system message corresponding to the error code.
|
||||
* `error_code` is a Windows error code as given by `GetLastError`.
|
||||
* If `error_code` is not a valid error code such as -1, the system message
|
||||
* will look like "error -1".
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* // This throws a system_error with the description
|
||||
* // cannot open file 'madeup': The system cannot find the file
|
||||
* specified.
|
||||
* // or similar (system message may vary).
|
||||
* const char *filename = "madeup";
|
||||
* LPOFSTRUCT of = LPOFSTRUCT();
|
||||
* HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
* if (file == HFILE_ERROR) {
|
||||
* throw fmt::windows_error(GetLastError(),
|
||||
* "cannot open file '{}'", filename);
|
||||
* }
|
||||
*/
|
||||
template <typename... Args>
|
||||
std::system_error windows_error(int error_code, string_view message,
|
||||
const Args&... args) {
|
||||
return vwindows_error(error_code, message, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
// Reports a Windows error without throwing an exception.
|
||||
// Can be used to report errors from destructors.
|
||||
FMT_API void report_windows_error(int error_code, const char* message) noexcept;
|
||||
#else
|
||||
inline auto system_category() noexcept -> const std::error_category& {
|
||||
return std::system_category();
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
// std::system is not available on some platforms such as iOS (#2248).
|
||||
#ifdef __OSX__
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
void say(const S& format_str, Args&&... args) {
|
||||
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE* file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE* f) : file_(f) {}
|
||||
|
||||
public:
|
||||
buffered_file(const buffered_file&) = delete;
|
||||
void operator=(const buffered_file&) = delete;
|
||||
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() noexcept : file_(nullptr) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() noexcept;
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
auto operator=(buffered_file&& other) -> buffered_file& {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Opens a file.
|
||||
FMT_API buffered_file(cstring_view filename, cstring_view mode);
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
auto get() const noexcept -> FILE* { return file_; }
|
||||
|
||||
FMT_API auto descriptor() const -> int;
|
||||
|
||||
template <typename... T>
|
||||
inline void print(string_view fmt, const T&... args) {
|
||||
const auto& vargs = fmt::make_format_args(args...);
|
||||
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
|
||||
: fmt::vprint(file_, fmt, vargs);
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with noexcept may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class FMT_API file {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a file object with a given descriptor.
|
||||
explicit file(int fd) : fd_(fd) {}
|
||||
|
||||
friend struct pipe;
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
||||
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
|
||||
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
|
||||
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
file() noexcept : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a file object representing this file.
|
||||
file(cstring_view path, int oflag);
|
||||
|
||||
public:
|
||||
file(const file&) = delete;
|
||||
void operator=(const file&) = delete;
|
||||
|
||||
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
// Move assignment is not noexcept because close may throw.
|
||||
auto operator=(file&& other) -> file& {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
~file() noexcept;
|
||||
|
||||
// Returns the file descriptor.
|
||||
auto descriptor() const noexcept -> int { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
auto size() const -> long long;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
auto read(void* buffer, size_t count) -> size_t;
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
auto write(const void* buffer, size_t count) -> size_t;
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
static auto dup(int fd) -> file;
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
void dup2(int fd, std::error_code& ec) noexcept;
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
auto fdopen(const char* mode) -> buffered_file;
|
||||
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Opens a file and constructs a file object representing this file by
|
||||
// wcstring_view filename. Windows only.
|
||||
static file open_windows_file(wcstring_view path, int oflag);
|
||||
# endif
|
||||
};
|
||||
|
||||
struct FMT_API pipe {
|
||||
file read_end;
|
||||
file write_end;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
pipe();
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
auto getpagesize() -> long;
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct buffer_size {
|
||||
buffer_size() = default;
|
||||
size_t value = 0;
|
||||
auto operator=(size_t val) const -> buffer_size {
|
||||
auto bs = buffer_size();
|
||||
bs.value = val;
|
||||
return bs;
|
||||
}
|
||||
};
|
||||
|
||||
struct ostream_params {
|
||||
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
|
||||
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
||||
|
||||
ostream_params() {}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
|
||||
oflag = new_oflag;
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, detail::buffer_size bs)
|
||||
: ostream_params(params...) {
|
||||
this->buffer_size = bs.value;
|
||||
}
|
||||
|
||||
// Intel has a bug that results in failure to deduce a constructor
|
||||
// for empty parameter packs.
|
||||
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
|
||||
ostream_params(int new_oflag) : oflag(new_oflag) {}
|
||||
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
|
||||
# endif
|
||||
};
|
||||
|
||||
class file_buffer final : public buffer<char> {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
FMT_API static void grow(buffer<char>& buf, size_t);
|
||||
|
||||
public:
|
||||
FMT_API file_buffer(cstring_view path, const ostream_params& params);
|
||||
FMT_API file_buffer(file_buffer&& other) noexcept;
|
||||
FMT_API ~file_buffer();
|
||||
|
||||
void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size() * sizeof(data()[0]));
|
||||
clear();
|
||||
}
|
||||
|
||||
void close() {
|
||||
flush();
|
||||
file_.close();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
constexpr auto buffer_size = detail::buffer_size();
|
||||
|
||||
/// A fast output stream for writing from a single thread. Writing from
|
||||
/// multiple threads without external synchronization may result in a data race.
|
||||
class FMT_API ostream {
|
||||
private:
|
||||
FMT_MSC_WARNING(suppress : 4251)
|
||||
detail::file_buffer buffer_;
|
||||
|
||||
ostream(cstring_view path, const detail::ostream_params& params)
|
||||
: buffer_(path, params) {}
|
||||
|
||||
public:
|
||||
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
|
||||
|
||||
~ostream();
|
||||
|
||||
void flush() { buffer_.flush(); }
|
||||
|
||||
template <typename... T>
|
||||
friend auto output_file(cstring_view path, T... params) -> ostream;
|
||||
|
||||
void close() { buffer_.close(); }
|
||||
|
||||
/// Formats `args` according to specifications in `fmt` and writes the
|
||||
/// output to the file.
|
||||
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||
vformat_to(appender(buffer_), fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a file for writing. Supported parameters passed in `params`:
|
||||
*
|
||||
* - `<integer>`: Flags passed to [open](
|
||||
* https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html)
|
||||
* (`file::WRONLY | file::CREATE | file::TRUNC` by default)
|
||||
* - `buffer_size=<integer>`: Output buffer size
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* auto out = fmt::output_file("guide.txt");
|
||||
* out.print("Don't {}", "Panic");
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto output_file(cstring_view path, T... params) -> ostream {
|
||||
return {path, detail::ostream_params(params...)};
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OS_H_
|
|
@ -0,0 +1,211 @@
|
|||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <fstream> // std::filebuf
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifdef __GLIBCXX__
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
# endif
|
||||
# include <io.h>
|
||||
#endif
|
||||
|
||||
#include "chrono.h" // formatbuf
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// Generate a unique explicit instantion in every translation unit using a tag
|
||||
// type in an anonymous namespace.
|
||||
namespace {
|
||||
struct file_access_tag {};
|
||||
} // namespace
|
||||
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
|
||||
class file_access {
|
||||
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
|
||||
};
|
||||
|
||||
#if FMT_MSC_VERSION
|
||||
template class file_access<file_access_tag, std::filebuf,
|
||||
&std::filebuf::_Myfile>;
|
||||
auto get_file(std::filebuf&) -> FILE*;
|
||||
#endif
|
||||
|
||||
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
|
||||
-> bool {
|
||||
FILE* f = nullptr;
|
||||
#if FMT_MSC_VERSION && FMT_USE_RTTI
|
||||
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
|
||||
f = get_file(*buf);
|
||||
else
|
||||
return false;
|
||||
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
|
||||
auto* rdbuf = os.rdbuf();
|
||||
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
||||
f = sfbuf->file();
|
||||
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
|
||||
f = fbuf->file();
|
||||
else
|
||||
return false;
|
||||
#else
|
||||
ignore_unused(os, data, f);
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
if (f) {
|
||||
int fd = _fileno(f);
|
||||
if (_isatty(fd)) {
|
||||
os.flush();
|
||||
return write_console(fd, data);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
inline auto write_ostream_unicode(std::wostream&,
|
||||
fmt::basic_string_view<wchar_t>) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the content of buf to os.
|
||||
// It is a separate function rather than a part of vprint to simplify testing.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
buf_data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value) {
|
||||
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
||||
auto&& output = std::basic_ostream<Char>(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
output.imbue(std::locale::classic()); // The default is always unlocalized.
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
}
|
||||
|
||||
template <typename T> struct streamed_view {
|
||||
const T& value;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename Char>
|
||||
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
||||
void set_debug_format() = delete;
|
||||
|
||||
template <typename T, typename Context>
|
||||
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
detail::format_value(buffer, value);
|
||||
return formatter<basic_string_view<Char>, Char>::format(
|
||||
{buffer.data(), buffer.size()}, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
using ostream_formatter = basic_ostream_formatter<char>;
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<detail::streamed_view<T>, Char>
|
||||
: basic_ostream_formatter<Char> {
|
||||
template <typename Context>
|
||||
auto format(detail::streamed_view<T> view, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return basic_ostream_formatter<Char>::format(view.value, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a view that formats `value` via an ostream `operator<<`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print("Current thread id: {}\n",
|
||||
* fmt::streamed(std::this_thread::get_id()));
|
||||
*/
|
||||
template <typename T>
|
||||
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
|
||||
return {value};
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void vprint_directly(std::ostream& os, string_view format_str,
|
||||
format_args args) {
|
||||
auto buffer = memory_buffer();
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_EXPORT template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os,
|
||||
basic_string_view<type_identity_t<Char>> format_str,
|
||||
typename detail::vformat_args<Char>::type args) {
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints formatted data to the stream `os`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print(cerr, "Don't {}!", "panic");
|
||||
*/
|
||||
FMT_EXPORT template <typename... T>
|
||||
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
const auto& vargs = fmt::make_format_args(args...);
|
||||
if (detail::use_utf8())
|
||||
vprint(os, fmt, vargs);
|
||||
else
|
||||
detail::vprint_directly(os, fmt, vargs);
|
||||
}
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename... Args>
|
||||
void print(std::wostream& os,
|
||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
||||
Args&&... args) {
|
||||
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
|
||||
}
|
||||
|
||||
FMT_EXPORT template <typename... T>
|
||||
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename... Args>
|
||||
void println(std::wostream& os,
|
||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
||||
Args&&... args) {
|
||||
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
|
@ -0,0 +1,656 @@
|
|||
// Formatting library for C++ - legacy printf implementation
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <algorithm> // std::max
|
||||
# include <limits> // std::numeric_limits
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
template <typename T> struct printf_formatter {
|
||||
printf_formatter() = delete;
|
||||
};
|
||||
|
||||
template <typename Char> class basic_printf_context {
|
||||
private:
|
||||
basic_appender<Char> out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
|
||||
static_assert(std::is_same<Char, char>::value ||
|
||||
std::is_same<Char, wchar_t>::value,
|
||||
"Unsupported code unit type.");
|
||||
|
||||
public:
|
||||
using char_type = Char;
|
||||
using parse_context_type = basic_format_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
/// Constructs a `printf_context` object. References to the arguments are
|
||||
/// stored in the context object so make sure they have appropriate lifetimes.
|
||||
basic_printf_context(basic_appender<Char> out,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args) {}
|
||||
|
||||
auto out() -> basic_appender<Char> { return out_; }
|
||||
void advance_to(basic_appender<Char>) {}
|
||||
|
||||
auto locale() -> detail::locale_ref { return {}; }
|
||||
|
||||
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
|
||||
return args_.get(id);
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||
unsigned max = to_unsigned(max_value<int>());
|
||||
return value <= max;
|
||||
}
|
||||
static auto fits_in_int(bool) -> bool { return true; }
|
||||
};
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||
return value >= (std::numeric_limits<int>::min)() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static auto fits_in_int(int) -> bool { return true; }
|
||||
};
|
||||
|
||||
struct printf_precision_handler {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
auto operator()(T value) -> int {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
report_error("number is too big");
|
||||
return (std::max)(static_cast<int>(value), 0);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
auto operator()(T) -> int {
|
||||
report_error("precision is not integer");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
struct is_zero_int {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
auto operator()(T value) -> bool {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
auto operator()(T) -> bool {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <> struct make_unsigned_or_bool<bool> {
|
||||
using type = bool;
|
||||
};
|
||||
|
||||
template <typename T, typename Context> class arg_converter {
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
basic_format_arg<Context>& arg_;
|
||||
char_type type_;
|
||||
|
||||
public:
|
||||
arg_converter(basic_format_arg<Context>& arg, char_type type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void operator()(bool value) {
|
||||
if (type_ != 's') operator()<bool>(value);
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
|
||||
void operator()(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
auto n = static_cast<int>(static_cast<target_type>(value));
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
auto n = static_cast<long long>(value);
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
} else {
|
||||
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
|
||||
void operator()(U) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// Converts an integer argument to T for printf, if T is an integral type.
|
||||
// If T is void, the argument is converted to corresponding signed or unsigned
|
||||
// type depending on the type specifier: 'd' and 'i' - signed, other -
|
||||
// unsigned).
|
||||
template <typename T, typename Context, typename Char>
|
||||
void convert_arg(basic_format_arg<Context>& arg, Char type) {
|
||||
arg.visit(arg_converter<T, Context>(arg, type));
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
template <typename Context> class char_converter {
|
||||
private:
|
||||
basic_format_arg<Context>& arg_;
|
||||
|
||||
public:
|
||||
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
auto c = static_cast<typename Context::char_type>(value);
|
||||
arg_ = detail::make_arg<Context>(c);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
void operator()(T) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
|
||||
auto operator()(const Char* s) -> const Char* { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
class printf_width_handler {
|
||||
private:
|
||||
format_specs& specs_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
auto operator()(T value) -> unsigned {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (detail::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = to_unsigned(max_value<int>());
|
||||
if (width > int_max) report_error("number is too big");
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
auto operator()(T) -> unsigned {
|
||||
report_error("width is not integer");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Workaround for a bug with the XL compiler when initializing
|
||||
// printf_arg_formatter's base class.
|
||||
template <typename Char>
|
||||
auto make_arg_formatter(basic_appender<Char> iter, format_specs& s)
|
||||
-> arg_formatter<Char> {
|
||||
return {iter, s, locale_ref()};
|
||||
}
|
||||
|
||||
// The `printf` argument formatter.
|
||||
template <typename Char>
|
||||
class printf_arg_formatter : public arg_formatter<Char> {
|
||||
private:
|
||||
using base = arg_formatter<Char>;
|
||||
using context_type = basic_printf_context<Char>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
void write_null_pointer(bool is_string = false) {
|
||||
auto s = this->specs;
|
||||
s.type = presentation_type::none;
|
||||
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
}
|
||||
|
||||
public:
|
||||
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
|
||||
context_type& ctx)
|
||||
: base(make_arg_formatter(iter, s)), context_(ctx) {}
|
||||
|
||||
void operator()(monostate value) { base::operator()(value); }
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||
// std::is_same instead.
|
||||
if (!std::is_same<T, Char>::value) {
|
||||
base::operator()(value);
|
||||
return;
|
||||
}
|
||||
format_specs s = this->specs;
|
||||
if (s.type != presentation_type::none && s.type != presentation_type::chr) {
|
||||
return (*this)(static_cast<int>(value));
|
||||
}
|
||||
s.sign = sign::none;
|
||||
s.alt = false;
|
||||
s.fill = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (s.align == align::none || s.align == align::numeric)
|
||||
s.align = align::right;
|
||||
write<Char>(this->out, static_cast<Char>(value), s);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
void operator()(T value) {
|
||||
base::operator()(value);
|
||||
}
|
||||
|
||||
void operator()(const char* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
void operator()(const wchar_t* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
void operator()(basic_string_view<Char> value) { base::operator()(value); }
|
||||
|
||||
void operator()(const void* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer();
|
||||
}
|
||||
|
||||
void operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
auto parse_ctx = basic_format_parse_context<Char>({});
|
||||
handle.format(parse_ctx, context_);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
|
||||
for (; it != end; ++it) {
|
||||
switch (*it) {
|
||||
case '-':
|
||||
specs.align = align::left;
|
||||
break;
|
||||
case '+':
|
||||
specs.sign = sign::plus;
|
||||
break;
|
||||
case '0':
|
||||
specs.fill = '0';
|
||||
break;
|
||||
case ' ':
|
||||
if (specs.sign != sign::plus) specs.sign = sign::space;
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename GetArg>
|
||||
auto parse_header(const Char*& it, const Char* end, format_specs& specs,
|
||||
GetArg get_arg) -> int {
|
||||
int arg_index = -1;
|
||||
Char c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
int value = parse_nonnegative_int(it, end, -1);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value != -1 ? value : max_value<int>();
|
||||
} else {
|
||||
if (c == '0') specs.fill = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
if (value == -1) report_error("number is too big");
|
||||
specs.width = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(specs, it, end);
|
||||
// Parse width.
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
specs.width = parse_nonnegative_int(it, end, -1);
|
||||
if (specs.width == -1) report_error("number is too big");
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(
|
||||
get_arg(-1).visit(detail::printf_width_handler(specs)));
|
||||
}
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
inline auto parse_printf_presentation_type(char c, type t, bool& upper)
|
||||
-> presentation_type {
|
||||
using pt = presentation_type;
|
||||
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
|
||||
switch (c) {
|
||||
case 'd':
|
||||
return in(t, integral_set) ? pt::dec : pt::none;
|
||||
case 'o':
|
||||
return in(t, integral_set) ? pt::oct : pt::none;
|
||||
case 'X':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH;
|
||||
case 'x':
|
||||
return in(t, integral_set) ? pt::hex : pt::none;
|
||||
case 'E':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH;
|
||||
case 'e':
|
||||
return in(t, float_set) ? pt::exp : pt::none;
|
||||
case 'F':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH;
|
||||
case 'f':
|
||||
return in(t, float_set) ? pt::fixed : pt::none;
|
||||
case 'G':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH;
|
||||
case 'g':
|
||||
return in(t, float_set) ? pt::general : pt::none;
|
||||
case 'A':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH;
|
||||
case 'a':
|
||||
return in(t, float_set) ? pt::hexfloat : pt::none;
|
||||
case 'c':
|
||||
return in(t, integral_set) ? pt::chr : pt::none;
|
||||
case 's':
|
||||
return in(t, string_set | cstring_set) ? pt::string : pt::none;
|
||||
case 'p':
|
||||
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
|
||||
default:
|
||||
return pt::none;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
using iterator = basic_appender<Char>;
|
||||
auto out = iterator(buf);
|
||||
auto context = basic_printf_context<Char>(out, args);
|
||||
auto parse_ctx = basic_format_parse_context<Char>(format);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
auto get_arg = [&](int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx.next_arg_id();
|
||||
else
|
||||
parse_ctx.check_arg_id(--arg_index);
|
||||
return detail::get_arg(context, arg_index);
|
||||
};
|
||||
|
||||
const Char* start = parse_ctx.begin();
|
||||
const Char* end = parse_ctx.end();
|
||||
auto it = start;
|
||||
while (it != end) {
|
||||
if (!find<false, Char>(it, end, '%', it)) {
|
||||
it = end; // find leaves it == nullptr if it doesn't find '%'.
|
||||
break;
|
||||
}
|
||||
Char c = *it++;
|
||||
if (it != end && *it == c) {
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
|
||||
|
||||
auto specs = format_specs();
|
||||
specs.align = align::right;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs, get_arg);
|
||||
if (arg_index == 0) report_error("argument not found");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
++it;
|
||||
c = it != end ? *it : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
specs.precision = parse_nonnegative_int(it, end, 0);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision =
|
||||
static_cast<int>(get_arg(-1).visit(printf_precision_handler()));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto arg = get_arg(arg_index);
|
||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||
// specified, the '0' flag is ignored
|
||||
if (specs.precision >= 0 && arg.is_integral()) {
|
||||
// Ignore '0' for non-numeric types or if '-' present.
|
||||
specs.fill = ' ';
|
||||
}
|
||||
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
|
||||
auto str = arg.visit(get_cstring<Char>());
|
||||
auto str_end = str + specs.precision;
|
||||
auto nul = std::find(str, str_end, Char());
|
||||
auto sv = basic_string_view<Char>(
|
||||
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
|
||||
arg = make_arg<basic_printf_context<Char>>(sv);
|
||||
}
|
||||
if (specs.alt && arg.visit(is_zero_int())) specs.alt = false;
|
||||
if (specs.fill.template get<Char>() == '0') {
|
||||
if (arg.is_arithmetic() && specs.align != align::left)
|
||||
specs.align = align::numeric;
|
||||
else
|
||||
specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-'
|
||||
// flag is also present.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
Char t = it != end ? *it : 0;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
if (t == 'h') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<signed char>(arg, t);
|
||||
} else {
|
||||
convert_arg<short>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (t == 'l') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<long long>(arg, t);
|
||||
} else {
|
||||
convert_arg<long>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, t);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<size_t>(arg, t);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, t);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
convert_arg<void>(arg, c);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (it == end) report_error("invalid format string");
|
||||
char type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (type) {
|
||||
case 'i':
|
||||
case 'u':
|
||||
type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
arg.visit(char_converter<basic_printf_context<Char>>(arg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool upper = false;
|
||||
specs.type = parse_printf_presentation_type(type, arg.type(), upper);
|
||||
if (specs.type == presentation_type::none)
|
||||
report_error("invalid format specifier");
|
||||
specs.upper = upper;
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
arg.visit(printf_arg_formatter<Char>(out, specs, context));
|
||||
}
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
using printf_context = basic_printf_context<char>;
|
||||
using wprintf_context = basic_printf_context<wchar_t>;
|
||||
|
||||
using printf_args = basic_format_args<printf_context>;
|
||||
using wprintf_args = basic_format_args<wprintf_context>;
|
||||
|
||||
/// Constructs an `format_arg_store` object that contains references to
|
||||
/// arguments and can be implicitly converted to `printf_args`.
|
||||
template <typename Char = char, typename... T>
|
||||
inline auto make_printf_args(T&... args)
|
||||
-> decltype(fmt::make_format_args<basic_printf_context<Char>>(args...)) {
|
||||
return fmt::make_format_args<basic_printf_context<Char>>(args...);
|
||||
}
|
||||
|
||||
template <typename Char> struct vprintf_args {
|
||||
using type = basic_format_args<basic_printf_context<Char>>;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
inline auto vsprintf(basic_string_view<Char> fmt,
|
||||
typename vprintf_args<Char>::type args)
|
||||
-> std::basic_string<Char> {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vprintf(buf, fmt, args);
|
||||
return to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats `args` according to specifications in `fmt` and returns the result
|
||||
* as as string.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
*/
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||
return vsprintf(detail::to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
|
||||
typename vprintf_args<Char>::type args) -> int {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vprintf(buf, fmt, args);
|
||||
size_t size = buf.size();
|
||||
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
: static_cast<int>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats `args` according to specifications in `fmt` and writes the output
|
||||
* to `f`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
*/
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||
return vfprintf(f, detail::to_string_view(fmt),
|
||||
make_printf_args<Char>(args...));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt,
|
||||
typename vprintf_args<Char>::type args)
|
||||
-> int {
|
||||
return vfprintf(stdout, fmt, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats `args` according to specifications in `fmt` and writes the output
|
||||
* to `stdout`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto printf(string_view fmt, const T&... args) -> int {
|
||||
return vfprintf(stdout, fmt, make_printf_args(args...));
|
||||
}
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
|
||||
const T&... args) -> int {
|
||||
return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
|
||||
}
|
||||
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
|
@ -0,0 +1,882 @@
|
|||
// Formatting library for C++ - range and tuple support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <initializer_list>
|
||||
# include <iterator>
|
||||
# include <string>
|
||||
# include <tuple>
|
||||
# include <type_traits>
|
||||
# include <utility>
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
FMT_EXPORT
|
||||
enum class range_format { disabled, map, set, sequence, string, debug_string };
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T> class is_map {
|
||||
template <typename U> static auto check(U*) -> typename U::mapped_type;
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename T> class is_set {
|
||||
template <typename U> static auto check(U*) -> typename U::key_type;
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
|
||||
};
|
||||
|
||||
template <typename... Ts> struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
|
||||
|
||||
# define FMT_DECLTYPE_RETURN(val) \
|
||||
->decltype(val) { return val; } \
|
||||
static_assert( \
|
||||
true, "") // This makes it so that a semicolon is required after the
|
||||
// macro, which helps clang-format handle the formatting.
|
||||
|
||||
// C array overload
|
||||
template <typename T, std::size_t N>
|
||||
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||
return arr;
|
||||
}
|
||||
template <typename T, std::size_t N>
|
||||
auto range_end(const T (&arr)[N]) -> const T* {
|
||||
return arr + N;
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_member_fn_begin_end_t : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),
|
||||
decltype(std::declval<T>().end())>>
|
||||
: std::true_type {};
|
||||
|
||||
// Member function overloads.
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
|
||||
|
||||
// ADL overloads. Only participate in overload resolution if member functions
|
||||
// are not found.
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng)
|
||||
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(begin(static_cast<T&&>(rng)))> {
|
||||
return begin(static_cast<T&&>(rng));
|
||||
}
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(end(static_cast<T&&>(rng)))> {
|
||||
return end(static_cast<T&&>(rng));
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_const_begin_end : std::false_type {};
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_mutable_begin_end : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_const_begin_end<
|
||||
T, void_t<decltype(*detail::range_begin(
|
||||
std::declval<const remove_cvref_t<T>&>())),
|
||||
decltype(detail::range_end(
|
||||
std::declval<const remove_cvref_t<T>&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_mutable_begin_end<
|
||||
T, void_t<decltype(*detail::range_begin(std::declval<T&>())),
|
||||
decltype(detail::range_end(std::declval<T&>())),
|
||||
// the extra int here is because older versions of MSVC don't
|
||||
// SFINAE properly unless there are distinct types
|
||||
int>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_range_<T, void>
|
||||
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
|
||||
has_mutable_begin_end<T>::value)> {};
|
||||
# undef FMT_DECLTYPE_RETURN
|
||||
#endif
|
||||
|
||||
// tuple_size and tuple_element check.
|
||||
template <typename T> class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||
|
||||
template <typename T, size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <size_t N>
|
||||
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
|
||||
|
||||
template <typename T, typename C, bool = is_tuple_like_<T>::value>
|
||||
class is_tuple_formattable_ {
|
||||
public:
|
||||
static constexpr const bool value = false;
|
||||
};
|
||||
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
||||
template <size_t... Is>
|
||||
static auto all_true(index_sequence<Is...>,
|
||||
integer_sequence<bool, (Is >= 0)...>) -> std::true_type;
|
||||
static auto all_true(...) -> std::false_type;
|
||||
|
||||
template <size_t... Is>
|
||||
static auto check(index_sequence<Is...>) -> decltype(all_true(
|
||||
index_sequence<Is...>{},
|
||||
integer_sequence<bool,
|
||||
(is_formattable<typename std::tuple_element<Is, T>::type,
|
||||
C>::value)...>{}));
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
decltype(check(tuple_index_sequence<T>{}))::value;
|
||||
};
|
||||
|
||||
template <typename Tuple, typename F, size_t... Is>
|
||||
FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
|
||||
using std::get;
|
||||
// Using a free function get<Is>(Tuple) now.
|
||||
const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
|
||||
ignore_unused(unused);
|
||||
}
|
||||
|
||||
template <typename Tuple, typename F>
|
||||
FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
|
||||
for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
|
||||
std::forward<Tuple>(t), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
|
||||
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
|
||||
using std::get;
|
||||
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
|
||||
ignore_unused(unused);
|
||||
}
|
||||
|
||||
template <typename Tuple1, typename Tuple2, typename F>
|
||||
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
|
||||
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
|
||||
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
|
||||
std::forward<F>(f));
|
||||
}
|
||||
|
||||
namespace tuple {
|
||||
// Workaround a bug in MSVC 2019 (v140).
|
||||
template <typename Char, typename... T>
|
||||
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
|
||||
|
||||
using std::get;
|
||||
template <typename Tuple, typename Char, std::size_t... Is>
|
||||
auto get_formatters(index_sequence<Is...>)
|
||||
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
|
||||
} // namespace tuple
|
||||
|
||||
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
|
||||
// Older MSVC doesn't get the reference type correctly for arrays.
|
||||
template <typename R> struct range_reference_type_impl {
|
||||
using type = decltype(*detail::range_begin(std::declval<R&>()));
|
||||
};
|
||||
|
||||
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
|
||||
using type = T&;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using range_reference_type = typename range_reference_type_impl<T>::type;
|
||||
#else
|
||||
template <typename Range>
|
||||
using range_reference_type =
|
||||
decltype(*detail::range_begin(std::declval<Range&>()));
|
||||
#endif
|
||||
|
||||
// We don't use the Range's value_type for anything, but we do need the Range's
|
||||
// reference type, with cv-ref stripped.
|
||||
template <typename Range>
|
||||
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
||||
|
||||
template <typename Formatter>
|
||||
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
|
||||
-> decltype(f.set_debug_format(set)) {
|
||||
f.set_debug_format(set);
|
||||
}
|
||||
template <typename Formatter>
|
||||
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
|
||||
|
||||
template <typename T>
|
||||
struct range_format_kind_
|
||||
: std::integral_constant<range_format,
|
||||
std::is_same<uncvref_type<T>, T>::value
|
||||
? range_format::disabled
|
||||
: is_map<T>::value ? range_format::map
|
||||
: is_set<T>::value ? range_format::set
|
||||
: range_format::sequence> {};
|
||||
|
||||
template <range_format K>
|
||||
using range_format_constant = std::integral_constant<range_format, K>;
|
||||
|
||||
// These are not generic lambdas for compatibility with C++11.
|
||||
template <typename ParseContext> struct parse_empty_specs {
|
||||
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
|
||||
f.parse(ctx);
|
||||
detail::maybe_set_debug_format(f, true);
|
||||
}
|
||||
ParseContext& ctx;
|
||||
};
|
||||
template <typename FormatContext> struct format_tuple_element {
|
||||
using char_type = typename FormatContext::char_type;
|
||||
|
||||
template <typename T>
|
||||
void operator()(const formatter<T, char_type>& f, const T& v) {
|
||||
if (i > 0) ctx.advance_to(detail::copy<char_type>(separator, ctx.out()));
|
||||
ctx.advance_to(f.format(v, ctx));
|
||||
++i;
|
||||
}
|
||||
|
||||
int i;
|
||||
FormatContext& ctx;
|
||||
basic_string_view<char_type> separator;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
static constexpr const bool value =
|
||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename C> struct is_tuple_formattable {
|
||||
static constexpr const bool value =
|
||||
detail::is_tuple_formattable_<T, C>::value;
|
||||
};
|
||||
|
||||
template <typename Tuple, typename Char>
|
||||
struct formatter<Tuple, Char,
|
||||
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
|
||||
fmt::is_tuple_formattable<Tuple, Char>::value>> {
|
||||
private:
|
||||
decltype(detail::tuple::get_formatters<Tuple, Char>(
|
||||
detail::tuple_index_sequence<Tuple>())) formatters_;
|
||||
|
||||
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
|
||||
basic_string_view<Char> opening_bracket_ =
|
||||
detail::string_literal<Char, '('>{};
|
||||
basic_string_view<Char> closing_bracket_ =
|
||||
detail::string_literal<Char, ')'>{};
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR formatter() {}
|
||||
|
||||
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
|
||||
separator_ = sep;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
|
||||
basic_string_view<Char> close) {
|
||||
opening_bracket_ = open;
|
||||
closing_bracket_ = close;
|
||||
}
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
if (it != ctx.end() && *it != '}') report_error("invalid format specifier");
|
||||
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const Tuple& value, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
ctx.advance_to(detail::copy<Char>(opening_bracket_, ctx.out()));
|
||||
detail::for_each2(
|
||||
formatters_, value,
|
||||
detail::format_tuple_element<FormatContext>{0, ctx, separator_});
|
||||
return detail::copy<Char>(closing_bracket_, ctx.out());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static constexpr const bool value =
|
||||
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename Context> struct range_mapper {
|
||||
using mapper = arg_mapper<Context>;
|
||||
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
|
||||
static auto map(T&& value) -> T&& {
|
||||
return static_cast<T&&>(value);
|
||||
}
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
|
||||
static auto map(T&& value)
|
||||
-> decltype(mapper().map(static_cast<T&&>(value))) {
|
||||
return mapper().map(static_cast<T&&>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Element>
|
||||
using range_formatter_type =
|
||||
formatter<remove_cvref_t<decltype(range_mapper<buffered_context<Char>>{}
|
||||
.map(std::declval<Element>()))>,
|
||||
Char>;
|
||||
|
||||
template <typename R>
|
||||
using maybe_const_range =
|
||||
conditional_t<has_const_begin_end<R>::value, const R, R>;
|
||||
|
||||
// Workaround a bug in MSVC 2015 and earlier.
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
||||
template <typename R, typename Char>
|
||||
struct is_formattable_delayed
|
||||
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
|
||||
#endif
|
||||
} // namespace detail
|
||||
|
||||
template <typename...> struct conjunction : std::true_type {};
|
||||
template <typename P> struct conjunction<P> : P {};
|
||||
template <typename P1, typename... Pn>
|
||||
struct conjunction<P1, Pn...>
|
||||
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
|
||||
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
struct range_formatter;
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct range_formatter<
|
||||
T, Char,
|
||||
enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
|
||||
is_formattable<T, Char>>::value>> {
|
||||
private:
|
||||
detail::range_formatter_type<Char, T> underlying_;
|
||||
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
|
||||
basic_string_view<Char> opening_bracket_ =
|
||||
detail::string_literal<Char, '['>{};
|
||||
basic_string_view<Char> closing_bracket_ =
|
||||
detail::string_literal<Char, ']'>{};
|
||||
bool is_debug = false;
|
||||
|
||||
template <typename Output, typename It, typename Sentinel, typename U = T,
|
||||
FMT_ENABLE_IF(std::is_same<U, Char>::value)>
|
||||
auto write_debug_string(Output& out, It it, Sentinel end) const -> Output {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
for (; it != end; ++it) buf.push_back(*it);
|
||||
auto specs = format_specs();
|
||||
specs.type = presentation_type::debug;
|
||||
return detail::write<Char>(
|
||||
out, basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||
}
|
||||
|
||||
template <typename Output, typename It, typename Sentinel, typename U = T,
|
||||
FMT_ENABLE_IF(!std::is_same<U, Char>::value)>
|
||||
auto write_debug_string(Output& out, It, Sentinel) const -> Output {
|
||||
return out;
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR range_formatter() {}
|
||||
|
||||
FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
|
||||
return underlying_;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
|
||||
separator_ = sep;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
|
||||
basic_string_view<Char> close) {
|
||||
opening_bracket_ = open;
|
||||
closing_bracket_ = close;
|
||||
}
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
detail::maybe_set_debug_format(underlying_, true);
|
||||
if (it == end) return underlying_.parse(ctx);
|
||||
|
||||
switch (detail::to_ascii(*it)) {
|
||||
case 'n':
|
||||
set_brackets({}, {});
|
||||
++it;
|
||||
break;
|
||||
case '?':
|
||||
is_debug = true;
|
||||
set_brackets({}, {});
|
||||
++it;
|
||||
if (it == end || *it != 's') report_error("invalid format specifier");
|
||||
FMT_FALLTHROUGH;
|
||||
case 's':
|
||||
if (!std::is_same<T, Char>::value)
|
||||
report_error("invalid format specifier");
|
||||
if (!is_debug) {
|
||||
set_brackets(detail::string_literal<Char, '"'>{},
|
||||
detail::string_literal<Char, '"'>{});
|
||||
set_separator({});
|
||||
detail::maybe_set_debug_format(underlying_, false);
|
||||
}
|
||||
++it;
|
||||
return it;
|
||||
}
|
||||
|
||||
if (it != end && *it != '}') {
|
||||
if (*it != ':') report_error("invalid format specifier");
|
||||
detail::maybe_set_debug_format(underlying_, false);
|
||||
++it;
|
||||
}
|
||||
|
||||
ctx.advance_to(it);
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename R, typename FormatContext>
|
||||
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
auto mapper = detail::range_mapper<buffered_context<Char>>();
|
||||
auto out = ctx.out();
|
||||
auto it = detail::range_begin(range);
|
||||
auto end = detail::range_end(range);
|
||||
if (is_debug) return write_debug_string(out, std::move(it), end);
|
||||
|
||||
out = detail::copy<Char>(opening_bracket_, out);
|
||||
int i = 0;
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) out = detail::copy<Char>(separator_, out);
|
||||
ctx.advance_to(out);
|
||||
auto&& item = *it; // Need an lvalue
|
||||
out = underlying_.format(mapper.map(item), ctx);
|
||||
++i;
|
||||
}
|
||||
out = detail::copy<Char>(closing_bracket_, out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
struct range_format_kind
|
||||
: conditional_t<
|
||||
is_range<T, Char>::value, detail::range_format_kind_<T>,
|
||||
std::integral_constant<range_format, range_format::disabled>> {};
|
||||
|
||||
template <typename R, typename Char>
|
||||
struct formatter<
|
||||
R, Char,
|
||||
enable_if_t<conjunction<
|
||||
bool_constant<
|
||||
range_format_kind<R, Char>::value != range_format::disabled &&
|
||||
range_format_kind<R, Char>::value != range_format::map &&
|
||||
range_format_kind<R, Char>::value != range_format::string &&
|
||||
range_format_kind<R, Char>::value != range_format::debug_string>
|
||||
// Workaround a bug in MSVC 2015 and earlier.
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
||||
,
|
||||
detail::is_formattable_delayed<R, Char>
|
||||
#endif
|
||||
>::value>> {
|
||||
private:
|
||||
using range_type = detail::maybe_const_range<R>;
|
||||
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
|
||||
|
||||
public:
|
||||
using nonlocking = void;
|
||||
|
||||
FMT_CONSTEXPR formatter() {
|
||||
if (detail::const_check(range_format_kind<R, Char>::value !=
|
||||
range_format::set))
|
||||
return;
|
||||
range_formatter_.set_brackets(detail::string_literal<Char, '{'>{},
|
||||
detail::string_literal<Char, '}'>{});
|
||||
}
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return range_formatter_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(range_type& range, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return range_formatter_.format(range, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// A map formatter.
|
||||
template <typename R, typename Char>
|
||||
struct formatter<
|
||||
R, Char,
|
||||
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> {
|
||||
private:
|
||||
using map_type = detail::maybe_const_range<R>;
|
||||
using element_type = detail::uncvref_type<map_type>;
|
||||
|
||||
decltype(detail::tuple::get_formatters<element_type, Char>(
|
||||
detail::tuple_index_sequence<element_type>())) formatters_;
|
||||
bool no_delimiters_ = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR formatter() {}
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
if (it != end) {
|
||||
if (detail::to_ascii(*it) == 'n') {
|
||||
no_delimiters_ = true;
|
||||
++it;
|
||||
}
|
||||
if (it != end && *it != '}') {
|
||||
if (*it != ':') report_error("invalid format specifier");
|
||||
++it;
|
||||
}
|
||||
ctx.advance_to(it);
|
||||
}
|
||||
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
|
||||
if (!no_delimiters_) out = detail::copy<Char>(open, out);
|
||||
int i = 0;
|
||||
auto mapper = detail::range_mapper<buffered_context<Char>>();
|
||||
basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
|
||||
for (auto&& value : map) {
|
||||
if (i > 0) out = detail::copy<Char>(sep, out);
|
||||
ctx.advance_to(out);
|
||||
detail::for_each2(formatters_, mapper.map(value),
|
||||
detail::format_tuple_element<FormatContext>{
|
||||
0, ctx, detail::string_literal<Char, ':', ' '>{}});
|
||||
++i;
|
||||
}
|
||||
basic_string_view<Char> close = detail::string_literal<Char, '}'>{};
|
||||
if (!no_delimiters_) out = detail::copy<Char>(close, out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
// A (debug_)string formatter.
|
||||
template <typename R, typename Char>
|
||||
struct formatter<
|
||||
R, Char,
|
||||
enable_if_t<range_format_kind<R, Char>::value == range_format::string ||
|
||||
range_format_kind<R, Char>::value ==
|
||||
range_format::debug_string>> {
|
||||
private:
|
||||
using range_type = detail::maybe_const_range<R>;
|
||||
using string_type =
|
||||
conditional_t<std::is_constructible<
|
||||
detail::std_string_view<Char>,
|
||||
decltype(detail::range_begin(std::declval<R>())),
|
||||
decltype(detail::range_end(std::declval<R>()))>::value,
|
||||
detail::std_string_view<Char>, std::basic_string<Char>>;
|
||||
|
||||
formatter<string_type, Char> underlying_;
|
||||
|
||||
public:
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(range_type& range, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
if (detail::const_check(range_format_kind<R, Char>::value ==
|
||||
range_format::debug_string))
|
||||
*out++ = '"';
|
||||
out = underlying_.format(
|
||||
string_type{detail::range_begin(range), detail::range_end(range)}, ctx);
|
||||
if (detail::const_check(range_format_kind<R, Char>::value ==
|
||||
range_format::debug_string))
|
||||
*out++ = '"';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename It, typename Sentinel, typename Char = char>
|
||||
struct join_view : detail::view {
|
||||
It begin;
|
||||
Sentinel end;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
join_view(It b, Sentinel e, basic_string_view<Char> s)
|
||||
: begin(std::move(b)), end(e), sep(s) {}
|
||||
};
|
||||
|
||||
template <typename It, typename Sentinel, typename Char>
|
||||
struct formatter<join_view<It, Sentinel, Char>, Char> {
|
||||
private:
|
||||
using value_type =
|
||||
#ifdef __cpp_lib_ranges
|
||||
std::iter_value_t<It>;
|
||||
#else
|
||||
typename std::iterator_traits<It>::value_type;
|
||||
#endif
|
||||
formatter<remove_cvref_t<value_type>, Char> value_formatter_;
|
||||
|
||||
using view_ref = conditional_t<std::is_copy_constructible<It>::value,
|
||||
const join_view<It, Sentinel, Char>&,
|
||||
join_view<It, Sentinel, Char>&&>;
|
||||
|
||||
public:
|
||||
using nonlocking = void;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
|
||||
return value_formatter_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(view_ref& value, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto it = std::forward<view_ref>(value).begin;
|
||||
auto out = ctx.out();
|
||||
if (it == value.end) return out;
|
||||
out = value_formatter_.format(*it, ctx);
|
||||
++it;
|
||||
while (it != value.end) {
|
||||
out = detail::copy<Char>(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
out = value_formatter_.format(*it, ctx);
|
||||
++it;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns a view that formats the iterator range `[begin, end)` with elements
|
||||
/// separated by `sep`.
|
||||
template <typename It, typename Sentinel>
|
||||
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
|
||||
return {std::move(begin), end, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a view that formats `range` with elements separated by `sep`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* auto v = std::vector<int>{1, 2, 3};
|
||||
* fmt::print("{}", fmt::join(v, ", "));
|
||||
* // Output: 1, 2, 3
|
||||
*
|
||||
* `fmt::join` applies passed format specifiers to the range elements:
|
||||
*
|
||||
* fmt::print("{:02}", fmt::join(v, ", "));
|
||||
* // Output: 01, 02, 03
|
||||
*/
|
||||
template <typename Range>
|
||||
auto join(Range&& r, string_view sep)
|
||||
-> join_view<decltype(detail::range_begin(r)),
|
||||
decltype(detail::range_end(r))> {
|
||||
return {detail::range_begin(r), detail::range_end(r), sep};
|
||||
}
|
||||
|
||||
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple(t), sep{s} {}
|
||||
};
|
||||
|
||||
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
|
||||
// support in tuple_join. It is disabled by default because of issues with
|
||||
// the dynamic width and precision.
|
||||
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
|
||||
# define FMT_TUPLE_JOIN_SPECIFIERS 0
|
||||
#endif
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_join_view<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const tuple_join_view<Char, T...>& value,
|
||||
FormatContext& ctx) const -> typename FormatContext::iterator {
|
||||
return do_format(value, ctx,
|
||||
std::integral_constant<size_t, sizeof...(T)>());
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
|
||||
std::integral_constant<size_t, 0>)
|
||||
-> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename ParseContext, size_t N>
|
||||
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
|
||||
std::integral_constant<size_t, N>)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto end = ctx.begin();
|
||||
#if FMT_TUPLE_JOIN_SPECIFIERS
|
||||
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
|
||||
if (N > 1) {
|
||||
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
|
||||
if (end != end1)
|
||||
report_error("incompatible format specs for tuple elements");
|
||||
}
|
||||
#endif
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
|
||||
std::integral_constant<size_t, 0>) const ->
|
||||
typename FormatContext::iterator {
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, size_t N>
|
||||
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||
std::integral_constant<size_t, N>) const ->
|
||||
typename FormatContext::iterator {
|
||||
auto out = std::get<sizeof...(T) - N>(formatters_)
|
||||
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
|
||||
if (N <= 1) return out;
|
||||
out = detail::copy<Char>(value.sep, out);
|
||||
ctx.advance_to(out);
|
||||
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
// Check if T has an interface like a container adaptor (e.g. std::stack,
|
||||
// std::queue, std::priority_queue).
|
||||
template <typename T> class is_container_adaptor_like {
|
||||
template <typename U> static auto check(U* p) -> typename U::container_type;
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename Container> struct all {
|
||||
const Container& c;
|
||||
auto begin() const -> typename Container::const_iterator { return c.begin(); }
|
||||
auto end() const -> typename Container::const_iterator { return c.end(); }
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
|
||||
bool_constant<range_format_kind<T, Char>::value ==
|
||||
range_format::disabled>>::value>>
|
||||
: formatter<detail::all<typename T::container_type>, Char> {
|
||||
using all = detail::all<typename T::container_type>;
|
||||
template <typename FormatContext>
|
||||
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
struct getter : T {
|
||||
static auto get(const T& t) -> all {
|
||||
return {t.*(&getter::c)}; // Access c through the derived class.
|
||||
}
|
||||
};
|
||||
return formatter<all>::format(getter::get(t), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
/**
|
||||
* Returns an object that formats `std::tuple` with elements separated by `sep`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* auto t = std::tuple<int, char>{1, 'a'};
|
||||
* fmt::print("{}", fmt::join(t, ", "));
|
||||
* // Output: 1, a
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
|
||||
-> tuple_join_view<char, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that formats `std::initializer_list` with elements
|
||||
* separated by `sep`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print("{}", fmt::join({1, 2, 3}, ", "));
|
||||
* // Output: "1, 2, 3"
|
||||
*/
|
||||
template <typename T>
|
||||
auto join(std::initializer_list<T> list, string_view sep)
|
||||
-> join_view<const T*, const T*> {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
|
@ -0,0 +1,699 @@
|
|||
// Formatting library for C++ - formatters for standard library types
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_STD_H_
|
||||
#define FMT_STD_H_
|
||||
|
||||
#include "format.h"
|
||||
#include "ostream.h"
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <atomic>
|
||||
# include <bitset>
|
||||
# include <complex>
|
||||
# include <cstdlib>
|
||||
# include <exception>
|
||||
# include <memory>
|
||||
# include <thread>
|
||||
# include <type_traits>
|
||||
# include <typeinfo>
|
||||
# include <utility>
|
||||
# include <vector>
|
||||
|
||||
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
|
||||
# if FMT_CPLUSPLUS >= 201703L
|
||||
# if FMT_HAS_INCLUDE(<filesystem>)
|
||||
# include <filesystem>
|
||||
# endif
|
||||
# if FMT_HAS_INCLUDE(<variant>)
|
||||
# include <variant>
|
||||
# endif
|
||||
# if FMT_HAS_INCLUDE(<optional>)
|
||||
# include <optional>
|
||||
# endif
|
||||
# endif
|
||||
// Use > instead of >= in the version check because <source_location> may be
|
||||
// available after C++17 but before C++20 is marked as implemented.
|
||||
# if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)
|
||||
# include <source_location>
|
||||
# endif
|
||||
# if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE(<expected>)
|
||||
# include <expected>
|
||||
# endif
|
||||
#endif // FMT_MODULE
|
||||
|
||||
#if FMT_HAS_INCLUDE(<version>)
|
||||
# include <version>
|
||||
#endif
|
||||
|
||||
// GCC 4 does not support FMT_HAS_INCLUDE.
|
||||
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
|
||||
# include <cxxabi.h>
|
||||
// Android NDK with gabi++ library on some architectures does not implement
|
||||
// abi::__cxa_demangle().
|
||||
# ifndef __GABIXX_CXXABI_H__
|
||||
# define FMT_HAS_ABI_CXA_DEMANGLE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
|
||||
#ifndef FMT_CPP_LIB_FILESYSTEM
|
||||
# ifdef __cpp_lib_filesystem
|
||||
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
|
||||
# else
|
||||
# define FMT_CPP_LIB_FILESYSTEM 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_CPP_LIB_VARIANT
|
||||
# ifdef __cpp_lib_variant
|
||||
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
|
||||
# else
|
||||
# define FMT_CPP_LIB_VARIANT 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_FILESYSTEM
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Char, typename PathChar>
|
||||
auto get_path_string(const std::filesystem::path& p,
|
||||
const std::basic_string<PathChar>& native) {
|
||||
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
|
||||
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
|
||||
else
|
||||
return p.string<Char>();
|
||||
}
|
||||
|
||||
template <typename Char, typename PathChar>
|
||||
void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
||||
const std::filesystem::path& p,
|
||||
const std::basic_string<PathChar>& native) {
|
||||
if constexpr (std::is_same_v<Char, char> &&
|
||||
std::is_same_v<PathChar, wchar_t>) {
|
||||
auto buf = basic_memory_buffer<wchar_t>();
|
||||
write_escaped_string<wchar_t>(std::back_inserter(buf), native);
|
||||
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
|
||||
FMT_ASSERT(valid, "invalid utf16");
|
||||
} else if constexpr (std::is_same_v<Char, PathChar>) {
|
||||
write_escaped_string<std::filesystem::path::value_type>(
|
||||
std::back_inserter(quoted), native);
|
||||
} else {
|
||||
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
bool debug_ = false;
|
||||
char path_type_ = 0;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||
|
||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
||||
if (it != end && *it == '?') {
|
||||
debug_ = true;
|
||||
++it;
|
||||
}
|
||||
if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++);
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
|
||||
auto specs = specs_;
|
||||
auto path_string =
|
||||
!path_type_ ? p.native()
|
||||
: p.generic_string<std::filesystem::path::value_type>();
|
||||
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
||||
ctx);
|
||||
if (!debug_) {
|
||||
auto s = detail::get_path_string<Char>(p, path_string);
|
||||
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
|
||||
}
|
||||
auto quoted = basic_memory_buffer<Char>();
|
||||
detail::write_escaped_path(quoted, p, path_string);
|
||||
return detail::write(ctx.out(),
|
||||
basic_string_view<Char>(quoted.data(), quoted.size()),
|
||||
specs);
|
||||
}
|
||||
};
|
||||
|
||||
class path : public std::filesystem::path {
|
||||
public:
|
||||
auto display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{}"), base);
|
||||
}
|
||||
auto system_string() const -> std::string { return string(); }
|
||||
|
||||
auto generic_display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{:g}"), base);
|
||||
}
|
||||
auto generic_system_string() const -> std::string { return generic_string(); }
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <std::size_t N, typename Char>
|
||||
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
|
||||
private:
|
||||
// Functor because C++11 doesn't support generic lambdas.
|
||||
struct writer {
|
||||
const std::bitset<N>& bs;
|
||||
|
||||
template <typename OutputIt>
|
||||
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
|
||||
for (auto pos = N; pos > 0; --pos) {
|
||||
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename FormatContext>
|
||||
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return write_padded(ctx, writer{bs});
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#ifdef __cpp_lib_optional
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::optional<T>, Char,
|
||||
std::enable_if_t<is_formattable<T, Char>::value>> {
|
||||
private:
|
||||
formatter<T, Char> underlying_;
|
||||
static constexpr basic_string_view<Char> optional =
|
||||
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
|
||||
'('>{};
|
||||
static constexpr basic_string_view<Char> none =
|
||||
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
|
||||
-> decltype(u.set_debug_format(set)) {
|
||||
u.set_debug_format(set);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
|
||||
|
||||
public:
|
||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
||||
maybe_set_debug_format(underlying_, true);
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::optional<T>& opt, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
if (!opt) return detail::write<Char>(ctx.out(), none);
|
||||
|
||||
auto out = ctx.out();
|
||||
out = detail::write<Char>(out, optional);
|
||||
ctx.advance_to(out);
|
||||
out = underlying_.format(*opt, ctx);
|
||||
return detail::write(out, ')');
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif // __cpp_lib_optional
|
||||
|
||||
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename Char, typename OutputIt, typename T>
|
||||
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
|
||||
if constexpr (has_to_string_view<T>::value)
|
||||
return write_escaped_string<Char>(out, detail::to_string_view(v));
|
||||
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
|
||||
return write<Char>(out, v);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
#ifdef __cpp_lib_expected
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename E, typename Char>
|
||||
struct formatter<std::expected<T, E>, Char,
|
||||
std::enable_if_t<is_formattable<T, Char>::value &&
|
||||
is_formattable<E, Char>::value>> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::expected<T, E>& value, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
|
||||
if (value.has_value()) {
|
||||
out = detail::write<Char>(out, "expected(");
|
||||
out = detail::write_escaped_alternative<Char>(out, *value);
|
||||
} else {
|
||||
out = detail::write<Char>(out, "unexpected(");
|
||||
out = detail::write_escaped_alternative<Char>(out, value.error());
|
||||
}
|
||||
*out++ = ')';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif // __cpp_lib_expected
|
||||
|
||||
#ifdef __cpp_lib_source_location
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <> struct formatter<std::source_location> {
|
||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::source_location& loc, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write(out, loc.file_name());
|
||||
out = detail::write(out, ':');
|
||||
out = detail::write<char>(out, loc.line());
|
||||
out = detail::write(out, ':');
|
||||
out = detail::write<char>(out, loc.column());
|
||||
out = detail::write(out, ": ");
|
||||
out = detail::write(out, loc.function_name());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_VARIANT
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
using variant_index_sequence =
|
||||
std::make_index_sequence<std::variant_size<T>::value>;
|
||||
|
||||
template <typename> struct is_variant_like_ : std::false_type {};
|
||||
template <typename... Types>
|
||||
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
|
||||
|
||||
// formattable element check.
|
||||
template <typename T, typename C> class is_variant_formattable_ {
|
||||
template <std::size_t... Is>
|
||||
static std::conjunction<
|
||||
is_formattable<std::variant_alternative_t<Is, T>, C>...>
|
||||
check(std::index_sequence<Is...>);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
decltype(check(variant_index_sequence<T>{}))::value;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_variant_like {
|
||||
static constexpr const bool value = detail::is_variant_like_<T>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename C> struct is_variant_formattable {
|
||||
static constexpr const bool value =
|
||||
detail::is_variant_formattable_<T, C>::value;
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::monostate, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::monostate&, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return detail::write<Char>(ctx.out(), "monostate");
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Variant, typename Char>
|
||||
struct formatter<
|
||||
Variant, Char,
|
||||
std::enable_if_t<std::conjunction_v<
|
||||
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const Variant& value, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
|
||||
out = detail::write<Char>(out, "variant(");
|
||||
FMT_TRY {
|
||||
std::visit(
|
||||
[&](const auto& v) {
|
||||
out = detail::write_escaped_alternative<Char>(out, v);
|
||||
},
|
||||
value);
|
||||
}
|
||||
FMT_CATCH(const std::bad_variant_access&) {
|
||||
detail::write<Char>(out, "valueless by exception");
|
||||
}
|
||||
*out++ = ')';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif // FMT_CPP_LIB_VARIANT
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::error_code, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write_bytes<Char>(out, ec.category().name(), format_specs());
|
||||
out = detail::write<Char>(out, Char(':'));
|
||||
out = detail::write<Char>(out, ec.value());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
namespace detail {
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
int status = 0;
|
||||
std::size_t size = 0;
|
||||
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
|
||||
|
||||
string_view demangled_name_view;
|
||||
if (demangled_name_ptr) {
|
||||
demangled_name_view = demangled_name_ptr.get();
|
||||
|
||||
// Normalization of stdlib inline namespace names.
|
||||
// libc++ inline namespaces.
|
||||
// std::__1::* -> std::*
|
||||
// std::__1::__fs::* -> std::*
|
||||
// libstdc++ inline namespaces.
|
||||
// std::__cxx11::* -> std::*
|
||||
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||
if (demangled_name_view.starts_with("std::")) {
|
||||
char* begin = demangled_name_ptr.get();
|
||||
char* to = begin + 5; // std::
|
||||
for (char *from = to, *end = begin + demangled_name_view.size();
|
||||
from < end;) {
|
||||
// This is safe, because demangled_name is NUL-terminated.
|
||||
if (from[0] == '_' && from[1] == '_') {
|
||||
char* next = from + 1;
|
||||
while (next < end && *next != ':') next++;
|
||||
if (next[0] == ':' && next[1] == ':') {
|
||||
from = next + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*to++ = *from++;
|
||||
}
|
||||
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||
}
|
||||
} else {
|
||||
demangled_name_view = string_view(ti.name());
|
||||
}
|
||||
return detail::write_bytes<Char>(out, demangled_name_view);
|
||||
# elif FMT_MSC_VERSION
|
||||
const string_view demangled_name(ti.name());
|
||||
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
|
||||
auto sub = demangled_name;
|
||||
sub.remove_prefix(i);
|
||||
if (sub.starts_with("enum ")) {
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
if (sub.starts_with("class ") || sub.starts_with("union ")) {
|
||||
i += 5;
|
||||
continue;
|
||||
}
|
||||
if (sub.starts_with("struct ")) {
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
if (*sub.begin() != ' ') *out++ = *sub.begin();
|
||||
}
|
||||
return out;
|
||||
# else
|
||||
return detail::write_bytes<Char>(out, string_view(ti.name()));
|
||||
# endif
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
|
||||
> {
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
auto format(const std::type_info& ti, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return detail::write_demangled_name<Char>(ctx.out(), ti);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char, // DEPRECATED! Mixing code unit types.
|
||||
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
|
||||
private:
|
||||
bool with_typename_ = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
if (*it == 't') {
|
||||
++it;
|
||||
with_typename_ = FMT_USE_RTTI != 0;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
auto format(const std::exception& ex, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
#if FMT_USE_RTTI
|
||||
if (with_typename_) {
|
||||
out = detail::write_demangled_name<Char>(out, typeid(ex));
|
||||
*out++ = ':';
|
||||
*out++ = ' ';
|
||||
}
|
||||
#endif
|
||||
return detail::write_bytes<Char>(out, string_view(ex.what()));
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_flip : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T> struct is_bit_reference_like {
|
||||
static constexpr const bool value =
|
||||
std::is_convertible<T, bool>::value &&
|
||||
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
|
||||
};
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
|
||||
// Workaround for libc++ incompatibility with C++ standard.
|
||||
// According to the Standard, `bitset::operator[] const` returns bool.
|
||||
template <typename C>
|
||||
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
||||
static constexpr const bool value = true;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// We can't use std::vector<bool, Allocator>::reference and
|
||||
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
|
||||
// in partial specialization.
|
||||
FMT_EXPORT
|
||||
template <typename BitRef, typename Char>
|
||||
struct formatter<BitRef, Char,
|
||||
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
|
||||
: formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<bool, Char>::format(v, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Deleter>
|
||||
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
|
||||
return p.get();
|
||||
}
|
||||
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
|
||||
return p.get();
|
||||
}
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::atomic<T>, Char,
|
||||
enable_if_t<is_formattable<T, Char>::value>>
|
||||
: formatter<T, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::atomic<T>& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<T, Char>::format(v.load(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __cpp_lib_atomic_flag_test
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::atomic_flag& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<bool, Char>::format(v.test(), ctx);
|
||||
}
|
||||
};
|
||||
#endif // __cpp_lib_atomic_flag_test
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
||||
private:
|
||||
detail::dynamic_format_specs<Char> specs_;
|
||||
|
||||
template <typename FormatContext, typename OutputIt>
|
||||
FMT_CONSTEXPR auto do_format(const std::complex<T>& c,
|
||||
detail::dynamic_format_specs<Char>& specs,
|
||||
FormatContext& ctx, OutputIt out) const
|
||||
-> OutputIt {
|
||||
if (c.real() != 0) {
|
||||
*out++ = Char('(');
|
||||
out = detail::write<Char>(out, c.real(), specs, ctx.locale());
|
||||
specs.sign = sign::plus;
|
||||
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
|
||||
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
|
||||
*out++ = Char('i');
|
||||
*out++ = Char(')');
|
||||
return out;
|
||||
}
|
||||
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
|
||||
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
|
||||
*out++ = Char('i');
|
||||
return out;
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
|
||||
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
|
||||
detail::type_constant<T, Char>::value);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::complex<T>& c, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto specs = specs_;
|
||||
if (specs.width_ref.kind != detail::arg_id_kind::none ||
|
||||
specs.precision_ref.kind != detail::arg_id_kind::none) {
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width,
|
||||
specs.width_ref, ctx);
|
||||
detail::handle_dynamic_spec<detail::precision_checker>(
|
||||
specs.precision, specs.precision_ref, ctx);
|
||||
}
|
||||
|
||||
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
|
||||
auto outer_specs = format_specs();
|
||||
outer_specs.width = specs.width;
|
||||
outer_specs.fill = specs.fill;
|
||||
outer_specs.align = specs.align;
|
||||
|
||||
specs.width = 0;
|
||||
specs.fill = {};
|
||||
specs.align = align::none;
|
||||
|
||||
do_format(c, specs, ctx, basic_appender<Char>(buf));
|
||||
return detail::write<Char>(ctx.out(),
|
||||
basic_string_view<Char>(buf.data(), buf.size()),
|
||||
outer_specs);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
#endif // FMT_STD_H_
|
|
@ -0,0 +1,322 @@
|
|||
// Formatting library for C++ - optional wchar_t and exotic character support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_XCHAR_H_
|
||||
#define FMT_XCHAR_H_
|
||||
|
||||
#include "color.h"
|
||||
#include "format.h"
|
||||
#include "ranges.h"
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <cwchar>
|
||||
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
# include <locale>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
||||
|
||||
template <typename S, typename = void> struct format_string_char {};
|
||||
|
||||
template <typename S>
|
||||
struct format_string_char<
|
||||
S, void_t<decltype(sizeof(detail::to_string_view(std::declval<S>())))>> {
|
||||
using type = char_t<S>;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
struct format_string_char<S, enable_if_t<is_compile_string<S>::value>> {
|
||||
using type = typename S::char_type;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
using format_string_char_t = typename format_string_char<S>::type;
|
||||
|
||||
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
|
||||
const format_specs& specs, locale_ref loc) -> bool {
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
auto& numpunct =
|
||||
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
|
||||
auto separator = std::wstring();
|
||||
auto grouping = numpunct.grouping();
|
||||
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
|
||||
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
using wstring_view = basic_string_view<wchar_t>;
|
||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||
using wformat_context = buffered_context<wchar_t>;
|
||||
using wformat_args = basic_format_args<wformat_context>;
|
||||
using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround broken conversion on older gcc.
|
||||
template <typename... Args> using wformat_string = wstring_view;
|
||||
inline auto runtime(wstring_view s) -> wstring_view { return s; }
|
||||
#else
|
||||
template <typename... Args>
|
||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
|
||||
return {{s}};
|
||||
}
|
||||
#endif
|
||||
|
||||
template <> struct is_char<wchar_t> : std::true_type {};
|
||||
template <> struct is_char<char16_t> : std::true_type {};
|
||||
template <> struct is_char<char32_t> : std::true_type {};
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
template <>
|
||||
struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled()> {};
|
||||
#endif
|
||||
|
||||
template <typename... T>
|
||||
constexpr auto make_wformat_args(T&... args)
|
||||
-> decltype(fmt::make_format_args<wformat_context>(args...)) {
|
||||
return fmt::make_format_args<wformat_context>(args...);
|
||||
}
|
||||
|
||||
inline namespace literals {
|
||||
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
constexpr auto operator""_a(const wchar_t* s, size_t)
|
||||
-> detail::udl_arg<wchar_t> {
|
||||
return {s};
|
||||
}
|
||||
#endif
|
||||
} // namespace literals
|
||||
|
||||
template <typename It, typename Sentinel>
|
||||
auto join(It begin, Sentinel end, wstring_view sep)
|
||||
-> join_view<It, Sentinel, wchar_t> {
|
||||
return {begin, end, sep};
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
auto join(Range&& range, wstring_view sep)
|
||||
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
|
||||
wchar_t> {
|
||||
return join(std::begin(range), std::end(range), sep);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto join(std::initializer_list<T> list, wstring_view sep)
|
||||
-> join_view<const T*, const T*, wchar_t> {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep)
|
||||
-> tuple_join_view<wchar_t, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto vformat(basic_string_view<Char> format_str,
|
||||
typename detail::vformat_args<Char>::type args)
|
||||
-> std::basic_string<Char> {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return to_string(buf);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
|
||||
return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename... T>
|
||||
auto format_to(OutputIt out, wformat_string<T...> fmt, T&&... args)
|
||||
-> OutputIt {
|
||||
return vformat_to(out, fmt::wstring_view(fmt),
|
||||
fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
// Pass char_t as a default template parameter instead of using
|
||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||
template <typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
|
||||
!std::is_same<Char, wchar_t>::value)>
|
||||
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
|
||||
return vformat(detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename Locale, typename S,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat(const Locale& loc, const S& format_str,
|
||||
typename detail::vformat_args<Char>::type args)
|
||||
-> std::basic_string<Char> {
|
||||
return detail::vformat(loc, detail::to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format(const Locale& loc, const S& format_str, T&&... args)
|
||||
-> std::basic_string<Char> {
|
||||
return detail::vformat(
|
||||
loc, detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
auto vformat_to(OutputIt out, const S& format_str,
|
||||
typename detail::vformat_args<Char>::type args) -> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, detail::to_string_view(format_str), args);
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value &&
|
||||
!std::is_same<Char, char>::value &&
|
||||
!std::is_same<Char, wchar_t>::value)>
|
||||
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
|
||||
return vformat_to(out, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename OutputIt, typename... Args,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||
typename detail::vformat_args<Char>::type args)
|
||||
-> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
vformat_to(buf, detail::to_string_view(format_str), args,
|
||||
detail::locale_ref(loc));
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Locale, typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
|
||||
detail::is_locale<Locale>::value &&
|
||||
detail::is_exotic_char<Char>::value>
|
||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||
T&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
return vformat_to(out, loc, detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat_to_n(OutputIt out, size_t n,
|
||||
basic_string_view<Char> format_str,
|
||||
typename detail::vformat_args<Char>::type args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
return vformat_to_n(out, n, fmt::basic_string_view<Char>(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
|
||||
auto buf = detail::counting_buffer<Char>();
|
||||
detail::vformat_to(buf, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
return buf.count();
|
||||
}
|
||||
|
||||
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||
auto buf = wmemory_buffer();
|
||||
detail::vformat_to(buf, fmt, args);
|
||||
buf.push_back(L'\0');
|
||||
if (std::fputws(buf.data(), f) == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
|
||||
inline void vprint(wstring_view fmt, wformat_args args) {
|
||||
vprint(stdout, fmt, args);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
|
||||
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
|
||||
-> std::wstring {
|
||||
auto buf = wmemory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
|
||||
-> std::wstring {
|
||||
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
|
||||
wformat_string<T...> fmt, const T&... args) {
|
||||
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
|
||||
const T&... args) {
|
||||
return print(stdout, ts, fmt, args...);
|
||||
}
|
||||
|
||||
/// Converts `value` to `std::wstring` using the default format for type `T`.
|
||||
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||
return format(FMT_STRING(L"{}"), value);
|
||||
}
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_XCHAR_H_
|
|
@ -0,0 +1,135 @@
|
|||
module;
|
||||
|
||||
// Put all implementation-provided headers into the global module fragment
|
||||
// to prevent attachment to this module.
|
||||
#ifndef FMT_IMPORT_STD
|
||||
# include <algorithm>
|
||||
# include <bitset>
|
||||
# include <chrono>
|
||||
# include <cmath>
|
||||
# include <complex>
|
||||
# include <cstddef>
|
||||
# include <cstdint>
|
||||
# include <cstdio>
|
||||
# include <cstdlib>
|
||||
# include <cstring>
|
||||
# include <ctime>
|
||||
# include <exception>
|
||||
# include <expected>
|
||||
# include <filesystem>
|
||||
# include <fstream>
|
||||
# include <functional>
|
||||
# include <iterator>
|
||||
# include <limits>
|
||||
# include <locale>
|
||||
# include <memory>
|
||||
# include <optional>
|
||||
# include <ostream>
|
||||
# include <source_location>
|
||||
# include <stdexcept>
|
||||
# include <string>
|
||||
# include <string_view>
|
||||
# include <system_error>
|
||||
# include <thread>
|
||||
# include <type_traits>
|
||||
# include <typeinfo>
|
||||
# include <utility>
|
||||
# include <variant>
|
||||
# include <vector>
|
||||
#else
|
||||
# include <limits.h>
|
||||
# include <stdint.h>
|
||||
# include <stdio.h>
|
||||
# include <time.h>
|
||||
#endif
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <version>
|
||||
|
||||
#if __has_include(<cxxabi.h>)
|
||||
# include <cxxabi.h>
|
||||
#endif
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
# include <intrin.h>
|
||||
#endif
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h>
|
||||
#endif
|
||||
#if __has_include(<winapifamily.h>)
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# include <io.h>
|
||||
# endif
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
# if defined(__GLIBCXX__)
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
# endif
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
export module fmt;
|
||||
|
||||
#ifdef FMT_IMPORT_STD
|
||||
import std;
|
||||
#endif
|
||||
|
||||
#define FMT_EXPORT export
|
||||
#define FMT_BEGIN_EXPORT export {
|
||||
#define FMT_END_EXPORT }
|
||||
|
||||
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
// - all declarations are detached from module 'fmt'
|
||||
// - the module behaves like a traditional static library, too
|
||||
// - all library symbols are mangled traditionally
|
||||
// - you can mix TUs with either importing or #including the {fmt} API
|
||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
extern "C++" {
|
||||
#endif
|
||||
|
||||
#ifndef FMT_OS
|
||||
# define FMT_OS 1
|
||||
#endif
|
||||
|
||||
// All library-provided declarations and definitions must be in the module
|
||||
// purview to be exported.
|
||||
#include "fmt/args.h"
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/compile.h"
|
||||
#include "fmt/format.h"
|
||||
#if FMT_OS
|
||||
# include "fmt/os.h"
|
||||
#endif
|
||||
#include "fmt/ostream.h"
|
||||
#include "fmt/printf.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "fmt/std.h"
|
||||
#include "fmt/xchar.h"
|
||||
|
||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
}
|
||||
#endif
|
||||
|
||||
// gcc doesn't yet implement private module fragments
|
||||
#if !FMT_GCC_VERSION
|
||||
module :private;
|
||||
#endif
|
||||
|
||||
#if FMT_HAS_INCLUDE("format.cc")
|
||||
# include "format.cc"
|
||||
#endif
|
||||
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
|
||||
# include "os.cc"
|
||||
#endif
|
|
@ -0,0 +1,43 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template FMT_API auto dragonbox::to_decimal(float x) noexcept
|
||||
-> dragonbox::decimal_fp<float>;
|
||||
template FMT_API auto dragonbox::to_decimal(double x) noexcept
|
||||
-> dragonbox::decimal_fp<double>;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template FMT_API locale_ref::locale_ref(const std::locale& loc);
|
||||
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
|
||||
#endif
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API auto thousands_sep_impl(locale_ref)
|
||||
-> thousands_sep_result<char>;
|
||||
template FMT_API auto decimal_point_impl(locale_ref) -> char;
|
||||
|
||||
template FMT_API void buffer<char>::append(const char*, const char*);
|
||||
|
||||
template FMT_API void vformat_to(buffer<char>&, string_view,
|
||||
typename vformat_args<>::type, locale_ref);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API auto thousands_sep_impl(locale_ref)
|
||||
-> thousands_sep_result<wchar_t>;
|
||||
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
|
||||
|
||||
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
|
||||
|
||||
} // namespace detail
|
||||
FMT_END_NAMESPACE
|
|
@ -0,0 +1,403 @@
|
|||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
// Disable bogus MSVC warnings.
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/os.h"
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <climits>
|
||||
|
||||
# if FMT_USE_FCNTL
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
|
||||
# ifdef _WRS_KERNEL // VxWorks7 kernel
|
||||
# include <ioLib.h> // getpagesize
|
||||
# endif
|
||||
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <io.h>
|
||||
# endif // _WIN32
|
||||
# endif // FMT_USE_FCNTL
|
||||
|
||||
# ifdef _WIN32
|
||||
# include <windows.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
# ifndef S_IRGRP
|
||||
# define S_IRGRP 0
|
||||
# endif
|
||||
# ifndef S_IWGRP
|
||||
# define S_IWGRP 0
|
||||
# endif
|
||||
# ifndef S_IROTH
|
||||
# define S_IROTH 0
|
||||
# endif
|
||||
# ifndef S_IWOTH
|
||||
# define S_IWOTH 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
using rwresult = int;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
inline unsigned convert_rwcount(std::size_t count) {
|
||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||
}
|
||||
#elif FMT_USE_FCNTL
|
||||
// Return type of read and write functions.
|
||||
using rwresult = ssize_t;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace detail {
|
||||
|
||||
class system_message {
|
||||
system_message(const system_message&) = delete;
|
||||
void operator=(const system_message&) = delete;
|
||||
|
||||
unsigned long result_;
|
||||
wchar_t* message_;
|
||||
|
||||
static bool is_whitespace(wchar_t c) noexcept {
|
||||
return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
|
||||
}
|
||||
|
||||
public:
|
||||
explicit system_message(unsigned long error_code)
|
||||
: result_(0), message_(nullptr) {
|
||||
result_ = FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<wchar_t*>(&message_), 0, nullptr);
|
||||
if (result_ != 0) {
|
||||
while (result_ != 0 && is_whitespace(message_[result_ - 1])) {
|
||||
--result_;
|
||||
}
|
||||
}
|
||||
}
|
||||
~system_message() { LocalFree(message_); }
|
||||
explicit operator bool() const noexcept { return result_ != 0; }
|
||||
operator basic_string_view<wchar_t>() const noexcept {
|
||||
return basic_string_view<wchar_t>(message_, result_);
|
||||
}
|
||||
};
|
||||
|
||||
class utf8_system_category final : public std::error_category {
|
||||
public:
|
||||
const char* name() const noexcept override { return "system"; }
|
||||
std::string message(int error_code) const override {
|
||||
auto&& msg = system_message(error_code);
|
||||
if (msg) {
|
||||
auto utf8_message = to_utf8<wchar_t>();
|
||||
if (utf8_message.convert(msg)) {
|
||||
return utf8_message.str();
|
||||
}
|
||||
}
|
||||
return "unknown error";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_API const std::error_category& system_category() noexcept {
|
||||
static const detail::utf8_system_category category;
|
||||
return category;
|
||||
}
|
||||
|
||||
std::system_error vwindows_error(int err_code, string_view format_str,
|
||||
format_args args) {
|
||||
auto ec = std::error_code(err_code, system_category());
|
||||
return std::system_error(ec, vformat(format_str, args));
|
||||
}
|
||||
|
||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
const char* message) noexcept {
|
||||
FMT_TRY {
|
||||
auto&& msg = system_message(error_code);
|
||||
if (msg) {
|
||||
auto utf8_message = to_utf8<wchar_t>();
|
||||
if (utf8_message.convert(msg)) {
|
||||
fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
|
||||
string_view(utf8_message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
void report_windows_error(int error_code, const char* message) noexcept {
|
||||
report_error(detail::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
buffered_file::~buffered_file() noexcept {
|
||||
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
|
||||
nullptr);
|
||||
if (!file_)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
|
||||
filename.c_str()));
|
||||
}
|
||||
|
||||
void buffered_file::close() {
|
||||
if (!file_) return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = nullptr;
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||
}
|
||||
|
||||
int buffered_file::descriptor() const {
|
||||
#ifdef FMT_HAS_SYSTEM
|
||||
// fileno is a macro on OpenBSD.
|
||||
# ifdef fileno
|
||||
# undef fileno
|
||||
# endif
|
||||
int fd = FMT_POSIX_CALL(fileno(file_));
|
||||
#elif defined(_WIN32)
|
||||
int fd = _fileno(file_);
|
||||
#else
|
||||
int fd = fileno(file_);
|
||||
#endif
|
||||
if (fd == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
|
||||
return fd;
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
# ifdef _WIN32
|
||||
using mode_t = int;
|
||||
# endif
|
||||
|
||||
constexpr mode_t default_open_mode =
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
|
||||
file::file(cstring_view path, int oflag) {
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
|
||||
*this = file::open_windows_file(converted.c_str(), oflag);
|
||||
# else
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
|
||||
if (fd_ == -1)
|
||||
FMT_THROW(
|
||||
system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
|
||||
# endif
|
||||
}
|
||||
|
||||
file::~file() noexcept {
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
void file::close() {
|
||||
if (fd_ == -1) return;
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||
}
|
||||
|
||||
long long file::size() const {
|
||||
# ifdef _WIN32
|
||||
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
||||
// is less than 0x0500 as is the case with some default MinGW builds.
|
||||
// Both functions support large file sizes.
|
||||
DWORD size_upper = 0;
|
||||
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
|
||||
DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
|
||||
if (size_lower == INVALID_FILE_SIZE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != NO_ERROR)
|
||||
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
|
||||
}
|
||||
unsigned long long long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
# else
|
||||
using Stat = struct stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes")));
|
||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||
"return type of file::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
# endif
|
||||
}
|
||||
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
file file::dup(int fd) {
|
||||
// Don't retry as dup doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||
if (new_fd == -1)
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
|
||||
return file(new_fd);
|
||||
}
|
||||
|
||||
void file::dup2(int fd) {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
|
||||
fd));
|
||||
}
|
||||
}
|
||||
|
||||
void file::dup2(int fd, std::error_code& ec) noexcept {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) ec = std::error_code(errno, std::generic_category());
|
||||
}
|
||||
|
||||
buffered_file file::fdopen(const char* mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
# if defined(__MINGW32__) && defined(_POSIX_)
|
||||
FILE* f = ::fdopen(fd_, mode);
|
||||
# else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
# endif
|
||||
if (!f) {
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot associate stream with file descriptor")));
|
||||
}
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return bf;
|
||||
}
|
||||
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
file file::open_windows_file(wcstring_view path, int oflag) {
|
||||
int fd = -1;
|
||||
auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
|
||||
if (fd == -1) {
|
||||
FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"),
|
||||
detail::to_utf8<wchar_t>(path.c_str()).c_str()));
|
||||
}
|
||||
return file(fd);
|
||||
}
|
||||
# endif
|
||||
|
||||
pipe::pipe() {
|
||||
int fds[2] = {};
|
||||
# ifdef _WIN32
|
||||
// Make the default pipe capacity same as on Linux 2.6.11+.
|
||||
enum { DEFAULT_CAPACITY = 65536 };
|
||||
int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
|
||||
# else
|
||||
// Don't retry as the pipe function doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
# endif
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
|
||||
// The following assignments don't throw.
|
||||
read_end = file(fds[0]);
|
||||
write_end = file(fds[1]);
|
||||
}
|
||||
|
||||
# if !defined(__MSDOS__)
|
||||
long getpagesize() {
|
||||
# ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
# else
|
||||
# ifdef _WRS_KERNEL
|
||||
long size = FMT_POSIX_CALL(getpagesize());
|
||||
# else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
# endif
|
||||
|
||||
if (size < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
|
||||
return size;
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
namespace detail {
|
||||
|
||||
void file_buffer::grow(buffer<char>& buf, size_t) {
|
||||
if (buf.size() == buf.capacity()) static_cast<file_buffer&>(buf).flush();
|
||||
}
|
||||
|
||||
file_buffer::file_buffer(cstring_view path, const ostream_params& params)
|
||||
: buffer<char>(grow), file_(path, params.oflag) {
|
||||
set(new char[params.buffer_size], params.buffer_size);
|
||||
}
|
||||
|
||||
file_buffer::file_buffer(file_buffer&& other) noexcept
|
||||
: buffer<char>(grow, other.data(), other.size(), other.capacity()),
|
||||
file_(std::move(other.file_)) {
|
||||
other.clear();
|
||||
other.set(nullptr, 0);
|
||||
}
|
||||
|
||||
file_buffer::~file_buffer() {
|
||||
flush();
|
||||
delete[] data();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
ostream::~ostream() = default;
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
|
@ -74,9 +74,28 @@ fun <T> T.loadConfig() where T : Activity, T : IHasConfigItems {
|
|||
} catch (e: SerializationException) {
|
||||
ProgramConfig()
|
||||
}
|
||||
if (programConfig.useAPIAssetsURL.isEmpty()) {
|
||||
programConfig.useAPIAssetsURL = getString(R.string.default_assets_check_api)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
||||
val lastStartPluginVersionFile = File(filesDir, "lastStartPluginVersion.txt")
|
||||
val lastStartPluginVersion = if (lastStartPluginVersionFile.exists()) {
|
||||
lastStartPluginVersionFile.readText()
|
||||
}
|
||||
else {
|
||||
"null"
|
||||
}
|
||||
val packInfo = packageManager.getPackageInfo(packageName, 0)
|
||||
val version = packInfo.versionName
|
||||
val versionCode = packInfo.longVersionCode
|
||||
val currentPluginVersion = "$version ($versionCode)"
|
||||
if (lastStartPluginVersion != currentPluginVersion) { // 插件版本更新,强制启用资源更新检查
|
||||
lastStartPluginVersionFile.writeText(currentPluginVersion)
|
||||
programConfig.checkBuiltInAssets = true
|
||||
}
|
||||
|
||||
val intent = Intent().apply {
|
||||
setClassName(
|
||||
"com.bandainamcoent.idolmaster_gakuen",
|
||||
|
@ -85,17 +104,29 @@ fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
|||
putExtra("gkmsData", getConfigContent())
|
||||
putExtra(
|
||||
"localData",
|
||||
getProgramConfigContent(listOf("transRemoteZipUrl", "p"), programConfig)
|
||||
getProgramConfigContent(listOf("transRemoteZipUrl", "useAPIAssetsURL",
|
||||
"localAPIAssetsVersion", "p"), programConfig)
|
||||
)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
|
||||
val updateFile = File(filesDir, "update_trans.zip")
|
||||
if (updateFile.exists()) {
|
||||
val updateAPIFile = File(filesDir, "remote_files/remote.zip")
|
||||
val targetFile = if (programConfig.useAPIAssets && updateAPIFile.exists()) {
|
||||
updateAPIFile
|
||||
}
|
||||
else if (programConfig.useRemoteAssets && updateFile.exists()) {
|
||||
updateFile
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
|
||||
if (targetFile != null) {
|
||||
val dirUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
"io.github.chinosk.gakumas.localify.fileprovider",
|
||||
File(updateFile.absolutePath)
|
||||
File(targetFile.absolutePath)
|
||||
)
|
||||
intent.setDataAndType(dirUri, "resource/file")
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.github.chinosk.gakumas.localify
|
||||
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
@ -10,6 +11,7 @@ import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModelFactory
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
|
||||
interface ConfigListener {
|
||||
|
@ -62,7 +64,14 @@ interface ConfigListener {
|
|||
fun mainPageAssetsViewDataUpdate(downloadAbleState: Boolean? = null,
|
||||
downloadProgressState: Float? = null,
|
||||
localResourceVersionState: String? = null,
|
||||
errorString: String? = null)
|
||||
errorString: String? = null,
|
||||
localAPIResourceVersion: String? = null)
|
||||
fun onPUseAPIAssetsChanged(value: Boolean)
|
||||
fun onPUseAPIAssetsURLChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||
fun mainUIConfirmStatUpdate(isShow: Boolean? = null, title: String? = null,
|
||||
content: String? = null,
|
||||
onConfirm: (() -> Unit)? = { mainUIConfirmStatUpdate(isShow = false) },
|
||||
onCancel: (() -> Unit)? = { mainUIConfirmStatUpdate(isShow = false) })
|
||||
}
|
||||
|
||||
class UserConfigViewModelFactory(private val initialValue: GakumasConfig) : ViewModelProvider.Factory {
|
||||
|
@ -503,16 +512,29 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
|||
|
||||
override fun onPCheckBuiltInAssetsChanged(value: Boolean) {
|
||||
programConfig.checkBuiltInAssets = value
|
||||
if (value) {
|
||||
programConfig.cleanLocalAssets = false
|
||||
}
|
||||
saveProgramConfig()
|
||||
}
|
||||
|
||||
override fun onPUseRemoteAssetsChanged(value: Boolean) {
|
||||
programConfig.useRemoteAssets = value
|
||||
if (value) {
|
||||
programConfig.checkBuiltInAssets = false
|
||||
programConfig.cleanLocalAssets = false
|
||||
programConfig.useAPIAssets = false
|
||||
}
|
||||
saveProgramConfig()
|
||||
}
|
||||
|
||||
override fun onPCleanLocalAssetsChanged(value: Boolean) {
|
||||
programConfig.cleanLocalAssets = value
|
||||
if (value) {
|
||||
programConfig.useRemoteAssets = false
|
||||
programConfig.useAPIAssets = false
|
||||
programConfig.checkBuiltInAssets = false
|
||||
}
|
||||
saveProgramConfig()
|
||||
}
|
||||
|
||||
|
@ -527,10 +549,59 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
|||
}
|
||||
|
||||
override fun mainPageAssetsViewDataUpdate(downloadAbleState: Boolean?, downloadProgressState: Float?,
|
||||
localResourceVersionState: String?, errorString: String?) {
|
||||
downloadAbleState?.let { programConfigViewModel.downloadAbleState.value = downloadAbleState }
|
||||
downloadProgressState?.let{ programConfigViewModel.downloadProgressState.value = downloadProgressState }
|
||||
localResourceVersionState?.let{ programConfigViewModel.localResourceVersionState.value = localResourceVersionState }
|
||||
errorString?.let{ programConfigViewModel.errorStringState.value = errorString }
|
||||
localResourceVersionState: String?, errorString: String?,
|
||||
localAPIResourceVersion: String?) {
|
||||
downloadAbleState?.let { programConfigViewModel.downloadAbleState.value = it }
|
||||
downloadProgressState?.let{ programConfigViewModel.downloadProgressState.value = it }
|
||||
localResourceVersionState?.let{ programConfigViewModel.localResourceVersionState.value = it }
|
||||
errorString?.let{ programConfigViewModel.errorStringState.value = it }
|
||||
localAPIResourceVersion?.let{ programConfigViewModel.localAPIResourceVersionState.value = it }
|
||||
}
|
||||
|
||||
override fun onPUseAPIAssetsChanged(value: Boolean) {
|
||||
programConfig.useAPIAssets = value
|
||||
if (value) {
|
||||
programConfig.checkBuiltInAssets = false
|
||||
programConfig.useRemoteAssets = false
|
||||
programConfig.cleanLocalAssets = false
|
||||
}
|
||||
saveProgramConfig()
|
||||
}
|
||||
|
||||
override fun onPUseAPIAssetsURLChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
programConfig.useAPIAssetsURL = s.toString()
|
||||
saveProgramConfig()
|
||||
}
|
||||
|
||||
override fun mainUIConfirmStatUpdate(isShow: Boolean?, title: String?, content: String?,
|
||||
onConfirm: (() -> Unit)?, onCancel: (() -> Unit)?
|
||||
) {
|
||||
val orig = programConfigViewModel.mainUIConfirmState.value
|
||||
isShow?.let {
|
||||
if (orig.isShow && it) {
|
||||
Log.e(TAG, "Duplicate mainUIConfirmStat")
|
||||
}
|
||||
orig.isShow = it
|
||||
}
|
||||
title?.let { orig.title = it }
|
||||
content?.let { orig.content = it }
|
||||
onConfirm?.let { orig.onConfirm = {
|
||||
try {
|
||||
it()
|
||||
}
|
||||
finally {
|
||||
mainUIConfirmStatUpdate(isShow = false)
|
||||
}
|
||||
} }
|
||||
onCancel?.let { orig.onCancel = {
|
||||
try {
|
||||
it()
|
||||
}
|
||||
finally {
|
||||
mainUIConfirmStatUpdate(isShow = false)
|
||||
}
|
||||
} }
|
||||
orig.p = false
|
||||
programConfigViewModel.mainUIConfirmState.value = orig.copy(p = true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import java.io.File
|
|||
import java.util.Locale
|
||||
import kotlin.system.measureTimeMillis
|
||||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.localizationFilesDir
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
|
||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||
|
@ -280,7 +281,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||
}
|
||||
|
||||
// 使用热更新文件
|
||||
if (programConfig?.useRemoteAssets == true) {
|
||||
if ((programConfig?.useRemoteAssets == true) || (programConfig?.useAPIAssets == true)) {
|
||||
val dataUri = intent.data
|
||||
if (dataUri != null) {
|
||||
if (!externalFilesChecked) {
|
||||
|
@ -290,6 +291,13 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||
programConfig.delRemoteAfterUpdate)
|
||||
}
|
||||
}
|
||||
else if (programConfig.useAPIAssets) {
|
||||
if (!File(activity.filesDir, localizationFilesDir).exists() &&
|
||||
(initConfig?.forceExportResource == false)) {
|
||||
// 使用 API 资源,不检查内置,API 资源无效,且游戏内没有插件数据时,释放内置数据
|
||||
FilesChecker.initAndCheck(activity.filesDir, modulePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadConfig(gkmsData)
|
||||
|
|
|
@ -16,7 +16,10 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
|
||||
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.RemoteAPIFilesChecker
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuApi
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||
import io.github.chinosk.gakumas.localify.models.ConfirmStateModel
|
||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModel
|
||||
|
@ -44,6 +47,11 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
|
|||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun gotoPatchActivity() {
|
||||
val intent = Intent(this, PatchActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun saveConfig() {
|
||||
try {
|
||||
config.pf = false
|
||||
|
@ -76,6 +84,10 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
|
|||
val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt")
|
||||
resVersionText = FilesChecker.convertToString(stream)
|
||||
|
||||
if (programConfig.useAPIAssets) {
|
||||
RemoteAPIFilesChecker.getLocalVersion(this)?.let { resVersionText = it }
|
||||
}
|
||||
|
||||
val packInfo = packageManager.getPackageInfo(packageName, 0)
|
||||
val version = packInfo.versionName
|
||||
val versionCode = packInfo.longVersionCode
|
||||
|
@ -122,6 +134,8 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
|
|||
)
|
||||
programConfigViewModel = ViewModelProvider(this, programConfigFactory)[ProgramConfigViewModel::class.java]
|
||||
|
||||
ShizukuApi.init()
|
||||
|
||||
setContent {
|
||||
GakumasLocalifyTheme(dynamicColor = false, darkTheme = false) {
|
||||
MainUI(context = this)
|
||||
|
@ -186,6 +200,17 @@ fun getProgramLocalResourceVersionState(context: MainActivity?): State<String> {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getProgramLocalAPIResourceVersionState(context: MainActivity?): State<String> {
|
||||
return if (context != null) {
|
||||
context.programConfigViewModel.localAPIResourceVersion.collectAsState()
|
||||
}
|
||||
else {
|
||||
val configMSF = MutableStateFlow("null")
|
||||
configMSF.asStateFlow().collectAsState()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getProgramDownloadErrorStringState(context: MainActivity?): State<String> {
|
||||
return if (context != null) {
|
||||
|
@ -196,3 +221,14 @@ fun getProgramDownloadErrorStringState(context: MainActivity?): State<String> {
|
|||
configMSF.asStateFlow().collectAsState()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getMainUIConfirmState(context: MainActivity?, previewData: ConfirmStateModel? = null): State<ConfirmStateModel> {
|
||||
return if (context != null) {
|
||||
context.programConfigViewModel.mainUIConfirm.collectAsState()
|
||||
}
|
||||
else {
|
||||
val configMSF = MutableStateFlow(previewData ?: ConfirmStateModel())
|
||||
configMSF.asStateFlow().collectAsState()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,489 @@
|
|||
package io.github.chinosk.gakumas.localify
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.FileProvider
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.IOnShell
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.LSPatchUtils
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuApi
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuShell
|
||||
import io.github.chinosk.gakumas.localify.ui.components.InstallDiag
|
||||
import io.github.chinosk.gakumas.localify.ui.pages.PatchPage
|
||||
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.lsposed.patch.LSPatch
|
||||
import org.lsposed.patch.util.Logger
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.attribute.PosixFilePermissions
|
||||
|
||||
|
||||
interface PatchCallback {
|
||||
fun onLog(message: String, isError: Boolean = false)
|
||||
fun onSuccess(outFiles: List<File>)
|
||||
fun onFailed(msg: String, exception: Throwable? = null)
|
||||
}
|
||||
|
||||
val patchTag = "${TAG}-Patcher"
|
||||
|
||||
|
||||
open class PatchLogger : Logger() {
|
||||
override fun d(msg: String) {
|
||||
if (this.verbose) {
|
||||
Log.d(patchTag, msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun i(msg: String) {
|
||||
Log.i(patchTag, msg)
|
||||
}
|
||||
|
||||
override fun e(msg: String) {
|
||||
Log.e(patchTag, msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LSPatchExt(outputDir: String, isDebuggable: Boolean, localMode: Boolean, logger: Logger) : LSPatch(logger, "123.apk --debuggable --manager -l 2") {
|
||||
init {
|
||||
val parentClass = LSPatch::class.java
|
||||
// val apkPathsField = parentClass.getDeclaredField("apkPaths")
|
||||
val outputPathField = parentClass.getDeclaredField("outputPath")
|
||||
val forceOverwriteField = parentClass.getDeclaredField("forceOverwrite")
|
||||
val debuggableFlagField = parentClass.getDeclaredField("debuggableFlag")
|
||||
val useManagerField = parentClass.getDeclaredField("useManager")
|
||||
|
||||
// apkPathsField.isAccessible = true
|
||||
outputPathField.isAccessible = true
|
||||
forceOverwriteField.isAccessible = true
|
||||
debuggableFlagField.isAccessible = true
|
||||
useManagerField.isAccessible = true
|
||||
|
||||
// apkPathsField.set(this, apkPaths)
|
||||
forceOverwriteField.set(this, true)
|
||||
outputPathField.set(this, outputDir)
|
||||
debuggableFlagField.set(this, isDebuggable)
|
||||
useManagerField.set(this, localMode)
|
||||
}
|
||||
|
||||
fun setModules(modules: List<String>) {
|
||||
val parentClass = LSPatch::class.java
|
||||
val modulesField = parentClass.getDeclaredField("modules")
|
||||
modulesField.isAccessible = true
|
||||
modulesField.set(this, modules)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PatchActivity : ComponentActivity() {
|
||||
private lateinit var outputDir: String
|
||||
private var mOutFiles: List<File> = listOf()
|
||||
private var reservePatchFiles: Boolean = false
|
||||
var patchCallback: PatchCallback? = null
|
||||
|
||||
private fun handleSelectedFile(uri: Uri) {
|
||||
val fileName = uri.path?.substringAfterLast('/')
|
||||
if (fileName != null) {
|
||||
Log.d(patchTag, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
outputDir = "${filesDir.absolutePath}/output"
|
||||
// ShizukuApi.init()
|
||||
|
||||
setContent {
|
||||
GakumasLocalifyTheme(dynamicColor = false, darkTheme = false) {
|
||||
val scope = rememberCoroutineScope()
|
||||
var installing by remember { mutableStateOf(false) }
|
||||
|
||||
PatchPage() { apks, isPatchLocalMode, isPatchDebuggable, isReservePatchFiles, onFinish, onLog ->
|
||||
reservePatchFiles = isReservePatchFiles
|
||||
|
||||
onClickPatch(apks, isPatchLocalMode, isPatchDebuggable, object : PatchCallback {
|
||||
init {
|
||||
patchCallback = this
|
||||
}
|
||||
|
||||
override fun onLog(message: String, isError: Boolean) {
|
||||
onLog(message, isError)
|
||||
}
|
||||
|
||||
override fun onSuccess(outFiles: List<File>) {
|
||||
// Handle success, e.g., notify user or update UI
|
||||
Log.i(patchTag, "Patch succeeded: $outFiles")
|
||||
onLog("Patch succeeded: $outFiles")
|
||||
onFinish()
|
||||
|
||||
scope.launch {
|
||||
mOutFiles = outFiles
|
||||
installing = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailed(msg: String, exception: Throwable?) {
|
||||
Log.i(patchTag, "Patch failed: $msg", exception)
|
||||
onLog("Patch failed: $msg\n$exception", true)
|
||||
LSPatchUtils.deleteDirectory(File(outputDir))
|
||||
onFinish()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (installing) InstallDiag(this@PatchActivity, mOutFiles, patchCallback, reservePatchFiles) { _, _ ->
|
||||
installing = false
|
||||
mOutFiles = listOf()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onClickPatch(apkPaths: List<Uri>, isLocalMode: Boolean, isDebuggable: Boolean, callback: PatchCallback) {
|
||||
var isPureApk = true
|
||||
|
||||
for (i in apkPaths) { // 判断是否全是apk
|
||||
val fileName = getFileName(i)
|
||||
if (fileName == null) {
|
||||
callback.onFailed("Get file name failed: $i")
|
||||
return
|
||||
}
|
||||
else {
|
||||
if (!fileName.lowercase().endsWith(".apk")) {
|
||||
isPureApk = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (apkPaths.size != 1 && !isPureApk) { // 多选,非全 apk
|
||||
callback.onFailed("Multiple selection files must be all apk files.")
|
||||
return
|
||||
}
|
||||
|
||||
if (isPureApk) {
|
||||
val apks: MutableList<File> = mutableListOf()
|
||||
// val apkPathStr: MutableList<String> = mutableListOf()
|
||||
for (i in apkPaths) {
|
||||
val apkFile = uriToFile(i)
|
||||
if (apkFile == null) {
|
||||
callback.onFailed("Get file failed: $i")
|
||||
return
|
||||
}
|
||||
apks.add(apkFile)
|
||||
// apkPathStr.add(apkFile.absolutePath)
|
||||
}
|
||||
patchApks(apks, isLocalMode, isDebuggable, callback)
|
||||
return
|
||||
}
|
||||
|
||||
val fileUri = apkPaths[0]
|
||||
val fileName = getFileName(fileUri)
|
||||
if (fileName == null) {
|
||||
callback.onFailed("Get file name failed: $fileUri")
|
||||
return
|
||||
}
|
||||
val lowerName = fileName.lowercase()
|
||||
if (!(lowerName.endsWith("apks") || lowerName.endsWith("xapk") || lowerName.endsWith("zip"))) {
|
||||
callback.onFailed("Unknown file: $fileName")
|
||||
return
|
||||
}
|
||||
|
||||
val inputStream: InputStream? = contentResolver.openInputStream(fileUri)
|
||||
if (inputStream == null) {
|
||||
callback.onFailed("Open file failed: $fileUri")
|
||||
return
|
||||
}
|
||||
val unzipCacheDir = File(cacheDir, "apks_unzip")
|
||||
if (unzipCacheDir.exists()) {
|
||||
LSPatchUtils.deleteDirectory(unzipCacheDir)
|
||||
}
|
||||
unzipCacheDir.mkdirs()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
// FileHotUpdater.unzip(inputStream, unzipCacheDir.absolutePath)
|
||||
withContext(Dispatchers.Main) {
|
||||
callback.onLog("Unzipping...")
|
||||
}
|
||||
|
||||
LSPatchUtils.unzipXAPKWithProgress(inputStream, unzipCacheDir.absolutePath) { /*percent ->
|
||||
runOnUiThread {
|
||||
Log.d(TAG, "unzip: $percent")
|
||||
}*/
|
||||
}
|
||||
|
||||
val files = unzipCacheDir.listFiles()
|
||||
if (files == null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
callback.onFailed("Can't get unzip files: $fileName")
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
callback.onLog("Unzip completed.")
|
||||
}
|
||||
|
||||
val apks: MutableList<File> = mutableListOf()
|
||||
for (file in files) {
|
||||
if (file.isFile) {
|
||||
if (file.name.lowercase().endsWith(".apk")) {
|
||||
apks.add(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
patchApks(apks, isLocalMode, isDebuggable, callback) {
|
||||
LSPatchUtils.deleteDirectory(unzipCacheDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun patchApks(apks: List<File>, isLocalMode: Boolean, isDebuggable: Boolean,
|
||||
callback: PatchCallback, onPatchEnd: (() -> Unit)? = null) {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val lspatch = LSPatchExt(outputDir, isDebuggable, isLocalMode, object : PatchLogger() {
|
||||
override fun d(msg: String) {
|
||||
super.d(msg)
|
||||
runOnUiThread {
|
||||
callback.onLog(msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun i(msg: String) {
|
||||
super.i(msg)
|
||||
runOnUiThread {
|
||||
callback.onLog(msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun e(msg: String) {
|
||||
super.e(msg)
|
||||
runOnUiThread {
|
||||
callback.onLog(msg, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!isLocalMode) {
|
||||
lspatch.setModules(listOf(applicationInfo.sourceDir))
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
callback.onLog("Patching started.")
|
||||
}
|
||||
|
||||
// lspatch.doCommandLine()
|
||||
val outBasePath = File(filesDir, "output")
|
||||
if (!outBasePath.exists()) {
|
||||
outBasePath.mkdirs()
|
||||
}
|
||||
|
||||
val outFiles: MutableList<File> = mutableListOf()
|
||||
for (i in apks) {
|
||||
val outFile = File(outBasePath, "patch-${i.name}")
|
||||
if (outFile.exists()) {
|
||||
outFile.delete()
|
||||
}
|
||||
callback.onLog("Patching $i")
|
||||
lspatch.patch(i, outFile)
|
||||
i.delete()
|
||||
outFiles.add(outFile)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
callback.onLog("Patching completed.")
|
||||
callback.onSuccess(outFiles)
|
||||
}
|
||||
} catch (e: Error) {
|
||||
// Log error and call the failure callback
|
||||
Log.e(patchTag, "Patch error", e)
|
||||
withContext(Dispatchers.Main) {
|
||||
callback.onFailed("Patch error: ${e.message}", e)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Log exception and call the failure callback
|
||||
Log.e(patchTag, "Patch exception", e)
|
||||
withContext(Dispatchers.Main) {
|
||||
callback.onFailed("Patch exception: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
onPatchEnd?.let { it() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileName(uri: Uri): String? {
|
||||
var fileName: String? = null
|
||||
val cursor = contentResolver.query(uri, null, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
fileName = it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
|
||||
}
|
||||
}
|
||||
return fileName
|
||||
}
|
||||
|
||||
private fun uriToFile(uri: Uri): File? {
|
||||
val fileName = getFileName(uri) ?: return null
|
||||
val file = File(cacheDir, fileName)
|
||||
try {
|
||||
val inputStream: InputStream? = contentResolver.openInputStream(uri)
|
||||
val outputStream: OutputStream = FileOutputStream(file)
|
||||
inputStream?.use { input ->
|
||||
outputStream.use { output ->
|
||||
val buffer = ByteArray(4 * 1024) // 4KB
|
||||
var read: Int
|
||||
while (input.read(buffer).also { read = it } != -1) {
|
||||
output.write(buffer, 0, read)
|
||||
}
|
||||
output.flush()
|
||||
}
|
||||
}
|
||||
return file
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun getUriFromFile(context: Context, file: File): Uri {
|
||||
return FileProvider.getUriForFile(
|
||||
context,
|
||||
"io.github.chinosk.gakumas.localify.fileprovider",
|
||||
file
|
||||
)
|
||||
}
|
||||
|
||||
fun saveFileTo(apkFiles: List<File>, targetDirectory: File, isMove: Boolean,
|
||||
enablePermission: Boolean): List<File> {
|
||||
val hasDirectory = if (!targetDirectory.exists()) {
|
||||
targetDirectory.mkdirs()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
if (!hasDirectory) {
|
||||
throw NoSuchFileException(targetDirectory, reason = "check targetDirectory failed.")
|
||||
}
|
||||
|
||||
if (enablePermission) {
|
||||
try {
|
||||
val origPermission = Files.getPosixFilePermissions(targetDirectory.toPath())
|
||||
val requiredPermissions = PosixFilePermissions.fromString("rwxrwxrwx")
|
||||
if (!origPermission.equals(requiredPermissions)) {
|
||||
Files.setPosixFilePermissions(targetDirectory.toPath(), requiredPermissions)
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
Log.e(TAG, "checkPosixFilePermissions failed.", e)
|
||||
}
|
||||
}
|
||||
|
||||
val movedFiles: MutableList<File> = mutableListOf()
|
||||
apkFiles.forEach { file ->
|
||||
val targetFile = File(targetDirectory, file.name)
|
||||
if (targetFile.exists()) targetFile.delete()
|
||||
file.copyTo(targetFile)
|
||||
movedFiles.add(targetFile)
|
||||
if (isMove) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
return movedFiles
|
||||
}
|
||||
|
||||
suspend fun installSplitApks(context: Context, apkFiles: List<File>, reservePatchFiles: Boolean,
|
||||
patchCallback: PatchCallback?): Pair<Int, String?> {
|
||||
Log.i(TAG, "Perform install patched apks")
|
||||
var status = PackageInstaller.STATUS_FAILURE
|
||||
var message: String? = null
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val sdcardPath = Environment.getExternalStorageDirectory().path
|
||||
val targetDirectory = File(sdcardPath, "Download/gkms_local_patch")
|
||||
val savedFiles = saveFileTo(apkFiles, targetDirectory, true, false)
|
||||
patchCallback?.onLog("Patched files: $savedFiles")
|
||||
|
||||
if (!ShizukuApi.isPermissionGranted) {
|
||||
status = PackageInstaller.STATUS_FAILURE
|
||||
message = "Shizuku Not Ready."
|
||||
if (!reservePatchFiles) savedFiles.forEach { file -> if (file.exists()) file.delete() }
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
val ioShell = object: IOnShell {
|
||||
override fun onShellLine(msg: String) {
|
||||
patchCallback?.onLog(msg)
|
||||
}
|
||||
|
||||
override fun onShellError(msg: String) {
|
||||
patchCallback?.onLog(msg, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (ShizukuApi.isPackageInstalledWithoutPatch("com.bandainamcoent.idolmaster_gakuen")) {
|
||||
val uninstallShell = ShizukuShell(mutableListOf(), "pm uninstall com.bandainamcoent.idolmaster_gakuen", ioShell)
|
||||
uninstallShell.exec()
|
||||
uninstallShell.destroy()
|
||||
}
|
||||
|
||||
val installDS = "/data/local/tmp/gkms_local_patch"
|
||||
|
||||
val action = if (reservePatchFiles) "cp" else "mv"
|
||||
val copyFilesCmd: MutableList<String> = mutableListOf()
|
||||
val movedFiles: MutableList<String> = mutableListOf()
|
||||
savedFiles.forEach { file ->
|
||||
val movedFileName = "$installDS/${file.name}"
|
||||
movedFiles.add(movedFileName)
|
||||
copyFilesCmd.add("$action ${file.absolutePath} $movedFileName")
|
||||
}
|
||||
val moveFileCommand = "mkdir $installDS && " +
|
||||
"chmod 777 $installDS && " +
|
||||
copyFilesCmd.joinToString(" && ")
|
||||
Log.d(TAG, "moveFileCommand: $moveFileCommand")
|
||||
|
||||
val cpFileShell = ShizukuShell(mutableListOf(), moveFileCommand, ioShell)
|
||||
cpFileShell.exec()
|
||||
cpFileShell.destroy()
|
||||
|
||||
val installFiles = movedFiles.joinToString(" ")
|
||||
val command = "pm install -r $installFiles && rm $installFiles"
|
||||
Log.d(TAG, "shell: $command")
|
||||
val sh = ShizukuShell(mutableListOf(), command, ioShell)
|
||||
sh.exec()
|
||||
sh.destroy()
|
||||
|
||||
status = PackageInstaller.STATUS_SUCCESS
|
||||
message = "Done."
|
||||
}.onFailure { e ->
|
||||
status = PackageInstaller.STATUS_FAILURE
|
||||
message = e.stackTraceToString()
|
||||
}
|
||||
}
|
||||
return Pair(status, message)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,14 @@ object FileDownloader {
|
|||
|
||||
private var call: Call? = null
|
||||
|
||||
fun requestGet(request: Request, callback: Callback) {
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.build()
|
||||
val call = client.newCall(request)
|
||||
call.enqueue(callback)
|
||||
}
|
||||
|
||||
fun downloadFile(
|
||||
url: String,
|
||||
onDownload: (Float, downloaded: Long, size: Long) -> Unit,
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package io.github.chinosk.gakumas.localify.mainUtils
|
||||
|
||||
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
object LSPatchUtils {
|
||||
fun deleteDirectory(directoryToBeDeleted: File): Boolean {
|
||||
if (directoryToBeDeleted.isDirectory) {
|
||||
val children = directoryToBeDeleted.listFiles()
|
||||
if (children != null) {
|
||||
for (child in children) {
|
||||
deleteDirectory(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
return directoryToBeDeleted.delete()
|
||||
}
|
||||
|
||||
fun unzipXAPK(inputStream: InputStream, destDir: String) {
|
||||
val destDirFile = File(destDir)
|
||||
if (!destDirFile.exists()) {
|
||||
destDirFile.mkdirs()
|
||||
}
|
||||
|
||||
val tempFile = File.createTempFile("xapk_temp", ".zip", destDirFile)
|
||||
tempFile.deleteOnExit()
|
||||
|
||||
inputStream.use { input ->
|
||||
FileOutputStream(tempFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
ZipFile(tempFile).extractAll(destDir)
|
||||
tempFile.delete()
|
||||
}
|
||||
|
||||
fun unzipXAPKWithProgress(inputStream: InputStream, destDir: String, progressCallback: (Int) -> Unit) {
|
||||
val destDirFile = File(destDir)
|
||||
if (!destDirFile.exists()) {
|
||||
destDirFile.mkdirs()
|
||||
}
|
||||
|
||||
val tempFile = File.createTempFile("xapk_temp", ".zip", destDirFile)
|
||||
tempFile.deleteOnExit()
|
||||
|
||||
inputStream.use { input ->
|
||||
FileOutputStream(tempFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
val zipFile = ZipFile(tempFile)
|
||||
val progressMonitor = zipFile.progressMonitor
|
||||
val extractionThread = Thread {
|
||||
zipFile.extractAll(destDir)
|
||||
}
|
||||
|
||||
extractionThread.start()
|
||||
|
||||
while (extractionThread.isAlive) {
|
||||
progressCallback(progressMonitor.percentDone)
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
progressCallback(100)
|
||||
tempFile.delete()
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package io.github.chinosk.gakumas.localify.mainUtils
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import io.github.chinosk.gakumas.localify.TAG
|
||||
import io.github.chinosk.gakumas.localify.models.GithubReleaseModel
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
object RemoteAPIFilesChecker {
|
||||
const val BASEPATH = "remote_files"
|
||||
|
||||
fun getLocalVersion(context: Context): String? {
|
||||
val basePath = File(context.filesDir, BASEPATH)
|
||||
val versionFile = File(basePath, "version.txt")
|
||||
if (!versionFile.exists()) {
|
||||
return null
|
||||
}
|
||||
return versionFile.readText()
|
||||
}
|
||||
|
||||
// version.txt in zip file should be same with version parameter
|
||||
fun saveDownloadData(context: Context, data: ByteArray, version: String): File {
|
||||
val basePath = File(context.filesDir, BASEPATH)
|
||||
if (!basePath.exists()) {
|
||||
basePath.mkdirs()
|
||||
}
|
||||
val versionFile = File(basePath, "version.txt")
|
||||
val dataFile = File(basePath, "remote.zip")
|
||||
dataFile.writeBytes(data)
|
||||
versionFile.writeText(version)
|
||||
return dataFile
|
||||
}
|
||||
|
||||
fun checkUpdateLocalAssets(context: Context, apiURL: String,
|
||||
onFailed: (Int, String) -> Unit,
|
||||
onResult: (data: GithubReleaseModel, localVersion: String?) -> Unit) {
|
||||
runCatching {
|
||||
val request = Request.Builder()
|
||||
.url(apiURL)
|
||||
.build()
|
||||
FileDownloader.requestGet(request, object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
onFailed(-1, e.toString())
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
runCatching {
|
||||
response.use {
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
|
||||
val responseBody = response.body?.string()
|
||||
if (responseBody != null) {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val releaseData = json.decodeFromString<GithubReleaseModel>(responseBody)
|
||||
|
||||
// Check update
|
||||
// val releaseVersion = releaseData.tag_name
|
||||
val localVersion = getLocalVersion(context)
|
||||
// if (releaseVersion != localVersion) {
|
||||
onResult(releaseData, localVersion)
|
||||
// }
|
||||
} else {
|
||||
onFailed(-1, "Response body is null")
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "checkUpdateLocalAssets failed", e)
|
||||
onFailed(-1, e.toString())
|
||||
}
|
||||
}
|
||||
})
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "checkUpdateLocalAssets failed", e)
|
||||
onFailed(-1, e.toString())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun updateLocalAssets(context: Context, apiURL: String,
|
||||
onDownload: (Float, downloaded: Long, size: Long) -> Unit,
|
||||
onFailed: (Int, String) -> Unit,
|
||||
onSuccess: (File, String) -> Unit) {
|
||||
runCatching {
|
||||
val request = Request.Builder()
|
||||
.url(apiURL)
|
||||
.build()
|
||||
FileDownloader.requestGet(request, object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
onFailed(-1, e.toString())
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
runCatching {
|
||||
response.use {
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
|
||||
val responseBody = response.body?.string()
|
||||
if (responseBody != null) {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val releaseData = json.decodeFromString<GithubReleaseModel>(responseBody)
|
||||
|
||||
// Check and save update
|
||||
val releaseVersion = releaseData.tag_name
|
||||
val localVersion = getLocalVersion(context)
|
||||
if (releaseVersion != localVersion) {
|
||||
for (asset in releaseData.assets) {
|
||||
if (!asset.name.endsWith(".zip")) continue
|
||||
FileDownloader.downloadFile(asset.browser_download_url,
|
||||
onDownload, {data ->
|
||||
runCatching {
|
||||
val saveFile = saveDownloadData(context, data, releaseVersion)
|
||||
onSuccess(saveFile, releaseVersion)
|
||||
}.onFailure { e ->
|
||||
onFailed(-1, e.toString())
|
||||
}
|
||||
},
|
||||
onFailed)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onFailed(-1, "Response body is null")
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "updateLocalAssets failed", e)
|
||||
onFailed(-1, e.toString())
|
||||
}
|
||||
}
|
||||
})
|
||||
}.onFailure { e ->
|
||||
Log.e(TAG, "updateLocalAssets failed", e)
|
||||
onFailed(-1, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package io.github.chinosk.gakumas.localify.mainUtils
|
||||
|
||||
|
||||
import android.content.IntentSender
|
||||
import android.content.pm.IPackageInstaller
|
||||
import android.content.pm.IPackageInstallerSession
|
||||
import android.content.pm.IPackageManager
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageInstallerHidden
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.IInterface
|
||||
import android.os.Process
|
||||
import android.os.SystemProperties
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.rikka.tools.refine.Refine
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.shizuku.ShizukuBinderWrapper
|
||||
import rikka.shizuku.SystemServiceHelper
|
||||
|
||||
|
||||
// From https://github.com/LSPosed/LSPatch/blob/master/manager/src/main/java/org/lsposed/lspatch/util/ShizukuApi.kt
|
||||
object ShizukuApi {
|
||||
private fun IBinder.wrap() = ShizukuBinderWrapper(this)
|
||||
private fun IInterface.asShizukuBinder() = this.asBinder().wrap()
|
||||
|
||||
private val iPackageManager: IPackageManager by lazy {
|
||||
IPackageManager.Stub.asInterface(SystemServiceHelper.getSystemService("package").wrap())
|
||||
}
|
||||
|
||||
private val iPackageInstaller: IPackageInstaller by lazy {
|
||||
IPackageInstaller.Stub.asInterface(iPackageManager.packageInstaller.asShizukuBinder())
|
||||
}
|
||||
|
||||
private val packageInstaller: PackageInstaller by lazy {
|
||||
val userId = Process.myUserHandle().hashCode()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
Refine.unsafeCast(PackageInstallerHidden(iPackageInstaller, "com.android.shell", null, userId))
|
||||
} else {
|
||||
Refine.unsafeCast(PackageInstallerHidden(iPackageInstaller, "com.android.shell", userId))
|
||||
}
|
||||
}
|
||||
|
||||
var isBinderAvailable = false
|
||||
var isPermissionGranted by mutableStateOf(false)
|
||||
|
||||
fun init() {
|
||||
HiddenApiBypass.addHiddenApiExemptions("")
|
||||
HiddenApiBypass.addHiddenApiExemptions("Landroid/content", "Landroid/os")
|
||||
Shizuku.addBinderReceivedListenerSticky {
|
||||
isBinderAvailable = true
|
||||
isPermissionGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
Shizuku.addBinderDeadListener {
|
||||
isBinderAvailable = false
|
||||
isPermissionGranted = false
|
||||
}
|
||||
}
|
||||
|
||||
fun createPackageInstallerSession(params: PackageInstaller.SessionParams): PackageInstaller.Session {
|
||||
val sessionId = packageInstaller.createSession(params)
|
||||
val iSession = IPackageInstallerSession.Stub.asInterface(iPackageInstaller.openSession(sessionId).asShizukuBinder())
|
||||
return Refine.unsafeCast(PackageInstallerHidden.SessionHidden(iSession))
|
||||
}
|
||||
|
||||
fun isPackageInstalledWithoutPatch(packageName: String): Boolean {
|
||||
val userId = Process.myUserHandle().hashCode()
|
||||
val app = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
iPackageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA.toLong(), userId)
|
||||
} else {
|
||||
iPackageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId)
|
||||
}
|
||||
return (app != null) && (app.metaData?.containsKey("lspatch") != true)
|
||||
}
|
||||
|
||||
fun uninstallPackage(packageName: String, intentSender: IntentSender) {
|
||||
// packageInstaller.uninstall(packageName, intentSender)
|
||||
}
|
||||
|
||||
fun performDexOptMode(packageName: String): Boolean {
|
||||
return iPackageManager.performDexOptMode(
|
||||
packageName,
|
||||
SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false),
|
||||
"verify", true, true, null
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package io.github.chinosk.gakumas.localify.mainUtils
|
||||
|
||||
import android.util.Log
|
||||
import io.github.chinosk.gakumas.localify.TAG
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.shizuku.ShizukuRemoteProcess
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
|
||||
|
||||
val shellTag = "${TAG}_Shell"
|
||||
|
||||
interface IOnShell {
|
||||
fun onShellLine(msg: String)
|
||||
fun onShellError(msg: String)
|
||||
}
|
||||
|
||||
/*
|
||||
* Created by sunilpaulmathew <sunil.kde@gmail.com> on November 12, 2022
|
||||
*/
|
||||
class ShizukuShell(private var mOutput: MutableList<String>, private var mCommand: String,
|
||||
private val shellCallback: IOnShell? = null) {
|
||||
val isBusy: Boolean
|
||||
get() = mOutput.size > 0 && mOutput[mOutput.size - 1] != "aShell: Finish"
|
||||
|
||||
fun exec() {
|
||||
try {
|
||||
Log.i(shellTag, "Execute: $mCommand")
|
||||
shellCallback?.onShellLine(mCommand)
|
||||
mProcess = Shizuku.newProcess(arrayOf("sh", "-c", mCommand), null, mDir)
|
||||
val mInput = BufferedReader(InputStreamReader(mProcess!!.getInputStream()))
|
||||
val mError = BufferedReader(InputStreamReader(mProcess!!.getErrorStream()))
|
||||
var line: String
|
||||
while ((mInput.readLine().also { line = it }) != null) {
|
||||
Log.i(shellTag, line)
|
||||
shellCallback?.onShellLine(line)
|
||||
mOutput.add(line)
|
||||
}
|
||||
while ((mError.readLine().also { line = it }) != null) {
|
||||
Log.e(shellTag, line)
|
||||
shellCallback?.onShellError(line)
|
||||
mOutput.add("<font color=#FF0000>$line</font>")
|
||||
}
|
||||
|
||||
// Handle current directory
|
||||
if (mCommand.startsWith("cd ") && !mOutput[mOutput.size - 1]
|
||||
.endsWith("</font>")
|
||||
) {
|
||||
val array: Array<String> =
|
||||
mCommand.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
var dir: String
|
||||
dir = if (array[array.size - 1] == "/") {
|
||||
"/"
|
||||
} else if (array[array.size - 1].startsWith("/")) {
|
||||
array[array.size - 1]
|
||||
} else {
|
||||
mDir + array[array.size - 1]
|
||||
}
|
||||
if (!dir.endsWith("/")) {
|
||||
dir = "$dir/"
|
||||
}
|
||||
mDir = dir
|
||||
}
|
||||
|
||||
mProcess!!.waitFor()
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
if (mProcess != null) mProcess!!.destroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var mProcess: ShizukuRemoteProcess? = null
|
||||
private var mDir = "/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package io.github.chinosk.gakumas.localify.mainUtils
|
||||
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.ZoneId
|
||||
|
||||
object TimeUtils {
|
||||
fun convertIsoToLocalTime(isoTimeString: String): String {
|
||||
val zonedDateTime = ZonedDateTime.parse(isoTimeString, DateTimeFormatter.ISO_DATE_TIME)
|
||||
val currentZoneId = ZoneId.systemDefault()
|
||||
val localZonedDateTime = zonedDateTime.withZoneSameInstant(currentZoneId)
|
||||
val outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
return localZonedDateTime.format(outputFormatter)
|
||||
}
|
||||
}
|
|
@ -21,7 +21,8 @@ data class MainContributors(
|
|||
@Serializable
|
||||
data class ContribImg(
|
||||
val plugin: String,
|
||||
val translation: String
|
||||
val translation: String,
|
||||
val translations: List<String> = listOf()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package io.github.chinosk.gakumas.localify.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@Serializable
|
||||
data class User(
|
||||
val avatar_url: String,
|
||||
val events_url: String,
|
||||
val followers_url: String,
|
||||
val following_url: String,
|
||||
val gists_url: String,
|
||||
val gravatar_id: String?,
|
||||
val html_url: String,
|
||||
val id: Int,
|
||||
val login: String,
|
||||
val node_id: String,
|
||||
val organizations_url: String,
|
||||
val received_events_url: String,
|
||||
val repos_url: String,
|
||||
val site_admin: Boolean,
|
||||
val starred_url: String,
|
||||
val subscriptions_url: String,
|
||||
val type: String,
|
||||
val url: String
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
data class Asset(
|
||||
val browser_download_url: String,
|
||||
// val content_type: String,
|
||||
// val created_at: String,
|
||||
// val download_count: Int,
|
||||
// val id: Int,
|
||||
// val label: String?,
|
||||
val name: String,
|
||||
// val node_id: String,
|
||||
// val size: Int,
|
||||
// val state: String,
|
||||
// val updated_at: String,
|
||||
// val uploader: User,
|
||||
// val url: String
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
data class GithubReleaseModel(
|
||||
val assets: List<Asset>,
|
||||
// val assets_url: String,
|
||||
// val author: User,
|
||||
val body: String,
|
||||
// val created_at: String,
|
||||
// val draft: Boolean,
|
||||
// val html_url: String,
|
||||
// val id: Int,
|
||||
val name: String?,
|
||||
// val node_id: String,
|
||||
// val prerelease: Boolean,
|
||||
val published_at: String,
|
||||
val tag_name: String,
|
||||
// val tarball_url: String,
|
||||
// val target_commitish: String,
|
||||
// val upload_url: String,
|
||||
// val url: String,
|
||||
// val zipball_url: String
|
||||
)
|
|
@ -16,8 +16,12 @@ data class ProgramConfig(
|
|||
var checkBuiltInAssets: Boolean = true,
|
||||
var transRemoteZipUrl: String = "",
|
||||
var useRemoteAssets: Boolean = false,
|
||||
var useAPIAssets: Boolean = false,
|
||||
var useAPIAssetsURL: String = "",
|
||||
var delRemoteAfterUpdate: Boolean = true,
|
||||
var cleanLocalAssets: Boolean = false,
|
||||
|
||||
// var localAPIAssetsVersion: String = "",
|
||||
var p: Boolean = false
|
||||
)
|
||||
|
||||
|
|
|
@ -53,6 +53,15 @@ class ProgramConfigViewModelFactory(private val initialValue: ProgramConfig,
|
|||
}
|
||||
}
|
||||
|
||||
data class ConfirmStateModel(
|
||||
var isShow: Boolean = false,
|
||||
var title: String = "GakuConfirm Title",
|
||||
var content: String = "GakuConfirm Content",
|
||||
var onConfirm: () -> Unit = {},
|
||||
var onCancel: () -> Unit = {},
|
||||
var p: Boolean = false
|
||||
)
|
||||
|
||||
class ProgramConfigViewModel(initValue: ProgramConfig, initLocalResourceVersion: String) : ViewModel() {
|
||||
val configState = MutableStateFlow(initValue)
|
||||
val config: StateFlow<ProgramConfig> = configState.asStateFlow()
|
||||
|
@ -66,6 +75,12 @@ class ProgramConfigViewModel(initValue: ProgramConfig, initLocalResourceVersion:
|
|||
val localResourceVersionState = MutableStateFlow(initLocalResourceVersion)
|
||||
val localResourceVersion: StateFlow<String> = localResourceVersionState.asStateFlow()
|
||||
|
||||
val localAPIResourceVersionState = MutableStateFlow(initLocalResourceVersion)
|
||||
val localAPIResourceVersion: StateFlow<String> = localAPIResourceVersionState.asStateFlow()
|
||||
|
||||
val errorStringState = MutableStateFlow("")
|
||||
val errorString: StateFlow<String> = errorStringState.asStateFlow()
|
||||
|
||||
val mainUIConfirmState = MutableStateFlow(ConfirmStateModel())
|
||||
val mainUIConfirm: StateFlow<ConfirmStateModel> = mainUIConfirmState.asStateFlow()
|
||||
}
|
||||
|
|
|
@ -35,14 +35,17 @@ fun GakuButton(
|
|||
shadowElevation: Dp = 8.dp, // 阴影的高度
|
||||
borderWidth: Dp = 1.dp, // 描边的宽度
|
||||
borderColor: Color = Color.Transparent, // 描边的颜色
|
||||
enabled: Boolean = true
|
||||
enabled: Boolean = true,
|
||||
bgColors: List<Color>? = null,
|
||||
textColor: Color? = null
|
||||
) {
|
||||
var buttonSize by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
val gradient = remember(buttonSize) {
|
||||
Brush.linearGradient(
|
||||
colors = if (enabled) listOf(Color(0xFFFF5F19), Color(0xFFFFA028)) else
|
||||
listOf(Color(0xFFF9F9F9), Color(0xFFF0F0F0)),
|
||||
colors = bgColors
|
||||
?: if (enabled) listOf(Color(0xFFFF5F19), Color(0xFFFFA028)) else
|
||||
listOf(Color(0xFFF9F9F9), Color(0xFFF0F0F0)),
|
||||
start = Offset(0f, 0f),
|
||||
end = Offset(buttonSize.width.toFloat(), buttonSize.height.toFloat()) // 动态终点
|
||||
)
|
||||
|
@ -64,7 +67,7 @@ fun GakuButton(
|
|||
.border(borderWidth, borderColor, shape),
|
||||
contentPadding = PaddingValues(0.dp)
|
||||
) {
|
||||
Text(text = text, color = if (enabled) Color.White else Color(0xFF111111))
|
||||
Text(text = text, color = textColor ?: if (enabled) Color.White else Color(0xFF111111))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
package io.github.chinosk.gakumas.localify.ui.components
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.github.chinosk.gakumas.localify.R
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
const val ANIMATION_TIME = 320
|
||||
|
||||
@Composable
|
||||
fun FullScreenBoxWithAnimation(
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val animatedAlpha by animateFloatAsState(
|
||||
targetValue = if (isVisible) 0.6f else 0f, label = "animatedAlpha",
|
||||
animationSpec = tween(durationMillis = ANIMATION_TIME)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = animatedAlpha))
|
||||
.clickable {
|
||||
// isVisible2 = false
|
||||
onDismiss()
|
||||
},
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@Composable
|
||||
fun GakuGroupConfirm(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String = "Title",
|
||||
maxWidth: Dp = 400.dp,
|
||||
contentPadding: Dp = 8.dp,
|
||||
rightHead: @Composable (() -> Unit)? = null,
|
||||
onHeadClick: () -> Unit = {},
|
||||
onConfirm: () -> Unit = {},
|
||||
onCancel: () -> Unit = {},
|
||||
contentHeightForAnimation: Float = 400f,
|
||||
initIsVisible: Boolean = false,
|
||||
baseModifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val scoop = rememberCoroutineScope()
|
||||
var isVisible by remember { mutableStateOf(initIsVisible) }
|
||||
val offsetY by animateFloatAsState(
|
||||
targetValue = if (isVisible) -35f else contentHeightForAnimation, // 控制Box移动的距离
|
||||
animationSpec = tween(durationMillis = ANIMATION_TIME), label = "offsetY"
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
Scaffold(modifier = baseModifier.background(
|
||||
color = Color.Transparent
|
||||
),
|
||||
containerColor = Color.Transparent) {
|
||||
FullScreenBoxWithAnimation(
|
||||
isVisible = isVisible,
|
||||
onDismiss = {
|
||||
isVisible = false
|
||||
scoop.launch {
|
||||
delay(ANIMATION_TIME.toLong())
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Box(Modifier
|
||||
.offset { IntOffset(0, offsetY.roundToInt()) }
|
||||
.widthIn(max = maxWidth)
|
||||
.clickable { }) {
|
||||
Column(modifier = modifier.widthIn(max = maxWidth)) {
|
||||
// Header
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.Transparent)
|
||||
.height(30.dp)
|
||||
.clickable {
|
||||
onHeadClick()
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.bg_sheet_title),
|
||||
contentDescription = null,
|
||||
// modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.FillBounds
|
||||
)
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = Color.White,
|
||||
modifier = modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.padding(start = (maxWidth.value * 0.043f).dp)
|
||||
)
|
||||
if (rightHead != null) {
|
||||
Box(modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(end = (maxWidth.value * 0.1f).dp)) {
|
||||
rightHead()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Row {
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Box(
|
||||
modifier = modifier
|
||||
.shadow(
|
||||
4.dp, RoundedCornerShape(
|
||||
bottomStart = 16.dp,
|
||||
bottomEnd = 8.dp,
|
||||
topEnd = 0.dp,
|
||||
topStart = 0.dp
|
||||
)
|
||||
)
|
||||
.background(
|
||||
color = Color.White,
|
||||
shape = RoundedCornerShape(
|
||||
bottomStart = 16.dp,
|
||||
bottomEnd = 8.dp
|
||||
)
|
||||
)
|
||||
.padding(
|
||||
contentPadding + 5.dp, contentPadding, contentPadding,
|
||||
contentPadding
|
||||
)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Column {
|
||||
content()
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
}
|
||||
|
||||
Box(modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()) {
|
||||
Row(Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center) {
|
||||
Spacer(modifier = Modifier.sizeIn(minWidth = 32.dp))
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
horizontalArrangement = Arrangement.Center) {
|
||||
GakuButton(modifier = Modifier
|
||||
.height(40.dp)
|
||||
.sizeIn(minWidth = 100.dp),
|
||||
text = stringResource(R.string.cancel),
|
||||
bgColors = listOf(Color(0xFFF9F9F9), Color(0xFFF0F0F0)),
|
||||
textColor = Color(0xFF111111),
|
||||
onClick = { scoop.launch {
|
||||
isVisible = false
|
||||
delay(ANIMATION_TIME.toLong())
|
||||
onCancel()
|
||||
} })
|
||||
}
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
horizontalArrangement = Arrangement.Center) {
|
||||
GakuButton(modifier = Modifier
|
||||
.height(40.dp)
|
||||
.sizeIn(minWidth = 100.dp),
|
||||
text = stringResource(R.string.ok),
|
||||
onClick = { scoop.launch {
|
||||
isVisible = false
|
||||
delay(ANIMATION_TIME.toLong())
|
||||
onConfirm()
|
||||
} })
|
||||
}
|
||||
Spacer(modifier = Modifier.sizeIn(minWidth = 32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||
@Composable
|
||||
fun PreviewGakuGroupConfirm() {
|
||||
GakuGroupConfirm(
|
||||
title = "Confirm Title",
|
||||
initIsVisible = true
|
||||
) {
|
||||
Column {
|
||||
Text("This is the content of the GakuGroupConfirm.")
|
||||
Text("This is the content of the GakuGroupConfirm.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package io.github.chinosk.gakumas.localify.ui.components
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import io.github.chinosk.gakumas.localify.PatchActivity
|
||||
import io.github.chinosk.gakumas.localify.PatchCallback
|
||||
import io.github.chinosk.gakumas.localify.R
|
||||
import io.github.chinosk.gakumas.localify.TAG
|
||||
import java.io.File
|
||||
|
||||
|
||||
@Composable
|
||||
fun InstallDiag(context: Context?, apkFiles: List<File>, patchCallback: PatchCallback?, reservePatchFiles: Boolean,
|
||||
onFinish: (Int, String?) -> Unit) {
|
||||
// val scope = rememberCoroutineScope()
|
||||
// var uninstallFirst by remember { mutableStateOf(ShizukuApi.isPackageInstalledWithoutPatch(patchApp.app.packageName)) }
|
||||
var installing by remember { mutableStateOf(-1) }
|
||||
var showInstallConfirm by remember { mutableStateOf(true) }
|
||||
|
||||
fun finish(code: Int, msg: String?) {
|
||||
patchCallback?.onLog("Install finished($code): $msg")
|
||||
onFinish(code, msg)
|
||||
if (code != PackageInstaller.STATUS_SUCCESS) {
|
||||
msg?.let{ patchCallback?.onFailed(it) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun doInstall() {
|
||||
Log.i(TAG, "Installing app $apkFiles")
|
||||
installing = 1
|
||||
val (status, message) = PatchActivity.installSplitApks(context!!, apkFiles, reservePatchFiles, patchCallback)
|
||||
installing = 0
|
||||
Log.i(TAG, "Installation end: $status, $message")
|
||||
finish(status, message)
|
||||
}
|
||||
|
||||
LaunchedEffect(showInstallConfirm) {
|
||||
if (installing == 0) {
|
||||
doInstall()
|
||||
}
|
||||
}
|
||||
|
||||
if (showInstallConfirm) {
|
||||
Box {
|
||||
GakuGroupConfirm(
|
||||
title = stringResource(R.string.install),
|
||||
// initIsVisible = true,
|
||||
onCancel = {
|
||||
showInstallConfirm = false
|
||||
installing = -1
|
||||
finish(PackageInstaller.STATUS_FAILURE, "User Cancelled.")
|
||||
showInstallConfirm = true },
|
||||
onConfirm = {
|
||||
showInstallConfirm = false
|
||||
installing = 0 },
|
||||
contentHeightForAnimation = 500f
|
||||
) {
|
||||
Column {
|
||||
Text(stringResource(R.string.patch_finished))
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (installing == 1) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
confirmButton = {},
|
||||
title = {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.installing),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 380)
|
||||
@Composable
|
||||
fun InstallDiagPreview(modifier: Modifier = Modifier) {
|
||||
InstallDiag(null, listOf(), null, false) { _, _ -> }
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package io.github.chinosk.gakumas.localify.ui.components.icons
|
||||
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
public val Icons.Outlined.AutoFixHigh: ImageVector
|
||||
get() {
|
||||
if (_autoFixHigh != null) {
|
||||
return _autoFixHigh!!
|
||||
}
|
||||
_autoFixHigh = materialIcon(name = "Outlined.AutoFixHigh") {
|
||||
materialPath {
|
||||
moveTo(20.0f, 7.0f)
|
||||
lineToRelative(0.94f, -2.06f)
|
||||
lineToRelative(2.06f, -0.94f)
|
||||
lineToRelative(-2.06f, -0.94f)
|
||||
lineToRelative(-0.94f, -2.06f)
|
||||
lineToRelative(-0.94f, 2.06f)
|
||||
lineToRelative(-2.06f, 0.94f)
|
||||
lineToRelative(2.06f, 0.94f)
|
||||
close()
|
||||
}
|
||||
materialPath {
|
||||
moveTo(8.5f, 7.0f)
|
||||
lineToRelative(0.94f, -2.06f)
|
||||
lineToRelative(2.06f, -0.94f)
|
||||
lineToRelative(-2.06f, -0.94f)
|
||||
lineToRelative(-0.94f, -2.06f)
|
||||
lineToRelative(-0.94f, 2.06f)
|
||||
lineToRelative(-2.06f, 0.94f)
|
||||
lineToRelative(2.06f, 0.94f)
|
||||
close()
|
||||
}
|
||||
materialPath {
|
||||
moveTo(20.0f, 12.5f)
|
||||
lineToRelative(-0.94f, 2.06f)
|
||||
lineToRelative(-2.06f, 0.94f)
|
||||
lineToRelative(2.06f, 0.94f)
|
||||
lineToRelative(0.94f, 2.06f)
|
||||
lineToRelative(0.94f, -2.06f)
|
||||
lineToRelative(2.06f, -0.94f)
|
||||
lineToRelative(-2.06f, -0.94f)
|
||||
close()
|
||||
}
|
||||
materialPath {
|
||||
moveTo(17.71f, 9.12f)
|
||||
lineToRelative(-2.83f, -2.83f)
|
||||
curveTo(14.68f, 6.1f, 14.43f, 6.0f, 14.17f, 6.0f)
|
||||
curveToRelative(-0.26f, 0.0f, -0.51f, 0.1f, -0.71f, 0.29f)
|
||||
lineTo(2.29f, 17.46f)
|
||||
curveToRelative(-0.39f, 0.39f, -0.39f, 1.02f, 0.0f, 1.41f)
|
||||
lineToRelative(2.83f, 2.83f)
|
||||
curveTo(5.32f, 21.9f, 5.57f, 22.0f, 5.83f, 22.0f)
|
||||
reflectiveCurveToRelative(0.51f, -0.1f, 0.71f, -0.29f)
|
||||
lineToRelative(11.17f, -11.17f)
|
||||
curveTo(18.1f, 10.15f, 18.1f, 9.51f, 17.71f, 9.12f)
|
||||
close()
|
||||
moveTo(14.17f, 8.42f)
|
||||
lineToRelative(1.41f, 1.41f)
|
||||
lineTo(14.41f, 11.0f)
|
||||
lineTo(13.0f, 9.59f)
|
||||
lineTo(14.17f, 8.42f)
|
||||
close()
|
||||
moveTo(5.83f, 19.59f)
|
||||
lineToRelative(-1.41f, -1.41f)
|
||||
lineTo(11.59f, 11.0f)
|
||||
lineTo(13.0f, 12.41f)
|
||||
lineTo(5.83f, 19.59f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
return _autoFixHigh!!
|
||||
}
|
||||
|
||||
private var _autoFixHigh: ImageVector? = null
|
|
@ -1,6 +1,7 @@
|
|||
package io.github.chinosk.gakumas.localify.ui.pages
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
@ -11,9 +12,16 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
@ -28,7 +36,11 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import io.github.chinosk.gakumas.localify.MainActivity
|
||||
import io.github.chinosk.gakumas.localify.R
|
||||
import io.github.chinosk.gakumas.localify.TAG
|
||||
import io.github.chinosk.gakumas.localify.getMainUIConfirmState
|
||||
import io.github.chinosk.gakumas.localify.getProgramConfigState
|
||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupConfirm
|
||||
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
|
||||
|
||||
|
||||
|
@ -36,10 +48,16 @@ import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
|
|||
fun MainUI(modifier: Modifier = Modifier, context: MainActivity? = null,
|
||||
previewData: GakumasConfig? = null) {
|
||||
val imagePainter = painterResource(R.drawable.bg_pattern)
|
||||
val versionInfo = remember {
|
||||
context?.getVersion() ?: listOf("", "Unknown")
|
||||
var versionInfo by remember {
|
||||
mutableStateOf(context?.getVersion() ?: listOf("", "Unknown"))
|
||||
}
|
||||
// val config = getConfigState(context, previewData)
|
||||
val confirmState by getMainUIConfirmState(context, null)
|
||||
val programConfig by getProgramConfigState(context)
|
||||
|
||||
LaunchedEffect(programConfig) {
|
||||
versionInfo = context?.getVersion() ?: listOf("", "Unknown")
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
@ -66,6 +84,23 @@ fun MainUI(modifier: Modifier = Modifier, context: MainActivity? = null,
|
|||
stringResource(R.string.advanced_settings)),
|
||||
context = context, previewData = previewData, screenH = screenH)
|
||||
}
|
||||
|
||||
if (confirmState.isShow) {
|
||||
GakuGroupConfirm(
|
||||
title = confirmState.title,
|
||||
onCancel = { confirmState.onCancel() },
|
||||
onConfirm = { confirmState.onConfirm() },
|
||||
contentHeightForAnimation = screenH.value * 1.8f
|
||||
) {
|
||||
LazyColumn(modifier =
|
||||
Modifier.sizeIn(maxHeight = (screenH.value * 0.45f).dp)
|
||||
.fillMaxWidth()) {
|
||||
item {
|
||||
Text(confirmState.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,356 @@
|
|||
package io.github.chinosk.gakumas.localify.ui.pages
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CheckCircle
|
||||
import androidx.compose.material.icons.outlined.Warning
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.github.chinosk.gakumas.localify.R
|
||||
import io.github.chinosk.gakumas.localify.TAG
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.ShizukuApi
|
||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox
|
||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupConfirm
|
||||
import io.github.chinosk.gakumas.localify.ui.components.GakuRadio
|
||||
import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch
|
||||
import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox
|
||||
import io.github.chinosk.gakumas.localify.ui.components.icons.AutoFixHigh
|
||||
import org.lsposed.lspatch.share.LSPConfig
|
||||
import rikka.shizuku.Shizuku
|
||||
|
||||
|
||||
data class LogText(var msg: String, val isErr: Boolean)
|
||||
|
||||
private val shizukuListener: (Int, Int) -> Unit = { _, grantResult ->
|
||||
ShizukuApi.isPermissionGranted = grantResult == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
|
||||
val LogTextListSaver = Saver<SnapshotStateList<LogText>, List<String>>(
|
||||
save = { logTextList -> logTextList.map { "${it.msg},${it.isErr}" } },
|
||||
restore = { savedStrings ->
|
||||
val restoredList = mutableStateListOf<LogText>()
|
||||
savedStrings.forEach { savedString ->
|
||||
val parts = savedString.split(",")
|
||||
restoredList.add(LogText(parts[0], parts[1].toBoolean()))
|
||||
}
|
||||
restoredList
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@Composable
|
||||
fun PatchPage(modifier: Modifier = Modifier,
|
||||
content: (@Composable () -> Unit)? = null,
|
||||
onClickPatch: (selectFiles: List<Uri>, isLocalMode: Boolean, isDebuggable: Boolean,
|
||||
reservePatchFiles: Boolean,
|
||||
onFinishCallback: () -> Unit,
|
||||
onLogCallback: (msg: String, isErr: Boolean) -> Unit) -> Unit) {
|
||||
LaunchedEffect(Unit) {
|
||||
Shizuku.addRequestPermissionResultListener(shizukuListener)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
Shizuku.removeRequestPermissionResultListener(shizukuListener)
|
||||
}
|
||||
}
|
||||
|
||||
val imagePainter = painterResource(R.drawable.bg_pattern)
|
||||
|
||||
var isPatchLocalMode by rememberSaveable { mutableStateOf(true) }
|
||||
var isPatchDebuggable by rememberSaveable { mutableStateOf(true) }
|
||||
var isPatching by rememberSaveable { mutableStateOf(false) }
|
||||
var reservePatchFiles by rememberSaveable { mutableStateOf(false) }
|
||||
var showUninstallConfirm by remember { mutableStateOf(false) }
|
||||
|
||||
val logMsgList = rememberSaveable(saver = LogTextListSaver) { mutableStateListOf(LogText("Patcher Logs", false)) }
|
||||
|
||||
fun addLogMsg(msg: String, isErr: Boolean) {
|
||||
val length = logMsgList.size
|
||||
if (length == 0) {
|
||||
logMsgList.add(LogText(msg, isErr))
|
||||
}
|
||||
else {
|
||||
val lastLog = logMsgList[length - 1]
|
||||
if (lastLog.isErr == isErr) {
|
||||
// lastLog.msg += "\n${msg}"
|
||||
logMsgList[length - 1] = LogText("${lastLog.msg}\n$msg", isErr)
|
||||
}
|
||||
else {
|
||||
logMsgList.add(LogText(msg, isErr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val storageLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { apks ->
|
||||
Log.d(TAG, apks.toString())
|
||||
if (apks.isEmpty()) {
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
isPatching = true
|
||||
logMsgList.clear()
|
||||
onClickPatch(apks, isPatchLocalMode, isPatchDebuggable, reservePatchFiles, { isPatching = false }) { msg, err ->
|
||||
addLogMsg(msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
content?.let { it() }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0xFFFDFDFD))
|
||||
) {
|
||||
val screenH = imageRepeater(
|
||||
painter = imagePainter,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.TopCenter)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp, 10.dp, 10.dp, 0.dp),
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(modifier = modifier.fillMaxWidth()
|
||||
.padding(10.dp, 10.dp, 10.dp, 0.dp)) {
|
||||
Text(text = "Gakumas Localify Patcher", fontSize = 18.sp)
|
||||
Text(text = "LSPatch version: ${LSPConfig.instance.VERSION_NAME} (${LSPConfig.instance.VERSION_CODE})", fontSize = 13.sp)
|
||||
Text(text = "Framework version: ${LSPConfig.instance.CORE_VERSION_NAME} (${LSPConfig.instance.CORE_VERSION_CODE}), API ${LSPConfig.instance.API_CODE}", fontSize = 13.sp)
|
||||
// Text(text = "Shuzuku: ${ShizukuApi.isBinderAvailable} ${ShizukuApi.isPermissionGranted}", fontSize = 13.sp)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(6.dp))
|
||||
|
||||
GakuGroupBox(modifier = modifier, "Shizuku", contentPadding = 0.dp) {
|
||||
ElevatedCard(
|
||||
shape = RoundedCornerShape(
|
||||
bottomStart = 16.dp,
|
||||
bottomEnd = 8.dp
|
||||
),
|
||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||
if (ShizukuApi.isPermissionGranted) MaterialTheme.colorScheme.background
|
||||
else MaterialTheme.colorScheme.errorContainer
|
||||
})
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (ShizukuApi.isBinderAvailable && !ShizukuApi.isPermissionGranted) {
|
||||
Shizuku.requestPermission(114514)
|
||||
}
|
||||
}
|
||||
.padding(18.dp, 10.dp, 18.dp, 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (ShizukuApi.isPermissionGranted) {
|
||||
Icon(Icons.Outlined.CheckCircle, stringResource(R.string.shizuku_available))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.shizuku_available),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "API " + Shizuku.getVersion(),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Icon(Icons.Outlined.Warning, stringResource(R.string.shizuku_unavailable))
|
||||
Column(Modifier.padding(start = 20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.shizuku_unavailable),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.home_shizuku_warning),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(6.dp))
|
||||
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
GakuGroupBox(modifier = modifier, stringResource(R.string.game_patch)) {
|
||||
|
||||
Column(modifier = Modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
|
||||
Text(stringResource(R.string.patch_mode))
|
||||
Row(modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
val radioModifier = remember {
|
||||
modifier
|
||||
.height(40.dp)
|
||||
.weight(1f)
|
||||
}
|
||||
|
||||
GakuRadio(modifier = radioModifier,
|
||||
text = stringResource(R.string.patch_local), selected = isPatchLocalMode,
|
||||
onClick = { isPatchLocalMode = true })
|
||||
|
||||
GakuRadio(modifier = radioModifier,
|
||||
text = stringResource(R.string.patch_integrated), selected = !isPatchLocalMode,
|
||||
onClick = { isPatchLocalMode = false })
|
||||
|
||||
}
|
||||
|
||||
CollapsibleBox(modifier = modifier,
|
||||
expandState = true,
|
||||
collapsedHeight = 0.dp,
|
||||
showExpand = false
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(text = stringResource(
|
||||
if (isPatchLocalMode)
|
||||
R.string.patch_local_desc
|
||||
else
|
||||
R.string.patch_integrated_desc
|
||||
), color = Color.Gray, fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
GakuSwitch(modifier, stringResource(R.string.patch_debuggable), checked = isPatchDebuggable) {
|
||||
isPatchDebuggable = !isPatchDebuggable
|
||||
}
|
||||
|
||||
GakuSwitch(modifier, stringResource(R.string.reserve_patched), checked = reservePatchFiles) {
|
||||
reservePatchFiles = !reservePatchFiles
|
||||
}
|
||||
|
||||
Text(stringResource(R.string.support_file_types))
|
||||
|
||||
CollapsibleBox(modifier = modifier,
|
||||
expandState = true,
|
||||
collapsedHeight = 0.dp,
|
||||
showExpand = false
|
||||
) {
|
||||
Box(modifier = Modifier) {
|
||||
LazyColumn(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.sizeIn(maxHeight = screenH),
|
||||
reverseLayout = true) {
|
||||
logMsgList.asReversed().forEach { logText ->
|
||||
item {
|
||||
Text(modifier = Modifier.animateContentSize(),
|
||||
text = logText.msg,
|
||||
color = if (logText.isErr) Color.Red else Color.Black,
|
||||
fontSize = 12.sp)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(0.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(12.dp))
|
||||
}
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = { if (!isPatching) {
|
||||
if (ShizukuApi.isPermissionGranted &&
|
||||
ShizukuApi.isPackageInstalledWithoutPatch("com.bandainamcoent.idolmaster_gakuen")) {
|
||||
showUninstallConfirm = true
|
||||
}
|
||||
else {
|
||||
storageLauncher.launch(arrayOf("*/*"))
|
||||
}
|
||||
} },
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(16.dp),
|
||||
containerColor = if (isPatching) Color.Gray else MaterialTheme.colorScheme.primary,
|
||||
shape = CircleShape
|
||||
) {
|
||||
Icon(modifier = Modifier.size(24.dp),
|
||||
imageVector = Icons.Outlined.AutoFixHigh,
|
||||
contentDescription = "GotoPatch")
|
||||
}
|
||||
|
||||
if (showUninstallConfirm) {
|
||||
GakuGroupConfirm(
|
||||
title = stringResource(R.string.warning),
|
||||
onCancel = { showUninstallConfirm = false },
|
||||
onConfirm = {
|
||||
showUninstallConfirm = false
|
||||
storageLauncher.launch(arrayOf("*/*"))},
|
||||
contentHeightForAnimation = screenH.value
|
||||
) {
|
||||
Column {
|
||||
Text(stringResource(R.string.patch_uninstall_text))
|
||||
Text(stringResource(R.string.patch_uninstall_confirm))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 680)
|
||||
@Composable
|
||||
fun PatchPagePreview(modifier: Modifier = Modifier) {
|
||||
PatchPage(modifier) { _, _, _, _, _, _ -> }
|
||||
}
|
|
@ -5,9 +5,12 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
|
@ -26,6 +29,7 @@ import io.github.chinosk.gakumas.localify.MainActivity
|
|||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||
import io.github.chinosk.gakumas.localify.onClickStartGame
|
||||
import io.github.chinosk.gakumas.localify.ui.components.GakuTabRow
|
||||
import io.github.chinosk.gakumas.localify.ui.components.icons.AutoFixHigh
|
||||
import io.github.chinosk.gakumas.localify.ui.pages.subPages.AboutPage
|
||||
import io.github.chinosk.gakumas.localify.ui.pages.subPages.AdvanceSettingsPage
|
||||
import io.github.chinosk.gakumas.localify.ui.pages.subPages.HomePage
|
||||
|
@ -66,14 +70,27 @@ fun SettingsTabs(modifier: Modifier = Modifier,
|
|||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 6.dp)) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
FloatingActionButton(
|
||||
onClick = { context?.onClickStartGame() },
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
shape = CircleShape
|
||||
) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = "StartGame")
|
||||
Row(modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
FloatingActionButton(
|
||||
onClick = { context?.gotoPatchActivity() },
|
||||
// modifier = Modifier.align(Alignment.End),
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
shape = CircleShape
|
||||
) {
|
||||
Icon(modifier = Modifier.size(24.dp),
|
||||
imageVector = Icons.Outlined.AutoFixHigh,
|
||||
contentDescription = "GotoPatch")
|
||||
}
|
||||
FloatingActionButton(
|
||||
onClick = { context?.onClickStartGame() },
|
||||
//modifier = Modifier.align(Alignment.End),
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
shape = CircleShape
|
||||
) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = "StartGame")
|
||||
}
|
||||
}
|
||||
|
||||
GakuTabRow(modifier, pagerState, titles) { }
|
||||
|
|
|
@ -144,6 +144,13 @@ fun AboutPage(modifier: Modifier = Modifier,
|
|||
url = contributorInfo.contrib_img.translation,
|
||||
contentDescription = "translation-contrib"
|
||||
)
|
||||
contributorInfo.contrib_img.translations.forEach { url ->
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
NetworkSvgImage(
|
||||
url = url,
|
||||
contentDescription = "translation-contrib"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.github.chinosk.gakumas.localify.ui.pages.subPages
|
|||
|
||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -17,8 +18,12 @@ import androidx.compose.material3.HorizontalDivider
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
@ -32,14 +37,18 @@ import androidx.compose.ui.unit.sp
|
|||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import io.github.chinosk.gakumas.localify.MainActivity
|
||||
import io.github.chinosk.gakumas.localify.R
|
||||
import io.github.chinosk.gakumas.localify.TAG
|
||||
import io.github.chinosk.gakumas.localify.getConfigState
|
||||
import io.github.chinosk.gakumas.localify.getProgramConfigState
|
||||
import io.github.chinosk.gakumas.localify.getProgramDownloadAbleState
|
||||
import io.github.chinosk.gakumas.localify.getProgramDownloadErrorStringState
|
||||
import io.github.chinosk.gakumas.localify.getProgramDownloadState
|
||||
import io.github.chinosk.gakumas.localify.getProgramLocalResourceVersionState
|
||||
import io.github.chinosk.gakumas.localify.getProgramLocalAPIResourceVersionState
|
||||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.FileDownloader
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.RemoteAPIFilesChecker
|
||||
import io.github.chinosk.gakumas.localify.mainUtils.TimeUtils
|
||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||
import io.github.chinosk.gakumas.localify.models.ResourceCollapsibleBoxViewModel
|
||||
import io.github.chinosk.gakumas.localify.models.ResourceCollapsibleBoxViewModelFactory
|
||||
|
@ -64,7 +73,9 @@ fun HomePage(modifier: Modifier = Modifier,
|
|||
val downloadProgress by getProgramDownloadState(context)
|
||||
val downloadAble by getProgramDownloadAbleState(context)
|
||||
val localResourceVersion by getProgramLocalResourceVersionState(context)
|
||||
val localAPIResourceVersion by getProgramLocalAPIResourceVersionState(context)
|
||||
val downloadErrorString by getProgramDownloadErrorStringState(context)
|
||||
var isFirstTimeInThisPage by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
// val scrollState = rememberScrollState()
|
||||
val keyboardOptionsNumber = remember {
|
||||
|
@ -77,12 +88,8 @@ fun HomePage(modifier: Modifier = Modifier,
|
|||
val resourceSettingsViewModel: ResourceCollapsibleBoxViewModel =
|
||||
viewModel(factory = ResourceCollapsibleBoxViewModelFactory(initiallyExpanded = false))
|
||||
|
||||
fun onClickDownload() {
|
||||
context?.mainPageAssetsViewDataUpdate(
|
||||
downloadAbleState = false,
|
||||
errorString = "",
|
||||
downloadProgressState = -1f
|
||||
)
|
||||
|
||||
fun zipResourceDownload() {
|
||||
val (_, newUrl) = FileDownloader.checkAndChangeDownloadURL(programConfig.value.transRemoteZipUrl)
|
||||
context?.onPTransRemoteZipUrlChanged(newUrl, 0, 0, 0)
|
||||
FileDownloader.downloadFile(
|
||||
|
@ -122,9 +129,93 @@ fun HomePage(modifier: Modifier = Modifier,
|
|||
errorString = reason,
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun onClickDownload(isZipResource: Boolean, isHumanClick: Boolean = true) {
|
||||
context?.mainPageAssetsViewDataUpdate(
|
||||
downloadAbleState = false,
|
||||
errorString = "",
|
||||
downloadProgressState = -1f
|
||||
)
|
||||
if (isZipResource) {
|
||||
zipResourceDownload()
|
||||
}
|
||||
else {
|
||||
RemoteAPIFilesChecker.checkUpdateLocalAssets(context!!,
|
||||
programConfig.value.useAPIAssetsURL,
|
||||
onFailed = { _, reason ->
|
||||
context.mainPageAssetsViewDataUpdate(
|
||||
downloadAbleState = true,
|
||||
errorString = "",
|
||||
downloadProgressState = -1f
|
||||
)
|
||||
context.mainUIConfirmStatUpdate(true, "Error", reason)
|
||||
},
|
||||
onResult = { data, localVersion ->
|
||||
if (!isHumanClick) {
|
||||
if (data.tag_name == localVersion) {
|
||||
context.mainPageAssetsViewDataUpdate(
|
||||
downloadAbleState = true,
|
||||
errorString = "",
|
||||
downloadProgressState = -1f
|
||||
)
|
||||
return@checkUpdateLocalAssets
|
||||
}
|
||||
}
|
||||
context.mainUIConfirmStatUpdate(true, context.getString(R.string.translation_resource_update),
|
||||
"${data.name}\n$localVersion -> ${data.tag_name}\n${data.body}\n\n${TimeUtils.convertIsoToLocalTime(data.published_at)}",
|
||||
onConfirm = {
|
||||
resourceSettingsViewModel.expanded = true
|
||||
RemoteAPIFilesChecker.updateLocalAssets(context, programConfig.value.useAPIAssetsURL,
|
||||
onDownload = { progress, _, _ ->
|
||||
context.mainPageAssetsViewDataUpdate(downloadProgressState = progress)
|
||||
},
|
||||
onFailed = { _, reason -> context.mainPageAssetsViewDataUpdate(
|
||||
downloadAbleState = true,
|
||||
errorString = reason,
|
||||
)},
|
||||
onSuccess = { saveFile, releaseVersion ->
|
||||
context.mainPageAssetsViewDataUpdate(
|
||||
downloadAbleState = true,
|
||||
errorString = "",
|
||||
downloadProgressState = -1f
|
||||
)
|
||||
context.mainPageAssetsViewDataUpdate(
|
||||
localAPIResourceVersion = RemoteAPIFilesChecker.getLocalVersion(context)
|
||||
)
|
||||
context.saveProgramConfig()
|
||||
Log.d(TAG, "saved: $releaseVersion $saveFile")
|
||||
})
|
||||
},
|
||||
onCancel = {
|
||||
context.mainPageAssetsViewDataUpdate(
|
||||
downloadAbleState = true,
|
||||
errorString = "",
|
||||
downloadProgressState = -1f
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
if (context == null) return@LaunchedEffect
|
||||
val localAPIResVer = RemoteAPIFilesChecker.getLocalVersion(context)
|
||||
context.mainPageAssetsViewDataUpdate(
|
||||
localAPIResourceVersion = localAPIResVer
|
||||
)
|
||||
if (isFirstTimeInThisPage) {
|
||||
if (programConfig.value.useAPIAssets && programConfig.value.useAPIAssetsURL.isNotEmpty()) {
|
||||
onClickDownload(false, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
isFirstTimeInThisPage = false
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(modifier = modifier
|
||||
.sizeIn(maxHeight = screenH)
|
||||
|
@ -188,6 +279,103 @@ fun HomePage(modifier: Modifier = Modifier,
|
|||
)
|
||||
}
|
||||
|
||||
item {
|
||||
GakuSwitch(modifier = modifier.padding(start = 8.dp, end = 8.dp),
|
||||
checked = programConfig.value.useAPIAssets,
|
||||
text = stringResource(R.string.check_resource_from_api)
|
||||
) { v -> context?.onPUseAPIAssetsChanged(v) }
|
||||
|
||||
CollapsibleBox(modifier = modifier.graphicsLayer(clip = false),
|
||||
expandState = programConfig.value.useAPIAssets,
|
||||
collapsedHeight = 0.dp,
|
||||
innerPaddingLeftRight = 8.dp,
|
||||
showExpand = false
|
||||
) {
|
||||
GakuSwitch(modifier = modifier,
|
||||
checked = programConfig.value.delRemoteAfterUpdate,
|
||||
text = stringResource(id = R.string.del_remote_after_update)
|
||||
) { v -> context?.onPDelRemoteAfterUpdateChanged(v) }
|
||||
|
||||
LazyColumn(modifier = modifier
|
||||
// .padding(8.dp)
|
||||
.sizeIn(maxHeight = screenH),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
item {
|
||||
Row(modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically) {
|
||||
|
||||
GakuTextInput(modifier = modifier
|
||||
.height(45.dp)
|
||||
.padding(end = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
fontSize = 14f,
|
||||
value = programConfig.value.useAPIAssetsURL,
|
||||
onValueChange = { c -> context?.onPUseAPIAssetsURLChanged(c, 0, 0, 0)},
|
||||
label = { Text(stringResource(R.string.api_addr)) },
|
||||
keyboardOptions = keyboardOptionsNumber)
|
||||
|
||||
if (downloadAble) {
|
||||
GakuButton(modifier = modifier
|
||||
.height(40.dp)
|
||||
.sizeIn(minWidth = 80.dp),
|
||||
text = stringResource(R.string.check_update),
|
||||
onClick = { onClickDownload(false) })
|
||||
}
|
||||
else {
|
||||
GakuButton(modifier = modifier
|
||||
.height(40.dp)
|
||||
.sizeIn(minWidth = 80.dp),
|
||||
text = stringResource(id = R.string.cancel), onClick = {
|
||||
FileDownloader.cancel()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadProgress >= 0) {
|
||||
item {
|
||||
GakuProgressBar(progress = downloadProgress, isError = downloadErrorString.isNotEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadErrorString.isNotEmpty()) {
|
||||
item {
|
||||
Text(text = downloadErrorString, color = Color(0xFFE2041B))
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Text(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
context?.mainPageAssetsViewDataUpdate(
|
||||
localAPIResourceVersion = RemoteAPIFilesChecker.getLocalVersion(
|
||||
context
|
||||
)
|
||||
)
|
||||
}, text = "${stringResource(R.string.downloaded_resource_version)}: $localAPIResourceVersion")
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(0.dp))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
GakuSwitch(modifier = modifier.padding(start = 8.dp, end = 8.dp),
|
||||
checked = programConfig.value.useRemoteAssets,
|
||||
|
@ -231,7 +419,7 @@ fun HomePage(modifier: Modifier = Modifier,
|
|||
.height(40.dp)
|
||||
.sizeIn(minWidth = 80.dp),
|
||||
text = stringResource(id = R.string.download),
|
||||
onClick = { onClickDownload() })
|
||||
onClick = { onClickDownload(true) })
|
||||
}
|
||||
else {
|
||||
GakuButton(modifier = modifier
|
||||
|
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 340 KiB |
Before Width: | Height: | Size: 650 KiB After Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 781 KiB |
Before Width: | Height: | Size: 5.1 MiB After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 876 KiB |
|
@ -73,8 +73,33 @@
|
|||
<string name="invalid_zip_file">文件解析失败</string>
|
||||
<string name="invalid_zip_file_warn">此文件不是一个有效的 ZIP 翻译资源包</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="ok">确定</string>
|
||||
<string name="downloaded_resource_version">已下载资源版本</string>
|
||||
<string name="del_remote_after_update">替换文件后删除下载缓存</string>
|
||||
<string name="warning">注意</string>
|
||||
<string name="install">安装</string>
|
||||
<string name="installing">安装中</string>
|
||||
<string name="check_resource_from_api">从服务器检查热更新资源</string>
|
||||
<string name="api_addr">API 地址(Github Latest Release API)</string>
|
||||
<string name="check_update">检查更新</string>
|
||||
<string name="translation_resource_update">翻译资源更新</string>
|
||||
|
||||
<string name="game_patch">游戏修补</string>
|
||||
<string name="patch_mode">修补模式</string>
|
||||
<string name="patch_local">本地模式</string>
|
||||
<string name="patch_local_desc">为未嵌入模块的应用程序打补丁。\nXposed 范围可动态更改,无需重新打补丁。\n打了本地补丁的应用程序只能在本地设备上运行。</string>
|
||||
<string name="patch_integrated">集成模式</string>
|
||||
<string name="patch_integrated_desc">修补 App 并内置模块。\n经修补的应用可以在没有管理器的情况下运行,但不能动态管理配置。\n以集成模式修补的应用可在未安装 LSPatch 管理器的设备上运行。</string>
|
||||
<string name="shizuku_available">Shizuku 服务可用</string>
|
||||
<string name="shizuku_unavailable">Shizuku 服务未连接</string>
|
||||
<string name="home_shizuku_warning">部分功能不可用</string>
|
||||
<string name="patch_debuggable">可调试</string>
|
||||
<string name="reserve_patched">安装时保留修补包</string>
|
||||
<string name="support_file_types">支持文件类型:\n单/多选 apk\n单选 apks, xapk, zip</string>
|
||||
<string name="patch_uninstall_text">由于签名不同,安装修补的应用前需要先卸载原应用。\n确保您已备份好个人数据。</string>
|
||||
<string name="patch_uninstall_confirm">您确定要卸载吗</string>
|
||||
<string name="patch_finished">修补完成,是否开始安装?</string>
|
||||
|
||||
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
|
||||
<string name="default_assets_check_api">https://uma.chinosk6.cn/api/gkms_trans_data</string>
|
||||
</resources>
|
|
@ -73,8 +73,33 @@
|
|||
<string name="invalid_zip_file">Invalid file</string>
|
||||
<string name="invalid_zip_file_warn">This file is not a valid ZIP translation resource pack.</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="downloaded_resource_version">Downloaded Version</string>
|
||||
<string name="del_remote_after_update">Delete Cache File After Update</string>
|
||||
<string name="warning">Warning</string>
|
||||
<string name="install">Install</string>
|
||||
<string name="installing">Installing</string>
|
||||
<string name="check_resource_from_api">Check Resource Update From API</string>
|
||||
<string name="api_addr">API Address(Github Latest Release API)</string>
|
||||
<string name="check_update">Check</string>
|
||||
<string name="translation_resource_update">Translation Resource Update</string>
|
||||
|
||||
<string name="game_patch">Game Patch</string>
|
||||
<string name="patch_mode">Patch Mode</string>
|
||||
<string name="patch_local">Local</string>
|
||||
<string name="patch_local_desc">Patch an app without modules embedded.\nXposed scope can be changed dynamically without re-patch.\nLocal patched apps can only run on the local device.</string>
|
||||
<string name="patch_integrated">Integrated</string>
|
||||
<string name="patch_integrated_desc">Patch an app with modules embedded.\nThe patched app can run without the manager, but cannot be managed dynamically.\nIntegrated patched apps can be used on devices that do not have LSPatch Manager installed.</string>
|
||||
<string name="shizuku_available">Shizuku service available</string>
|
||||
<string name="shizuku_unavailable">Shizuku service not connected</string>
|
||||
<string name="home_shizuku_warning">Some functions unavailable</string>
|
||||
<string name="patch_debuggable">Debuggable</string>
|
||||
<string name="reserve_patched">Reserve Patched APK</string>
|
||||
<string name="support_file_types">Supported files:\nSingle/Multiple Choice: apk\nSingle Choice: apks, xapk, zip</string>
|
||||
<string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string>
|
||||
<string name="patch_uninstall_confirm">Are you sure you want to uninstall?</string>
|
||||
<string name="patch_finished">Patch finished. Start installing?</string>
|
||||
|
||||
<string name="about_contributors_asset_file">about_contributors_en.json</string>
|
||||
<string name="default_assets_check_api">https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest</string>
|
||||
</resources>
|
|
@ -1,6 +1,10 @@
|
|||
[versions]
|
||||
accompanistPager = "0.30.0"
|
||||
activityCompose = "1.9.0"
|
||||
hiddenapibypass = "4.3"
|
||||
shizukuApi = "12.1.0"
|
||||
hiddenapi-refine = "4.3.0"
|
||||
hiddenapi-stub = "4.2.0"
|
||||
okhttpBom = "4.12.0"
|
||||
xposedApi = "82"
|
||||
appcompat = "1.7.0"
|
||||
|
@ -15,6 +19,7 @@ navigationCompose = "2.7.7"
|
|||
xdl = "2.1.1"
|
||||
shadowhook = "1.0.9"
|
||||
serialization="1.7.1"
|
||||
zip4j = "2.9.1"
|
||||
|
||||
[libraries]
|
||||
accompanist-pager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanistPager" }
|
||||
|
@ -36,6 +41,12 @@ androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
|||
androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
||||
androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
|
||||
androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
|
||||
hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenapibypass" }
|
||||
shizukuApi = { module = "dev.rikka.shizuku:api", version.ref = "shizukuApi" }
|
||||
rikka-shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizukuApi" }
|
||||
rikka-refine = { module = "dev.rikka.tools.refine:runtime", version.ref = "hiddenapi-refine" }
|
||||
rikka-hidden-stub = { module = "dev.rikka.hidden:stub", version.ref = "hiddenapi-stub" }
|
||||
|
||||
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp" }
|
||||
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" }
|
||||
|
@ -46,6 +57,7 @@ material = { module = "com.google.android.material:material", version.ref = "mat
|
|||
shadowhook = { module = "com.bytedance.android:shadowhook", version.ref = "shadowhook" }
|
||||
xdl = { module = "io.github.hexhacking:xdl", version.ref = "xdl" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
|
||||
zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" }
|
||||
|
||||
[plugins]
|
||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||
|
|