Skip to content
This repository was archived by the owner on Feb 4, 2024. It is now read-only.

Commit 4c81880

Browse files
committed
🔖 First build
1 parent 38749c7 commit 4c81880

39 files changed

Lines changed: 1991 additions & 37 deletions

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
APP_VERSION=0.0.1
2+
GITHUB_REPOSITORY_URL=https://github.com/techouse/alfred-stackexchange
3+
STACK_EXCHANGE_API_BASE_URL=https://api.stackexchange.com/2.3/
4+
STACK_EXCHANGE_API_KEY=
5+
STACK_EXCHANGE_CLIENT_ID=

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ build/
1111
# macOS specific
1212
.DS_Store
1313

14-
bin/query_cache/
15-
sign.sh
14+
bin/*_cache/
15+
sign.sh
16+
17+
.env

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Klemen Tušar
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Makefile
2+
3+
help:
4+
@printf "%-20s %s\n" "Target" "Description"
5+
@printf "%-20s %s\n" "------" "-----------"
6+
@make -pqR : 2>/dev/null \
7+
| awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \
8+
| sort \
9+
| egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \
10+
| xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"'
11+
12+
analyze:
13+
@# Help: Analyze the project's Dart code.
14+
dart analyze --fatal-infos
15+
16+
compile:
17+
@# Help: Compile the executable binary
18+
bash ./build.sh
19+
20+
check_format:
21+
@# Help: Check the formatting of one or more Dart files.
22+
dart format --output=none --set-exit-if-changed .
23+
24+
check_outdated:
25+
@# Help: Check which of the project's packages are outdated.
26+
dart pub outdated
27+
28+
check_style:
29+
@# Help: Analyze the project's Dart code and check the formatting one or more Dart files.
30+
make analyze && make check_format
31+
32+
code_gen:
33+
@# Help: Run the build system for Dart code generation and modular compilation.
34+
dart run build_runner build --delete-conflicting-outputs
35+
36+
code_gen_watcher:
37+
@# Help: Run the build system for Dart code generation and modular compilation as a watcher.
38+
dart run build_runner watch --delete-conflicting-outputs
39+
40+
format:
41+
@# Help: Format one or more Dart files.
42+
dart format .
43+
44+
install:
45+
@# Help: Install all the project's packages
46+
dart pub get
47+
48+
sure:
49+
@# Help: Analyze the project's Dart code, check the formatting one or more Dart files and run unit tests for the current project.
50+
make check_style && make tests
51+
52+
upgrade:
53+
@# Help: Upgrade all the project's packages.
54+
dart pub upgrade

analysis_options.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
include: package:lints/recommended.yaml
22

33
analyzer:
4+
exclude:
5+
- "**.g.dart"
6+
- "**.chopper.dart"
47
plugins:
58
- dart_code_metrics
69

710
dart_code_metrics:
811
metrics:
912
cyclomatic-complexity: 20
10-
number-of-arguments: 4
13+
number-of-arguments: 5
14+
number-of-parameters: 5
1115
maximum-nesting-level: 5
1216
metrics-exclude:
1317
- test/**
@@ -26,3 +30,4 @@ linter:
2630
rules:
2731
avoid_print: true
2832
prefer_single_quotes: true
33+
prefer_relative_imports: false

assets/alfredhatcog.png

14.6 KB
Loading

assets/google.png

17.7 KB
Loading

assets/icon.png

9.3 KB
Loading

bin/main.dart

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,97 @@
1-
void main(List<String> arguments) async {
2-
// TODO
3-
}
1+
// ignore_for_file: long-method
2+
3+
import 'dart:io' show exitCode, stdout;
4+
5+
import 'package:alfred_workflow/alfred_workflow.dart';
6+
import 'package:args/args.dart';
7+
import 'package:chopper/chopper.dart';
8+
import 'package:cli_script/cli_script.dart';
9+
import 'package:collection/collection.dart';
10+
import 'package:html_unescape/html_unescape.dart';
11+
import 'package:stash/stash_api.dart';
12+
13+
import 'src/api/api.dart';
14+
import 'src/env/env.dart';
15+
import 'src/extensions/string_helpers.dart';
16+
import 'src/models/item_list.dart';
17+
import 'src/models/question.dart';
18+
19+
part 'main_helpers.dart';
20+
21+
bool _verbose = false;
22+
bool _update = false;
23+
24+
void main(List<String> arguments) {
25+
wrapMain(() async {
26+
try {
27+
exitCode = 0;
28+
29+
_workflow.clearItems();
30+
31+
final ArgParser parser = ArgParser()
32+
..addOption('query', abbr: 'q', defaultsTo: '')
33+
..addOption('delay', abbr: 'd', defaultsTo: '0')
34+
..addFlag('verbose', abbr: 'v', defaultsTo: false)
35+
..addFlag('update', abbr: 'u', defaultsTo: false);
36+
final ArgResults args = parser.parse(arguments);
37+
38+
int? delay = int.tryParse(args['delay']);
39+
if (delay != null && delay > 0) {
40+
await Future.delayed(Duration(milliseconds: delay));
41+
}
42+
43+
_update = args['update'];
44+
if (_update) {
45+
stdout.writeln('Updating workflow...');
46+
47+
return await _updater.update();
48+
}
49+
50+
_verbose = args['verbose'];
51+
52+
final List<String> query =
53+
args['query'].replaceAll(RegExp(r'\s+'), ' ').trim().split(' ');
54+
55+
final Set<String> tags = (query
56+
.where((String el) => el.startsWith('.'))
57+
.map((String el) => el.substring(1))
58+
.toList()
59+
..sort())
60+
.toSet();
61+
62+
final String queryString = query
63+
.whereNot((String el) => el.startsWith('.'))
64+
.join(' ')
65+
.trim()
66+
.toLowerCase();
67+
68+
if (_verbose) stdout.writeln('Query: "$queryString"');
69+
70+
if (queryString.isEmpty) {
71+
_showPlaceholder();
72+
} else {
73+
_workflow.cacheKey = tags.isNotEmpty
74+
? '${queryString}_${tags.join('.').md5hex}'
75+
: queryString;
76+
if (await _workflow.getItems() == null) {
77+
await _performSearch(query: queryString, tags: tags);
78+
}
79+
}
80+
} on FormatException catch (err) {
81+
exitCode = 2;
82+
_workflow.addItem(AlfredItem(title: err.toString()));
83+
} catch (err) {
84+
exitCode = 1;
85+
_workflow.addItem(AlfredItem(title: err.toString()));
86+
if (_verbose) rethrow;
87+
} finally {
88+
if (!_update) {
89+
if (await _updater.updateAvailable()) {
90+
_workflow.run(addToBeginning: updateItem);
91+
} else {
92+
_workflow.run();
93+
}
94+
}
95+
}
96+
});
97+
}

bin/main_helpers.dart

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
part of 'main.dart';
2+
3+
final HtmlUnescape _unescape = HtmlUnescape();
4+
5+
final AlfredWorkflow _workflow = AlfredWorkflow(
6+
cache: AlfredCache<AlfredItems>(
7+
fromEncodable: (Map<String, dynamic> json) => AlfredItems.fromJson(json),
8+
maxEntries: 10000,
9+
expiryPolicy: const CreatedExpiryPolicy(
10+
Duration(days: 7),
11+
),
12+
),
13+
);
14+
15+
final AlfredUpdater _updater = AlfredUpdater(
16+
githubRepositoryUrl: Uri.parse(Env.githubRepositoryUrl),
17+
currentVersion: Env.appVersion,
18+
updateInterval: Duration(days: 1),
19+
);
20+
21+
final Api _api = Api();
22+
23+
const updateItem = AlfredItem(
24+
title: 'Auto-Update available!',
25+
subtitle: 'Press <enter> to auto-update to a new version of this workflow.',
26+
arg: 'update:workflow',
27+
match:
28+
'Auto-Update available! Press <enter> to auto-update to a new version of this workflow.',
29+
icon: AlfredItemIcon(path: 'alfredhatcog.png'),
30+
valid: true,
31+
);
32+
33+
void _showPlaceholder() {
34+
_workflow.addItem(
35+
const AlfredItem(
36+
title: 'Search StackOverflow for ...',
37+
icon: AlfredItemIcon(path: 'icon.png'),
38+
),
39+
);
40+
}
41+
42+
Future<void> _performSearch({
43+
required String query,
44+
Set<String>? tags,
45+
}) async {
46+
final Response<ItemList<Question>> response = await _api.service.search(
47+
siteId: 'stackoverflow',
48+
query: query,
49+
tagged: tags,
50+
page: 1,
51+
pageSize: 20,
52+
);
53+
54+
if (response.isSuccessful && (response.body?.isNotEmpty ?? false)) {
55+
_workflow.addItems(
56+
response.body!
57+
.map(
58+
(Question question) => AlfredItem(
59+
uid: question.id.toString(),
60+
title: _unescape.convert(question.title),
61+
subtitle: _unescape.convert(question.tags.join(', ')),
62+
arg: question.link.toString(),
63+
match: _unescape.convert(
64+
'${question.title} ${question.tags.join(' ')}',
65+
),
66+
text: AlfredItemText(
67+
largeType: question.title,
68+
copy: question.link.toString(),
69+
),
70+
quickLookUrl: question.link.toString(),
71+
icon: AlfredItemIcon(path: 'icon.png'),
72+
valid: true,
73+
),
74+
)
75+
.toList(),
76+
);
77+
} else {
78+
final Uri url = Uri.https('www.google.com', '/search', {'q': query});
79+
80+
_workflow.addItem(
81+
AlfredItem(
82+
title: 'No matching questions found',
83+
subtitle: 'Shall I try and search Google?',
84+
arg: url.toString(),
85+
text: AlfredItemText(
86+
copy: url.toString(),
87+
),
88+
quickLookUrl: url.toString(),
89+
icon: AlfredItemIcon(path: 'google.png'),
90+
valid: true,
91+
),
92+
);
93+
}
94+
}

0 commit comments

Comments
 (0)