1
0
Fork 0

Add Built-in Patch Support (#3)

This commit is contained in:
chinosk 2024-08-06 11:27:40 -05:00 committed by GitHub
parent 0e1ad6959b
commit adb1a687b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 19701 additions and 45 deletions

View File

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

View File

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

BIN
app/libs/lspatch.jar Normal file

Binary file not shown.

Binary file not shown.

View File

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

45
app/libs/rm_duplicate.py Normal file
View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -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);
}

View File

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

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
};
}

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
}
}

View File

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

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

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

View File

@ -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()
}
}

View File

@ -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())
}
}
}

View File

@ -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
)
}
}

View File

@ -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 = "/"
}
}

View File

@ -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)
}
}

View File

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

View File

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

View File

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

View File

@ -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()
}

View File

@ -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))
}
}

View File

@ -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.")
}
}
}

View File

@ -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) { _, _ -> }
}

View File

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

View File

@ -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)
}
}
}
}
}
}

View File

@ -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) { _, _, _, _, _, _ -> }
}

View File

@ -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) { }

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 781 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 876 KiB

View File

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

View File

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

View File

@ -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" }