-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
337 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
part of katana_cli; | ||
|
||
/// Contents of katana.yaml. | ||
/// | ||
/// katana.yamlの中身。 | ||
class KatanaCliCode extends CliCode { | ||
/// Contents of katana.yaml. | ||
/// | ||
/// katana.yamlの中身。 | ||
const KatanaCliCode(); | ||
|
||
@override | ||
String get name => "katana"; | ||
|
||
@override | ||
String get prefix => "katana"; | ||
|
||
@override | ||
String get directory => ""; | ||
|
||
@override | ||
String get description => | ||
"Create katana.yaml for katana_cli. katana_cli用のkatana.yamlを作成します。"; | ||
|
||
@override | ||
String import(String path, String baseName, String className) { | ||
return ""; | ||
} | ||
|
||
@override | ||
String header(String path, String baseName, String className) { | ||
return ""; | ||
} | ||
|
||
@override | ||
String body(String path, String baseName, String className) { | ||
return r""" | ||
# Describe the application information. | ||
# アプリケーション情報を記載します。 | ||
app: | ||
# Retrieve data from a spreadsheet retrieved by a specific Google Form. | ||
# Please include the URL of the spreadsheet in [url] and the email address you collected in [email]. | ||
# 特定のGoogleフォームで取得したスプレッドシートからデータを取得します。 | ||
# [url]にスプレッドシートのURL、[email]に収集したメールアドレスを記載してください。 | ||
spread_sheet: | ||
url: | ||
email: | ||
"""; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
library katana_cli.app; | ||
|
||
import 'dart:convert'; | ||
import 'dart:io'; | ||
|
||
import 'package:csv/csv.dart'; | ||
import 'package:katana_cli/katana_cli.dart'; | ||
import 'package:xml/xml.dart'; | ||
|
||
part 'info.dart'; | ||
|
||
class AppCliCommand extends CliCommandGroup { | ||
const AppCliCommand(); | ||
|
||
@override | ||
String get groupDescription => | ||
"Configure settings for the application. アプリケーション用の設定を行います。"; | ||
|
||
@override | ||
Map<String, CliCommand> get commands => const { | ||
"info": AppInfoCliCommand(), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
part of katana_cli.app; | ||
|
||
final _mapping = [ | ||
"timestamp", | ||
"email", | ||
"name", | ||
"locale", | ||
"title", | ||
"short_title", | ||
"overview", | ||
"detail", | ||
"keyword", | ||
"official_url", | ||
"support_url", | ||
"support_email", | ||
"terms_url", | ||
"privacy_url", | ||
"icon", | ||
"feature", | ||
"screenshot_5.5", | ||
"screenshot_6.5", | ||
"screenshot_tablet", | ||
]; | ||
|
||
/// Set the application information. | ||
/// | ||
/// アプリケーションの情報を設定します。 | ||
class AppInfoCliCommand extends CliCommand { | ||
/// Set the application information. | ||
/// | ||
/// アプリケーションの情報を設定します。 | ||
const AppInfoCliCommand(); | ||
|
||
@override | ||
String get description => | ||
"Set the application title, icon, and other information based on the information in `katana.yaml`. `katana.yaml`の情報を元にアプリケーションのタイトルやアイコンなどの情報を設定します。"; | ||
|
||
@override | ||
Future<void> exec(ExecContext context) async { | ||
final app = context.yaml.getAsMap("app"); | ||
if (app.isEmpty) { | ||
print("The item [app] is missing. Please add an item."); | ||
return; | ||
} | ||
final spreadSheet = app.getAsMap("spread_sheet"); | ||
if (spreadSheet.isEmpty) { | ||
print("The item [app]->[spread_sheet] is missing. Please add an item."); | ||
return; | ||
} | ||
final url = spreadSheet.get("url", ""); | ||
if (url.isEmpty) { | ||
print( | ||
"The item [app]->[spread_sheet]->[url] is missing. Please include the URL of the spreadsheet here.", | ||
); | ||
return; | ||
} | ||
final email = spreadSheet.get("email", ""); | ||
if (email.isEmpty) { | ||
print( | ||
"The item [app]->[spread_sheet]->[email] is missing. Include here the email address of the collection account to be retrieved in the spreadsheet.", | ||
); | ||
return; | ||
} | ||
final endpoint = | ||
url.replaceAllMapped(RegExp(r"/edit(#gid=([0-9]+))?$"), (match) { | ||
final gid = match.group(2); | ||
if (gid.isEmpty) { | ||
return "/export?format=csv"; | ||
} | ||
return "/export?format=csv&gid=$gid"; | ||
}); | ||
label("Load from $endpoint"); | ||
String? defaultLocale; | ||
final request = await HttpClient().getUrl(Uri.parse(endpoint)); | ||
final response = await request.close(); | ||
final csv = await response.transform(utf8.decoder).join(); | ||
final raw = const CsvToListConverter().convert(csv); | ||
final data = <String, Map<String, String>>{}; | ||
for (int i = 1; i < raw.length; i++) { | ||
final line = raw[i]; | ||
if (line.length <= 1) { | ||
continue; | ||
} | ||
final mapped = <String, String>{}; | ||
for (int j = 0; j < line.length; j++) { | ||
if (_mapping.length <= j) { | ||
break; | ||
} | ||
mapped[_mapping.get(j, "")] = line.get(j, ""); | ||
} | ||
final id = mapped.get("email", ""); | ||
final locale = mapped | ||
.get("locale", "en") | ||
.replaceAllMapped(RegExp(r"^.+\(([a-z]+)\)$"), (m) { | ||
return m.group(1) ?? "en"; | ||
}); | ||
if (id.isEmpty || id != email || locale.isEmpty) { | ||
continue; | ||
} | ||
defaultLocale ??= locale; | ||
data[locale] = mapped; | ||
print( | ||
"[${mapped.get("email", "")}] ${mapped.get("short_title", "")} (${mapped.get("locale", "")})", | ||
); | ||
} | ||
label("Replace android information"); | ||
await _createAndroidResValues({ | ||
if (defaultLocale.isNotEmpty) "": data[defaultLocale!]!, | ||
...data, | ||
}); | ||
await _removeAndroidResValues(data); | ||
await _replaceAndroidManifest(); | ||
} | ||
|
||
Future<void> _createAndroidResValues( | ||
Map<String, Map<String, String>> data, | ||
) async { | ||
for (final tmp in data.entries) { | ||
final dir = Directory( | ||
"android/app/src/main/res/values${tmp.key.isEmpty ? "" : "-${tmp.key}"}", | ||
); | ||
if (!dir.existsSync()) { | ||
await dir.create(recursive: true); | ||
} | ||
final file = File("${dir.path}/strings.xml"); | ||
if (!file.existsSync()) { | ||
final builder = XmlBuilder(); | ||
builder.processing('xml', 'version="1.0" encoding="utf-8" '); | ||
builder.element( | ||
"resources", | ||
nest: () { | ||
builder.element( | ||
"string", | ||
nest: () { | ||
builder.attribute("name", "app_name"); | ||
builder.text(tmp.value.get("short_title", "")); | ||
}, | ||
); | ||
}, | ||
); | ||
final document = builder.buildDocument(); | ||
await file.writeAsString( | ||
document.toXmlString(pretty: true, indent: " ", newLine: "\n"), | ||
); | ||
} else { | ||
final document = XmlDocument.parse(await file.readAsString()); | ||
final resources = document.findElements("resources"); | ||
if (resources.isEmpty) { | ||
document.rootElement.children.add( | ||
XmlElement( | ||
XmlName("resources"), | ||
[], | ||
[ | ||
XmlElement( | ||
XmlName("string"), | ||
[XmlAttribute(XmlName("name"), "app_name")], | ||
[XmlText(tmp.value.get("short_title", ""))], | ||
), | ||
], | ||
), | ||
); | ||
} else { | ||
final strings = resources.first.findElements("string"); | ||
if (strings.isEmpty) { | ||
resources.first.children.add( | ||
XmlElement( | ||
XmlName("string"), | ||
[XmlAttribute(XmlName("name"), "app_name")], | ||
[XmlText(tmp.value.get("short_title", ""))], | ||
), | ||
); | ||
} else { | ||
final appName = strings.firstWhereOrNull( | ||
(item) => item.attributes.any( | ||
(p0) => p0.name.local == "name" && p0.value == "app_name", | ||
), | ||
); | ||
if (appName != null) { | ||
appName.children | ||
..clear() | ||
..add( | ||
XmlText(tmp.value.get("short_title", "")), | ||
); | ||
} else { | ||
resources.first.children.add( | ||
XmlElement( | ||
XmlName("string"), | ||
[XmlAttribute(XmlName("name"), "app_name")], | ||
[XmlText(tmp.value.get("short_title", ""))], | ||
), | ||
); | ||
} | ||
} | ||
} | ||
await file.writeAsString( | ||
document.toXmlString(pretty: true, indent: " ", newLine: "\n"), | ||
); | ||
} | ||
} | ||
} | ||
|
||
Future<void> _removeAndroidResValues( | ||
Map<String, Map<String, String>> data, | ||
) async { | ||
final dir = Directory("android/app/src/main/res"); | ||
final regExp = RegExp(r"/values-([a-z]{2})$"); | ||
await for (final tmp in dir.list()) { | ||
final match = regExp.firstMatch(tmp.path); | ||
if (match == null) { | ||
continue; | ||
} | ||
final locale = match.group(1); | ||
if (!data.containsKey(locale)) { | ||
print("Remove at `android/app/src/main/res/values-$locale`"); | ||
await tmp.delete(recursive: true); | ||
} | ||
} | ||
} | ||
|
||
Future<void> _replaceAndroidManifest() async { | ||
final file = File("android/app/src/main/AndroidManifest.xml"); | ||
if (!file.existsSync()) { | ||
throw Exception( | ||
"AndroidManifest does not exist in `android/app/src/main/AndroidManifest.xml`. Do `katana create` to complete the initial setup of the project.", | ||
); | ||
} | ||
final document = XmlDocument.parse(await file.readAsString()); | ||
final application = document.findAllElements("application"); | ||
if (application.isEmpty) { | ||
throw Exception( | ||
"The structure of AndroidManifest.xml is broken. Do `katana create` to complete the initial setup of the project.", | ||
); | ||
} | ||
final label = application.first.getAttributeNode("android:label"); | ||
if (label != null) { | ||
label.value = "@string/app_name"; | ||
} else { | ||
application.first.attributes.add( | ||
XmlAttribute(XmlName("android:label"), "@string/app_name"), | ||
); | ||
} | ||
await file.writeAsString( | ||
document.toXmlString(pretty: true, indent: " ", newLine: "\n"), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.