e-pick 1.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Base File Parser (Template Method Pattern)
3
+ *
4
+ * Defines the skeleton of the file parsing algorithm.
5
+ * Subclasses implement specific steps while the overall flow remains the same.
6
+ */
7
+
8
+ class BaseFileParser {
9
+ /**
10
+ * Template method - defines the parsing algorithm structure
11
+ * @param {File} file - File to parse
12
+ * @returns {Promise<object>} Parsed data
13
+ */
14
+ async parse(file) {
15
+ // Step 1: Validate file
16
+ this.validateFile(file);
17
+
18
+ // Step 2: Read file content
19
+ const rawData = await this.readFile(file);
20
+
21
+ // Step 3: Structure the data
22
+ const structured = this.structureData(rawData);
23
+
24
+ // Step 4: Format and return result
25
+ return this.formatResult(structured);
26
+ }
27
+
28
+ /**
29
+ * Validate file (can be overridden by subclasses)
30
+ * @param {File} file - File to validate
31
+ * @throws {Error} If file is invalid
32
+ */
33
+ validateFile(file) {
34
+ if (!file) {
35
+ throw new Error('No file provided');
36
+ }
37
+
38
+ if (!this.isValidFileType(file)) {
39
+ throw new Error(`Invalid file type: ${file.type}`);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Check if file type is valid (must be implemented by subclasses)
45
+ * @param {File} file - File to check
46
+ * @returns {boolean} True if valid
47
+ */
48
+ isValidFileType(file) {
49
+ throw new Error('isValidFileType() must be implemented by subclass');
50
+ }
51
+
52
+ /**
53
+ * Read file content (must be implemented by subclasses)
54
+ * @param {File} file - File to read
55
+ * @returns {Promise<any>} Raw file data
56
+ */
57
+ async readFile(file) {
58
+ throw new Error('readFile() must be implemented by subclass');
59
+ }
60
+
61
+ /**
62
+ * Structure the raw data (must be implemented by subclasses)
63
+ * @param {any} rawData - Raw data from file
64
+ * @returns {object} Structured data with columns and rows
65
+ */
66
+ structureData(rawData) {
67
+ throw new Error('structureData() must be implemented by subclass');
68
+ }
69
+
70
+ /**
71
+ * Format the final result (can be overridden)
72
+ * @param {object} data - Structured data
73
+ * @returns {object} Formatted result
74
+ */
75
+ formatResult(data) {
76
+ // Format columns as objects with name and index
77
+ const formattedColumns = data.columns.map((col, index) => ({
78
+ name: col != null && col !== '' ? String(col) : `Column ${index + 1}`,
79
+ index: index
80
+ }));
81
+
82
+ return {
83
+ success: true,
84
+ columns: formattedColumns,
85
+ rows: data.rows,
86
+ meta: data.meta || {}
87
+ };
88
+ }
89
+ }
90
+
91
+ // Export for use in other scripts
92
+ window.BaseFileParser = BaseFileParser;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * CSV Parser (Template Method Pattern)
3
+ *
4
+ * Implements CSV-specific parsing logic
5
+ */
6
+
7
+ class CSVParser extends BaseFileParser {
8
+ /**
9
+ * Check if file is valid CSV type
10
+ * @param {File} file - File to check
11
+ * @returns {boolean} True if valid
12
+ */
13
+ isValidFileType(file) {
14
+ const validTypes = ['text/csv', 'text/plain', 'application/csv'];
15
+ const validExtensions = ['.csv', '.tsv', '.txt'];
16
+
17
+ const hasValidType = validTypes.includes(file.type);
18
+ const hasValidExtension = validExtensions.some(ext =>
19
+ file.name.toLowerCase().endsWith(ext)
20
+ );
21
+
22
+ return hasValidType || hasValidExtension || file.type === '';
23
+ }
24
+
25
+ /**
26
+ * Read CSV file
27
+ * @param {File} file - CSV file
28
+ * @returns {Promise<object>} Parsed CSV data
29
+ */
30
+ async readFile(file) {
31
+ return new Promise((resolve, reject) => {
32
+ Papa.parse(file, {
33
+ complete: (results) => {
34
+ resolve(results);
35
+ },
36
+ error: (error) => {
37
+ reject(new Error(`Failed to parse CSV: ${error.message}`));
38
+ },
39
+ skipEmptyLines: true
40
+ });
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Structure CSV data
46
+ * @param {object} parsed - PapaParse result
47
+ * @returns {object} Structured data
48
+ */
49
+ structureData(parsed) {
50
+ if (!parsed.data || parsed.data.length === 0) {
51
+ throw new Error('CSV file is empty');
52
+ }
53
+
54
+ // First row is headers
55
+ const columns = parsed.data[0];
56
+ const rows = parsed.data.slice(1).filter(row =>
57
+ row.some(cell => cell !== null && cell !== '')
58
+ );
59
+
60
+ return {
61
+ columns,
62
+ rows,
63
+ meta: {
64
+ totalRows: rows.length,
65
+ errors: parsed.errors
66
+ }
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Format result with CSV-specific metadata
72
+ * @param {object} data - Structured data
73
+ * @returns {object} Formatted result
74
+ */
75
+ formatResult(data) {
76
+ const result = super.formatResult(data);
77
+
78
+ // Add CSV-specific warnings if there were parsing errors
79
+ if (data.meta.errors && data.meta.errors.length > 0) {
80
+ result.warnings = data.meta.errors.map(err => err.message);
81
+ }
82
+
83
+ return result;
84
+ }
85
+ }
86
+
87
+ // Export for use in other scripts
88
+ window.CSVParser = CSVParser;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Excel Parser (Template Method Pattern)
3
+ *
4
+ * Implements Excel-specific parsing logic
5
+ */
6
+
7
+ class ExcelParser extends BaseFileParser {
8
+ constructor() {
9
+ super();
10
+ this.workbook = null;
11
+ }
12
+
13
+ /**
14
+ * Check if file is valid Excel type
15
+ * @param {File} file - File to check
16
+ * @returns {boolean} True if valid
17
+ */
18
+ isValidFileType(file) {
19
+ const validTypes = [
20
+ 'application/vnd.ms-excel',
21
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
22
+ 'application/vnd.ms-excel.sheet.macroEnabled.12'
23
+ ];
24
+
25
+ const validExtensions = ['.xlsx', '.xls', '.xlsm'];
26
+ const hasValidType = validTypes.includes(file.type);
27
+ const hasValidExtension = validExtensions.some(ext =>
28
+ file.name.toLowerCase().endsWith(ext)
29
+ );
30
+
31
+ return hasValidType || hasValidExtension;
32
+ }
33
+
34
+ /**
35
+ * Read Excel file
36
+ * @param {File} file - Excel file
37
+ * @returns {Promise<object>} Workbook object
38
+ */
39
+ async readFile(file) {
40
+ return new Promise((resolve, reject) => {
41
+ const reader = new FileReader();
42
+
43
+ reader.onload = (e) => {
44
+ try {
45
+ const data = new Uint8Array(e.target.result);
46
+ const workbook = XLSX.read(data, { type: 'array' });
47
+ this.workbook = workbook;
48
+ resolve(workbook);
49
+ } catch (error) {
50
+ reject(new Error(`Failed to read Excel file: ${error.message}`));
51
+ }
52
+ };
53
+
54
+ reader.onerror = () => {
55
+ reject(new Error('Failed to read file'));
56
+ };
57
+
58
+ reader.readAsArrayBuffer(file);
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Structure workbook data
64
+ * @param {object} workbook - XLSX workbook
65
+ * @returns {object} Structured data
66
+ */
67
+ structureData(workbook) {
68
+ const sheetNames = workbook.SheetNames;
69
+
70
+ // Check if multiple sheets exist
71
+ if (sheetNames.length > 1) {
72
+ return {
73
+ needsSheetSelection: true,
74
+ sheets: sheetNames,
75
+ parser: this,
76
+ meta: {
77
+ sheetCount: sheetNames.length
78
+ }
79
+ };
80
+ }
81
+
82
+ // Single sheet - parse directly
83
+ const sheetName = sheetNames[0];
84
+ return this.parseSheet(workbook, sheetName);
85
+ }
86
+
87
+ /**
88
+ * Parse a specific sheet from workbook
89
+ * @param {object} workbook - XLSX workbook
90
+ * @param {string} sheetName - Sheet name to parse
91
+ * @returns {object} Parsed sheet data
92
+ */
93
+ parseSheet(workbook, sheetName) {
94
+ const worksheet = workbook.Sheets[sheetName];
95
+ const jsonData = XLSX.utils.sheet_to_json(worksheet, {
96
+ header: 1,
97
+ defval: ''
98
+ });
99
+
100
+ if (jsonData.length === 0) {
101
+ throw new Error('Sheet is empty');
102
+ }
103
+
104
+ // First row is headers
105
+ const columns = jsonData[0];
106
+ const rows = jsonData.slice(1).filter(row =>
107
+ row.some(cell => cell !== null && cell !== '')
108
+ );
109
+
110
+ return {
111
+ columns,
112
+ rows,
113
+ meta: {
114
+ sheetName,
115
+ totalRows: rows.length
116
+ }
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Format result with Excel-specific metadata
122
+ * @param {object} data - Structured data
123
+ * @returns {object} Formatted result
124
+ */
125
+ formatResult(data) {
126
+ // If needs sheet selection, return early
127
+ if (data.needsSheetSelection) {
128
+ return {
129
+ success: true,
130
+ needsSheetSelection: true,
131
+ sheets: data.sheets,
132
+ parser: this,
133
+ meta: data.meta
134
+ };
135
+ }
136
+
137
+ return super.formatResult(data);
138
+ }
139
+ }
140
+
141
+ // Export for use in other scripts
142
+ window.ExcelParser = ExcelParser;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Parser Factory (Factory Pattern)
3
+ *
4
+ * Creates appropriate parser based on file type
5
+ */
6
+
7
+ class ParserFactory {
8
+ /**
9
+ * Create parser for file
10
+ * @param {File} file - File to parse
11
+ * @returns {BaseFileParser} Appropriate parser instance
12
+ * @throws {Error} If no suitable parser found
13
+ */
14
+ static createParser(file) {
15
+ if (!file) {
16
+ throw new Error('No file provided');
17
+ }
18
+
19
+ // Try Excel parser first
20
+ const excelParser = new ExcelParser();
21
+ if (excelParser.isValidFileType(file)) {
22
+ return excelParser;
23
+ }
24
+
25
+ // Try CSV parser
26
+ const csvParser = new CSVParser();
27
+ if (csvParser.isValidFileType(file)) {
28
+ return csvParser;
29
+ }
30
+
31
+ // No suitable parser found
32
+ throw new Error(
33
+ `Unsupported file type: ${file.type || 'unknown'}. ` +
34
+ `Please upload .xlsx, .xls, or .csv files.`
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Parse file using appropriate parser
40
+ * @param {File} file - File to parse
41
+ * @returns {Promise<object>} Parsed result
42
+ */
43
+ static async parseFile(file) {
44
+ const parser = ParserFactory.createParser(file);
45
+ return await parser.parse(file);
46
+ }
47
+
48
+ /**
49
+ * Parse specific sheet from Excel file
50
+ * @param {ExcelParser} parser - Excel parser instance
51
+ * @param {string} sheetName - Sheet name to parse
52
+ * @returns {object} Parsed sheet data
53
+ */
54
+ static parseSheet(parser, sheetName) {
55
+ if (!(parser instanceof ExcelParser)) {
56
+ throw new Error('Parser must be an ExcelParser instance');
57
+ }
58
+
59
+ if (!parser.workbook) {
60
+ throw new Error('No workbook loaded');
61
+ }
62
+
63
+ const data = parser.parseSheet(parser.workbook, sheetName);
64
+ return parser.formatResult(data);
65
+ }
66
+ }
67
+
68
+ // Export for use in other scripts
69
+ window.ParserFactory = ParserFactory;
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Stepper States (State Pattern)
3
+ *
4
+ * Each step in the wizard is represented as a state object
5
+ * with specific behaviors and validation logic.
6
+ */
7
+
8
+ /**
9
+ * Base Step State
10
+ */
11
+ class StepState {
12
+ constructor(context) {
13
+ this.context = context;
14
+ this.stepNumber = 0;
15
+ this.stepName = 'Unknown';
16
+ }
17
+
18
+ /**
19
+ * Called when entering this step
20
+ */
21
+ enter() {
22
+ this.context.setCurrentStep(this.stepNumber);
23
+ }
24
+
25
+ /**
26
+ * Called when leaving this step
27
+ */
28
+ exit() {
29
+ // Override in subclasses if needed
30
+ }
31
+
32
+ /**
33
+ * Validate if we can proceed to next step
34
+ * @returns {boolean} True if can proceed
35
+ */
36
+ canProceed() {
37
+ return true;
38
+ }
39
+
40
+ /**
41
+ * Get next state
42
+ * @returns {StepState|null} Next state or null if last step
43
+ */
44
+ next() {
45
+ return null;
46
+ }
47
+
48
+ /**
49
+ * Get previous state
50
+ * @returns {StepState|null} Previous state or null if first step
51
+ */
52
+ previous() {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Step 1: Upload File
59
+ */
60
+ class UploadFileState extends StepState {
61
+ constructor(context) {
62
+ super(context);
63
+ this.stepNumber = 1;
64
+ this.stepName = 'Upload File';
65
+ }
66
+
67
+ enter() {
68
+ super.enter();
69
+ // File upload UI is already visible
70
+ }
71
+
72
+ canProceed() {
73
+ return this.context.hasFile();
74
+ }
75
+
76
+ next() {
77
+ return new SelectColumnsState(this.context);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Step 2: Select Columns
83
+ */
84
+ class SelectColumnsState extends StepState {
85
+ constructor(context) {
86
+ super(context);
87
+ this.stepNumber = 2;
88
+ this.stepName = 'Select Columns';
89
+ }
90
+
91
+ enter() {
92
+ super.enter();
93
+ // Column selectors should be visible
94
+ }
95
+
96
+ canProceed() {
97
+ return this.context.hasSelectedColumns();
98
+ }
99
+
100
+ next() {
101
+ return new ValidateState(this.context);
102
+ }
103
+
104
+ previous() {
105
+ return new UploadFileState(this.context);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Step 3: Validate
111
+ */
112
+ class ValidateState extends StepState {
113
+ constructor(context) {
114
+ super(context);
115
+ this.stepNumber = 3;
116
+ this.stepName = 'Validate';
117
+ }
118
+
119
+ enter() {
120
+ super.enter();
121
+ // Show validation progress
122
+ }
123
+
124
+ canProceed() {
125
+ return this.context.hasValidationResults();
126
+ }
127
+
128
+ next() {
129
+ return new ReviewResultsState(this.context);
130
+ }
131
+
132
+ previous() {
133
+ return new SelectColumnsState(this.context);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Step 4: Review Results & Get Command
139
+ */
140
+ class ReviewResultsState extends StepState {
141
+ constructor(context) {
142
+ super(context);
143
+ this.stepNumber = 4;
144
+ this.stepName = 'Review & Get Command';
145
+ }
146
+
147
+ enter() {
148
+ super.enter();
149
+ // Show validation results and command
150
+ }
151
+
152
+ canProceed() {
153
+ return this.context.hasGeneratedCommand();
154
+ }
155
+
156
+ next() {
157
+ return new ViewFilesState(this.context);
158
+ }
159
+
160
+ previous() {
161
+ return new ValidateState(this.context);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Step 5: View Changed Files
167
+ */
168
+ class ViewFilesState extends StepState {
169
+ constructor(context) {
170
+ super(context);
171
+ this.stepNumber = 5;
172
+ this.stepName = 'View Changed Files';
173
+ }
174
+
175
+ enter() {
176
+ super.enter();
177
+ // Show changed files with diff commands
178
+ }
179
+
180
+ canProceed() {
181
+ return false; // Last step
182
+ }
183
+
184
+ next() {
185
+ return null; // No next step
186
+ }
187
+
188
+ previous() {
189
+ return new ReviewResultsState(this.context);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Stepper Context
195
+ * Manages state transitions
196
+ */
197
+ class StepperContext {
198
+ constructor(uiManager) {
199
+ this.uiManager = uiManager;
200
+ this.currentState = new UploadFileState(this);
201
+ this.parsedData = null;
202
+ this.validationResults = null;
203
+ this.generatedCommand = null;
204
+ }
205
+
206
+ /**
207
+ * Set current step in UI
208
+ * @param {number} stepNumber - Step number
209
+ */
210
+ setCurrentStep(stepNumber) {
211
+ if (this.uiManager && this.uiManager.advanceToStep) {
212
+ this.uiManager.advanceToStep(stepNumber);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Advance to next step
218
+ * @returns {boolean} True if advanced successfully
219
+ */
220
+ async advanceToNext() {
221
+ if (!this.currentState.canProceed()) {
222
+ console.warn(`Cannot proceed from ${this.currentState.stepName}`);
223
+ return false;
224
+ }
225
+
226
+ const nextState = this.currentState.next();
227
+ if (!nextState) {
228
+ console.warn('Already at last step');
229
+ return false;
230
+ }
231
+
232
+ this.currentState.exit();
233
+ this.currentState = nextState;
234
+ this.currentState.enter();
235
+
236
+ return true;
237
+ }
238
+
239
+ /**
240
+ * Go back to previous step
241
+ * @returns {boolean} True if went back successfully
242
+ */
243
+ goToPrevious() {
244
+ const previousState = this.currentState.previous();
245
+ if (!previousState) {
246
+ console.warn('Already at first step');
247
+ return false;
248
+ }
249
+
250
+ this.currentState.exit();
251
+ this.currentState = previousState;
252
+ this.currentState.enter();
253
+
254
+ return true;
255
+ }
256
+
257
+ /**
258
+ * Go directly to a specific step
259
+ * @param {number} stepNumber - Step number to go to
260
+ */
261
+ goToStep(stepNumber) {
262
+ const stateMap = {
263
+ 1: UploadFileState,
264
+ 2: SelectColumnsState,
265
+ 3: ValidateState,
266
+ 4: ReviewResultsState,
267
+ 5: ViewFilesState
268
+ };
269
+
270
+ const StateClass = stateMap[stepNumber];
271
+ if (!StateClass) {
272
+ console.error(`Invalid step number: ${stepNumber}`);
273
+ return;
274
+ }
275
+
276
+ this.currentState.exit();
277
+ this.currentState = new StateClass(this);
278
+ this.currentState.enter();
279
+ }
280
+
281
+ // Context state checks
282
+ hasFile() {
283
+ return this.parsedData !== null;
284
+ }
285
+
286
+ hasSelectedColumns() {
287
+ return true; // In current implementation, columns are always selected
288
+ }
289
+
290
+ hasValidationResults() {
291
+ return this.validationResults !== null;
292
+ }
293
+
294
+ hasGeneratedCommand() {
295
+ return this.generatedCommand !== null;
296
+ }
297
+
298
+ // Context state setters
299
+ setParsedData(data) {
300
+ this.parsedData = data;
301
+ }
302
+
303
+ setValidationResults(results) {
304
+ this.validationResults = results;
305
+ }
306
+
307
+ setGeneratedCommand(command) {
308
+ this.generatedCommand = command;
309
+ }
310
+ }
311
+
312
+ // Export for use in other scripts
313
+ window.StepState = StepState;
314
+ window.UploadFileState = UploadFileState;
315
+ window.SelectColumnsState = SelectColumnsState;
316
+ window.ValidateState = ValidateState;
317
+ window.ReviewResultsState = ReviewResultsState;
318
+ window.ViewFilesState = ViewFilesState;
319
+ window.StepperContext = StepperContext;