├── .eslintrc.json
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets
├── logo.png
└── usage.gif
├── generate_code_mappings.py
├── package-lock.json
├── package.json
├── src
├── commands
│ ├── clean_architecture_command.ts
│ ├── clean_architecture_feat_command.ts
│ ├── clean_main_command.ts
│ ├── feature_structure_command.ts
│ ├── generate_endpoint_command.ts
│ ├── horizontal_structure_command.ts
│ ├── index.ts
│ └── vertical_structure_command.ts
├── extension.ts
├── mappings
│ └── code_template_mapping.ts
├── templates
│ ├── clean_architecture_feat_template.json
│ ├── clean_architecture_template.json
│ ├── code
│ │ ├── code.ts
│ │ ├── extensions
│ │ │ ├── date_extension.ts
│ │ │ ├── int_extension.ts
│ │ │ ├── str_extension.ts
│ │ │ └── time_extension.ts
│ │ ├── features
│ │ │ ├── auth
│ │ │ │ ├── logic
│ │ │ │ │ ├── authorization
│ │ │ │ │ │ ├── authorization_bloc.ts
│ │ │ │ │ │ ├── authorization_event.ts
│ │ │ │ │ │ └── authorization_state.ts
│ │ │ │ │ ├── login
│ │ │ │ │ │ ├── login_cubit.ts
│ │ │ │ │ │ └── login_state.ts
│ │ │ │ │ └── register
│ │ │ │ │ │ ├── register_cubit.ts
│ │ │ │ │ │ └── register_state.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── auth_error.ts
│ │ │ │ │ ├── login_request.ts
│ │ │ │ │ └── register_request.ts
│ │ │ │ ├── repo
│ │ │ │ │ └── auth_repo.ts
│ │ │ │ └── utils
│ │ │ │ │ └── auth_error_helper.ts
│ │ │ ├── bottom_nav
│ │ │ │ ├── blocs
│ │ │ │ │ └── navigation
│ │ │ │ │ │ └── navigation_cubit.ts
│ │ │ │ ├── components
│ │ │ │ │ ├── bottom_nav_widget.ts
│ │ │ │ │ └── tab_navigator.ts
│ │ │ │ ├── models
│ │ │ │ │ └── nav_item.ts
│ │ │ │ └── views
│ │ │ │ │ └── bottom_nav_view.ts
│ │ │ ├── form
│ │ │ │ ├── components
│ │ │ │ │ ├── form_builder.ts
│ │ │ │ │ └── password_field.ts
│ │ │ │ ├── models
│ │ │ │ │ └── form_item.ts
│ │ │ │ └── utils
│ │ │ │ │ └── validator.ts
│ │ │ └── onboarding
│ │ │ │ ├── components
│ │ │ │ ├── onboarding_content_widget.ts
│ │ │ │ └── pagination.ts
│ │ │ │ ├── models
│ │ │ │ └── onboarding_content.ts
│ │ │ │ └── views
│ │ │ │ └── onboarding_view.ts
│ │ ├── main.ts
│ │ ├── res
│ │ │ ├── colors.ts
│ │ │ ├── dimens.ts
│ │ │ └── styles.ts
│ │ ├── services
│ │ │ ├── network
│ │ │ │ └── dio_inst.ts
│ │ │ └── storage
│ │ │ │ └── prefs.ts
│ │ ├── utils.ts
│ │ └── views
│ │ │ └── home_view.ts
│ ├── endpoint
│ │ ├── cubit.ts
│ │ ├── data_class.ts
│ │ ├── interface.ts
│ │ └── repo.ts
│ ├── feat_template.json
│ ├── features
│ │ ├── auth_template.json
│ │ ├── bottom_nav_template.json
│ │ ├── form_template.json
│ │ └── onboarding_template.json
│ ├── horiz_template.json
│ └── vert_template.json
├── types
│ └── metadata.ts
└── utils
│ ├── type_utils.ts
│ └── utils.ts
├── tsconfig.json
├── vsc-extension-quickstart.md
└── webpack.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": [
9 | "@typescript-eslint"
10 | ],
11 | "rules": {
12 | "@typescript-eslint/naming-convention": "warn",
13 | "@typescript-eslint/semi": "warn",
14 | "curly": "warn",
15 | "eqeqeq": "warn",
16 | "no-throw-literal": "warn",
17 | "semi": "off"
18 | },
19 | "ignorePatterns": [
20 | "out",
21 | "dist",
22 | "**/*.d.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | *.vsix
6 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"]
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/dist/**/*.js"
17 | ],
18 | "preLaunchTask": "${defaultBuildTask}"
19 | },
20 | {
21 | "name": "Extension Tests",
22 | "type": "extensionHost",
23 | "request": "launch",
24 | "args": [
25 | "--extensionDevelopmentPath=${workspaceFolder}",
26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
27 | ],
28 | "outFiles": [
29 | "${workspaceFolder}/out/**/*.js",
30 | "${workspaceFolder}/dist/**/*.js"
31 | ],
32 | "preLaunchTask": "tasks: watch-tests"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files
5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files
6 | },
7 | "search.exclude": {
8 | "out": true, // set this to false to include "out" folder in search results
9 | "dist": true // set this to false to include "dist" folder in search results
10 | },
11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12 | "typescript.tsc.autoDetect": "off"
13 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": [
10 | "$ts-webpack-watch",
11 | "$tslint-webpack-watch"
12 | ],
13 | "isBackground": true,
14 | "presentation": {
15 | "reveal": "never",
16 | "group": "watchers"
17 | },
18 | "group": {
19 | "kind": "build",
20 | "isDefault": true
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "watch-tests",
26 | "problemMatcher": "$tsc-watch",
27 | "isBackground": true,
28 | "presentation": {
29 | "reveal": "never",
30 | "group": "watchers"
31 | },
32 | "group": "build"
33 | },
34 | {
35 | "label": "tasks: watch-tests",
36 | "dependsOn": [
37 | "npm: watch",
38 | "npm: watch-tests"
39 | ],
40 | "problemMatcher": []
41 | }
42 | ]
43 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/**
4 | node_modules/**
5 | src/**
6 | .gitignore
7 | .yarnrc
8 | webpack.config.js
9 | vsc-extension-quickstart.md
10 | **/tsconfig.json
11 | **/.eslintrc.json
12 | **/*.map
13 | **/*.ts
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to the "flutter-folder-structure-generator" extension will be documented in this file.
4 |
5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6 |
7 | ## 1.0.5
8 |
9 | - Fix: Injectable Generate Endpoint Code Syntax
10 |
11 | ## 1.0.4
12 |
13 | - Fix: List case and void case
14 |
15 | ## 1.0.3
16 |
17 | - Feat: Add Generate Endpoint support for injectable
18 |
19 | ## 1.0.2
20 |
21 | - Fix: Minor Issue on Import
22 | - Feat: Generate models if they are custom type
23 |
24 | ## 1.0.1
25 |
26 | - Add Url, Param Type, Return Type to Endpoint Generation
27 |
28 | ## 1.0.0
29 |
30 | - Generate Endpoint boilerplate compatible with Clean Architecture
31 |
32 | ## 0.0.7
33 |
34 | - Adds Functionality to generate boilerplate code
35 |
36 | ## [Unreleased]
37 |
38 | - Initial release
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sushan Shakya
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ## Overview
7 | [VSCode](https://code.visualstudio.com/) support for generating [Flutter](https://flutter.dev/) project Architecture
8 |
9 | ## Commands
10 |
11 | | Command | Description |
12 | | ------------------ | -------------------- |
13 | | `Folder : Horizontal` | Generate Horizonal Architecture |
14 | | `Folder : Vertical` | Generate Vertical Architecture |
15 | | `Folder : Feature (Vertical)` | Generate sub-folders for a feature in verticle architecture |
16 | | `Clean : main.dart` | Clean the defaul code for main.dart |
17 | | `Clean Architecture` | Generate the Folder Structure for Clean Architecture |
18 | | `Clean Architecture : Feature` | Genearte sub-folder for a feature in Clean Architecture |
19 |
20 | Use the commands by right clicking a folder in explorer.
21 | Command cannot be used directly from command palette
22 |
23 | ## Usage
24 |
25 | 
26 |
27 |
28 | Clean : main.dart
29 | -> Right click on lib folder and click on Clean : main.dart
30 |
31 | ## Hidden Features
32 |
33 | Command :
34 | Folder : Feature (Vertical)
35 |
36 | Using `form` or `forms` as feature name will generate code associated with forms.
37 | -> Provides `FormBuilder` Widget which can generate forms easily
38 |
39 | Using `bottom_nav` as feature name will generate code associated with Bottom Navigation.
40 | -> Provides `BottomNavView` widget which can be used as a screen
41 |
42 | Using `onboarding` as feature name will generate code associated with Onboarding Screens.
43 | -> Provides `OnboardingView` widget which can be used as a screen
44 |
45 | Using `auth` as feature name will generate code associated with Authorization, Login and Register.
46 | -> Provides `AuthorizationBloc`, `RegisterCubit`, `LoginCubit` widgets
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SushanShakya/flutter_folder_structure_generator/0024f76ea0d22e4994a291cf57a6ff128086ab1b/assets/logo.png
--------------------------------------------------------------------------------
/assets/usage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SushanShakya/flutter_folder_structure_generator/0024f76ea0d22e4994a291cf57a6ff128086ab1b/assets/usage.gif
--------------------------------------------------------------------------------
/generate_code_mappings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | dirpath = os.getcwd()
4 | foldername = os.path.basename(dirpath)
5 |
6 | fullPath = dirpath+ "\\" + "src\\templates\\code"
7 |
8 | class DirStructure:
9 | def __init__(self, map):
10 | self.parent = map['parent']
11 | self.name = map['name']
12 |
13 | def __str__(self):
14 | return f"{self.parent}/{self.name}"
15 |
16 |
17 | def getListOfFiles(dirName, parent):
18 | # create a list of file and sub directories
19 | # names in the given directory
20 | listOfFile = os.listdir(dirName)
21 | allFiles = list()
22 | # Iterate over all the entries
23 | for entry in listOfFile:
24 | # Create full path
25 | fullPath = os.path.join(dirName, entry)
26 | # If entry is a directory then get the list of files in this directory
27 | if os.path.isdir(fullPath):
28 | allFiles = allFiles + getListOfFiles(fullPath, f"{parent}/{entry}")
29 | else:
30 | allFiles.append(DirStructure({
31 | 'parent' : parent,
32 | 'name' : entry
33 | }))
34 |
35 | return allFiles
36 |
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flutter-folder-structure-generator",
3 | "displayName": "Flutter Architecture Generator",
4 | "description": "Extension to easily generate folder structre associated with flutter",
5 | "version": "1.0.5",
6 | "publisher": "SushanShakya",
7 | "bugs": {
8 | "url": "https://github.com/SushanShakya/flutter_folder_structure_generator/issues",
9 | "email": "sushaanshakya88@gmail.com"
10 | },
11 | "repository": {
12 | "url": "https://github.com/SushanShakya/flutter_folder_structure_generator",
13 | "type": "git"
14 | },
15 | "engines": {
16 | "vscode": "^1.65.0"
17 | },
18 | "categories": [
19 | "Other",
20 | "Extension Packs"
21 | ],
22 | "keywords": [
23 | "dart",
24 | "flutter",
25 | "folder-structure",
26 | "productivity"
27 | ],
28 | "icon": "assets/logo.png",
29 | "activationEvents": [
30 | "onCommand:extension.horizStruct",
31 | "onCommand:extension.vertStruct",
32 | "onCommand:extension.featStruct",
33 | "onCommand:extension.cleanMain",
34 | "onCommand:extension.cleanArchitecture",
35 | "onCommand:extension.cleanArchitectureFeat",
36 | "onCommand:extension.generateEndpointCode",
37 | "onCommand:extension.generateEndpointCodeInjectable"
38 | ],
39 | "main": "./dist/extension.js",
40 | "contributes": {
41 | "commands": [
42 | {
43 | "command": "extension.generateEndpointCode",
44 | "title": "Generate Endpoint Code",
45 | "icon": "assets/logo.png"
46 | },
47 | {
48 | "command": "extension.generateEndpointCodeInjectable",
49 | "title": "Generate Endpoint Code (Injectable)",
50 | "icon": "assets/logo.png"
51 | },
52 | {
53 | "command": "extension.featStruct",
54 | "title": "Folder: Feature (Vertical)",
55 | "icon": "assets/logo.png"
56 | },
57 | {
58 | "command": "extension.vertStruct",
59 | "title": "Folder: Vertical",
60 | "icon": "assets/logo.png"
61 | },
62 | {
63 | "command": "extension.horizStruct",
64 | "title": "Folder: Horizontal",
65 | "icon": "assets/logo.png"
66 | },
67 | {
68 | "command": "extension.cleanMain",
69 | "title": "Clean : main.dart",
70 | "icon": "assets/logo.png"
71 | },
72 | {
73 | "command": "extension.cleanArchitecture",
74 | "title": "Clean Architecture",
75 | "icon": "assets/logo.png"
76 | },
77 | {
78 | "command": "extension.cleanArchitectureFeat",
79 | "title": "Clean Architecture: Feature",
80 | "icon": "assets/logo.png"
81 | }
82 | ],
83 | "menus": {
84 | "explorer/context": [
85 | {
86 | "command": "extension.featStruct",
87 | "group": "structGroup@1",
88 | "when": "explorerResourceIsFolder"
89 | },
90 | {
91 | "command": "extension.vertStruct",
92 | "group": "structGroup@2",
93 | "when": "explorerResourceIsFolder"
94 | },
95 | {
96 | "command": "extension.horizStruct",
97 | "group": "structGroup@3",
98 | "when": "explorerResourceIsFolder"
99 | },
100 | {
101 | "command": "extension.cleanArchitecture",
102 | "group": "cleanGroup@3",
103 | "when": "explorerResourceIsFolder"
104 | },
105 | {
106 | "command": "extension.cleanArchitectureFeat",
107 | "group": "cleanGroup@3",
108 | "when": "explorerResourceIsFolder"
109 | },
110 | {
111 | "command": "extension.cleanMain",
112 | "group": "mainGroup@1",
113 | "when": "explorerResourceIsFolder"
114 | },
115 | {
116 | "command": "extension.generateEndpointCode",
117 | "group": "ep@4",
118 | "when": "explorerResourceIsFolder"
119 | },
120 | {
121 | "command": "extension.generateEndpointCodeInjectable",
122 | "group": "ep@4",
123 | "when": "explorerResourceIsFolder"
124 | }
125 | ]
126 | }
127 | },
128 | "scripts": {
129 | "vscode:prepublish": "npm run package",
130 | "compile": "webpack",
131 | "watch": "webpack --watch",
132 | "package": "webpack --mode production --devtool hidden-source-map",
133 | "compile-tests": "tsc -p . --outDir out",
134 | "watch-tests": "tsc -p . -w --outDir out",
135 | "pretest": "npm run compile-tests && npm run compile && npm run lint",
136 | "lint": "eslint src --ext ts",
137 | "test": "node ./out/test/runTest.js"
138 | },
139 | "devDependencies": {
140 | "@types/glob": "^7.2.0",
141 | "@types/lodash": "^4.14.202",
142 | "@types/mkdirp": "^1.0.2",
143 | "@types/mocha": "^9.1.0",
144 | "@types/node": "14.x",
145 | "@types/vscode": "^1.65.0",
146 | "@typescript-eslint/eslint-plugin": "^5.12.1",
147 | "@typescript-eslint/parser": "^5.12.1",
148 | "@vscode/test-electron": "^2.1.2",
149 | "eslint": "^8.9.0",
150 | "glob": "^7.2.0",
151 | "mocha": "^9.2.1",
152 | "ts-loader": "^9.2.6",
153 | "typescript": "^4.5.5",
154 | "webpack": "^5.69.1",
155 | "webpack-cli": "^4.9.2"
156 | },
157 | "dependencies": {
158 | "lodash": "^4.17.21",
159 | "mkdirp": "^1.0.4"
160 | }
161 | }
--------------------------------------------------------------------------------
/src/commands/clean_architecture_command.ts:
--------------------------------------------------------------------------------
1 | import { Uri } from "vscode";
2 | import * as template from "../templates/clean_architecture_template.json"
3 | import { generateStructure } from "../utils/utils";
4 |
5 | export const cleanArchitecture = async (uri: Uri) => {
6 | console.log('---- Clean Architecture')
7 | generateStructure(uri, template)
8 | }
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/commands/clean_architecture_feat_command.ts:
--------------------------------------------------------------------------------
1 | import * as template from "../templates/clean_architecture_feat_template.json"
2 | import { generateTemplate } from "../utils/utils";
3 | import { InputBoxOptions, Uri, window } from "vscode";
4 |
5 | export const cleanArchitectureFeat = async (uri: Uri) => {
6 | console.log('---- Clean Architecture feat');
7 | // [full_path] is the path where we generate the folder structure into.
8 | let full_path: string;
9 |
10 | // Check for when user invokes command from Ctrl + Shift + p
11 | if (typeof uri === "undefined") {
12 | window.showErrorMessage("Please use command from explorer !");
13 | return;
14 | } else {
15 | full_path = uri.fsPath;
16 | }
17 |
18 | let featureName = await promptForName();
19 | if ((typeof featureName === "undefined") || (featureName.trim() === "")) {
20 | window.showErrorMessage("The Feature name cannot be empty !");
21 | return;
22 | }
23 | let new_template: any = {};
24 | new_template[featureName] = template
25 |
26 | generateTemplate(new_template, full_path);
27 |
28 | }
29 |
30 |
31 | function promptForName(): Thenable {
32 | const options: InputBoxOptions = {
33 | prompt: "Feature Name",
34 | placeHolder: "feature",
35 | };
36 | return window.showInputBox(options);
37 | }
38 |
--------------------------------------------------------------------------------
/src/commands/clean_main_command.ts:
--------------------------------------------------------------------------------
1 | import { InputBoxOptions, Uri, window } from "vscode";
2 | import { getCleanedMain } from "../templates/code/main";
3 | import { createFile } from "../utils/utils";
4 |
5 | export const cleanMain = async (uri: Uri) => {
6 | // [full_path] is the path where we generate the folder structure into.
7 | let full_path: string;
8 |
9 | // Check for when user invokes command from Ctrl + Shift + p
10 | if (typeof uri === "undefined") {
11 | window.showErrorMessage("Please use command from explorer !");
12 | return;
13 | } else {
14 | full_path = uri.fsPath;
15 | }
16 |
17 | var appName = await promptForName();
18 | if ((typeof appName === "undefined") || (appName.trim() === "")) {
19 | window.showErrorMessage("The App name cannot be empty !");
20 | return;
21 | }
22 |
23 | let mainPath = `${full_path}\\main.dart`;
24 | let content = getCleanedMain(appName);
25 |
26 | createFile(mainPath, content);
27 | };
28 |
29 | function promptForName(): Thenable {
30 | const cubitNamePromptOptions: InputBoxOptions = {
31 | prompt: "App Name",
32 | placeHolder: "app name in snake_case",
33 | };
34 | return window.showInputBox(cubitNamePromptOptions);
35 | }
--------------------------------------------------------------------------------
/src/commands/feature_structure_command.ts:
--------------------------------------------------------------------------------
1 | import { InputBoxOptions, Uri, window } from "vscode";
2 | import { generateTemplate } from "../utils/utils";
3 | import * as template from "../templates/feat_template.json";
4 | import * as form_template from "../templates/features/form_template.json";
5 | import * as bot_nav_template from "../templates/features/bottom_nav_template.json";
6 | import * as onboarding_template from "../templates/features/onboarding_template.json";
7 | import * as auth_template from "../templates/features/auth_template.json";
8 |
9 | export const featStruct = async (uri: Uri) => {
10 | // [full_path] is the path where we generate the folder structure into.
11 | let full_path: string;
12 |
13 | // Check for when user invokes command from Ctrl + Shift + p
14 | if (typeof uri === "undefined") {
15 | window.showErrorMessage("Please use command from explorer !");
16 | return;
17 | } else {
18 | full_path = uri.fsPath;
19 | }
20 |
21 | let featureName = await promptForName();
22 | if ((typeof featureName === "undefined") || (featureName.trim() === "")) {
23 | window.showErrorMessage("The Feature name cannot be empty !");
24 | return;
25 | }
26 |
27 | let new_template: any = {};
28 | if (featureName === "form" || featureName === "forms") {
29 | new_template = form_template;
30 | } else if (featureName === "bottom_nav") {
31 | new_template = bot_nav_template;
32 | } else if (featureName === "onboarding") {
33 | new_template = onboarding_template;
34 | } else if (featureName === "auth") {
35 | new_template = auth_template;
36 | } else {
37 | new_template[featureName] = template;
38 | }
39 | generateTemplate(new_template, full_path);
40 | };
41 |
42 | function promptForName(): Thenable {
43 | const cubitNamePromptOptions: InputBoxOptions = {
44 | prompt: "Feature Name",
45 | placeHolder: "feature",
46 | };
47 | return window.showInputBox(cubitNamePromptOptions);
48 | }
--------------------------------------------------------------------------------
/src/commands/generate_endpoint_command.ts:
--------------------------------------------------------------------------------
1 | import { isNil } from "lodash";
2 | import { InputBoxOptions, Uri, window } from "vscode";
3 | import { generateCubit } from "../templates/endpoint/cubit";
4 | import { generateRepo } from "../templates/endpoint/repo";
5 | import { generateInterface } from "../templates/endpoint/interface";
6 | import { getFirstLetterCapital, upperCamelToSnake } from "../templates/code/utils";
7 | import { createDirectory, createFile } from "../utils/utils";
8 | import path from "path";
9 | import { EndpointMetadata, TemplateMetadata } from "../types/metadata";
10 | import { generateDataClass } from "../templates/endpoint/data_class";
11 | import { extractListType, isPrimitive } from "../utils/type_utils";
12 |
13 | type EndpointTemplate = {
14 | cubit: string,
15 | interfaceStr: string,
16 | repo: string,
17 | param: string,
18 | response: string,
19 | }
20 |
21 |
22 |
23 | const generateTemplate = (metadata: TemplateMetadata): EndpointTemplate => {
24 | let cubit = generateCubit(metadata);
25 | let repo = generateRepo(metadata);
26 | let interfaceStr = generateInterface(metadata);
27 | let request = generateDataClass(metadata.endpoint.paramType ?? "");
28 | let response = generateDataClass(metadata.endpoint.returnType ?? "");
29 |
30 | return {
31 | cubit,
32 | interfaceStr,
33 | repo,
34 | param: request,
35 | response,
36 | }
37 | }
38 |
39 | const promptName = async (options: InputBoxOptions): Promise => {
40 | let name = await window.showInputBox(options)
41 | if (isNil(name) || name.trim() === "") {
42 | window.showErrorMessage("The name must not be empty");
43 | return;
44 | }
45 | return name;
46 | }
47 |
48 | const handlePrompts = async (): Promise => {
49 |
50 | let url = await promptName({
51 | prompt: "Endpoint Url",
52 | placeHolder: "Defaults to /",
53 | });
54 |
55 | let className = await promptName({
56 | prompt: "Class Name",
57 | placeHolder: "In Upper Camel Case",
58 | });
59 | if (!className) return;
60 |
61 | let fnName = await promptName({
62 | prompt: "Function Name",
63 | placeHolder: "In Lower Camel Case",
64 | });
65 | if (!fnName) return;
66 |
67 |
68 | let paramType = await window.showInputBox({
69 | prompt: "Param Type",
70 | placeHolder: "In Upper Camel Case (Optional)",
71 | });
72 |
73 | if (isNil(paramType) || paramType.trim() === "") {
74 | paramType = undefined;
75 | }
76 |
77 | let returnType = await promptName({
78 | prompt: "Return Type",
79 | placeHolder: "In Upper Camel Case",
80 | });
81 | if (!returnType) return;
82 |
83 | return {
84 | url: url ?? '/',
85 | className, fnName, paramType, returnType
86 | }
87 | }
88 |
89 | const generateDataClassFileName = (type: string): string => {
90 | if (type.startsWith("List<")) {
91 | let listType = extractListType(type);
92 | return generateDataClassFileName(listType);
93 | }
94 | return upperCamelToSnake(type);
95 | }
96 |
97 | export const generateEndpointCode = async (uri: Uri, {
98 | injectable = false,
99 | }: { injectable: boolean }) => {
100 | if (typeof uri === 'undefined') {
101 | window.showErrorMessage("Please use command from explorer !");
102 | return;
103 | }
104 | let dirPath = uri.fsPath;
105 |
106 | let prompts = await handlePrompts();
107 | if (!prompts) return;
108 | const {
109 | className,
110 | paramType,
111 | returnType
112 | } = prompts;
113 |
114 | let param = prompts.paramType ? `${prompts.paramType} param` : "";
115 |
116 | let classNameInSnake = upperCamelToSnake(className);
117 | let paramNameInSnake = generateDataClassFileName(paramType ?? "");
118 | let returnTypeNameInSnake = generateDataClassFileName(returnType);
119 |
120 | let generateParamCode = !isPrimitive(paramType ?? "int");
121 | let generateResponseCode = !isPrimitive(returnType);
122 |
123 | let cubitPath = path.join(path.join(dirPath, "gui"), "presenters");
124 | let interfacePath = path.join(path.join(path.join(dirPath, "data"), "repo"), "interface");
125 | let repoPath = path.join(path.join(dirPath, "data"), "repo");
126 | let modelPath = path.join(path.join(dirPath, "data"), "models");
127 |
128 | let cubitFile = `${classNameInSnake}_cubit.dart`;
129 | let interfaceFile = `i${classNameInSnake}_repo.dart`;
130 | let repoFile = `${classNameInSnake}_repo.dart`;
131 | let paramFile = `${paramNameInSnake}.dart`;
132 | let responseFile = `${returnTypeNameInSnake}.dart`;
133 |
134 |
135 | let metadata: TemplateMetadata = {
136 | endpoint: prompts, interfaceFileName: interfaceFile, param,
137 | generateParamCode, generateResponseCode,
138 | paramFileName: paramFile,
139 | responseFileName: responseFile,
140 | injectable,
141 | };
142 |
143 | let template = generateTemplate(metadata);
144 |
145 | try {
146 | createDirectory(cubitPath);
147 | createDirectory(interfacePath);
148 | createDirectory(repoPath);
149 | if (generateParamCode || generateResponseCode) {
150 | createDirectory(modelPath);
151 | }
152 | createFile(path.join(cubitPath, cubitFile), template.cubit);
153 | createFile(path.join(interfacePath, interfaceFile), template.interfaceStr);
154 | createFile(path.join(repoPath, repoFile), template.repo);
155 | if (generateParamCode) {
156 | createFile(path.join(modelPath, paramFile), template.param);
157 | }
158 | if (generateResponseCode) {
159 | createFile(path.join(modelPath, responseFile), template.response);
160 | }
161 | } catch (e) {
162 | console.log('---- Error while trying to create file');
163 | console.log(e);
164 | window.showErrorMessage("Failed to generate code");
165 | return;
166 | }
167 | }
168 |
169 | export const generateEndpointCodeInjectable = async (uri: Uri) => generateEndpointCode(uri, {
170 | injectable: true,
171 | });
--------------------------------------------------------------------------------
/src/commands/horizontal_structure_command.ts:
--------------------------------------------------------------------------------
1 | import { Uri } from "vscode";
2 | import * as template from "../templates/horiz_template.json"
3 | import { generateStructure } from "../utils/utils";
4 |
5 | export const horizStruct = async (uri: Uri) => {
6 | generateStructure(uri, template)
7 | }
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/commands/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./feature_structure_command";
2 | export * from "./horizontal_structure_command";
3 | export * from "./vertical_structure_command";
4 | export * from "./clean_main_command";
5 | export * from "./clean_architecture_command"
6 | export * from "./clean_architecture_feat_command"
7 | export * from "./generate_endpoint_command"
--------------------------------------------------------------------------------
/src/commands/vertical_structure_command.ts:
--------------------------------------------------------------------------------
1 | import { Uri } from "vscode";
2 | import { generateStructure } from "../utils/utils";
3 | import * as template from "../templates/vert_template.json"
4 |
5 | export const vertStruct = async (uri: Uri) => {
6 | generateStructure(uri, template)
7 | }
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import {
3 | horizStruct,
4 | vertStruct,
5 | featStruct,
6 | cleanMain,
7 | cleanArchitecture,
8 | cleanArchitectureFeat,
9 | generateEndpointCode,
10 | generateEndpointCodeInjectable,
11 | } from "./commands"
12 |
13 | // Start of the Extension
14 | // -> Called when the extension is active
15 | export function activate(context: vscode.ExtensionContext) {
16 | context.subscriptions.push(
17 | vscode.commands.registerCommand("extension.featStruct", featStruct),
18 | vscode.commands.registerCommand("extension.vertStruct", vertStruct),
19 | vscode.commands.registerCommand("extension.horizStruct", horizStruct),
20 | vscode.commands.registerCommand("extension.cleanArchitecture", cleanArchitecture),
21 | vscode.commands.registerCommand("extension.cleanArchitectureFeat", cleanArchitectureFeat),
22 | vscode.commands.registerCommand("extension.cleanMain", cleanMain),
23 | vscode.commands.registerCommand("extension.generateEndpointCode", generateEndpointCode),
24 | vscode.commands.registerCommand("extension.generateEndpointCodeInjectable", generateEndpointCodeInjectable),
25 | )
26 | }
27 |
28 |
29 | // Called when the extension is deactivated
30 | export function deactivate() { }
31 |
--------------------------------------------------------------------------------
/src/mappings/code_template_mapping.ts:
--------------------------------------------------------------------------------
1 | import * as t from "../templates/code/code"
2 |
3 | let mappings: any = {}
4 | mappings['colors'] = t.colors
5 | mappings['dimens'] = t.dimens
6 | mappings['styles'] = t.styles
7 | mappings['dio_inst'] = t.dio_inst
8 | mappings['prefs'] = t.prefs
9 | mappings['home_view'] = t.home_view
10 | mappings['form_builder'] = t.form_builder
11 | mappings['password_field'] = t.password_field
12 | mappings['form_item'] = t.form_item
13 | mappings['validator'] = t.validator
14 |
15 | mappings['navigation_cubit'] = t.navigation_cubit
16 | mappings['bottom_nav_widget'] = t.bottom_nav_widget
17 | mappings['tab_navigator'] = t.tab_navigator
18 | mappings['nav_item'] = t.nav_item
19 | mappings['bottom_nav_view'] = t.bottom_nav_view
20 |
21 | mappings['onboarding_content_widget'] = t.onboarding_content_widget
22 | mappings['pagination'] = t.pagination
23 | mappings['onboarding_content'] = t.onboarding_content
24 | mappings['onboarding_view'] = t.onboarding_view
25 |
26 | mappings['date_extension'] = t.date_extension
27 | mappings['time_extension'] = t.time_extension
28 | mappings['str_extension'] = t.str_extension
29 | mappings['int_extension'] = t.int_extension
30 |
31 | mappings['authorization_bloc'] = t.authorization_bloc
32 | mappings['authorization_event'] = t.authorization_event
33 | mappings['authorization_state'] = t.authorization_state
34 | mappings['login_cubit'] = t.login_cubit
35 | mappings['login_state'] = t.login_state
36 | mappings['register_cubit'] = t.register_cubit
37 | mappings['register_state'] = t.register_state
38 | mappings['auth_error'] = t.auth_error
39 | mappings['login_request'] = t.login_request
40 | mappings['register_request'] = t.register_request
41 | mappings['auth_repo'] = t.auth_repo
42 | mappings['auth_error_helper'] = t.auth_error_helper
43 |
44 | export default mappings
--------------------------------------------------------------------------------
/src/templates/clean_architecture_feat_template.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "repo": {
4 | "interface": {}
5 | },
6 | "models": {}
7 | },
8 | "domain": {
9 | "entities": {},
10 | "usecases": {}
11 | },
12 | "gui": {
13 | "views": {},
14 | "components": {},
15 | "presenters": {},
16 | "view_models": {}
17 | }
18 | }
--------------------------------------------------------------------------------
/src/templates/clean_architecture_template.json:
--------------------------------------------------------------------------------
1 | {
2 | "src": {
3 | "core": {
4 | "styles": {}
5 | },
6 | "modules": {
7 | "common": {
8 | "data": {
9 | "repo": {
10 | "interface": {}
11 | },
12 | "models": {}
13 | },
14 | "domain": {
15 | "entities": {},
16 | "usecases": {}
17 | },
18 | "gui": {
19 | "views": {},
20 | "components": {},
21 | "presenters": {},
22 | "view_models": {}
23 | }
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/templates/code/code.ts:
--------------------------------------------------------------------------------
1 | // ---- Architecture Base Exports
2 | export { colors } from "./res/colors"
3 | export { dimens } from "./res/dimens"
4 | export { styles } from "./res/styles"
5 | export { dio_inst } from "./services/network/dio_inst"
6 | export { prefs } from "./services/storage/prefs"
7 | export { home_view } from "./views/home_view"
8 | // ---- Architecture Base Exports
9 |
10 | // ---- Form Exports
11 | export { form_builder } from "./features/form/components/form_builder";
12 | export { password_field } from "./features/form/components/password_field";
13 | export { form_item } from "./features/form/models/form_item";
14 | export { validator } from "./features/form/utils/validator";
15 | // ---- Form Exports
16 |
17 | // ---- Bottom Nav Exports
18 | export { navigation_cubit } from "./features/bottom_nav/blocs/navigation/navigation_cubit";
19 | export { bottom_nav_widget } from "./features/bottom_nav/components/bottom_nav_widget";
20 | export { tab_navigator } from "./features/bottom_nav/components/tab_navigator";
21 | export { nav_item } from "./features/bottom_nav/models/nav_item";
22 | export { bottom_nav_view } from "./features/bottom_nav/views/bottom_nav_view";
23 | // ---- Bottom Nav Exports
24 |
25 |
26 | // ---- Onboarding Nav Exports
27 | export { onboarding_content_widget } from './features/onboarding/components/onboarding_content_widget';
28 | export { pagination } from './features/onboarding/components/pagination';
29 | export { onboarding_content } from './features/onboarding/models/onboarding_content';
30 | export { onboarding_view } from './features/onboarding/views/onboarding_view';
31 | // ---- Onboarding Nav Exports
32 |
33 | // ---- Extension Exports
34 | export { date_extension } from './extensions/date_extension';
35 | export { int_extension } from './extensions/int_extension';
36 | export { str_extension } from './extensions/str_extension';
37 | export { time_extension } from './extensions/time_extension';
38 | // ---- Extension Exports
39 |
40 | // ---- Authentication Exports
41 | export { authorization_bloc } from './features/auth/logic/authorization/authorization_bloc';
42 | export { authorization_event } from './features/auth/logic/authorization/authorization_event';
43 | export { authorization_state } from './features/auth/logic/authorization/authorization_state';
44 |
45 | export { login_cubit } from './features/auth/logic/login/login_cubit';
46 | export { login_state } from './features/auth/logic/login/login_state';
47 |
48 | export { register_cubit } from './features/auth/logic/register/register_cubit';
49 | export { register_state } from './features/auth/logic/register/register_state';
50 |
51 | export { auth_error } from './features/auth/models/auth_error';
52 | export { login_request } from './features/auth/models/login_request';
53 | export { register_request } from './features/auth/models/register_request';
54 |
55 | export { auth_repo } from './features/auth/repo/auth_repo';
56 |
57 | export { auth_error_helper } from './features/auth/utils/auth_error_helper';
58 |
59 | // ---- Authentication Exports
--------------------------------------------------------------------------------
/src/templates/code/extensions/date_extension.ts:
--------------------------------------------------------------------------------
1 | export let date_extension = `import 'package:flutter/material.dart';
2 | import 'time_extension.dart';
3 |
4 | extension DateUtils on DateTime {
5 | static final Map _months = {
6 | 1: "Jan",
7 | 2: "Feb",
8 | 3: "Mar",
9 | 4: "Apr",
10 | 5: "May",
11 | 6: "Jun",
12 | 7: "Jul",
13 | 8: "Aug",
14 | 9: "Sep",
15 | 10: "Oct",
16 | 11: "Nov",
17 | 12: "Dec",
18 | };
19 | // static final Map _fullmonths = {
20 | // 1: "January",
21 | // 2: "February",
22 | // 3: "March",
23 | // 4: "April",
24 | // 5: "May",
25 | // 6: "June",
26 | // 7: "July",
27 | // 8: "August",
28 | // 9: "September",
29 | // 10: "October",
30 | // 11: "November",
31 | // 12: "December",
32 | // };
33 |
34 | String get stringify => "$year-$month-$day";
35 |
36 | String get excludeYear => "$month/$day";
37 |
38 | String get dayFormat {
39 | var m = _months[month];
40 | return '$m $day';
41 | }
42 |
43 | String get namedFormat {
44 | var m = _months[month];
45 | return "$m $day, $year";
46 | }
47 |
48 | static final Map _weekDay = {
49 | 1: 'Mon',
50 | 2: 'Tue',
51 | 3: 'Wed',
52 | 4: 'Thu',
53 | 5: 'Fri',
54 | 6: 'Sat',
55 | 7: 'Sun'
56 | };
57 | static final Map _weekDayFull = {
58 | 1: 'Monday',
59 | 2: 'Tuesday',
60 | 3: 'Wednesday',
61 | 4: 'Thursday',
62 | 5: 'Friday',
63 | 6: 'Saturday',
64 | 7: 'Sunday'
65 | };
66 |
67 | String get weekFormat => _weekDayFull[weekday] ?? '';
68 | String get weekFormatCut => _weekDay[weekday] ?? '';
69 |
70 | String get formatted {
71 | var date = this;
72 | return "\${_weekDay[date.weekday]}, \${_months[date.month]} \${date.day}";
73 | }
74 |
75 | String get msgFormat {
76 | TimeOfDay cur = TimeOfDay.fromDateTime(this);
77 | if (DateTime.now().difference(this).inDays == 0) {
78 | return cur.stringify;
79 | }
80 | return '\${_weekDay[weekday]} @ \${cur.stringify}';
81 | }
82 | }
83 | `
--------------------------------------------------------------------------------
/src/templates/code/extensions/int_extension.ts:
--------------------------------------------------------------------------------
1 | export let int_extension = `extension IntFormat on int {
2 | String get nepaliFormat {
3 | String str = toString();
4 | int len = str.length;
5 | List formatted = str.split("").reversed.toList();
6 | if (len > 3) {
7 | formatted = [];
8 | for (int i = 0; i < len; i++) {
9 | if (i == 3 || (i % 2 != 0 && i > 3)) formatted.add(",");
10 | formatted.add(str[len - i - 1]);
11 | }
12 | }
13 | return formatted.reversed.join();
14 | }
15 |
16 | static Map nepaliNumbers = {
17 | "0": "०",
18 | "1": "१",
19 | "2": "२",
20 | "3": "३",
21 | "4": "४",
22 | "5": "५",
23 | "6": "६",
24 | "7": "७",
25 | "8": "८",
26 | "9": "९",
27 | ",": ",",
28 | };
29 |
30 | String get toNepali {
31 | return nepaliFormat.split('').map((e) => nepaliNumbers[e]).join();
32 | }
33 |
34 | String get cartFormat {
35 | return (this > 9) ? '9+' : toString();
36 | }
37 | }
38 | `
--------------------------------------------------------------------------------
/src/templates/code/extensions/str_extension.ts:
--------------------------------------------------------------------------------
1 | export let str_extension = `extension StrFormat on String {
2 | String get firstCapital {
3 | List values = split('');
4 | values[0] = values[0].toUpperCase();
5 | return values.join();
6 | }
7 | }
8 | `
--------------------------------------------------------------------------------
/src/templates/code/extensions/time_extension.ts:
--------------------------------------------------------------------------------
1 | export let time_extension = `import 'package:flutter/material.dart';
2 |
3 | extension TimeUtil on TimeOfDay {
4 | String get stringify {
5 | var time = this;
6 | if (time.hour > 12 || (time.hour == 12 && time.minute > 0)) {
7 | // PM
8 | var hr = time.hour == 12 ? 12 : time.hour - 12;
9 | return "$hr:\${time.minute.toString().padLeft(2, '0')} PM";
10 | } else {
11 | //AM
12 | var hr = time.hour == 0 ? 12 : time.hour;
13 | return "$hr:\${time.minute.toString().padLeft(2, '0')} AM";
14 | }
15 | }
16 | }
17 | `
--------------------------------------------------------------------------------
/src/templates/code/features/auth/logic/authorization/authorization_bloc.ts:
--------------------------------------------------------------------------------
1 | export let authorization_bloc = `import 'dart:async';
2 |
3 | import 'package:bloc/bloc.dart';
4 | import 'package:equatable/equatable.dart';
5 |
6 | import '../../../../services/network/dio_inst.dart';
7 | import '../../../../services/storage/prefs.dart';
8 |
9 | part 'authorization_event.dart';
10 | part 'authorization_state.dart';
11 |
12 | class AuthorizationBloc extends Bloc {
13 | AuthorizationBloc() : super(AuthorizedAsGuest()) {
14 | add(CheckAuthorization());
15 | }
16 |
17 | @override
18 | Stream mapEventToState(
19 | AuthorizationEvent event,
20 | ) async* {
21 | if (event is RemoveAuthorization) {
22 | dio.options.headers['Authorization'] = '';
23 | await Prefs.deleteString(TOKENKEY);
24 | yield AuthorizedAsGuest();
25 | }
26 | if (event is CheckAuthorization) {
27 | try {
28 | var token = Prefs.getString(TOKENKEY);
29 | if (token == null) {
30 | yield AuthorizedAsGuest();
31 | } else {
32 | dio.options.headers['Authorization'] = 'Bearer $token';
33 | yield AuthorizedAsUser();
34 | }
35 | } catch (e) {
36 | yield AuthorizedAsGuest();
37 | }
38 | }
39 | }
40 | }
41 | `
--------------------------------------------------------------------------------
/src/templates/code/features/auth/logic/authorization/authorization_event.ts:
--------------------------------------------------------------------------------
1 | export let authorization_event = `part of 'authorization_bloc.dart';
2 |
3 | abstract class AuthorizationEvent extends Equatable {
4 | const AuthorizationEvent();
5 |
6 | @override
7 | List