-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclean_flutter.py
More file actions
executable file
·363 lines (289 loc) · 11 KB
/
clean_flutter.py
File metadata and controls
executable file
·363 lines (289 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#!/usr/bin/env python3
"""Create a Flutter app and clean it by modifying the generated template files."""
import argparse
import re
import shutil
import subprocess
from pathlib import Path
def run(cmd, cwd=None):
"""Execute a shell command."""
print('+ ' + ' '.join(cmd))
subprocess.run(cmd, cwd=cwd, check=True)
def remove_comments_from_dart(content):
"""Remove all comments from Dart code."""
lines = content.split('\n')
cleaned_lines = []
in_multiline_comment = False
for line in lines:
# Handle multiline comments
if '/*' in line:
in_multiline_comment = True
# Remove everything from /* onwards
line = line[:line.index('/*')]
if in_multiline_comment:
if '*/' in line:
# Remove everything up to and including */
line = line[line.index('*/') + 2:]
in_multiline_comment = False
else:
continue # Skip this line entirely
# Remove single-line comments
if '//' in line:
line = line[:line.index('//')]
# Keep non-empty lines
if line.strip():
cleaned_lines.append(line.rstrip())
return '\n'.join(cleaned_lines)
def create_simple_app_dart(lib_dir):
"""
Create a simple app.dart with just an App class and empty Scaffold.
No MyHomePage or other complexity.
"""
app_dart_content = """import 'package:flutter/material.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(),
);
}
}
"""
app_dart_path = lib_dir / 'app.dart'
app_dart_path.write_text(app_dart_content, encoding='utf-8')
print(f"✅ Created simple {app_dart_path}")
def create_clean_main_dart(lib_dir):
"""Create a clean main.dart without comments."""
main_dart_content = """import 'package:flutter/material.dart';
import 'app.dart';
void main() {
runApp(const App());
}
"""
main_dart_path = lib_dir / 'main.dart'
main_dart_path.write_text(main_dart_content, encoding='utf-8')
print(f"✅ Created clean {main_dart_path}")
def remove_pubspec_comments(pubspec_path):
"""Remove comments and excessive blank lines from pubspec.yaml."""
if not pubspec_path.exists():
print("❌ Warning: pubspec.yaml not found")
return
content = pubspec_path.read_text(encoding='utf-8')
lines = content.split('\n')
# Remove comment lines and inline comments
cleaned_lines = []
for line in lines:
# Skip lines that are only comments
if line.strip().startswith('#'):
continue
# Remove inline comments (but preserve URLs with #)
if '#' in line and 'http' not in line and '://' not in line:
parts = line.split('#', 1)
if parts[0].strip():
cleaned_lines.append(parts[0].rstrip())
else:
cleaned_lines.append(line)
# Remove excessive blank lines (keep max 1 consecutive blank line)
final_lines = []
prev_blank = False
for line in cleaned_lines:
if not line.strip():
if not prev_blank:
final_lines.append('')
prev_blank = True
else:
final_lines.append(line)
prev_blank = False
# Write back
cleaned_content = '\n'.join(final_lines).strip() + '\n'
pubspec_path.write_text(cleaned_content, encoding='utf-8')
print(f"✅ Cleaned {pubspec_path}")
def clean_analysis_options(analysis_options_path):
"""Remove all comments from analysis_options.yaml and add linter rules."""
if not analysis_options_path.exists():
# Create new file without comments
content = """include: package:flutter_lints/flutter.yaml
linter:
rules:
prefer_single_quotes: true
avoid_print: true
"""
analysis_options_path.write_text(content, encoding='utf-8')
print(f"✅ Created {analysis_options_path}")
return
content = analysis_options_path.read_text(encoding='utf-8')
lines = content.split('\n')
# Remove all comment lines
cleaned_lines = []
for line in lines:
# Skip comment-only lines
if line.strip().startswith('#'):
continue
# Keep non-comment lines
if line.strip():
cleaned_lines.append(line)
# Check if our rules exist
content_str = '\n'.join(cleaned_lines)
has_single_quotes = re.search(r'^\s*prefer_single_quotes:\s*true', content_str, re.MULTILINE) is not None
has_avoid_print = re.search(r'^\s*avoid_print:\s*true', content_str, re.MULTILINE) is not None
if not (has_single_quotes and has_avoid_print):
# Add rules after 'rules:' line
final_lines = []
for line in cleaned_lines:
final_lines.append(line)
if line.strip() == 'rules:':
# Detect indent
indent = ' '
if not has_single_quotes:
final_lines.append(f"{indent}prefer_single_quotes: true")
if not has_avoid_print:
final_lines.append(f"{indent}avoid_print: true")
cleaned_lines = final_lines
# Write back with minimal blank lines
final_content = []
prev_blank = False
for line in cleaned_lines:
if not line.strip():
if not prev_blank:
final_content.append('')
prev_blank = True
else:
final_content.append(line)
prev_blank = False
result = '\n'.join(final_content).strip() + '\n'
analysis_options_path.write_text(result, encoding='utf-8')
print(f"✅ Cleaned {analysis_options_path}")
def ensure_flutter_lints(pubspec_path):
"""Add flutter_lints to dev_dependencies if not present."""
text = pubspec_path.read_text(encoding='utf-8')
if 'flutter_lints:' in text:
return
if re.search(r'(?m)^\s*dev_dependencies:\s*$', text) is None:
text += '\ndev_dependencies:\n flutter_lints: ^5.0.0\n'
else:
text = re.sub(
r'(?m)^(\s*dev_dependencies:\s*\n)',
r'\1 flutter_lints: ^5.0.0\n',
text,
count=1,
)
pubspec_path.write_text(text, encoding='utf-8')
print("✅ Added flutter_lints to pubspec.yaml")
def setup_tests(test_dir, project_name):
"""
Remove widget_test.dart and create placeholder_test.dart.
"""
# Remove widget_test.dart if it exists
widget_test_path = test_dir / 'widget_test.dart'
if widget_test_path.exists():
widget_test_path.unlink()
print(f"✅ Removed {widget_test_path}")
# Create placeholder_test.dart
placeholder_test_content = """import 'package:flutter_test/flutter_test.dart';
void main() {
test('placeholder: 1 + 1 = 2', () {
expect(1 + 1, 2);
});
}
"""
placeholder_test_path = test_dir / 'placeholder_test.dart'
placeholder_test_path.write_text(placeholder_test_content, encoding='utf-8')
print(f"✅ Created {placeholder_test_path}")
def write_github_workflow(project_dir, project_name):
"""Create GitHub Actions CI workflow as main.yml."""
workflow_dir = project_dir / '.github' / 'workflows'
workflow_dir.mkdir(parents=True, exist_ok=True)
# Convert snake_case to Title Case for display name
display_name = project_name.replace('_', ' ').title()
workflow_content = f"""name: {display_name}
on:
push:
branches: [ main ]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Get dependencies
run: flutter pub get
- name: Format check
run: dart format --set-exit-if-changed .
- name: Analyze
run: flutter analyze
- name: Test
run: flutter test
"""
(workflow_dir / 'main.yml').write_text(workflow_content, encoding='utf-8')
print(f"✅ Created GitHub Actions workflow: main.yml (name: {display_name})")
def main():
parser = argparse.ArgumentParser(
description='Create a Flutter app and clean it by modifying generated files.'
)
parser.add_argument('name', help='Project name (use snake_case)')
parser.add_argument('--org', default=None, help='Organization identifier (e.g., com.example)')
parser.add_argument('--platforms', default=None, help='Comma-separated platforms (e.g., android,ios)')
parser.add_argument('--overwrite', action='store_true', help='Overwrite if directory exists')
args = parser.parse_args()
project_dir = Path(args.name).resolve()
# Check if directory exists
if project_dir.exists():
if not args.overwrite:
raise SystemExit(f'❌ Target directory already exists: {project_dir}\n Use --overwrite to replace it')
print(f"⚠️ Removing existing directory: {project_dir}")
shutil.rmtree(project_dir)
# Step 1: Run flutter create with latest template
print("\n📦 Step 1: Creating Flutter project with latest template...")
cmd = ['flutter', 'create', args.name]
if args.org:
cmd += ['--org', args.org]
if args.platforms:
cmd += ['--platforms', args.platforms]
run(cmd)
# Step 2: Ensure flutter_lints is in pubspec.yaml
print("\n📝 Step 2: Adding flutter_lints...")
pubspec_path = project_dir / 'pubspec.yaml'
ensure_flutter_lints(pubspec_path)
# Step 3: Get dependencies
print("\n📦 Step 3: Getting dependencies...")
run(['flutter', 'pub', 'get'], cwd=project_dir)
# Step 4: Create simple app.dart and clean main.dart (no MyHomePage)
print("\n✂️ Step 4: Creating clean app structure...")
lib_dir = project_dir / 'lib'
create_simple_app_dart(lib_dir)
create_clean_main_dart(lib_dir)
# Step 5: Clean pubspec.yaml
print("\n🧹 Step 5: Cleaning pubspec.yaml...")
remove_pubspec_comments(pubspec_path)
# Step 6: Clean analysis_options.yaml and add linter rules
print("\n📏 Step 6: Cleaning analysis_options.yaml and adding linter rules...")
analysis_options_path = project_dir / 'analysis_options.yaml'
clean_analysis_options(analysis_options_path)
# Step 7: Setup tests (remove widget_test, add placeholder_test)
print("\n🧪 Step 7: Setting up tests...")
test_dir = project_dir / 'test'
setup_tests(test_dir, args.name)
# Step 8: Add GitHub workflow as main.yml
print("\n🔄 Step 8: Creating GitHub Actions workflow...")
write_github_workflow(project_dir, args.name)
# Step 9: Format code
print("\n💅 Step 9: Formatting code...")
run(['dart', 'format', '.'], cwd=project_dir)
# Step 10: Analyze
print("\n🔍 Step 10: Running analyzer...")
run(['flutter', 'analyze'], cwd=project_dir)
# Step 11: Test
print("\n🧪 Step 11: Running tests...")
run(['flutter', 'test'], cwd=project_dir)
print(f'\n✅ Done! Clean Flutter project created at: {project_dir}')
print(f'\n📖 Next steps:')
print(f' cd {args.name}')
print(f' flutter run')
if __name__ == '__main__':
main()