Skip to content

Commit

Permalink
feat: Add app info command
Browse files Browse the repository at this point in the history
  • Loading branch information
mathrunet committed Dec 26, 2022
1 parent b2fa5c7 commit 5ed6610
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 9 deletions.
2 changes: 2 additions & 0 deletions packages/katana_cli/bin/katana.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import 'package:yaml/yaml.dart';
///
/// コマンドの一覧を定義します。
const commands = <String, CliCommand>{
"app": AppCliCommand(),
"pub": PubCliCommand(),
"code": CodeCliCommand(),
"create": CreateCliCommand(),
"submodule": SubmoduleCliCommand(),
};

Expand Down
53 changes: 53 additions & 0 deletions packages/katana_cli/lib/code/katana.dart
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:
""";
}
}
23 changes: 23 additions & 0 deletions packages/katana_cli/lib/command/app/app.dart
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(),
};
}
246 changes: 246 additions & 0 deletions packages/katana_cli/lib/command/app/info.dart
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"),
);
}
}
2 changes: 0 additions & 2 deletions packages/katana_cli/lib/command/code/code.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'tmp/tmp.dart';

part 'collection.dart';
part 'controller.dart';
part 'create.dart';
part 'document.dart';
part 'format.dart';
part 'generate.dart';
Expand All @@ -27,7 +26,6 @@ class CodeCliCommand extends CliCommandGroup {
@override
Map<String, CliCommand> get commands => const {
"tmp": CodeTmpCliCommand(),
"create": CodeCreateCliCommand(),
"format": CodeFormatCliCommand(),
"generate": CodeGenerateCliCommand(),
"watch": CodeWatchCliCommand(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
part of katana_cli.code;
import 'package:katana_cli/katana_cli.dart';

/// Package to import.
///
Expand Down Expand Up @@ -42,11 +42,11 @@ const otherFiles = {
/// Create a new Flutter project.
///
/// 新しいFlutterプロジェクトを作成します。
class CodeCreateCliCommand extends CliCommand {
class CreateCliCommand extends CliCommand {
/// Create a new Flutter project.
///
/// 新しいFlutterプロジェクトを作成します。
const CodeCreateCliCommand();
const CreateCliCommand();

@override
String get description =>
Expand Down Expand Up @@ -114,6 +114,8 @@ class CodeCreateCliCommand extends CliCommand {
for (final file in otherFiles.entries) {
await file.value.generateFile(file.key);
}
label("Create a katana.yaml");
await const KatanaCliCode().generateFile("katana.yaml");
await command(
"Run the project's build_runner to generate code.",
[
Expand Down

0 comments on commit 5ed6610

Please sign in to comment.