diff --git a/README.md b/README.md index 1c452adb..ab7c4b58 100644 --- a/README.md +++ b/README.md @@ -132,9 +132,44 @@ Find a more complete list on [Wikipedia](https://en.wikipedia.org/wiki/List_of_p - Provide a level 2 challenge for creating an HTML report besides the Plain Text report. ## Solutions -To see solutions, switch to the [solutions branch](https://github.com/christianhujer/expensereport/tree/solutions#solutions). +**Warning** The solutions branch will be rebased and force-pushed! -**Warning** The solutions branch will be rebased! +Currently, solutions exist in the following languages: +- [Ada](expensereport-ada/) +- [Assembler](expensereport-asm-68k-masm/) (Motorola 68020, Amiga OS, MaxonASM) +- [Assembler](expensereport-asm-aarch64-linux-gasm/) (AArch64, Linux, GNU Assembler) +- [BASIC](expensereport-basic-amiga/) (Amiga BASIC, Commodore Amiga) ⇐ Quite amazing! First BASIC without line numbers! +- [C](expensereport-c/) +- [C#](expensereport-csharp/) +- [C++](expensereport-cxx/) +- [Fortran](expensereport-fortran/) +- [Go](expensereport-go/) (with BDD) +- [Groovy](expensereport-groovy-script/) +- [Haskell](expensereport-haskell/) +- [Java](expensereport-java/) (with BDD) +- [Kotlin](expensereport-kotlin/) (with BDD) +- [Pascal](expensereport-pascal/) +- [PostScript](expensereport-postscript/) +- [Python](expensereport-python/) (with BDD) +- [Rexx](expensereport-rexx/) (tested with Regina Rexx and ARexx) +- [Rust](expensereport-rust/) +- [SQL](expensereport-sql/) +- [XML/XSLT](expensereport-xslt/) + +### Solution Rationale +- Make adding the new requirement (lunch with an expense limit 2000) as easy and simple and error-free as possible. That means solving the OCP-violations of `printReport()`. +- Small ("atomic") functions. Extract until you can no longer reasonably extract further methods. +- A balance between a language independent design expressed idiomatic in the target language. + +Currently, soltuions exist in the following languages: +- [Java](expensereport-java/) +- [Kotlin](expensereport-kotlin/) +- [Rust](expensereport-rust/) + +### Solution Rationale +- Make adding the new requirement (lunch with an expense limit 2000) as easy and simple and error-free as possible. That means solving the OCP-violations of `printReport()`. +- Small ("atomic") functions. Extract until you can no longer reasonably extract further methods. +- A balance between a language independent design expressed idiomatic in the target language. ## Credits and License I first encountered the ExpenseReport example during a bootcamp at Equal Experts. diff --git a/expensereport-ada/Makefile b/expensereport-ada/Makefile index 92e1ce43..b3fd3542 100644 --- a/expensereport-ada/Makefile +++ b/expensereport-ada/Makefile @@ -1,9 +1,9 @@ SHELL:=/bin/bash - CPPFLAGS:=-W -Wall -pedantic -Werror .PHONY: all all: expensereport + diff expected.txt <(./expensereport) .PHONY: clean clean:: diff --git a/expensereport-ada/expected.txt b/expensereport-ada/expected.txt new file mode 100644 index 00000000..0ba3c514 --- /dev/null +++ b/expensereport-ada/expected.txt @@ -0,0 +1,10 @@ +Expenses: +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal expenses: 16003 +Total expenses: 16007 diff --git a/expensereport-ada/expensereport.adb b/expensereport-ada/expensereport.adb index 1befdd7a..b485a6b2 100644 --- a/expensereport-ada/expensereport.adb +++ b/expensereport-ada/expensereport.adb @@ -2,56 +2,108 @@ with Text_IO, Text_IO.Unbounded_IO, Ada.Command_Line, Ada.Strings.Unbounded, Ada use Text_IO, Text_IO.Unbounded_IO, Ada.Command_Line, Ada.Strings.Unbounded; procedure expensereport is - type ExpenseType is (Breakfast, Dinner, CarRental); + type ExpenseType is tagged record + name: Unbounded_String; + limit: Integer; + isMeal: Boolean; + end record; type Expense is tagged record eType: ExpenseType; amount: Integer; end record; + Dinner: constant ExpenseType := (To_Unbounded_String("Dinner"), 5000, true); + Breakfast: constant ExpenseType := (To_Unbounded_String("Breakfast"), 1000, true); + CarRental: constant ExpenseType := (To_Unbounded_String("Car Rental"), 2147483647, false); + Lunch: constant ExpenseType := (To_Unbounded_String("Lunch"), 2000, true); + type ExpenseList is array(Positive range <>) of Expense; - procedure printReport(expenses: in ExpenseList) is - total : Integer := 0; - mealExpenses : Integer := 0; - expenseName : Unbounded_String; - mealOverExpensesMarker : Character := ' '; + function isMeal(exp: Expense) return Boolean is begin - Put_Line("Expenses:"); + return exp.eType.isMeal; + end; + + function getName(exp: Expense) return Unbounded_String is + begin + return exp.eType.name; + end; + function isOverLimit(exp: Expense) return Boolean is + begin + return exp.amount > exp.eType.limit; + end; + + function sumMeals(expenses: in ExpenseList) return Integer is + meals : Integer := 0; + begin + meals := 0; for i in expenses'Range loop - if (expenses(i).eType = Breakfast or expenses(i).eType = Dinner) then - mealExpenses := mealExpenses + expenses(i).amount; + if (isMeal(expenses(i))) then + meals := meals + expenses(i).amount; end if; + end loop; + return meals; + end; - expenseName := To_Unbounded_String("Foo" & "Foo"); - case expenses(i).eType is - when Breakfast => - expenseName := To_Unbounded_String("Breakfast"); - when Dinner => - expenseName := To_Unbounded_String("Dinner"); - when CarRental => - expenseName := To_Unbounded_String("Car Rental"); - end case; - - if ((expenses(i).eType = Breakfast and expenses(i).amount > 1000) or (expenses(i).eType = Dinner and expenses(i).amount > 5000)) then - mealOverExpensesMarker := 'X'; - else - mealOverExpensesMarker := ' '; - end if; + function sumTotal(expenses: in ExpenseList) return Integer is + total : Integer := 0; + begin + total := 0; + for i in expenses'Range loop + total := total + expenses(i).amount; + end loop; + return total; + end; + + procedure printHeader is + begin + Put_Line("Expenses:"); + end printHeader; - Put_Line(expenseName & Ada.Characters.Latin_1.HT & Ada.Strings.Fixed.Trim(Integer'Image(expenses(i).amount), Ada.Strings.Left) & Ada.Characters.Latin_1.HT & mealOverExpensesMarker); + function overLimitMarker(exp: Expense) return Character is + begin + if (isOverLimit(exp)) then + return 'X'; + else + return ' '; + end if; + end; - total := total + expenses(i).amount; + procedure printDetail(exp: Expense) is + begin + Put_Line(getName(exp) & Ada.Characters.Latin_1.HT & Ada.Strings.Fixed.Trim(Integer'Image(exp.amount), Ada.Strings.Left) & Ada.Characters.Latin_1.HT & overLimitMarker(exp)); + end printDetail; + + procedure printDetails(expenses: in ExpenseList) is + begin + for i in expenses'Range loop + printDetail(expenses(i)); end loop; + end printDetails; - Put_Line("Meal expenses:" & Integer'Image(mealExpenses)); - Put_Line("Total expenses:" & Integer'Image(total)); + procedure printSummary(expenses: in ExpenseList) is + begin + Put_Line("Meal expenses:" & Integer'Image(sumMeals(expenses))); + Put_Line("Total expenses:" & Integer'Image(sumTotal(expenses))); + end printSummary; + + procedure printReport(expenses: in ExpenseList) is + begin + printHeader; + printDetails(expenses); + printSummary(expenses); end printReport; expenses : ExpenseList := ( - (Breakfast, 1), - (Breakfast, 1) + (Dinner, 5000), + (Dinner, 5001), + (Breakfast, 1000), + (Breakfast, 1001), + (CarRental, 4), + (Lunch, 2000), + (Lunch, 2001) ); begin printReport(expenses); diff --git a/expensereport-asm-68k-masm/ExpenseReport.s b/expensereport-asm-68k-masm/ExpenseReport.s index 29fb084b..5ce01192 100644 --- a/expensereport-asm-68k-masm/ExpenseReport.s +++ b/expensereport-asm-68k-masm/ExpenseReport.s @@ -6,10 +6,6 @@ _LVOWrite EQU -48 OS_VERSION EQU 33 -DINNER EQU 1 -BREAKFAST EQU 2 -CAR_RENTAL EQU 3 - MC68020 ; using divul.l for itoa SECTION TEXT @@ -39,8 +35,9 @@ main move.l (160,a0),d0 move.l d0,(stdout) + move.l (dosBase),a6 lea (expenses),a0 - bsr.s printReport + bsr ExpenseList_printReport .closeDos move.l (dosBase),a1 @@ -50,184 +47,214 @@ main moveq #1,d1 rts -printReport - movem.l d2/d3/d5/d6/d7/a2/a6,-(sp) +ExpenseList_printReport + movem.l a2,-(sp) + move.l a0,a2 - moveq #0,d5 ; mealExpenses - moveq #0,d6 ; totalExpenses + bsr ExpenseList_printReportHeader - move.l (dosBase),a6 + move.l a2,a0 + bsr ExpenseList_printReportDetails - move.l (stdout),d1 - move.l #expenseReport,d2 - moveq #expenseReportEnd-expenseReport,d3 - jsr (_LVOWrite,a6) + move.l a2,a0 + bsr ExpenseList_printReportSummary - move.l (stdout),d1 - move.l #newline,d2 - moveq #1,d3 - jsr (_LVOWrite,a6) + movem.l (sp)+,a2 + rts +ExpenseList_printReportHeader + lea (expenseReport),a0 + bsr printString + bra _newline + +ExpenseList_printReportDetails + movem.l a2,-(sp) + move.l a0,a2 .loop move.l (a2),d0 beq .loopDone + move.l a2,a0 + bsr Expense_printDetail + addq #8,a2 ; next expense + bra .loop +.loopDone - cmp.l #DINNER,d0 - beq.s .meal - cmp.l #BREAKFAST,d0 - beq.s .meal - bra.s .notMeal -.meal - move.l (4,a2),d1 - add.l d1,d5 -.notMeal + movem.l (sp)+,a2 + rts - lea (expenseNames),a0 - move.l (a0,d0*4),a0 - move.l a0,d2 -.countExpenseName - move.b (a0)+,d0 - bne.s .countExpenseName - move.l a0,d3 - sub.l d2,d3 - subq #1,d3 - move.l (stdout),d1 - jsr (_LVOWrite,a6) +Expense_printDetail + movem.l d2/d3/a2,-(sp) + move.l a0,a2 + bsr Expense_getName + move.l d0,a0 + bsr printString - lea (tab),a0 - move.l (stdout),d1 - move.l a0,d2 - moveq #1,d3 - jsr (_LVOWrite,a6) + bsr _tab move.l (4,a2),d0 - lea buffer,a0 - bsr itoa10 - move.l d0,d2 - move.l d2,a0 -.countAmount - move.b (a0)+,d0 - bne.s .countAmount - move.l a0,d3 - sub.l d2,d3 - subq #1,d3 - move.l (stdout),d1 - jsr (_LVOWrite,a6) + bsr printNumber - lea (tab),a0 - move.l (stdout),d1 - move.l a0,d2 - moveq #1,d3 - jsr (_LVOWrite,a6) + bsr _tab - move.l (a2),d0 - move.l (4,a2),d1 - cmp.l #DINNER,d0 - bne.s .notDinner - cmp.l #5000,d1 - bgt.s .mealOverExpense - bra.s .mealNotOverExpense -.notDinner - cmp.l #BREAKFAST,d0 - bne.s .notBreakfast - cmp.l #1000,d1 - bgt.s .mealOverExpense - bra.s .mealNotOverExpense -.notBreakfast - bra.s .mealNotOverExpense + move.l a2,a0 + bsr Expense_isOverLimit + beq.s .mealNotOverExpense .mealOverExpense lea (mealOverExpensesMarker),a0 bra.s .printMealOverExpensesMarker .mealNotOverExpense lea (mealNotOverExpensesMarker),a0 .printMealOverExpensesMarker - move.l (stdout),d1 - move.l a0,d2 - moveq #1,d3 - jsr (_LVOWrite,a6) + bsr printString + bsr _newline + movem.l (sp)+,d2/d3/a2 + rts - lea (newline),a0 - move.l (stdout),d1 - move.l a0,d2 - moveq #1,d3 - jsr (_LVOWrite,a6) +ExpenseList_printReportSummary + movem.l a3,-(sp) + move.l a0,a3 - move.l (4,a2),d1 - add.l d1,d6 + move.l a3,a0 + bsr ExpenseList_sumMeals + lea (meals),a0 + bsr .printReportSummaryLine - addq #8,a2 ; next expense + move.l a3,a0 + bsr ExpenseList_sumTotal + lea (total),a0 + bsr .printReportSummaryLine + + movem.l (sp)+,a3 + rts +; @param a0 Label to print. +; @param d0 Number to print +.printReportSummaryLine + movem.l d0,-(sp) + bsr printString + movem.l (sp)+,d0 + bsr printNumber + bra _newline + +; Calculates the sum of all expenses. +; @param a0 Expenses, terminated with an expense of type NULL +; @return d0 Sum of all expenses. +; @destroys a0,d0,d1 +ExpenseList_sumTotal + moveq #0,d0 +.loop + move.l (a0)+,d1 + beq .loopDone + add.l (a0)+,d0 bra .loop .loopDone + rts - move.l (stdout),d1 - lea (meals),a0 - move.l a0,d2 - moveq #mealsEnd-meals,d3 - jsr (_LVOWrite,a6) +; Calculates the sum of all meal expenses. +; @param a0 Expenses, terminated with an expense of type NULL +; @return d0 Sum of all expenses. +; @destroys a0,a1,d0,d1 +ExpenseList_sumMeals + movem.l d2/d3,-(a7) + move.l a0,a1 + moveq #0,d3 +.loop + move.l (a1)+,d0 + beq .loopDone + move.l d0,a0 + move.l (a1)+,d2 + bsr ExpenseType_isMeal + beq .notMeal + add.l d2,d3 +.notMeal + bra.s .loop +.loopDone + move.l d3,d0 + movem.l (a7)+,d2/d3 + rts - move.l d5,d0 - lea (buffer),a0 - bsr itoa10 - move.l d0,d2 - move.l d2,a0 -.count2 - move.b (a0)+,d0 - bne.s .count2 - move.l a0,d3 - sub.l d2,d3 - subq #1,d3 - move.l (stdout),d1 - move.l (dosBase),a6 - jsr (_LVOWrite,a6) +; Returns whether an expense is a meal. +; @param a0 Expense +; @return d0 1 if the expense is a meal, 0 otherwise. +; @return CC=Z̅ if the expense is a meal, Z otherwise. +; @destroys a0,d0 +Expense_isMeal + move.l (a0),a0 + ; fallthrough +; Returns whether an expense type is a meal. +; @param a0 ExpenseType +; @return d0 1 if the expense is a meal, 0 otherwise. +; @return CC=Z̅ if the expense is a meal, Z otherwise. +; @destroys d0 +ExpenseType_isMeal + move.l (8,a0),d0 + rts - move.l (stdout),d1 - lea (newline),a0 - move.l a0,d2 - moveq #1,d3 - jsr (_LVOWrite,a6) +; Returns whether an expense is over its limit. +; @param a0 Expense +; @return d0 1 if the expense is over its limit, 0 otherwise. +; @return CC=Z̅ if the expense is over its limit, Z otherwise. +; @destroys a0,d0 +Expense_isOverLimit + move.l (4,a0),d0 + move.l (a0),a0 + cmp.l (4,a0),d0 + bls.s .notOverLimit +.overLimit + moveq #1,d0 + rts +.notOverLimit + moveq #0,d0 + rts - move.l (stdout),d1 - lea (total),a0 - move.l a0,d2 - moveq #totalEnd-total,d3 - jsr (_LVOWrite,a6) +; Returns the name of an expense. +; @param a0 Expense +; @return d0 The name of the expense. +; @destroys a0,d0 +Expense_getName + move.l (a0),a0 + ; fallthrough +; Returns the name of an expense type. +; @param a0 ExpenseType +; @return d0 The name of the expense. +; @destroys d0 +ExpenseType_getName + move.l (a0),d0 + rts - move.l d6,d0 - lea (buffer),a0 - bsr itoa10 - move.l d0,d2 - move.l d2,a0 -.count3 +; @param a0 String to print +printString + movem.l d2/d3,-(sp) + move.l a0,d2 +.strlen move.b (a0)+,d0 - bne.s .count3 + bne.s .strlen move.l a0,d3 sub.l d2,d3 subq #1,d3 move.l (stdout),d1 - move.l (dosBase),a6 jsr (_LVOWrite,a6) + movem.l (sp)+,d2/d3 + rts - move.l (stdout),d1 - lea (newline),a0 +; @param d0 Number to print +printNumber + lea buffer,a0 + bsr itoa10 + move.l d0,a0 + bra printString + +_print1 move.l a0,d2 + move.l (stdout),d1 moveq #1,d3 - jsr (_LVOWrite,a6) - movem.l (sp)+,d2/d3/d5/d6/d7/a2/a6 - rts + jmp (_LVOWrite,a6) +_tab + lea (tab),a0 + bra _print1 +_newline + lea (newline),a0 + bra _print1 -; Returns the length of a NUL-terminated string, without the NUL byte -; @param a0 NUL-terminated string -; @return d0 Length of the NUL-terminated string -; @destroys d0,a0,a1 -strlen - move.l a0,a1 -.count - move.b (a1)+,d0 - bne.s .count - move.l a1,d0 - sub.l a0,d0 - subq #1,d0 - rts ; Converts a number into a decimal string ; @param a0 buffer in which to convert the number @@ -242,7 +269,7 @@ itoa10 add.l #'0',d1 move.b d1,(a0)+ tst.l d0 - bne.s .loop + bne.s .loop move.b #0,d1 move.b d1,(a0)+ @@ -277,16 +304,20 @@ expenseNames dc.l 0,dinner,breakfast,car_rental dinner dc.b "Dinner",0 breakfast dc.b "Breakfast",0 car_rental dc.b "Car Rental",0 +lunch dc.b "Lunch",0 tab dc.b 9,0 newline dc.b 10,0 -expenseReport dc.b "Expenses: " -expenseReportEnd dc.b 0 -meals dc.b "Meal Expenses: " -mealsEnd dc.b 0 -total dc.b "Total Expenses: " -totalEnd dc.b 0 +expenseReport dc.b "Expenses: ",0 +meals dc.b "Meal Expenses: ",0 +total dc.b "Total Expenses: ",0 mealOverExpensesMarker dc.b "X",0 mealNotOverExpensesMarker dc.b " ",0 + align.l +ExpenseTypes +DINNER dc.l dinner,5000,1 +BREAKFAST dc.l breakfast,1000,1 +CAR_RENTAL dc.l car_rental,-1,0 +LUNCH dc.l lunch,2000,1 SECTION DATA align.l @@ -296,6 +327,8 @@ expenses dc.l BREAKFAST,1000 dc.l BREAKFAST,1001 dc.l CAR_RENTAL,4 + dc.l LUNCH,2000 + dc.l LUNCH,2001 dc.l 0,0 SECTION BSS diff --git a/expensereport-asm-aarch64-linux-gasm/ExpenseReport.S b/expensereport-asm-aarch64-linux-gasm/ExpenseReport.S index 085895e7..3faa8427 100644 --- a/expensereport-asm-aarch64-linux-gasm/ExpenseReport.S +++ b/expensereport-asm-aarch64-linux-gasm/ExpenseReport.S @@ -3,9 +3,21 @@ .equ write, 64 .equ exit, 93 -.equ DINNER, 1 -.equ BREAKFAST, 2 -.equ CAR_RENTAL, 3 +.macro push reg + str \reg, [sp, #-16]! +.endm + +.macro pop reg + ldr \reg, [sp], #16 +.endm + +.macro pushp reg1 reg2 + stp \reg1, \reg2, [sp, #-16]! +.endm + +.macro popp reg1 reg2 + ldp \reg1, \reg2, [sp], #16 +.endm .text .global _start @@ -19,156 +31,175 @@ _start: svc #0 printReport: - str x30, [sp,#16]! - mov x9, x0 // x9 = current expese pointer - mov x12, xzr // x12 = meal expenses - mov x13, xzr // x13 = total expenses - - mov x0, #STDOUT - ldr x1, =header - mov x2, #headerLen - mov x8, #write - svc #0 + pushp x29, lr -.loop: - ldr x10, [x9], #8 // x10 = expense type - cmp xzr, x10 - beq .loopDone - ldr x11, [x9], #8 // x11 = expense amount - - cmp x10, #DINNER - beq .mealExpense - cmp x10, #BREAKFAST - beq .mealExpense - b .notMeal -.mealExpense: - add x12, x12, x11 -.notMeal: + mov x29, x0 + bl printHeader -.printExpenseName: - mov x0, #STDOUT - ldr x1, =expenseNames - ldr x1, [x1, x10, LSL#3] - ldr x2, =expenseNameLengths - ldr x2, [x2, x10, LSL#3] - mov x8, #write - svc #0 + mov x0, x29 + bl printDetails - mov x0, #STDOUT - ldr x1, =tab - mov x2, #tabLen - mov x8, #write - svc #0 + mov x0, x29 + bl printSummary - ldr x0, =buffer - mov x1, x11 - bl itoa10 - mov x1, x0 -.lenLoop: - ldrb w2, [x0], 1 - cbnz w2, .lenLoop - sub x2, x0, x1 - sub x2, x2, #1 - mov x0, #STDOUT - mov x8, #write - svc #0 + popp x29, lr + ret - mov x0, #STDOUT - ldr x1, =tab - mov x2, #tabLen - mov x8, #write - svc #0 +printHeader: + ldr x0, =header + b print - cmp x10, DINNER - cset x1, eq - mov x0, 5000 - cmp x11, x0 - cset x2, gt - ands x3, x2, x1 - bne .mealOverExpense - cmp x10, BREAKFAST - cset x1, eq - mov x0, 1000 - cmp x11, x0 - cset x2, gt - ands x3, x2, x1 - bne .mealOverExpense -.mealNotOverExpense: - ldr x1, =mealNotOverExpensesMarker - b .printMealOverExpensesMarker -.mealOverExpense: - ldr x1, =mealOverExpensesMarker - b .printMealOverExpensesMarker -.printMealOverExpensesMarker: - mov x2, #1 - mov x0, #STDOUT - mov x8, #write - svc #0 +printDetails: + pushp x29, lr + mov x29, x0 +.loop: + ldr x0, [x29] + cbz x0, .loopDone - mov x0, #STDOUT - ldr x1, =newline - mov x2, #newlineLen - mov x8, #write - svc #0 + mov x0, x29 + bl printDetail - add x13, x13, x11 + add x29, x29, #16 b .loop .loopDone: + popp x29, lr + ret - mov x0, #STDOUT - ldr x1, =meals - mov x2, #mealsLen - mov x8, #write - svc #0 +printDetail: + pushp x29, lr + mov x29, x0 - ldr x0, =buffer - mov x1, x12 - bl itoa10 + bl Expense_getName + bl print + + bl printTab + + mov x0, x29 + bl Expense_getAmount + bl printN + + bl printTab + + mov x0, x29 + bl Expense_getOverLimitMarker + bl print + + bl printNL + + popp x29, lr + ret + +Expense_getOverLimitMarker: + push lr + + bl Expense_isOverLimit + cbnz x0, .overLimitMarker +.notOverLimitMarker: + ldr x0, =mealNotOverExpensesMarker + b .markerDone +.overLimitMarker: + ldr x0, =mealOverExpensesMarker + b .markerDone +.markerDone: + + pop lr + ret + +printSummary: + pushp x29, lr + mov x29, x0 + + ldr x0, =meals + bl print + mov x0, x29 + bl sumMeals + bl printN + bl printNL + + ldr x0, =total + bl print + mov x0, x29 + bl sumTotal + bl printN + bl printNL + + popp x29, lr + ret + +sumMeals: + push lr + mov x3, x0 + mov x2, xzr +.sumMealsLoop: + mov x0, x3 + bl Expense_getType + cbz x0, .sumMealsLoopDone + mov x0, x3 + bl Expense_isMeal + cbz x0, .notMeal + mov x0, x3 + bl Expense_getAmount + add x2, x2, x0 +.notMeal: + add x3, x3, #16 + b .sumMealsLoop +.sumMealsLoopDone: + pop lr + mov x0, x2 + ret + +sumTotal: + push lr mov x1, x0 -.lenLoop2: - ldrb w2, [x0], 1 - cbnz w2, .lenLoop2 - sub x2, x0, x1 - sub x2, x2, #1 - mov x0, #STDOUT - mov x8, #write - svc #0 + mov x2, xzr +.sumTotalLoop: + mov x0, x1 + bl Expense_getType + cbz x0, .sumTotalLoopDone + mov x0, x1 + bl Expense_getAmount + add x2, x2, x0 + add x1, x1, #16 + b .sumTotalLoop +.sumTotalLoopDone: + pop lr + mov x0, x2 + ret - mov x0, #STDOUT - ldr x1, =newline - mov x2, #newlineLen - mov x8, #write - svc #0 +printNL: + ldr x0, =newline + b print - mov x0, #STDOUT - ldr x1, =total - mov x2, #totalLen - mov x8, #write - svc #0 +printTab: + ldr x0, =tab + b print +// Writes a number to STDOUT. +// @param x0 Number to write +printN: + push lr + mov x1, x0 ldr x0, =buffer - mov x1, x13 bl itoa10 + pop lr + b print + +// Writes a string to STDOUT. +// @param x0 String to write +print: mov x1, x0 -.lenLoop3: +.loopX: ldrb w2, [x0], 1 - cbnz w2, .lenLoop3 + cbnz w2, .loopX sub x2, x0, x1 sub x2, x2, #1 mov x0, #STDOUT mov x8, #write svc #0 - - mov x0, #STDOUT - ldr x1, =newline - mov x2, #newlineLen - mov x8, #write - svc #0 - - ldr x30, [sp], #16 ret + // Converts a number into a decimal string. // @param x0 buffer in which to convert the number. // @param x1 number to convert @@ -184,8 +215,7 @@ itoa10: add x4, x4, x5 strb w4, [x0], #1 mov x1, x3 - cmp x1, xzr - bne .itoa10_loop + cbnz x1, .itoa10_loop strb wzr, [x0], #-1 mov x1, x6 @@ -202,54 +232,92 @@ itoa10: mov x0,x6 ret +Expense_getName: + ldr x0, [x0] + ldr x0, [x0] + ret + +Expense_getType: + ldr x0, [x0] + ret + +Expense_getAmount: + ldr x0, [x0, #8] + ret + +Expense_isMeal: + push lr + bl Expense_getType + ldr x0, [x0, #16] + pop lr + ret + +Expense_isOverLimit: + push lr + mov x3, x0 + bl Expense_getType + ldr x2, [x0, #8] + mov x0, x3 + bl Expense_getAmount + cmp x0, x2 + cset x0, hi + pop lr + ret + .data header: - .ascii "Expense Report\n" - headerLen = . - header + .asciz "Expense Report\n" meals: - .ascii "Meal Expenses: " - mealsLen = . - meals + .asciz "Meal Expenses: " total: - .ascii "Total Expenses: " - totalLen = . - total + .asciz "Total Expenses: " tab: - .ascii "\t" - tabLen = . - tab + .asciz "\t" newline: - .ascii "\n" - newlineLen = . - newline + .asciz "\n" mealOverExpensesMarker: - .ascii "X" + .asciz "X" mealNotOverExpensesMarker: - .ascii " " + .asciz " " + +dinnerName: + .asciz "Dinner" +breakfastName: + .asciz "Breakfast" +carRentalName: + .asciz "Car Rental" +lunchName: + .asciz "Lunch" dinner: - .ascii "Dinner" - dinnerLen = . - dinner + .quad dinnerName + .quad 5000 + .quad 1 + breakfast: - .ascii "Breakfast" - breakfastLen = . - breakfast -carRental: - .ascii "Car Rental" - carRentalLen = . - carRental + .quad breakfastName + .quad 1000 + .quad 1 -expenseNames: - .quad 0 - .quad dinner - .quad breakfast - .quad carRental -expenseNameLengths: +carRental: + .quad carRentalName + .quad 18446744073709551615 .quad 0 - .quad dinnerLen - .quad breakfastLen - .quad carRentalLen +lunch: + .quad lunchName + .quad 2000 + .quad 1 + +.data expenses: - .quad DINNER, 5000 - .quad DINNER, 5001 - .quad BREAKFAST, 1000 - .quad BREAKFAST, 1001 - .quad CAR_RENTAL, 4 + .quad dinner, 5000 + .quad dinner, 5001 + .quad breakfast, 1000 + .quad breakfast, 1001 + .quad carRental, 4 + .quad lunch, 2000 + .quad lunch, 2001 .quad 0, 0 .bss diff --git a/expensereport-asm-aarch64-linux-gasm/Makefile b/expensereport-asm-aarch64-linux-gasm/Makefile index bfa5f68f..ca0a4ce3 100644 --- a/expensereport-asm-aarch64-linux-gasm/Makefile +++ b/expensereport-asm-aarch64-linux-gasm/Makefile @@ -5,7 +5,11 @@ LD:=aarch64-linux-gnu-ld ASFLAGS:=-g .PHONY: all -all: ExpenseReport +all: test + +.PHONY: test +test: ExpenseReport + diff expected.txt <(./ExpenseReport) ExpenseReport: ExpenseReport.o $(LD) -o $@ $^ diff --git a/expensereport-asm-aarch64-linux-gasm/expected.txt b/expensereport-asm-aarch64-linux-gasm/expected.txt new file mode 100644 index 00000000..87cf5971 --- /dev/null +++ b/expensereport-asm-aarch64-linux-gasm/expected.txt @@ -0,0 +1,10 @@ +Expense Report +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal Expenses: 16003 +Total Expenses: 16007 diff --git a/expensereport-basic-amiga/ExpenseReport.bas b/expensereport-basic-amiga/ExpenseReport.bas index e819e9a4..f4dc5376 100644 --- a/expensereport-basic-amiga/ExpenseReport.bas +++ b/expensereport-basic-amiga/ExpenseReport.bas @@ -1,38 +1,60 @@ DIM SHARED DINNER, BREAKFAST, CARRENTAL -DINNER = 0: BREAKFAST = 1: CARRENTAL = 2 +DINNER = 0: BREAKFAST = 1: CARRENTAL = 2: LUNCH = 3 -DIM type(4), amount(4) -type(0) = DINNER: amount(0) = 5000 -type(1) = DINNER: amount(1) = 5001 -type(2) = BREAKFAST: amount(2) = 1000 -type(3) = BREAKFAST: amount(3) = 1001 -type(4) = CARRENTAL: amount(4) = 4 -CALL PrintExpenses(type(), amount()) -END +DIM SHARED names$(3), limits(3), isMeal(3) +names$(0) = "Dinner": limits(0) = 5000: isMeal(0) = 1 +names$(1) = "Breakfast": limits(1) = 1000: isMeal(1) = 1 +names$(2) = "Car Rental": limits(2) = 65535: isMeal(2) = 0 +names$(3) = "Lunch": limits(3) = 2000: isMeal(3) = 1 + +DEF FNisOverLimit(t, a) = a > limits(t) + +DIM SHARED mealOverExpensesMarker$(1) +mealOverExpensesMarker$(0) = " " +mealOverExpensesMarker$(1) = "X" +DEF FNmealOverExpensesMarker$(t, a) = mealOverExpensesMarker$(-FNisOverLimit(t, a)) +DEF FNdetail$(t, a) = names$(t) + CHR$(9) + STR$(a) + CHR$(9) + FNmealOverExpensesMarker$(t, a) + +DEF FNHeader$(n) = "Expenses: " + DATE$ SUB PrintExpenses(type(), amount()) STATIC + CALL PrintHeader + CALL PrintDetails(type(), amount()) + CALL PrintSummary(type(), amount()) +END SUB + +SUB PrintHeader STATIC + PRINT FNHeader$(0) +END SUB + +SUB PrintDetails(type(), amount()) STATIC + FOR i=LBOUND(type) TO UBOUND(type) + PRINT FNdetail$(type(i), amount(i)) + NEXT i +END SUB + +SUB PrintSummary(type(), amount()) STATIC total = 0 mealExpenses = 0 - PRINT "Expenses: " DATE$ - FOR i=LBOUND(type) TO UBOUND(type) - IF type(i) = DINNER OR type(i) = BREAKFAST THEN + IF isMeal(type(i)) THEN mealExpenses = mealExpenses + amount(i) END IF - - expenseName$ = "" - IF type(i) = DINNER THEN expenseName$ = "Dinner" - IF type(i) = BREAKFAST THEN expenseName$ = "Breakfast" - IF type(i) = CARRENTAL THEN expenseName$ = "Car Rental" - - IF type(i) = DINNER AND amount(i) > 5000 OR type(i) = BREAKFAST AND amount(i) > 1000 THEN mealOverExpensesMarker$ = "X" ELSE mealOverExpensesMarker$ = " " - - PRINT expenseName$ CHR$(9) STR$(amount(i)) CHR$(9) mealOverExpensesMarker$ - total = total + amount(i) NEXT i - PRINT "Meal Expenses:" STR$(mealExpenses) + PRINT "Meal Expenses:" STR$(mealExpenses) PRINT "Total Expenses:" STR$(total) END SUB + +DIM type(6), amount(6) +type(0) = DINNER: amount(0) = 5000 +type(1) = DINNER: amount(1) = 5001 +type(2) = BREAKFAST: amount(2) = 1000 +type(3) = BREAKFAST: amount(3) = 1001 +type(4) = CARRENTAL: amount(4) = 4 +type(5) = LUNCH: amount(5) = 2000 +type(6) = LUNCH: amount(6) = 2001 +CALL PrintExpenses(type(), amount()) +END diff --git a/expensereport-c/.gitignore b/expensereport-c/.gitignore index 67e7ed8a..5bb7e13b 100644 --- a/expensereport-c/.gitignore +++ b/expensereport-c/.gitignore @@ -1 +1,3 @@ *.[adios] +ExpenseReportTest +actual.txt diff --git a/expensereport-c/Expense.c b/expensereport-c/Expense.c new file mode 100644 index 00000000..ccaf699e --- /dev/null +++ b/expensereport-c/Expense.c @@ -0,0 +1,13 @@ +#include "Expense.h" + +const char *Expense_getName(const struct Expense *expense) { + return expense->type->name; +} + +bool Expense_isOverLimit(const struct Expense *expense) { + return expense->amount > expense->type->limit; +} + +bool Expense_isMeal(const struct Expense *expense) { + return expense->type->isMeal; +} diff --git a/expensereport-c/Expense.h b/expensereport-c/Expense.h new file mode 100644 index 00000000..69ba679d --- /dev/null +++ b/expensereport-c/Expense.h @@ -0,0 +1,15 @@ +#ifndef EXPENSE_H +#define EXPENSE_H + +#include "ExpenseType.h" + +struct Expense { + const struct ExpenseType *type; + int amount; +}; + +extern const char *Expense_getName(const struct Expense *expense); +extern bool Expense_isOverLimit(const struct Expense *expense); +extern bool Expense_isMeal(const struct Expense *expense); + +#endif diff --git a/expensereport-c/ExpenseReport.c b/expensereport-c/ExpenseReport.c index 748c9f56..d69d19f9 100644 --- a/expensereport-c/ExpenseReport.c +++ b/expensereport-c/ExpenseReport.c @@ -1,41 +1,50 @@ #include "ExpenseReport.h" + +#include +#include +#include #include #include -void printReport(struct Expense expenses[], size_t numExpenses) { - int total = 0; +int sumMealExpenses(const struct Expense expenses[], size_t numExpenses) { int mealExpenses = 0; + for (size_t i = 0; i < numExpenses; i++) + if (Expense_isMeal(&expenses[i])) + mealExpenses += expenses[i].amount; + return mealExpenses; +} +int sumTotal(const struct Expense expenses[], size_t numExpenses) { + int total = 0; + for (size_t i = 0; i < numExpenses; i++) + total += expenses[i].amount; + return total; +} + +void printReportHeader() { time_t now; if (time(&now) == -1) return; - printf("Expenses %s", ctime(&now)); +} - for (size_t i = 0; i < numExpenses; i++) { - if (expenses[i].type == DINNER || expenses[i].type == BREAKFAST) { - mealExpenses += expenses[i].amount; - } - - char *expenseName; - switch (expenses[i].type) { - case DINNER: - expenseName = "Dinner"; - break; - case BREAKFAST: - expenseName = "Breakfast"; - break; - case CAR_RENTAL: - expenseName = "Car Rental"; - break; - } - - char *mealOverExpensesMarker = ((expenses[i].type == DINNER && expenses[i].amount > 5000) || (expenses[i].type == BREAKFAST && expenses[i].amount > 1000)) ? "X" : " "; - - printf("%s\t%d\t%s\n", expenseName, expenses[i].amount, mealOverExpensesMarker); - total += expenses[i].amount; - } +void printReportDetail(const struct Expense *expense) { + char *expenseOverLimitMarker = Expense_isOverLimit(expense) ? "X" : " "; + printf("%s\t%d\t%s\n", Expense_getName(expense), expense->amount, expenseOverLimitMarker); +} - printf("Meal expenses: %d\n", mealExpenses); - printf("Total expenses: %d\n", total); +void printReportDetails(struct Expense expenses[], size_t numExpenses) { + for (size_t i = 0; i < numExpenses; i++) + printReportDetail(&expenses[i]); +} + +void printReportSummary(struct Expense expenses[], size_t numExpenses) { + printf("Meal expenses: %d\n", sumMealExpenses(expenses, numExpenses)); + printf("Total expenses: %d\n", sumTotal(expenses, numExpenses)); +} + +void printReport(struct Expense expenses[], size_t numExpenses) { + printReportHeader(); + printReportDetails(expenses, numExpenses); + printReportSummary(expenses, numExpenses); } diff --git a/expensereport-c/ExpenseReport.h b/expensereport-c/ExpenseReport.h index 428a67c0..8a0aa56e 100644 --- a/expensereport-c/ExpenseReport.h +++ b/expensereport-c/ExpenseReport.h @@ -3,17 +3,11 @@ #include -enum Type { - DINNER, - BREAKFAST, - CAR_RENTAL -}; - -struct Expense { - enum Type type; - int amount; -}; +#include "Expense.h" extern void printReport(struct Expense expenses[], size_t numExpenses); +extern int sumMealExpenses(const struct Expense expenses[], size_t numExpenses); +extern int sumTotal(const struct Expense expenses[], size_t numExpenses); + #endif diff --git a/expensereport-c/ExpenseReportTest.c b/expensereport-c/ExpenseReportTest.c new file mode 100644 index 00000000..3153e7a4 --- /dev/null +++ b/expensereport-c/ExpenseReportTest.c @@ -0,0 +1,23 @@ +#include +#include + +#include "ExpenseReport.h" + +int main(void) { + struct Expense expenses[] = { + { DINNER, 1 }, + { BREAKFAST, 2 }, + { CAR_RENTAL, 4 }, + { LUNCH, 8 }, + { DINNER, 5000 }, + { DINNER, 5001 }, + { BREAKFAST, 1000 }, + { BREAKFAST, 1001 }, + { LUNCH, 2000 }, + { LUNCH, 2001 }, + }; + assert(sumMealExpenses(expenses, sizeof(expenses) / sizeof(expenses[0])) == 16014); + assert(sumTotal(expenses, sizeof(expenses) / sizeof(expenses[0])) == 16018); + printReport(expenses, sizeof(expenses) / sizeof(expenses[0])); + return EXIT_SUCCESS; +} diff --git a/expensereport-c/ExpenseType.c b/expensereport-c/ExpenseType.c new file mode 100644 index 00000000..05415de2 --- /dev/null +++ b/expensereport-c/ExpenseType.c @@ -0,0 +1,9 @@ +#include "ExpenseType.h" + +#include + +const struct ExpenseType + *const DINNER = &(const struct ExpenseType) { "Dinner", 5000, true }, + *const BREAKFAST = &(const struct ExpenseType) { "Breakfast", 1000, true }, + *const CAR_RENTAL = &(const struct ExpenseType) { "Car Rental", INT_MAX, false }, + *const LUNCH = &(const struct ExpenseType) { "Lunch", 2000, true }; diff --git a/expensereport-c/ExpenseType.h b/expensereport-c/ExpenseType.h new file mode 100644 index 00000000..cb6fd7fe --- /dev/null +++ b/expensereport-c/ExpenseType.h @@ -0,0 +1,18 @@ +#ifndef EXPENSETYPE_H +#define EXPENSETYPE_H + +#include + +struct ExpenseType { + const char *const name; + const int limit; + const bool isMeal; +}; + +extern const struct ExpenseType + *const DINNER, + *const BREAKFAST, + *const CAR_RENTAL, + *const LUNCH; + +#endif diff --git a/expensereport-c/Makefile b/expensereport-c/Makefile index bc4e261d..292d63c2 100644 --- a/expensereport-c/Makefile +++ b/expensereport-c/Makefile @@ -1,11 +1,18 @@ CPPFLAGS:=-MMD -CFLAGS:=-W -Wall -pedantic -Werror +CFLAGS:=-W -Wall -pedantic -Werror -O3 .PHONY: all -all: ExpenseReport.o +all: test + +.PHONY: test +test: ExpenseReportTest + faketime -f '2022-05-20 10:24:09' ./ExpenseReportTest >actual.txt + diff expected.txt actual.txt + +ExpenseReportTest: ExpenseReportTest.o ExpenseReport.o ExpenseType.o Expense.o .PHONY: clean clean:: - $(RM) *.[adios] + $(RM) *.[adios] ExpenseReportTest -include *.d diff --git a/expensereport-c/expected.txt b/expensereport-c/expected.txt new file mode 100644 index 00000000..ab136a6b --- /dev/null +++ b/expensereport-c/expected.txt @@ -0,0 +1,13 @@ +Expenses Fri May 20 10:24:09 2022 +Dinner 1 +Breakfast 2 +Car Rental 4 +Lunch 8 +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Lunch 2000 +Lunch 2001 X +Meal expenses: 16014 +Total expenses: 16018 diff --git a/expensereport-csharp/Makefile b/expensereport-csharp/Makefile index 5fbb9141..8dfeff0c 100644 --- a/expensereport-csharp/Makefile +++ b/expensereport-csharp/Makefile @@ -1,3 +1,4 @@ .PHONY: all +## Build and test the project. all: - dotnet build + dotnet test diff --git a/expensereport-csharp/expensereport-csharp/Class1.cs b/expensereport-csharp/expensereport-csharp/Class1.cs index d668147d..e1b5b2fa 100644 --- a/expensereport-csharp/expensereport-csharp/Class1.cs +++ b/expensereport-csharp/expensereport-csharp/Class1.cs @@ -1,62 +1,71 @@ using System; using System.Collections.Generic; +using System.Linq; -namespace expensereport_csharp +namespace expensereport_csharp; + +public class ExpenseType { - public enum ExpenseType - { - DINNER, BREAKFAST, CAR_RENTAL - } + public readonly string Name; + public readonly int Limit; + public readonly bool IsMeal; - public class Expense + public static readonly ExpenseType Dinner = new("Dinner", 5000, true); + public static readonly ExpenseType Breakfast = new("Breakfast", 1000, true); + public static readonly ExpenseType CarRental = new("Car Rental", int.MaxValue, false); + public static readonly ExpenseType Lunch = new("Lunch", 2000, true); + + private ExpenseType(string name, int limit, bool isMeal) { - public ExpenseType type; - public int amount; + Name = name; + Limit = limit; + IsMeal = isMeal; } +} - public class ExpenseReport +public class Expense +{ + private readonly ExpenseType _type; + public readonly int Amount; + + public string Name => _type.Name; + public bool IsOverLimit => Amount > _type.Limit; + public bool IsMeal => _type.IsMeal; + + public Expense(ExpenseType type, int amount) { - public void PrintReport(List expenses) - { - int total = 0; - int mealExpenses = 0; - - Console.WriteLine("Expenses " + DateTime.Now); - - foreach (Expense expense in expenses) - { - if (expense.type == ExpenseType.DINNER || expense.type == ExpenseType.BREAKFAST) - { - mealExpenses += expense.amount; - } - - String expenseName = ""; - switch (expense.type) - { - case ExpenseType.DINNER: - expenseName = "Dinner"; - break; - case ExpenseType.BREAKFAST: - expenseName = "Breakfast"; - break; - case ExpenseType.CAR_RENTAL: - expenseName = "Car Rental"; - break; - } - - String mealOverExpensesMarker = - expense.type == ExpenseType.DINNER && expense.amount > 5000 || - expense.type == ExpenseType.BREAKFAST && expense.amount > 1000 - ? "X" - : " "; - - Console.WriteLine(expenseName + "\t" + expense.amount + "\t" + mealOverExpensesMarker); - - total += expense.amount; - } - - Console.WriteLine("Meal expenses: " + mealExpenses); - Console.WriteLine("Total expenses: " + total); - } + _type = type; + Amount = amount; } +} + +public class ExpenseReport +{ + public void PrintReport(List expenses) => + Console.Write(GenerateReport(expenses, DateTime.Now)); + + public static string GenerateReport(List expenses, DateTime now) => + Header(now) + Details(expenses) + Summary(expenses); + + private static string Header(DateTime now) => "Expenses: " + now + "\n"; + + private static string Details(List expenses) => + expenses.Aggregate("", (current, expense) => current + Detail(expense)); + + private static string Detail(Expense expense) => + $"{expense.Name}\t{expense.Amount}\t{OverLimitMarker(expense)}\n"; + + private static string OverLimitMarker(Expense expense) => + expense.IsOverLimit ? "X" : " "; + + private static string Summary(List expenses) => + $@"Meal expenses: {SumMeals(expenses)} +Total expenses: {SumTotal(expenses)} +"; + + private static int SumMeals(List expenses) => + expenses.Where(expense => expense.IsMeal).Sum(expense => expense.Amount); + + private static int SumTotal(List expenses) => + expenses.Sum(expense => expense.Amount); } \ No newline at end of file diff --git a/expensereport-csharp/expensereport-csharp/expensereport-csharp.csproj b/expensereport-csharp/expensereport-csharp/expensereport-csharp.csproj index bbca9dbd..54adb478 100644 --- a/expensereport-csharp/expensereport-csharp/expensereport-csharp.csproj +++ b/expensereport-csharp/expensereport-csharp/expensereport-csharp.csproj @@ -1,7 +1,7 @@ - netcoreapp2.2 + netcoreapp6.0 expensereport_csharp diff --git a/expensereport-csharp/expensereport-ut/UnitTest1.cs b/expensereport-csharp/expensereport-ut/UnitTest1.cs index 794c4081..07d669fd 100644 --- a/expensereport-csharp/expensereport-ut/UnitTest1.cs +++ b/expensereport-csharp/expensereport-ut/UnitTest1.cs @@ -1,18 +1,36 @@ +using System; +using System.Collections.Generic; +using expensereport_csharp; using NUnit.Framework; -namespace Tests +namespace Tests; + +public class Tests { - public class Tests + [Test] + public void Test1() { - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } + var expenses = new List { + new Expense(ExpenseType.Dinner, 5000), + new Expense(ExpenseType.Dinner, 5001), + new Expense(ExpenseType.Breakfast, 1000), + new Expense(ExpenseType.Breakfast, 1001), + new Expense(ExpenseType.CarRental, 4), + new Expense(ExpenseType.Lunch, 2000), + new Expense(ExpenseType.Lunch, 2001), + }; + var actual = ExpenseReport.GenerateReport(expenses, DateTime.Parse("2022-06-02T15:15:00")); + var expected = @"Expenses: 02/06/2022 3:15:00 pm +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal expenses: 16003 +Total expenses: 16007 +"; + Assert.AreEqual(expected, actual); } } \ No newline at end of file diff --git a/expensereport-csharp/expensereport-ut/expensereport-ut.csproj b/expensereport-csharp/expensereport-ut/expensereport-ut.csproj index 89b9b0d1..f6bfef64 100644 --- a/expensereport-csharp/expensereport-ut/expensereport-ut.csproj +++ b/expensereport-csharp/expensereport-ut/expensereport-ut.csproj @@ -1,15 +1,19 @@ - netcoreapp2.2 + netcoreapp6.0 false - - - + + + + + + + diff --git a/expensereport-csharp/global.json b/expensereport-csharp/global.json new file mode 100644 index 00000000..9e5e1fd1 --- /dev/null +++ b/expensereport-csharp/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/expensereport-cxx/.gitignore b/expensereport-cxx/.gitignore index 67e7ed8a..5bb7e13b 100644 --- a/expensereport-cxx/.gitignore +++ b/expensereport-cxx/.gitignore @@ -1 +1,3 @@ *.[adios] +ExpenseReportTest +actual.txt diff --git a/expensereport-cxx/Expense.cpp b/expensereport-cxx/Expense.cpp new file mode 100644 index 00000000..abe667b7 --- /dev/null +++ b/expensereport-cxx/Expense.cpp @@ -0,0 +1,16 @@ +#include "Expense.h" + +Expense::Expense(const ExpenseType& type, const int amount) : type(type), amount(amount) { +} + +bool Expense::isOverLimit() const { + return amount > type.limit; +} + +bool Expense::isMeal() const { + return type.isMeal; +} + +std::string Expense::name() const { + return type.name; +} diff --git a/expensereport-cxx/Expense.h b/expensereport-cxx/Expense.h new file mode 100644 index 00000000..7bbb3f5b --- /dev/null +++ b/expensereport-cxx/Expense.h @@ -0,0 +1,17 @@ +#ifndef EXPENSE_H +#define EXPENSE_H + +#include "ExpenseType.h" + +class Expense +{ +public: + const ExpenseType& type; + const int amount; + bool isOverLimit() const; + bool isMeal() const; + std::string name() const; + Expense(const ExpenseType& type, const int amount); +}; + +#endif diff --git a/expensereport-cxx/ExpenseReport.cpp b/expensereport-cxx/ExpenseReport.cpp index bd055059..a1148b25 100644 --- a/expensereport-cxx/ExpenseReport.cpp +++ b/expensereport-cxx/ExpenseReport.cpp @@ -1,43 +1,52 @@ #include +#include #include #include +#include +#include +#include +#include +#include +#include #include "ExpenseReport.h" using namespace std; -void printReport(list expenses) -{ - int total = 0; - int mealExpenses = 0; +template int sumTotal(InputIterator& expenses) { + return accumulate(begin(expenses), end(expenses), 0, [](int acc, const Expense &expense) { return acc + expense.amount; }); +} + +int sumMeals(const list& expenses) { + auto meals = expenses | views::filter(&Expense::isMeal); + return sumTotal(meals); +} +void printHeader(ostream& cout) { auto now = chrono::system_clock::to_time_t(chrono::system_clock::now()); cout << "Expenses " << ctime(&now); +} + +void printDetail(ostream& cout, const Expense& expense) { + string mealOverExpensesMarker = expense.isOverLimit() ? "X" : " "; + cout << expense.name() << '\t' << expense.amount << '\t' << mealOverExpensesMarker << endl; +} + +void printDetails(ostream& cout, const list& expenses) { + for (auto const &expense : expenses) + printDetail(cout, expense); +} + +void printSummary(ostream& cout, const list& expenses) { + cout << "Meal expenses: " << sumMeals(expenses) << endl; + cout << "Total expenses: " << sumTotal(expenses) << endl; +} + +void printReport(ostream& cout, const list& expenses) { + printHeader(cout); + printDetails(cout, expenses); + printSummary(cout, expenses); +} - for (list::iterator expense = expenses.begin(); expense != expenses.end(); ++expense) { - if (expense->type == BREAKFAST || expense->type == DINNER) { - mealExpenses += expense->amount; - } - - string expenseName = ""; - switch (expense->type) { - case DINNER: - expenseName = "Dinner"; - break; - case BREAKFAST: - expenseName = "Breakfast"; - break; - case CAR_RENTAL: - expenseName = "Car Rental"; - break; - } - - string mealOverExpensesMarker = (expense->type == DINNER && expense->amount > 5000) || (expense->type == BREAKFAST && expense->amount > 1000) ? "X" : " "; - - cout << expenseName << '\t' << expense->amount << '\t' << mealOverExpensesMarker << '\n'; - - total += expense->amount; - } - - cout << "Meal expenses: " << mealExpenses << '\n'; - cout << "Total expenses: " << total << '\n'; +void printReport(const list& expenses) { + printReport(cout, expenses); } diff --git a/expensereport-cxx/ExpenseReport.h b/expensereport-cxx/ExpenseReport.h index 98687cb8..e12d987a 100644 --- a/expensereport-cxx/ExpenseReport.h +++ b/expensereport-cxx/ExpenseReport.h @@ -2,21 +2,13 @@ #define EXPENSEREPORT_H #include +#include +#include -using namespace std; +#include "ExpenseType.h" +#include "Expense.h" -enum Type -{ - BREAKFAST, DINNER, CAR_RENTAL -}; - -class Expense -{ - public: - Type type; - int amount; -}; - -extern void printReport(list expenses); +extern void printReport(const std::list& expenses); +extern void printReport(std::ostream& out, const std::list& expenses); #endif diff --git a/expensereport-cxx/ExpenseReportTest.cpp b/expensereport-cxx/ExpenseReportTest.cpp new file mode 100644 index 00000000..f66a73bc --- /dev/null +++ b/expensereport-cxx/ExpenseReportTest.cpp @@ -0,0 +1,23 @@ +#include "ExpenseReport.h" +#include +#include +#include +#include + +int main(void) { + std::list expenses = { + Expense(ExpenseType::DINNER, 1), + Expense(ExpenseType::BREAKFAST, 2), + Expense(ExpenseType::CAR_RENTAL, 4), + Expense(ExpenseType::LUNCH, 8), + Expense(ExpenseType::DINNER, 5000), + Expense(ExpenseType::DINNER, 5001), + Expense(ExpenseType::BREAKFAST, 1000), + Expense(ExpenseType::BREAKFAST, 1001), + Expense(ExpenseType::LUNCH, 2000), + Expense(ExpenseType::LUNCH, 2001), + }; + printReport(std::cout, expenses); + + assert(&ExpenseType::DINNER == &expenses.front().type); +} diff --git a/expensereport-cxx/ExpenseType.cpp b/expensereport-cxx/ExpenseType.cpp new file mode 100644 index 00000000..ff18e74c --- /dev/null +++ b/expensereport-cxx/ExpenseType.cpp @@ -0,0 +1,10 @@ +#include "ExpenseType.h" + +#include + +const ExpenseType& ExpenseType::BREAKFAST = ExpenseType("Breakfast", 1000, true); +const ExpenseType& ExpenseType::DINNER = ExpenseType("Dinner", 5000, true); +const ExpenseType& ExpenseType::CAR_RENTAL = ExpenseType("Car Rental", INT_MAX, false); +const ExpenseType& ExpenseType::LUNCH = ExpenseType("Lunch", 2000, true); + +ExpenseType::ExpenseType(const std::string& name, const int limit, const bool isMeal) : name(name), limit(limit), isMeal(isMeal) {} diff --git a/expensereport-cxx/ExpenseType.h b/expensereport-cxx/ExpenseType.h new file mode 100644 index 00000000..e7a233b0 --- /dev/null +++ b/expensereport-cxx/ExpenseType.h @@ -0,0 +1,19 @@ +#ifndef EXPENSETYPE_H +#define EXPENSETYPE_H + +#include + +class ExpenseType { +public: + const std::string name; + const int limit; + const bool isMeal; + static const ExpenseType& BREAKFAST; + static const ExpenseType& DINNER; + static const ExpenseType& CAR_RENTAL; + static const ExpenseType& LUNCH; +private: + ExpenseType(const std::string& name, const int limit, const bool isMeal); +}; + +#endif diff --git a/expensereport-cxx/Makefile b/expensereport-cxx/Makefile index dd71eaad..8b1c2375 100644 --- a/expensereport-cxx/Makefile +++ b/expensereport-cxx/Makefile @@ -1,11 +1,19 @@ CPPFLAGS:=-MMD -CXXFLAGS:=-W -Wall -pedantic -Werror +CXXFLAGS:=-W -Wall -pedantic -Werror -std=c++20 -O3 +LDLIBS:=-lstdc++ .PHONY: all -all: ExpenseReport.o +all: test + +.PHONY: test +test: ExpenseReportTest + faketime -f '2022-05-10 13:40:55' ./ExpenseReportTest >actual.txt + diff expected.txt actual.txt + +ExpenseReportTest: ExpenseReportTest.o ExpenseReport.o ExpenseType.o Expense.o .PHONY: clean clean:: - $(RM) *.[adios] + $(RM) *.[adios] ExpenseReportTest -include *.d diff --git a/expensereport-cxx/expected.txt b/expensereport-cxx/expected.txt new file mode 100644 index 00000000..6e07aecb --- /dev/null +++ b/expensereport-cxx/expected.txt @@ -0,0 +1,13 @@ +Expenses Tue May 10 13:40:55 2022 +Dinner 1 +Breakfast 2 +Car Rental 4 +Lunch 8 +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Lunch 2000 +Lunch 2001 X +Meal expenses: 16014 +Total expenses: 16018 diff --git a/expensereport-fortran/.gitignore b/expensereport-fortran/.gitignore index fff6bc1d..4765edd1 100644 --- a/expensereport-fortran/.gitignore +++ b/expensereport-fortran/.gitignore @@ -1,2 +1,3 @@ ExpenseReport *.mod +actual.txt diff --git a/expensereport-fortran/ExpenseReport.f b/expensereport-fortran/ExpenseReport.f index 6385a5df..cbee4d8c 100644 --- a/expensereport-fortran/ExpenseReport.f +++ b/expensereport-fortran/ExpenseReport.f @@ -1,45 +1,98 @@ MODULE ExpenseReport IMPLICIT NONE - ENUM, BIND(C) - ENUMERATOR :: BREAKFAST, DINNER, CAR_RENTAL - ENDENUM + TYPE :: expenseType + CHARACTER(LEN = 20) :: name + INTEGER :: limit + LOGICAL :: isMeal + END TYPE + + TYPE(expenseType) :: DINNER = expenseType("Dinner", 5000, .TRUE.) + TYPE(expenseType) :: BREAKFAST = expenseType("Breakfast", 1000, .TRUE.) + TYPE(expenseType) :: CAR_RENTAL = expenseType("Car Rental", huge(0), .FALSE.) + TYPE(expenseType) :: LUNCH = expenseType("Lunch", 2000, .TRUE.) - TYPE :: expense - INTEGER :: type + TYPE, PUBLIC :: expense + TYPE(expenseType) :: type INTEGER :: amount + CONTAINS + PROCEDURE :: isMeal => expense_isMeal + PROCEDURE :: getName => expense_getName + PROCEDURE :: isOverLimit => expense_isOverLimit END TYPE - CONTAINS - SUBROUTINE printReport(expenses) + CONTAINS + FUNCTION expense_isMeal(this) RESULT(meal) + CLASS(expense), INTENT(IN) :: this + LOGICAL :: meal + meal = this%type%isMeal + END FUNCTION expense_isMeal + + FUNCTION expense_getName(this) RESULT(expenseName) + CLASS(expense), INTENT(IN) :: this + CHARACTER(LEN = 20) :: expenseName + expenseName = this%type%name + END FUNCTION expense_getName + + FUNCTION expense_isOverLimit(this) RESULT(overLimit) + CLASS(expense), INTENT(IN) :: this + LOGICAL :: overLimit + overLimit = this%amount > this%type%limit + END FUNCTION expense_isOverLimit + + FUNCTION sumTotal(expenses) RESULT(total) TYPE(expense), DIMENSION (:), ALLOCATABLE :: expenses - INTEGER :: total = 0 - INTEGER :: mealExpenses = 0 + INTEGER :: total INTEGER :: i - CHARACTER(LEN = 20) :: expenseName - CHARACTER(LEN = 1) :: mealOverExpensesMarker - PRINT "(a)", 'Expenses' + total = 0 DO i = LBOUND(expenses, 1), UBOUND(expenses, 1) - IF (expenses(i)%type == BREAKFAST .OR. expenses(i)%type == DINNER) & - mealExpenses = mealExpenses + expenses(i)%amount - SELECT CASE (expenses(i)%type) - CASE (DINNER) - expenseName = "Dinner" - CASE (BREAKFAST) - expenseName = "Breakfast" - CASE (CAR_RENTAL) - expenseName = "Car Rental" - END SELECT - IF (expenses(i)%type == BREAKFAST .AND. expenses(i)%amount > 1000 .OR. & - expenses(i)%type == DINNER .AND. expenses(i)%amount > 5000) THEN - mealOverExpensesMarker = "X" - ELSE - mealOverExpensesMarker = " " - END IF total = total + expenses(i)%amount - PRINT "(a,1x,i10,1x,a)", expenseName, expenses(i)%amount, mealOverExpensesMarker END DO - PRINT "(a,i10)", 'Meal expenses: ', mealExpenses - PRINT "(a,i10)", 'Total: ', total + END FUNCTION sumTotal + + FUNCTION sumMeals(expenses) RESULT(meals) + TYPE(expense), DIMENSION (:), ALLOCATABLE :: expenses + INTEGER :: meals + INTEGER :: i + meals = 0 + DO i = LBOUND(expenses, 1), UBOUND(expenses, 1) + IF (expenses(i)%isMeal()) meals = meals + expenses(i)%amount + END DO + END FUNCTION sumMeals + + SUBROUTINE printHeader() + PRINT "(a)", 'Expenses' + END SUBROUTINE + + SUBROUTINE printDetails(expenses) + TYPE(expense), DIMENSION (:), ALLOCATABLE :: expenses + INTEGER :: i + DO i = LBOUND(expenses, 1), UBOUND(expenses, 1) + CALL printDetail(expenses(i)) + END DO + END SUBROUTINE + + SUBROUTINE printDetail(exp) + TYPE(expense) :: exp + CHARACTER(LEN = 1) :: mealOverExpensesMarker + IF (exp%isOverLimit()) THEN + mealOverExpensesMarker = "X" + ELSE + mealOverExpensesMarker = " " + END IF + PRINT "(a,1x,i10,1x,a)", exp%getName(), exp%amount, mealOverExpensesMarker + END SUBROUTINE + + SUBROUTINE printSummary(expenses) + TYPE(expense), DIMENSION (:), ALLOCATABLE :: expenses + PRINT "(a,i10)", 'Meal expenses: ', sumMeals(expenses) + PRINT "(a,i10)", 'Total: ', sumTotal(expenses) + END SUBROUTINE + + SUBROUTINE printReport(expenses) + TYPE(expense), DIMENSION (:), ALLOCATABLE :: expenses + CALL printHeader() + CALL printDetails(expenses) + CALL printSummary(expenses) END SUBROUTINE printReport END MODULE ExpenseReport @@ -48,7 +101,7 @@ PROGRAM main USE ExpenseReport IMPLICIT NONE TYPE(expense), DIMENSION (:), ALLOCATABLE :: expenses - ALLOCATE(expenses(5)) + ALLOCATE(expenses(7)) expenses(1)%type = BREAKFAST expenses(1)%amount = 1000 expenses(2)%type = BREAKFAST @@ -59,6 +112,10 @@ PROGRAM main expenses(4)%amount = 5001 expenses(5)%type = CAR_RENTAL expenses(5)%amount = 4 + expenses(6)%type = LUNCH + expenses(6)%amount = 2000 + expenses(7)%type = LUNCH + expenses(7)%amount = 2001 CALL printReport(expenses) DEALLOCATE(expenses) END PROGRAM main diff --git a/expensereport-fortran/Makefile b/expensereport-fortran/Makefile index faf194ad..782db054 100644 --- a/expensereport-fortran/Makefile +++ b/expensereport-fortran/Makefile @@ -2,7 +2,8 @@ FFLAGS:=--std=f2018 -ffree-form .PHONY: all all: ExpenseReport - ./ExpenseReport + ./ExpenseReport >actual.txt + diff expected.txt actual.txt .PHONY: clean clean:: diff --git a/expensereport-fortran/README.md b/expensereport-fortran/README.md new file mode 100644 index 00000000..ec180a61 --- /dev/null +++ b/expensereport-fortran/README.md @@ -0,0 +1,28 @@ +# Fortran Solution + +The Fotran Solution deserves some commentary. +The Fortran language has received many updates since its inception in 1957. +The language version used here is Fortran 2018. +Fortran 2018 offers object-oriented programming with polymorphism and even map-reduce. + +The solution provided here has exactly the same structure as the solutions in other languages. +At the time I'm writing this, I had already provided solutions in C, C++, Golang, Java, Kotlin, and Rust. +The solution in Fortran has exactly the same structure. + +In this structure, Fortran shows its age. +Compared with other languages, the price for a fine-grained structure is particularly high in Fortran. +The overall source code length approximately doubled. +This is a consequence of the verbosity of the Fotran language. +In Kotlin, trivial functions can conveniently be written as one-liners. +In Fortran, a trivial function requires at least 3 lines, and that also only in the simplest case when no arguments and no return value are involved. + +However, I decided to still provide a solution with the same structure as in the other languages. +In the case of Fortran, the solution is not to demonstrate what is best. +Because in Fortran, this isn't. +In Fortran, this is just a practice exercise. +For real-world Fortran, we should rather find a balance that feels better supported by the language. +And that means avoiding tiny function. + +However, note that the primary goal of the refactoring is achieved: It is possible to add new expense types without modifying existing functions. +All you need to do is add a `TYPE(expenseType) :: LUNCH = expenseType("Lunch", 2000, .TRUE.)` for example. +And this can also be achieved with fewer but larger functions, if you like. diff --git a/expensereport-fortran/expected.txt b/expensereport-fortran/expected.txt new file mode 100644 index 00000000..ef557417 --- /dev/null +++ b/expensereport-fortran/expected.txt @@ -0,0 +1,10 @@ +Expenses +Breakfast 1000 +Breakfast 1001 X +Dinner 5000 +Dinner 5001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal expenses: 16003 +Total: 16007 diff --git a/expensereport-go/Makefile b/expensereport-go/Makefile index 82e90b50..717fff43 100644 --- a/expensereport-go/Makefile +++ b/expensereport-go/Makefile @@ -1,7 +1,9 @@ .PHONY: all all: - ~/go/bin/godog run + go test + ~/go/bin/go-mutesting . .PHONY: install-deps install-deps: go install github.com/cucumber/godog/cmd/godog@upd-go1.18 + go install github.com/zimmski/go-mutesting/...@latest diff --git a/expensereport-go/expensereport.go b/expensereport-go/expensereport.go index 3264ca15..19357d92 100644 --- a/expensereport-go/expensereport.go +++ b/expensereport-go/expensereport.go @@ -2,54 +2,88 @@ package main import ( "fmt" + "math" "time" ) -type ExpenseType int +type ExpenseType struct { + name string + isMeal bool + limit int +} -const ( - Dinner ExpenseType = iota + 1 - Breakfast - CarRental -) +// Note: As Golang does not support immutable variables, the ExpenseType multiton is not safe. + +var Dinner = ExpenseType{name: "Dinner", isMeal: true, limit: 5000} +var Breakfast = ExpenseType{name: "Breakfast", isMeal: true, limit: 1000} +var CarRental = ExpenseType{name: "Car Rental", isMeal: false, limit: math.MaxInt} +var Lunch = ExpenseType{name: "Lunch", isMeal: true, limit: 2000} type Expense struct { Type ExpenseType Amount int } +func (expense Expense) GetName() string { + return expense.Type.name +} + +func (expense Expense) IsMeal() bool { + return expense.Type.isMeal +} + +func (expense Expense) IsOverLimit() bool { + return expense.Amount > expense.Type.limit +} + func PrintReport(expenses []Expense) { - total := 0 - mealExpenses := 0 + PrintHeader() + PrintDetails(expenses) + PrintSummary(expenses) +} +func PrintHeader() { fmt.Printf("Expenses %s\n", time.Now().Format("2006-01-02")) +} +func PrintDetails(expenses []Expense) { for _, expense := range expenses { - if expense.Type == Dinner || expense.Type == Breakfast { - mealExpenses += expense.Amount - } + PrintDetail(expense) + } +} - var expenseName string - switch expense.Type { - case Dinner: - expenseName = "Dinner" - case Breakfast: - expenseName = "Breakfast" - case CarRental: - expenseName = "Car Rental" - } +func PrintDetail(expense Expense) { + mealOverExpensesMarker := getOverLimitMarker(expense) + fmt.Printf("%s\t%d\t%s\n", expense.GetName(), expense.Amount, mealOverExpensesMarker) +} - var mealOverExpensesMarker string - if expense.Type == Dinner && expense.Amount > 5000 || expense.Type == Breakfast && expense.Amount > 1000 { - mealOverExpensesMarker = "X" - } else { - mealOverExpensesMarker = " " - } +func PrintSummary(expenses []Expense) { + fmt.Printf("Meal expenses: %d\n", SumMeals(expenses)) + fmt.Printf("Total expenses: %d\n", SumTotal(expenses)) +} - fmt.Printf("%s\t%d\t%s\n", expenseName, expense.Amount, mealOverExpensesMarker) - total += expense.Amount +func getOverLimitMarker(expense Expense) string { + if expense.IsOverLimit() { + return "X" + } else { + return " " } +} - fmt.Printf("Meal expenses: %d\n", mealExpenses) - fmt.Printf("Total expenses: %d\n", total) +func SumMeals(expenses []Expense) int { + return Sum(expenses, Expense.IsMeal) +} + +func SumTotal(expenses []Expense) int { + return Sum(expenses, func(expense Expense) bool { return true }) +} + +func Sum(expenses []Expense, predicate func(Expense) bool) int { + sum := 0 + for _, expense := range expenses { + if predicate(expense) { + sum += expense.Amount + } + } + return sum } diff --git a/expensereport-go/expensereport_test.go b/expensereport-go/expensereport_test.go new file mode 100644 index 00000000..4b75c981 --- /dev/null +++ b/expensereport-go/expensereport_test.go @@ -0,0 +1,80 @@ +package main + +import ( + "expenses/interceptor" + "fmt" + "github.com/cucumber/godog" + "github.com/cucumber/messages-go/v16" + "strconv" + "strings" + "testing" + "time" +) + +var actualReport string + +func expenseEntry(row *messages.PickleTableRow) (*Expense, error) { + expenseTypes := map[string]ExpenseType{ + "DINNER": Dinner, + "BREAKFAST": Breakfast, + "CAR_RENTAL": CarRental, + "LUNCH": Lunch, + } + amount, err := strconv.Atoi(row.Cells[1].Value) + if err != nil { + return nil, err + } + expenseType, present := expenseTypes[row.Cells[0].Value] + if !present { + return nil, fmt.Errorf("unknown expense type %s", row.Cells[0].Value) + } + return &Expense{Type: expenseType, Amount: amount}, nil +} + +func generatingAReportForTheFollowingExpenses(dataTable *godog.Table) error { + var expenses []Expense + for _, row := range dataTable.Rows[1:] { + expense, err := expenseEntry(row) + if err != nil { + return err + } + expenses = append(expenses, *expense) + } + actualReport = interceptor.InterceptStdout(func() { PrintReport(expenses) }) + return nil +} + +func translateEscapes(s string) string { + s = strings.ReplaceAll(s, "\\s", " ") + s = strings.ReplaceAll(s, "\\t", "\t") + return s +} + +func theReportMUSTLookLikeThis(arg1 *godog.DocString) error { + expectedReport := strings.ReplaceAll(translateEscapes(arg1.Content), "$now", time.Now().Format("2006-01-02")) + "\n" + if actualReport != expectedReport { + return fmt.Errorf("expected report: <%s>, actual report: <%s>", expectedReport, actualReport) + } + return nil +} + +//goland:noinspection GoUnusedExportedFunction +func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Step(`^generating a report for the following expenses:$`, generatingAReportForTheFollowingExpenses) + ctx.Step(`^the report MUST look like this:$`, theReportMUSTLookLikeThis) +} + +func TestFeatures(t *testing.T) { + suite := godog.TestSuite{ + ScenarioInitializer: InitializeScenario, + Options: &godog.Options{ + Format: "pretty", + Paths: []string{"features"}, + TestingT: t, // Testing instance that will run subtests. + }, + } + + if suite.Run() != 0 { + t.Fatal("non-zero status returned, failed to run feature tests") + } +} diff --git a/expensereport-go/features/ExpenseReportCharacterization.feature b/expensereport-go/features/ExpenseReportCharacterization.feature new file mode 100644 index 00000000..f3f0eb1c --- /dev/null +++ b/expensereport-go/features/ExpenseReportCharacterization.feature @@ -0,0 +1,88 @@ +Feature: Expense Report + Scenario: Empty Report + When generating a report for the following expenses: + | type | amount | + Then the report MUST look like this: + """ + Expenses $now + Meal expenses: 0 + Total expenses: 0 + """ + + Scenario: Dinner is a meal with a limit of 5000 + When generating a report for the following expenses: + | type | amount | + | DINNER | 5000 | + | DINNER | 5001 | + Then the report MUST look like this: + """ + Expenses $now + Dinner\t5000\t\s + Dinner\t5001\tX + Meal expenses: 10001 + Total expenses: 10001 + """ + + Scenario: Breakfast is a meal with a limit of 1000 + When generating a report for the following expenses: + | type | amount | + | BREAKFAST | 1000 | + | BREAKFAST | 1001 | + Then the report MUST look like this: + """ + Expenses $now + Breakfast\t1000\t\s + Breakfast\t1001\tX + Meal expenses: 2001 + Total expenses: 2001 + """ + + Scenario: Car Rental is not a meal and has no limit + When generating a report for the following expenses: + | type | amount | + | CAR_RENTAL | 2147483647 | + Then the report MUST look like this: + """ + Expenses $now + Car Rental\t2147483647\t\s + Meal expenses: 0 + Total expenses: 2147483647 + """ + + Scenario: Lunch is a meal with a limit of 2000 + When generating a report for the following expenses: + | type | amount | + | LUNCH | 2000 | + | LUNCH | 2001 | + Then the report MUST look like this: + """ + Expenses $now + Lunch\t2000\t\s + Lunch\t2001\tX + Meal expenses: 4001 + Total expenses: 4001 + """ + + Scenario: Combined characterization test + When generating a report for the following expenses: + | type | amount | + | DINNER | 5000 | + | DINNER | 5001 | + | BREAKFAST | 1000 | + | BREAKFAST | 1001 | + | CAR_RENTAL | 4 | + | LUNCH | 2000 | + | LUNCH | 2001 | + Then the report MUST look like this: + """ + Expenses $now + Dinner\t5000\t\s + Dinner\t5001\tX + Breakfast\t1000\t\s + Breakfast\t1001\tX + Car Rental\t4\t\s + Lunch\t2000\t\s + Lunch\t2001\tX + Meal expenses: 16003 + Total expenses: 16007 + """ diff --git a/expensereport-go/go.mod b/expensereport-go/go.mod index 1ddc2f73..5e25ee1b 100644 --- a/expensereport-go/go.mod +++ b/expensereport-go/go.mod @@ -9,9 +9,18 @@ require ( require ( github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect github.com/hashicorp/go-memdb v1.3.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/jessevdk/go-flags v1.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.7.0 // indirect + github.com/zimmski/go-mutesting v0.0.0-20210610104036-6d9217011a00 // indirect + github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439 // indirect + github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac // indirect + golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc // indirect + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect ) diff --git a/expensereport-go/go.sum b/expensereport-go/go.sum index 0fb595db..436d22cd 100644 --- a/expensereport-go/go.sum +++ b/expensereport-go/go.sum @@ -107,7 +107,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -115,6 +118,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -125,6 +129,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/goveralls v0.0.3/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -137,8 +142,10 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -180,11 +187,18 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/zimmski/go-mutesting v0.0.0-20210610104036-6d9217011a00 h1:KNiPkpQpqXvq40f8hh/1T7QasLJT/1MuBoOYA2vlxJk= +github.com/zimmski/go-mutesting v0.0.0-20210610104036-6d9217011a00/go.mod h1:RJt5SMnyha63GbdwCKJiX9djvvEC4KsfXJSZ5oTmSPw= +github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439 h1:yHqsjUkj0HWbKPw/6ZqC0/eMklaRpqubA199vaRLzzE= +github.com/zimmski/go-tool v0.0.0-20150119110811-2dfdc9ac8439/go.mod h1:G4FVqCRvfz74AEB1crDNdQuvMfOoKtk7DlePsnV2yGs= +github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac h1:uiFRlKzyIzHeLOthe0ethUkSGW7POlqxU3Tc21R8QpQ= +github.com/zimmski/osutil v0.0.0-20190128123334-0d0b3ca231ac/go.mod h1:wJ9WGevuM/rw8aB2pQPFMUgXZWeaouI0ueFamR0DUPE= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -256,6 +270,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -270,6 +285,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -298,10 +315,13 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/expensereport-go/interceptor/interceptor.go b/expensereport-go/interceptor/interceptor.go new file mode 100644 index 00000000..f3201542 --- /dev/null +++ b/expensereport-go/interceptor/interceptor.go @@ -0,0 +1,33 @@ +package interceptor + +import ( + "bytes" + "io" + "log" + "os" +) + +func InterceptStdout(code func()) string { + originalStdout := os.Stdout + read, write, err := os.Pipe() + if err != nil { + log.Fatal(err) + } + os.Stdout = write + + outC := make(chan string) + go func() { + var buf bytes.Buffer + _, _ = io.Copy(&buf, read) + outC <- buf.String() + }() + + code() + + _ = write.Close() + + os.Stdout = originalStdout + out := <-outC + _ = read.Close() + return out +} diff --git a/expensereport-groovy-script/ExpenseReport.groovy b/expensereport-groovy-script/ExpenseReport.groovy index 9a13ed9a..a275614e 100755 --- a/expensereport-groovy-script/ExpenseReport.groovy +++ b/expensereport-groovy-script/ExpenseReport.groovy @@ -3,47 +3,77 @@ import java.util.Date; enum ExpenseType { - DINNER, - BREAKFAST, - CAR_RENTAL, + DINNER("Dinner", 5000, true), + BREAKFAST("Breakfast", 1000, true), + CAR_RENTAL("Car Rental", Integer.MAX_VALUE, false), + LUNCH("Lunch", 2000, true), + ; + final String name; + final int limit; + final boolean isMeal; + ExpenseType(String name, int limit, boolean isMeal) { + this.name = name; + this.limit = limit; + this.isMeal = isMeal; + } } class Expense { ExpenseType type; int amount; + Expense(ExpenseType type, int amount) { + this.type = type; + this.amount = amount; + } + boolean isOverLimit() { + amount > type.limit; + } + String getName() { + type.name; + } + boolean isMeal() { + type.isMeal; + } } def printReport(Expense... expenses) { - int total = 0; - int mealExpenses = 0; + printHeader(); + printDetails(expenses); + printSummary(expenses); +} +def printHeader() { println "Expenses: ${new Date()}"; +} - for (Expense expense in expenses) { - if (expense.type == ExpenseType.DINNER || expense.type == ExpenseType.BREAKFAST) { - mealExpenses += expense.amount; - } - - String expenseName = ""; - switch (expense.type) { - case ExpenseType.DINNER: - expenseName = "Dinner"; - break; - case ExpenseType.BREAKFAST: - expenseName = "Breakfast"; - break; - case ExpenseType.CAR_RENTAL: - expenseName = "Car Rental"; - break; - } - - String mealOverExpensesMarker = expense.type == ExpenseType.DINNER && expense.amount > 5000 || expense.type == ExpenseType.BREAKFAST && expense.amount > 1000 ? "X" : " "; - - println "$expenseName\t$expense.amount\t$mealOverExpensesMarker"; - - total += expense.amount; - } +def printDetails(Expense... expenses) { + expenses.each { printDetail(it) } +} + +def printDetail(Expense expense) { + String overLimitMarker = expense.isOverLimit() ? "X" : " "; + println "$expense.name\t$expense.amount\t$overLimitMarker"; +} + +def printSummary(Expense... expenses) { + println "Meal Expenses: ${sumMeals(expenses)}"; + println "Total Expenses: ${sumTotal(expenses)}"; +} + +def sumMeals(Expense... expenses) { + expenses.findAll { it.isMeal() }.amount.sum(); +} - println "Meal Expenses: $mealExpenses"; - println "Total Expenses: $total"; +def sumTotal(Expense... expenses) { + expenses.amount.sum(); } + +printReport( + new Expense(ExpenseType.DINNER, 5000), + new Expense(ExpenseType.DINNER, 5001), + new Expense(ExpenseType.BREAKFAST, 1000), + new Expense(ExpenseType.BREAKFAST, 1001), + new Expense(ExpenseType.CAR_RENTAL, 4), + new Expense(ExpenseType.LUNCH, 2000), + new Expense(ExpenseType.LUNCH, 2001), +); diff --git a/expensereport-groovy-script/Makefile b/expensereport-groovy-script/Makefile index ad61629c..1d087f09 100644 --- a/expensereport-groovy-script/Makefile +++ b/expensereport-groovy-script/Makefile @@ -1,3 +1,4 @@ +SHELL:=bash .PHONY: all all: - ./ExpenseReport.groovy + diff expected.txt <(TZ=UTC faketime -f '2022-06-05 19:45:30' ./ExpenseReport.groovy) diff --git a/expensereport-groovy-script/expected.txt b/expensereport-groovy-script/expected.txt new file mode 100644 index 00000000..b85ac54a --- /dev/null +++ b/expensereport-groovy-script/expected.txt @@ -0,0 +1,10 @@ +Expenses: Sun Jun 05 19:45:30 UTC 2022 +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal Expenses: 16003 +Total Expenses: 16007 diff --git a/expensereport-haskell/Makefile b/expensereport-haskell/Makefile index e560a65a..02bba37c 100644 --- a/expensereport-haskell/Makefile +++ b/expensereport-haskell/Makefile @@ -1,7 +1,11 @@ SHELL:=/bin/bash .PHONY: all -all: expensereport +all: test + +.PHONY: test +test: expensereport + diff expected.txt <(./expensereport) %: %.hs ghc $^ diff --git a/expensereport-haskell/expected.txt b/expensereport-haskell/expected.txt new file mode 100644 index 00000000..75d83b27 --- /dev/null +++ b/expensereport-haskell/expected.txt @@ -0,0 +1,10 @@ +Expense Report +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal expenses: 16003 +Total expenses: 16007 diff --git a/expensereport-haskell/expensereport.hs b/expensereport-haskell/expensereport.hs index 4772d6fd..c5148ca9 100644 --- a/expensereport-haskell/expensereport.hs +++ b/expensereport-haskell/expensereport.hs @@ -1,42 +1,59 @@ -import Control.Monad (forM_) -import Data.Array -import Data.IORef - -data ExpenseType = Dinner | Breakfast | CarRental +data ExpenseType = ExpenseType { name :: String, limit :: Int, isMeal3 :: Bool } +dinner = ExpenseType "Dinner" 5000 True +breakfast = ExpenseType "Breakfast" 1000 True +carRental = ExpenseType "Car Rental" (maxBound :: Int) False +lunch = ExpenseType "Lunch" 2000 True data Expense = Expense { eType :: ExpenseType, amount :: Int } +expenseName :: Expense -> String +expenseName expense = name (eType expense) + +isMeal :: Expense -> Bool +isMeal expense = isMeal3 (eType expense) + +isOverLimit :: Expense -> Bool +isOverLimit expense = (amount expense) > (limit (eType expense)) + printReport :: [Expense] -> IO () -printReport expenses = do - total <- newIORef 0 - --total :: IORef Integer - mealExpenses <- newIORef 0 - --mealExpenses :: IORef Integer - putStrLn("Expense Report") - forM_ expenses $ \expense -> do - modifyIORef mealExpenses (+ ( - case (eType expense) of - Dinner -> (amount expense) - Breakfast -> (amount expense) - _ -> 0 - )) - let expenseName = "" ++ case (eType expense) of - Dinner -> "Dinner" - Breakfast -> "Breakfast" - CarRental -> "Car Rental" - let mealOverExpensesMarker = case (eType expense) of - Dinner -> if (amount expense) > 5000 then "X" else " " - Breakfast -> if (amount expense) > 1000 then "X" else " " - _ -> " " - putStrLn(expenseName ++ "\t" ++ (show (amount expense)) ++ "\t" ++ mealOverExpensesMarker) - modifyIORef total (+ (amount expense)) - putStr("Meal expenses: ") - readIORef mealExpenses >>= print - putStr("Total expenses: ") - readIORef total >>= print +printReport expenses = putStr(generateReport expenses) + +generateReport :: [Expense] -> String +generateReport expenses = header ++ (details expenses) ++ (summary expenses) + +header :: String +header = "Expense Report\n" + +details :: [Expense] -> String +details [] = "" +details (expense:expenses) = detail(expense) ++ details(expenses) + +detail :: Expense -> String +detail expense = expenseName(expense) ++ "\t" ++ (show (amount expense)) ++ "\t" ++ (overLimitMarker expense) ++ "\n" + +overLimitMarker :: Expense -> String +overLimitMarker expense = if isOverLimit expense then "X" else " " + +summary :: [Expense] -> String +summary expenses = "Meal expenses: " ++ (show (sumMeals expenses)) ++ "\n" ++ "Total expenses: " ++ (show (sumTotal expenses)) ++ "\n" + +sumMeals :: [Expense] -> Int +sumMeals [] = 0 +sumMeals (expense:expenses) = (if isMeal expense then amount expense else 0) + sumMeals expenses + +sumTotal :: [Expense] -> Int +sumTotal [] = 0 +sumTotal (expense:expenses) = (amount expense) + (sumTotal expenses) + main :: IO () main = do printReport([ - Expense Breakfast 50 + Expense dinner 5000, + Expense dinner 5001, + Expense breakfast 1000, + Expense breakfast 1001, + Expense carRental 4, + Expense lunch 2000, + Expense lunch 2001 ]) diff --git a/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java index b85ee9af..6615bd98 100644 --- a/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java +++ b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java @@ -2,49 +2,93 @@ import java.util.Date; import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.lang.Integer.MAX_VALUE; enum ExpenseType { - DINNER, BREAKFAST, CAR_RENTAL + DINNER("Dinner", 5000, true), + BREAKFAST("Breakfast", 1000, true), + CAR_RENTAL("Car Rental", MAX_VALUE, false), + LUNCH("Lunch", 2000, true), + ; + final String name; + final int limit; + final boolean isMeal; + + ExpenseType(final String name, final int limit, final boolean isMeal) { + this.name = name; + this.limit = limit; + this.isMeal = isMeal; + } } -class Expense { - ExpenseType type; - int amount; +record Expense( + ExpenseType type, + int amount +) { + String getName() { + return type.name; + } + + boolean isMeal() { + return type.isMeal; + } + + boolean isOverLimit() { + return amount > type.limit; + } } public class ExpenseReport { - public void printReport(List expenses) { - int total = 0; - int mealExpenses = 0; - - System.out.println("Expenses " + new Date()); - - for (Expense expense : expenses) { - if (expense.type == ExpenseType.DINNER || expense.type == ExpenseType.BREAKFAST) { - mealExpenses += expense.amount; - } - - String expenseName = ""; - switch (expense.type) { - case DINNER: - expenseName = "Dinner"; - break; - case BREAKFAST: - expenseName = "Breakfast"; - break; - case CAR_RENTAL: - expenseName = "Car Rental"; - break; - } - - String mealOverExpensesMarker = expense.type == ExpenseType.DINNER && expense.amount > 5000 || expense.type == ExpenseType.BREAKFAST && expense.amount > 1000 ? "X" : " "; - - System.out.println(expenseName + "\t" + expense.amount + "\t" + mealOverExpensesMarker); - - total += expense.amount; - } - - System.out.println("Meal expenses: " + mealExpenses); - System.out.println("Total expenses: " + total); + public void printReport(final List expenses) { + printReport(expenses, new Date()); + } + + public void printReport(final List expenses, final Date date) { + System.out.print(generateReport(expenses, date)); + } + + private String generateReport(final List expenses, final Date date) { + return header(date) + body(expenses) + summary(expenses); + } + + private String header(final Date date) { + return "Expenses " + date + "\n"; + } + + private String body(final List expenses) { + return expenses.stream() + .map(this::detail) + .collect(Collectors.joining()); + } + + private String detail(final Expense expense) { + return expense.getName() + "\t" + expense.amount() + "\t" + getOverLimitMarker(expense) + "\n"; + } + + private String getOverLimitMarker(final Expense expense) { + return expense.isOverLimit() ? "X" : " "; + } + + private String summary(final List expenses) { + return "Meal expenses: " + sumMeals(expenses) + "\n" + + "Total expenses: " + sumTotal(expenses) + "\n"; + } + + private int sumMeals(final List expenses) { + return sumExpenses(expenses, Expense::isMeal); + } + + private int sumTotal(final List expenses) { + return sumExpenses(expenses, e -> true); + } + + private int sumExpenses(final List expenses, final Predicate predicate) { + return expenses.stream() + .filter(predicate) + .mapToInt(Expense::amount) + .sum(); } } diff --git a/expensereport-java/src/test/java/com/nelkinda/training/CucumberTest.java b/expensereport-java/src/test/java/com/nelkinda/training/CucumberTest.java index fef072f4..dde1975a 100644 --- a/expensereport-java/src/test/java/com/nelkinda/training/CucumberTest.java +++ b/expensereport-java/src/test/java/com/nelkinda/training/CucumberTest.java @@ -1,12 +1,44 @@ package com.nelkinda.training; +import io.cucumber.java.DataTableType; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectClasspathResource; import org.junit.platform.suite.api.Suite; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static com.nelkinda.training.Interceptor.interceptStdout; +import static org.junit.jupiter.api.Assertions.assertEquals; + @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features") @SuppressWarnings("java:S2187") public class CucumberTest { + private final Date now = new Date(); + private String actualReport; + + @DataTableType + public Expense expenseEntry(final Map entry) { + return new Expense(ExpenseType.valueOf(entry.get("type")), Integer.parseInt(entry.get("amount"))); + } + + @When("generating a report for the following expenses:") + public void generating_a_report_for_the_following_expenses(final List expenses) { + actualReport = interceptStdout( + () -> new ExpenseReport().printReport(expenses, now) + ); + } + + @Then("the report MUST look like this:") + public void the_report_must_look_like_this(final String expectedReport) { + assertEquals( + expectedReport.translateEscapes().replaceAll("[$]now", now.toString()) + System.lineSeparator(), + actualReport + ); + } } diff --git a/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportTest.java b/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportTest.java new file mode 100644 index 00000000..16f71f3d --- /dev/null +++ b/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportTest.java @@ -0,0 +1,51 @@ +package com.nelkinda.training; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Date; +import java.util.List; + +import static com.nelkinda.training.ExpenseType.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ExpenseReportTest { + @Test + void characterizePrintReport() { + final Date now = new Date(); + final String actualReport = Interceptor.interceptStdout(() -> { + new ExpenseReport().printReport( + List.of( + new Expense(BREAKFAST, 1), + new Expense(BREAKFAST, 1000), + new Expense(BREAKFAST, 1001), + new Expense(DINNER, 2), + new Expense(DINNER, 5000), + new Expense(DINNER, 5001), + new Expense(CAR_RENTAL, 4), + new Expense(LUNCH, 8), + new Expense(LUNCH, 2000), + new Expense(LUNCH, 2001) + ), + now + ); + }); + final String expectedReport = "Expenses " + now + "\n" + + "Breakfast\t1\t \n" + + "Breakfast\t1000\t \n" + + "Breakfast\t1001\tX\n" + + "Dinner\t2\t \n" + + "Dinner\t5000\t \n" + + "Dinner\t5001\tX\n" + + "Car Rental\t4\t \n" + + "Lunch\t8\t \n" + + "Lunch\t2000\t \n" + + "Lunch\t2001\tX\n" + + "Meal expenses: 16014\n" + + "Total expenses: 16018\n"; + assertEquals(expectedReport, actualReport); + } +} diff --git a/expensereport-java/src/test/java/com/nelkinda/training/Interceptor.java b/expensereport-java/src/test/java/com/nelkinda/training/Interceptor.java new file mode 100644 index 00000000..d30809e3 --- /dev/null +++ b/expensereport-java/src/test/java/com/nelkinda/training/Interceptor.java @@ -0,0 +1,18 @@ +package com.nelkinda.training; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class Interceptor { + public static String interceptStdout(final Runnable code) { + final PrintStream originalStdout = System.out; + final ByteArrayOutputStream interceptedStdout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(interceptedStdout)); + try { + code.run(); + } finally { + System.setOut(originalStdout); + } + return interceptedStdout.toString(); + } +} diff --git a/expensereport-java/src/test/resources/features/ExpenseReportCharacterization.feature b/expensereport-java/src/test/resources/features/ExpenseReportCharacterization.feature new file mode 100644 index 00000000..f3f0eb1c --- /dev/null +++ b/expensereport-java/src/test/resources/features/ExpenseReportCharacterization.feature @@ -0,0 +1,88 @@ +Feature: Expense Report + Scenario: Empty Report + When generating a report for the following expenses: + | type | amount | + Then the report MUST look like this: + """ + Expenses $now + Meal expenses: 0 + Total expenses: 0 + """ + + Scenario: Dinner is a meal with a limit of 5000 + When generating a report for the following expenses: + | type | amount | + | DINNER | 5000 | + | DINNER | 5001 | + Then the report MUST look like this: + """ + Expenses $now + Dinner\t5000\t\s + Dinner\t5001\tX + Meal expenses: 10001 + Total expenses: 10001 + """ + + Scenario: Breakfast is a meal with a limit of 1000 + When generating a report for the following expenses: + | type | amount | + | BREAKFAST | 1000 | + | BREAKFAST | 1001 | + Then the report MUST look like this: + """ + Expenses $now + Breakfast\t1000\t\s + Breakfast\t1001\tX + Meal expenses: 2001 + Total expenses: 2001 + """ + + Scenario: Car Rental is not a meal and has no limit + When generating a report for the following expenses: + | type | amount | + | CAR_RENTAL | 2147483647 | + Then the report MUST look like this: + """ + Expenses $now + Car Rental\t2147483647\t\s + Meal expenses: 0 + Total expenses: 2147483647 + """ + + Scenario: Lunch is a meal with a limit of 2000 + When generating a report for the following expenses: + | type | amount | + | LUNCH | 2000 | + | LUNCH | 2001 | + Then the report MUST look like this: + """ + Expenses $now + Lunch\t2000\t\s + Lunch\t2001\tX + Meal expenses: 4001 + Total expenses: 4001 + """ + + Scenario: Combined characterization test + When generating a report for the following expenses: + | type | amount | + | DINNER | 5000 | + | DINNER | 5001 | + | BREAKFAST | 1000 | + | BREAKFAST | 1001 | + | CAR_RENTAL | 4 | + | LUNCH | 2000 | + | LUNCH | 2001 | + Then the report MUST look like this: + """ + Expenses $now + Dinner\t5000\t\s + Dinner\t5001\tX + Breakfast\t1000\t\s + Breakfast\t1001\tX + Car Rental\t4\t\s + Lunch\t2000\t\s + Lunch\t2001\tX + Meal expenses: 16003 + Total expenses: 16007 + """ diff --git a/expensereport-kotlin/src/main/kotlin/com/nelkinda/training/ExpenseReport.kt b/expensereport-kotlin/src/main/kotlin/com/nelkinda/training/ExpenseReport.kt index 7b1145cc..659c64ab 100644 --- a/expensereport-kotlin/src/main/kotlin/com/nelkinda/training/ExpenseReport.kt +++ b/expensereport-kotlin/src/main/kotlin/com/nelkinda/training/ExpenseReport.kt @@ -1,43 +1,42 @@ package com.nelkinda.training +import java.lang.Integer.MAX_VALUE import java.util.Date -enum class ExpenseType { - DINNER, BREAKFAST, CAR_RENTAL +enum class ExpenseType( + val expenseName: String, + val limit: Int, + val isMeal: Boolean, +) { + DINNER("Dinner", 5000, true), + BREAKFAST("Breakfast", 1000, true), + CAR_RENTAL("Car Rental", MAX_VALUE, false), + LUNCH("Lunch", 2000, true), + ; } -class Expense { - lateinit var type: ExpenseType - var amount: Int = 0 +class Expense( + private val type: ExpenseType, + val amount: Int, +) { + val name: String get() = type.expenseName + val isMeal: Boolean get() = type.isMeal + fun isOverLimit() = amount > type.limit } class ExpenseReport { - fun printReport(expenses: List) { - var total = 0 - var mealExpenses = 0 + fun printReport(expenses: List, timestamp: Date = Date()) = + print(expenses.asSequence().generateReport(timestamp)) - println("Expenses ${Date()}") + private fun Sequence.generateReport(timestamp: Date) = header(timestamp) + body() + summary() - for (expense in expenses) { - if (expense.type == ExpenseType.DINNER || expense.type == ExpenseType.BREAKFAST) { - mealExpenses += expense.amount - } + private fun header(timestamp: Date) = "Expenses $timestamp\n" - var expenseName = "" - when (expense.type) { - ExpenseType.DINNER -> expenseName = "Dinner" - ExpenseType.BREAKFAST -> expenseName = "Breakfast" - ExpenseType.CAR_RENTAL -> expenseName = "Car Rental" - } + private fun Sequence.body() = joinToString(separator = "") { it.detail() } + private fun Expense.detail() = "$name\t$amount\t$overLimitMarker\n" + private val Expense.overLimitMarker get() = if (isOverLimit()) "X" else " " - val mealOverExpensesMarker = if (expense.type == ExpenseType.DINNER && expense.amount > 5000 || expense.type == ExpenseType.BREAKFAST && expense.amount > 1000) "X" else " " - - println(expenseName + "\t" + expense.amount + "\t" + mealOverExpensesMarker) - - total += expense.amount - } - - println("Meal expenses: $mealExpenses") - println("Total expenses: $total") - } + private fun Sequence.summary() = "Meal expenses: ${sumMeals()}\nTotal expenses: ${sumAll()}\n" + private fun Sequence.sumAll() = sumOf { it.amount } + private fun Sequence.sumMeals() = filter { it.isMeal }.sumAll() } diff --git a/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/CucumberTest.kt b/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/CucumberTest.kt index 1b5ee687..3f564e0d 100644 --- a/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/CucumberTest.kt +++ b/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/CucumberTest.kt @@ -1,10 +1,37 @@ package com.nelkinda.training +import io.cucumber.java.DataTableType +import io.cucumber.java.en.Then +import io.cucumber.java.en.When +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.platform.suite.api.IncludeEngines import org.junit.platform.suite.api.SelectClasspathResource import org.junit.platform.suite.api.Suite +import java.util.Date @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features") -class CucumberTest +class CucumberTest { + private val now = Date() + private lateinit var actualReport: String + + @DataTableType + fun expenseEntry(entry: Map): Expense = + Expense(ExpenseType.valueOf(entry["type"]!!), entry["amount"]!!.toInt()) + + @When("generating a report for the following expenses:") + fun generating_a_report_for_the_following_expenses(expenses: List) { + actualReport = interceptStdout { + ExpenseReport().printReport(expenses, now) + } + } + + @Then("the report MUST look like this:") + fun the_report_must_look_like_this(expectedReport: String) { + assertEquals( + expectedReport.translateEscapes().replace(Regex("[$]now"), now.toString()) + System.lineSeparator(), + actualReport + ) + } +} diff --git a/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/ExpenseReportTest.kt b/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/ExpenseReportTest.kt new file mode 100644 index 00000000..3cc8c05b --- /dev/null +++ b/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/ExpenseReportTest.kt @@ -0,0 +1,47 @@ +package com.nelkinda.training + +import com.nelkinda.training.ExpenseType.BREAKFAST +import com.nelkinda.training.ExpenseType.CAR_RENTAL +import com.nelkinda.training.ExpenseType.DINNER +import com.nelkinda.training.ExpenseType.LUNCH +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.Date + +class ExpenseReportTest { + @Test + fun `characterize printReport`() { + val now = Date() + val actualReport = interceptStdout { + ExpenseReport().printReport( + listOf( + Expense(BREAKFAST, 1), + Expense(BREAKFAST, 1000), + Expense(BREAKFAST, 1001), + Expense(DINNER, 2), + Expense(DINNER, 5000), + Expense(DINNER, 5001), + Expense(CAR_RENTAL, 4), + Expense(LUNCH, 8), + Expense(LUNCH, 2000), + Expense(LUNCH, 2001), + ), + now, + ) + } + val expectedReport = "Expenses $now\n" + + "Breakfast\t1\t \n" + + "Breakfast\t1000\t \n" + + "Breakfast\t1001\tX\n" + + "Dinner\t2\t \n" + + "Dinner\t5000\t \n" + + "Dinner\t5001\tX\n" + + "Car Rental\t4\t \n" + + "Lunch\t8\t \n" + + "Lunch\t2000\t \n" + + "Lunch\t2001\tX\n" + + "Meal expenses: 16014\n" + + "Total expenses: 16018\n" + assertEquals(expectedReport, actualReport) + } +} diff --git a/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/Interceptor.kt b/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/Interceptor.kt new file mode 100644 index 00000000..70f7aa90 --- /dev/null +++ b/expensereport-kotlin/src/test/kotlin/com/nelkinda/training/Interceptor.kt @@ -0,0 +1,18 @@ +@file:JvmName("Interceptor") + +package com.nelkinda.training + +import java.io.ByteArrayOutputStream +import java.io.PrintStream + +fun interceptStdout(code: () -> Unit): String { + val originalStdout = System.out + val interceptedStdout = ByteArrayOutputStream() + System.setOut(PrintStream(interceptedStdout)) + try { + code() + } finally { + System.setOut(originalStdout) + } + return interceptedStdout.toString() +} diff --git a/expensereport-kotlin/src/test/resources/features/ExpenseReportCharacterization.feature b/expensereport-kotlin/src/test/resources/features/ExpenseReportCharacterization.feature new file mode 100644 index 00000000..f3f0eb1c --- /dev/null +++ b/expensereport-kotlin/src/test/resources/features/ExpenseReportCharacterization.feature @@ -0,0 +1,88 @@ +Feature: Expense Report + Scenario: Empty Report + When generating a report for the following expenses: + | type | amount | + Then the report MUST look like this: + """ + Expenses $now + Meal expenses: 0 + Total expenses: 0 + """ + + Scenario: Dinner is a meal with a limit of 5000 + When generating a report for the following expenses: + | type | amount | + | DINNER | 5000 | + | DINNER | 5001 | + Then the report MUST look like this: + """ + Expenses $now + Dinner\t5000\t\s + Dinner\t5001\tX + Meal expenses: 10001 + Total expenses: 10001 + """ + + Scenario: Breakfast is a meal with a limit of 1000 + When generating a report for the following expenses: + | type | amount | + | BREAKFAST | 1000 | + | BREAKFAST | 1001 | + Then the report MUST look like this: + """ + Expenses $now + Breakfast\t1000\t\s + Breakfast\t1001\tX + Meal expenses: 2001 + Total expenses: 2001 + """ + + Scenario: Car Rental is not a meal and has no limit + When generating a report for the following expenses: + | type | amount | + | CAR_RENTAL | 2147483647 | + Then the report MUST look like this: + """ + Expenses $now + Car Rental\t2147483647\t\s + Meal expenses: 0 + Total expenses: 2147483647 + """ + + Scenario: Lunch is a meal with a limit of 2000 + When generating a report for the following expenses: + | type | amount | + | LUNCH | 2000 | + | LUNCH | 2001 | + Then the report MUST look like this: + """ + Expenses $now + Lunch\t2000\t\s + Lunch\t2001\tX + Meal expenses: 4001 + Total expenses: 4001 + """ + + Scenario: Combined characterization test + When generating a report for the following expenses: + | type | amount | + | DINNER | 5000 | + | DINNER | 5001 | + | BREAKFAST | 1000 | + | BREAKFAST | 1001 | + | CAR_RENTAL | 4 | + | LUNCH | 2000 | + | LUNCH | 2001 | + Then the report MUST look like this: + """ + Expenses $now + Dinner\t5000\t\s + Dinner\t5001\tX + Breakfast\t1000\t\s + Breakfast\t1001\tX + Car Rental\t4\t\s + Lunch\t2000\t\s + Lunch\t2001\tX + Meal expenses: 16003 + Total expenses: 16007 + """ diff --git a/expensereport-pascal/ExpenseReport.pas b/expensereport-pascal/ExpenseReport.pas index 05009120..17dbf8bd 100644 --- a/expensereport-pascal/ExpenseReport.pas +++ b/expensereport-pascal/ExpenseReport.pas @@ -3,38 +3,110 @@ USES sysutils; TYPE - ExpenseType = (Dinner, Breakfast, CarRental); + ExpenseType = RECORD + name: string; + limit: INTEGER; + isMeal: BOOLEAN; + END; Expense = RECORD - type_: ExpenseType; + type_: ^ ExpenseType; amount: integer; END; -PROCEDURE printReport(expenses: ARRAY OF Expense); -VAR total: integer = 0; -VAR mealExpenses: integer = 0; +CONST + Dinner: ExpenseType = ( name: 'Dinner'; limit: 5000; isMeal: true; ); + Breakfast: ExpenseType = ( name: 'Breakfast'; limit: 1000; isMeal: true; ); + CarRental: ExpenseType = ( name: 'Car Rental'; limit: 32767; isMeal: false; ); + Lunch: ExpenseType = ( name: 'Lunch'; limit: 2000; isMeal: true; ); + TAB: STRING = #9; + NL: STRING = #10; + +FUNCTION isMeal(exp: Expense): BOOLEAN; +BEGIN + isMeal := exp.type_^.isMeal; +END; + +FUNCTION getName(exp: Expense): STRING; +BEGIN + getName := exp.type_^.name; +END; + +FUNCTION isOverLimit(exp: Expense): BOOLEAN; +BEGIN + isOverLimit := exp.amount > exp.type_^.limit; +END; + +FUNCTION sumTotal(expenses: ARRAY OF Expense): INTEGER; +VAR exp: Expense; +BEGIN + sumTotal := 0; + FOR exp IN expenses DO + BEGIN + sumTotal := sumTotal + exp.amount; + END; +END; + +FUNCTION sumMeals(expenses: ARRAY OF Expense): INTEGER; +VAR exp : Expense; +BEGIN + sumMeals := 0; + FOR exp IN expenses DO + BEGIN + IF (isMeal(exp)) THEN sumMeals := sumMeals + exp.amount; + END; +END; + +FUNCTION header(): STRING; +BEGIN + header := 'Expenses: ' + FormatDateTime('YYYY-MM-DD hh:mm:ss', Now) + NL; +END; + +FUNCTION overLimitMarker(exp: Expense): STRING; +BEGIN + IF (isOverLimit(exp)) THEN overLimitMarker := 'X' ELSE overLimitMarker := ' '; +END; + +FUNCTION detail(exp: Expense): STRING; +BEGIN + detail := getName(exp) + TAB + IntToStr(exp.amount) + TAB + overLimitMarker(exp) + NL; +END; + +FUNCTION details(expenses: ARRAY OF Expense): STRING; VAR exp: Expense; -VAR expenseName: string; -VAR mealOverExpensesMarker: string; BEGIN - writeln('Expenses: ', FormatDateTime('YYYY-MM-DD hh:mm:ss', Now)); + details := ''; FOR exp IN expenses DO BEGIN - IF (exp.type_ = Dinner) OR (exp.type_ = Breakfast) THEN mealExpenses := mealExpenses + exp.amount; - CASE (exp.type_) of - Dinner: expenseName := 'Dinner'; - Breakfast: expenseName := 'Breakfast'; - CarRental: expenseName := 'Car Rental'; - END; - IF (exp.type_ = Dinner) AND (exp.amount > 5000) OR (exp.type_ = Breakfast) AND (exp.amount > 1000) THEN mealOverExpensesMarker := 'X' ELSE mealOverExpensesMarker := ' '; - writeln(expenseName, #9, exp.amount, #9, mealOverExpensesMarker); - total := total + exp.amount; + details := details + detail(exp); END; - writeln('Meal expenses: ', mealExpenses); - writeln('Total expenses: ', total); +END; + +FUNCTION summary(expenses: ARRAY OF Expense): STRING; +BEGIN + summary := + 'Meal expenses: ' + IntToStr(sumMeals(expenses)) + NL + + 'Total expenses: ' + IntToStr(sumTotal(expenses)) + NL; +END; + +FUNCTION report(expenses: ARRAY OF Expense): STRING; +BEGIN + report := header() + details(expenses) + summary(expenses); +END; + +PROCEDURE printReport(expenses: ARRAY OF Expense); +BEGIN + write(report(expenses)); END; -VAR expenses: ARRAY OF Expense; +VAR expenses: ARRAY [1 .. 7] OF Expense; BEGIN - writeln('Hello, world!'); + expenses[1].type_ := @Dinner; expenses[1].amount := 5000; + expenses[2].type_ := @Dinner; expenses[2].amount := 5001; + expenses[3].type_ := @Breakfast; expenses[3].amount := 1000; + expenses[4].type_ := @Breakfast; expenses[4].amount := 1001; + expenses[5].type_ := @CarRental; expenses[5].amount := 4; + expenses[6].type_ := @Lunch; expenses[6].amount := 2000; + expenses[7].type_ := @Lunch; expenses[7].amount := 2001; + printReport(expenses); END. diff --git a/expensereport-pascal/Makefile b/expensereport-pascal/Makefile index e2b5d3f9..372e22b9 100644 --- a/expensereport-pascal/Makefile +++ b/expensereport-pascal/Makefile @@ -1,6 +1,8 @@ +SHELL:=bash + .PHONY: all all: ExpenseReport - ./ExpenseReport + diff -I '^Expenses: ' expected.txt <(./ExpenseReport) .PHONY: clean clean:: diff --git a/expensereport-pascal/expected.txt b/expensereport-pascal/expected.txt new file mode 100644 index 00000000..50a8e504 --- /dev/null +++ b/expensereport-pascal/expected.txt @@ -0,0 +1,10 @@ +Expenses: 2022-06-05 14:12:23 +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal expenses: 16003 +Total expenses: 16007 diff --git a/expensereport-perl/ExpenseReport.pl b/expensereport-perl/ExpenseReport.pl index 816e0ce5..a692398c 100755 --- a/expensereport-perl/ExpenseReport.pl +++ b/expensereport-perl/ExpenseReport.pl @@ -1,50 +1,101 @@ #!/usr/bin/perl -package Expense; +package ExpenseType; + +sub new { + my $class = shift; + my $self = { + 'name' => shift, + 'limit' => shift, + 'isMeal' => shift, + }; + bless $self, $class; + $self; +} use constant { - DINNER => 1, - BREAKFAST => 2, - CAR_RENTAL => 3, + DINNER => new ExpenseType("Dinner", 5000, 1), + BREAKFAST => new ExpenseType("Breakfast", 1000, 1), + CAR_RENTAL => new ExpenseType("Car Rental", ~0>>1, 0), + LUNCH => new ExpenseType("Lunch", 2000, 1), }; + +package Expense; + sub new { my $class = shift; my $self = { 'type' => shift, - 'amount' => shift + 'amount' => shift, }; bless $self, $class; - return $self; + $self; } +sub getName() { + shift->{'type'}->{'name'}; +} + +sub isMeal() { + shift->{'type'}->{'isMeal'}; +} + +sub isOverLimit() { + my $self = shift; + $self->{'amount'} > $self->{'type'}->{'limit'}; +} + + +package ExpenseReport; + sub printReport(@) { - my @expenses = @_; + print report(@_); +} + +sub report(@) { + header() . details(@_) . summary(@_); +} + +sub header() { + "Expenses: ".localtime()."\n"; +} + +sub detail($) { + my $expense = shift; + my $mealOverExpensesMarker = $expense->isOverLimit() ? "X" : " "; + $expense->getName()."\t".$expense->{'amount'}."\t$mealOverExpensesMarker\n"; +} + +sub details(@) { + my $details = ""; + $details .= detail($_) for @_; + $details; +} + +sub summary(@) { + "Meal Expenses: ".sumMeals(@_)."\n". + "Total Expenses: ".sumTotal(@_)."\n"; +} + +sub sumMeals(@) { my $mealExpenses = 0; + $mealExpenses += $_->{'amount'} * $_->isMeal() for @_; + $mealExpenses; +} + +sub sumTotal(@) { my $total = 0; - my $datestring = localtime(); - print "Expenses: $datestring\n"; - for my $expense (@expenses) { - if ($expense->{'type'} == DINNER or $expense->{'type'} == BREAKFAST) { - $mealExpenses += $expense->{'amount'}; - } - - my $expenseName = ""; - if ($expense->{'type'} == DINNER) { - $expenseName = "Dinner"; - } elsif ($expense->{'type'} == BREAKFAST) { - $expenseName = "Breakfast"; - } elsif ($expense->{'type'} == CAR_RENTAL) { - $expenseName = "Car Rental"; - } - - my $mealOverExpensesMarker = $expense->{'type'} == DINNER && $expense->{'amount'} > 5000 || $expense->{'type'} == BREAKFAST && $expense->{'amount'} > 1000 ? "X" : " "; - - print "$expenseName\t".$expense->{'amount'}."\t$mealOverExpensesMarker\n"; - $total += $expense->{'amount'}; - } - print "Meal Expenses: $mealExpenses\n"; - print "Total Expenses: $total\n"; -} - -printReport(new Expense(CAR_RENTAL, 5), new Expense(DINNER, 5000), new Expense(BREAKFAST, 1001), new Expense(CAR_RENTAL, 4)); + $total += $_->{'amount'} for @_; + $total; +} + +printReport( + new Expense(ExpenseType::DINNER, 5000), + new Expense(ExpenseType::DINNER, 5001), + new Expense(ExpenseType::BREAKFAST, 1000), + new Expense(ExpenseType::BREAKFAST, 1001), + new Expense(ExpenseType::CAR_RENTAL, 4), + new Expense(ExpenseType::LUNCH, 2000), + new Expense(ExpenseType::LUNCH, 2001), +); diff --git a/expensereport-perl/Makefile b/expensereport-perl/Makefile index da9821cd..8692ef38 100644 --- a/expensereport-perl/Makefile +++ b/expensereport-perl/Makefile @@ -1,3 +1,5 @@ +SHELL:=bash + .PHONY: all all: - ./ExpenseReport.pl + diff expected.txt <(faketime -f '2022-07-01 10:20:30' ./ExpenseReport.pl) diff --git a/expensereport-perl/expected.txt b/expensereport-perl/expected.txt new file mode 100644 index 00000000..22fd7418 --- /dev/null +++ b/expensereport-perl/expected.txt @@ -0,0 +1,10 @@ +Expenses: Fri Jul 1 10:20:30 2022 +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal Expenses: 16003 +Total Expenses: 16007 diff --git a/expensereport-postscript/ExpenseReport.ps b/expensereport-postscript/ExpenseReport.ps index bb78730a..61b39c29 100644 Binary files a/expensereport-postscript/ExpenseReport.ps and b/expensereport-postscript/ExpenseReport.ps differ diff --git a/expensereport-postscript/Makefile b/expensereport-postscript/Makefile index 392b5a83..9a740068 100644 --- a/expensereport-postscript/Makefile +++ b/expensereport-postscript/Makefile @@ -1,3 +1,12 @@ +SHELL:=bash + +.PHONY: all +all: test + .PHONY: run run: gs -q -sDEVICE=nullpage -- ExpenseReport.ps + +.PHONY: test +test: + diff expected.txt <($(MAKE) -s run) diff --git a/expensereport-postscript/expected.txt b/expensereport-postscript/expected.txt new file mode 100644 index 00000000..d5d69d29 --- /dev/null +++ b/expensereport-postscript/expected.txt @@ -0,0 +1,10 @@ +Expenses: +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal Expenses: 16003 +Total Expenses: 16007 diff --git a/expensereport-prolog/ExpenseReport.pl b/expensereport-prolog/ExpenseReport.pl index 48ca2034..0144f852 100644 --- a/expensereport-prolog/ExpenseReport.pl +++ b/expensereport-prolog/ExpenseReport.pl @@ -1,32 +1,76 @@ -expense(Type, Amount, [Type, Amount]). +expense([Type, Amount], Type, Amount). -print_report([X|Y], Meals, Total) :- - expense(Type, Amount, X), - ((Type = dinner; Type = breakfast) -> NMeals is Meals + Amount; NMeals is Meals), +expense_type(dinner, 'Dinner', 5000). +expense_type(breakfast, 'Breakfast', 1000). +expense_type(car_rental, 'Car Rental', inf). +expense_type(lunch, 'Lunch', 2000). +is_meal(dinner). +is_meal(breakfast). +is_meal(lunch). - (Type = dinner -> Name = 'Dinner'; - Type = breakfast -> Name = 'Breakfast'; - Type = car_rental -> Name = 'Car Rental'), +is_meal(Expense) :- expense(Expense, Type, _), is_meal(Type). +is_over_limit(Expense) :- expense(Expense, Type, Amount), expense_type(Type, _, Limit), Amount > Limit. +get_amount(Expense, Amount) :- expense(Expense, _, Amount). - ((Type = dinner, Amount > 5000; Type = breakfast, Amount > 1000) -> Marker = 'X'; Marker = ' '), +print_report(Expenses) :- + generate_report(Expenses, Report), + write(Report). - format('~a\t~a\t~a~n', [Name, Amount, Marker]), - NTotal is Total + Amount, - print_report(Y, NMeals, NTotal). +generate_report(Expenses, Report) :- + report_header(Header), + report_details(Expenses, Details), + report_summary(Expenses, Summary), + atomic_list_concat([Header, Details, Summary], '', Report). -print_report([], Meals, Total) :- - format('Meal expenses: ~a~n', [Meals]), - format('Total expenses: ~a~n', [Total]). +report_header(Header) :- + get_time(Time), + stamp_date_time(Time, Date, 'UTC'), + format_time(atom(TS), '%FT%T%z', Date, posix), + format(string(Header), 'Expenses: ~a~n', [TS]). -print_report(X) :- - format('Expenses: ~a~n', ['']), - print_report(X, 0, 0). +report_details(Expenses, Details) :- + maplist(report_detail, Expenses, Detail), + atomic_list_concat(Detail, '', Details). + +report_detail(Expense, Detail) :- + expense(Expense, Type, Amount), + expense_type(Type, Name, _), + is_over_limit_marker(Expense, Marker), + format(string(Detail), '~a\t~a\t~a~n', [Name, Amount, Marker]). + +is_over_limit_marker(Expense, 'X') :- is_over_limit(Expense). +is_over_limit_marker(Expense, ' ') :- not(is_over_limit(Expense)). + +report_summary(Expenses, Summary) :- + sum_meals(Meals, Expenses), + sum_total(Total, Expenses), + format(string(Summary), 'Meal expenses: ~a~nTotal expenses: ~a~n', [Meals, Total]). + +sum_meals(Sum, Expenses) :- include(is_meal, Expenses, Meals), sum_total(Sum, Meals). +sum_total(Sum, Expenses) :- maplist(get_amount, Expenses, Amounts), foldl(plus, Amounts, 0, Sum). + +test() :- + sum_meals(0, []), + sum_total(0, []), + sum_meals(11, [[dinner, 1], [breakfast, 2], [car_rental, 4], [lunch, 8]]), + sum_total(15, [[dinner, 1], [breakfast, 2], [car_rental, 4], [lunch, 8]]), + is_over_limit([dinner, 5001]), + is_over_limit([breakfast, 1001]), + is_over_limit([lunch, 2001]), + not(is_over_limit([dinner, 5000])), + not(is_over_limit([breakfast, 1000])), + not(is_over_limit([lunch, 2000])), + not(is_over_limit([car_rental, inf])), + !. main() :- + test(), print_report([ [dinner, 5000], [dinner, 5001], [breakfast, 1000], [breakfast, 1001], - [car_rental, 4] + [car_rental, 4], + [lunch, 2000], + [lunch, 2001] ]). diff --git a/expensereport-python/.gitignore b/expensereport-python/.gitignore index 07da7345..7c686ab6 100644 --- a/expensereport-python/.gitignore +++ b/expensereport-python/.gitignore @@ -1,3 +1,5 @@ .idea/ __pycache__/ venv/ +.coverage +coverage-reports/ diff --git a/expensereport-python/Makefile b/expensereport-python/Makefile index 2f92dc41..7da0d919 100644 --- a/expensereport-python/Makefile +++ b/expensereport-python/Makefile @@ -16,6 +16,10 @@ pip-install: .PHONY: test test: .coverage +.PHONY: black +black: + black . + .PHONY: lint lint: mypy *.py diff --git a/expensereport-python/expense_report.py b/expensereport-python/expense_report.py index 4795833e..0edc9123 100644 --- a/expensereport-python/expense_report.py +++ b/expensereport-python/expense_report.py @@ -1,43 +1,68 @@ import locale -from enum import Enum, unique, auto +import sys from datetime import datetime -from typing import List +from dataclasses import dataclass -@unique -class ExpenseType(Enum): - DINNER = auto() - BREAKFAST = auto() - CAR_RENTAL = auto() +@dataclass(frozen=True) +class ExpenseType: + name: str + limit: int + is_meal: bool +# pylint: disable=too-few-public-methods +class ExpenseTypes: + DINNER = ExpenseType("Dinner", 5000, True) + BREAKFAST = ExpenseType("Breakfast", 1000, True) + CAR_RENTAL = ExpenseType("Car Rental", sys.maxsize, False) + LUNCH = ExpenseType("Lunch", 2000, True) + + @classmethod + def value_of(cls, name: str): + return getattr(cls, name) + + +@dataclass(frozen=True) class Expense: - type: ExpenseType + expenseType: ExpenseType amount: int + def name(self): + return self.expenseType.name + + def is_meal(self): + return self.expenseType.is_meal + + def is_over_limit(self): + return self.amount > self.expenseType.limit + class ExpenseReport: - def print_report(self, expenses: List[Expense]): - total = 0 - meals = 0 - - print("Expense Report", datetime.now().strftime(locale.nl_langinfo(locale.D_T_FMT))) - - for expense in expenses: - if expense.type == ExpenseType.DINNER or expense.type == ExpenseType.BREAKFAST: - meals += expense.amount - - name = "" - if expense.type == ExpenseType.DINNER: - name = "Dinner" - elif expense.type == ExpenseType.BREAKFAST: - name = "Breakfast" - elif expense.type == ExpenseType.CAR_RENTAL: - name = "Car Rental" - - meal_over_expenses_marker = "X" if expense.type == ExpenseType.DINNER and expense.amount > 5000 or expense.type == ExpenseType.BREAKFAST and expense.amount > 1000 else " " - print(name, "\t", expense.amount, "\t", meal_over_expenses_marker) - total += expense.amount - - print("Meals:", meals) - print("Total:", total) + def print_report(self, expenses: list[Expense], timestamp=datetime.now()): + print(self.generate_report(expenses, timestamp), end="") + + def generate_report(self, expenses: list[Expense], timestamp=datetime.now()): + return self.header(timestamp) + self.body(expenses) + self.summary(expenses) + + def header(self, timestamp): + return f"Expenses {timestamp.strftime(locale.nl_langinfo(locale.D_T_FMT))}\n" + + def body(self, expenses: list[Expense]): + return "".join(map(self.detail, expenses)) + + def detail(self, expense: Expense): + over_limit_marker = "X" if expense.is_over_limit() else " " + return f"{expense.name()}\t{expense.amount}\t{over_limit_marker}\n" + + def summary(self, expenses: list[Expense]): + return ( + f"Meal expenses: {self.sum_meals(expenses)}\n" + f"Total expenses: {self.sum_total(expenses)}\n" + ) + + def sum_total(self, expenses: list[Expense]): + return sum(map(lambda e: e.amount, expenses)) + + def sum_meals(self, expenses: list[Expense]): + return sum(map(lambda e: e.amount, filter(Expense.is_meal, expenses))) diff --git a/expensereport-python/features/ExpenseReportCharacterization.feature b/expensereport-python/features/ExpenseReportCharacterization.feature new file mode 100644 index 00000000..8376a7f2 --- /dev/null +++ b/expensereport-python/features/ExpenseReportCharacterization.feature @@ -0,0 +1,94 @@ +Feature: Expense Report + Scenario: Empty Report + When generating a report for the following expenses: + | type | amount | + Then the report MUST look like this: + """ + Expenses $now + Meal expenses: 0 + Total expenses: 0 + + """ + + Scenario: Dinner is a meal with a limit of 5000 + When generating a report for the following expenses: + | type | amount | + | DINNER | 5000 | + | DINNER | 5001 | + Then the report MUST look like this: + """ + Expenses $now + Dinner\t5000\t\s + Dinner\t5001\tX + Meal expenses: 10001 + Total expenses: 10001 + + """ + + Scenario: Breakfast is a meal with a limit of 1000 + When generating a report for the following expenses: + | type | amount | + | BREAKFAST | 1000 | + | BREAKFAST | 1001 | + Then the report MUST look like this: + """ + Expenses $now + Breakfast\t1000\t\s + Breakfast\t1001\tX + Meal expenses: 2001 + Total expenses: 2001 + + """ + + Scenario: Car Rental is not a meal and has no limit + When generating a report for the following expenses: + | type | amount | + | CAR_RENTAL | 2147483647 | + Then the report MUST look like this: + """ + Expenses $now + Car Rental\t2147483647\t\s + Meal expenses: 0 + Total expenses: 2147483647 + + """ + + Scenario: Lunch is a meal with a limit of 2000 + When generating a report for the following expenses: + | type | amount | + | LUNCH | 2000 | + | LUNCH | 2001 | + Then the report MUST look like this: + """ + Expenses $now + Lunch\t2000\t\s + Lunch\t2001\tX + Meal expenses: 4001 + Total expenses: 4001 + + """ + + Scenario: Combined characterization test + When generating a report for the following expenses: + | type | amount | + | DINNER | 5000 | + | DINNER | 5001 | + | BREAKFAST | 1000 | + | BREAKFAST | 1001 | + | CAR_RENTAL | 4 | + | LUNCH | 2000 | + | LUNCH | 2001 | + Then the report MUST look like this: + """ + Expenses $now + Dinner\t5000\t\s + Dinner\t5001\tX + Breakfast\t1000\t\s + Breakfast\t1001\tX + Car Rental\t4\t\s + Lunch\t2000\t\s + Lunch\t2001\tX + Meal expenses: 16003 + Total expenses: 16007 + + """ diff --git a/expensereport-python/features/steps/steps_expense_report.py b/expensereport-python/features/steps/steps_expense_report.py new file mode 100644 index 00000000..82741e54 --- /dev/null +++ b/expensereport-python/features/steps/steps_expense_report.py @@ -0,0 +1,29 @@ +from behave import * +from expense_report import ExpenseReport, Expense, ExpenseTypes +import locale +from datetime import datetime + + +@when("generating a report for the following expenses") +def step_impl(context): + expenses = list( + map( + lambda row: Expense(ExpenseTypes.value_of(row["type"]), int(row["amount"])), + context.table, + ) + ) + context.timestamp = datetime.now() + ExpenseReport().print_report(expenses, context.timestamp) + context.captured_stdout = context.stdout_capture.getvalue() + + +@then("the report MUST look like this") +def step_impl(context): + expected = ( + context.text.replace( + "$now", context.timestamp.strftime(locale.nl_langinfo(locale.D_T_FMT)) + ) + .replace("\\t", "\t") + .replace("\\s", " ") + ) + assert expected == context.captured_stdout diff --git a/expensereport-python/main.py b/expensereport-python/main.py index 7a6f5ab5..c1d0970f 100644 --- a/expensereport-python/main.py +++ b/expensereport-python/main.py @@ -1,11 +1,16 @@ -from expense_report import ExpenseReport, Expense, ExpenseType +from expense_report import ExpenseReport, Expense, ExpenseTypes # Press the green button in the gutter to run the script. -if __name__ == '__main__': - expense = Expense() - expense.type = ExpenseType.DINNER - expense.amount = 7500 - ExpenseReport().print_report([expense]) +if __name__ == "__main__": + ExpenseReport().print_report( + [ + Expense(ExpenseTypes.DINNER, 5000), + Expense(ExpenseTypes.DINNER, 5001), + Expense(ExpenseTypes.BREAKFAST, 1000), + Expense(ExpenseTypes.BREAKFAST, 1001), + Expense(ExpenseTypes.CAR_RENTAL, 4), + ] + ) # See PyCharm help at https://www.jetbrains.com/help/pycharm/ diff --git a/expensereport-python/requirements.txt b/expensereport-python/requirements.txt index a2b44fb3..ca74fabf 100644 --- a/expensereport-python/requirements.txt +++ b/expensereport-python/requirements.txt @@ -1,32 +1,6 @@ -#astroid==2.4.2 -behave==1.2.6 -coverage==5.3 -dodgy==0.2.1 -flake8==3.8.4 -flake8-polyfill==1.0.2 -#isort==5.5.4 -#lazy-object-proxy==1.5.1 -mccabe==0.6.1 -mypy==0.782 -mypy-extensions==0.4.3 -parse==1.18.0 -parse-type==0.5.2 -#pep8-naming==0.11.1 -prospector==1.3.1 -pycodestyle==2.6.0 -pydocstyle==5.1.1 -pyflakes==2.2.0 -#pylint==2.6.0 -pylint-celery==0.3 -#pylint-django==2.3.0 -pylint-flask==0.6 -pylint-plugin-utils==0.6 -PyYAML==5.3.1 -requirements-detector==0.7 -setoptconf==0.2.0 -six==1.15.0 -snowballstemmer==2.0.0 -toml==0.10.1 -typed-ast==1.4.1 -typing-extensions==3.7.4.3 -wrapt==1.12.1 +behave +black +coverage +flake8 +mypy +prospector diff --git a/expensereport-rexx/ExpenseReport.rexx b/expensereport-rexx/ExpenseReport.rexx index 3bc39503..3f4c6781 100644 --- a/expensereport-rexx/ExpenseReport.rexx +++ b/expensereport-rexx/ExpenseReport.rexx @@ -1,39 +1,69 @@ /* Expense Report */ -DINNER = 0; BREAKFAST = 1; CAR_RENTAL = 2 +expenseType.DINNER.name = "Dinner" +expenseType.DINNER.limit = 5000 +expenseType.DINNER.isMeal = 1 +expenseType.BREAKFAST.name = "Breakfast" +expenseType.BREAKFAST.limit = 1000 +expenseType.BREAKFAST.isMeal = 1 +expenseType.CAR_RENTAL.name = "Car Rental" +expenseType.CAR_RENTAL.limit = 65535 +expenseType.CAR_RENTAL.isMeal = 0 +expenseType.LUNCH.name = "Lunch" +expenseType.LUNCH.limit = 2000 +expenseType.LUNCH.isMeal = 1 -expense.0.type = DINNER; expense.0.amount = 5000 -expense.1.type = DINNER; expense.1.amount = 5001 -expense.2.type = BREAKFAST; expense.2.amount = 1000 -expense.3.type = BREAKFAST; expense.3.amount = 1001 -expense.4.type = CAR_RENTAL; expense.4.amount = 4 +expense.0 = 7 +expense.1.type = DINNER; expense.1.amount = 5000 +expense.2.type = DINNER; expense.2.amount = 5001 +expense.3.type = BREAKFAST; expense.3.amount = 1000 +expense.4.type = BREAKFAST; expense.4.amount = 1001 +expense.5.type = CAR_RENTAL; expense.5.amount = 4 +expense.6.type = LUNCH; expense.6.amount = 2000 +expense.7.type = LUNCH; expense.7.amount = 2001 CALL printReport EXIT 0 printReport: - total = 0 - mealExpenses = 0 + CALL printHeader + CALL printDetails + CALL printSummary + RETURN +printHeader: SAY "Expenses:" DATE() TIME() + RETURN - DO i = 0 TO 4 - IF expense.i.type = DINNER | expense.i.type = BREAKFAST THEN mealExpenses = mealExpenses + expense.i.amount +printDetail: + typ = expense.i.type + expenseName = expenseType.typ.name + IF expense.i.amount > expenseType.typ.limit THEN mealOverExpensesMarker = "X" + ELSE mealOverExpensesMarker = " " + SAY expenseName expense.i.amount mealOverExpensesMarker + RETURN - expenseName = "" - SELECT - WHEN expense.i.type = DINNER THEN expenseName = "Dinner" - WHEN expense.i.type = BREAKFAST THEN expenseName = "Breakfast" - WHEN expense.i.type = CAR_RENTAL THEN expenseName = "Car Rental" - END +printDetails: + DO i = 1 TO expense.0 + CALL printDetail + END + RETURN - IF expense.i.type = DINNER & expense.i.amount > 5000 | expense.i.type = BREAKFAST & expense.i.amount > 1000 THEN mealOverExpensesMarker = "X" - ELSE mealOverExpensesMarker = " " +printSummary: + SAY "Meal expenses:" sumMeals() + SAY "Total expenses:" sumTotal() + RETURN - SAY expenseName expense.i.amount mealOverExpensesMarker +sumMeals: + meals = 0 + DO i = 1 TO expense.0 + typ = expense.i.type + IF expenseType.typ.isMeal THEN meals = meals + expense.i.amount + END + RETURN meals +sumTotal: + total = 0 + DO i = 1 TO expense.0 total = total + expense.i.amount END - - SAY "Meal expenses:" mealExpenses - SAY "Total expenses:" total - RETURN + RETURN total diff --git a/expensereport-rexx/Makefile b/expensereport-rexx/Makefile index dbeeb182..74da1d80 100644 --- a/expensereport-rexx/Makefile +++ b/expensereport-rexx/Makefile @@ -1,3 +1,5 @@ +SHELL:=bash + .PHONY: all all: - rexx ExpenseReport.rexx + diff expected.txt <(faketime -f '2022-06-05 17:08:23' rexx ExpenseReport.rexx) diff --git a/expensereport-rexx/expected.txt b/expensereport-rexx/expected.txt new file mode 100644 index 00000000..56496630 --- /dev/null +++ b/expensereport-rexx/expected.txt @@ -0,0 +1,10 @@ +Expenses: 5 Jun 2022 17:08:23 +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal expenses: 16003 +Total expenses: 16007 diff --git a/expensereport-rust/Makefile b/expensereport-rust/Makefile index 358286b9..4f18a137 100644 --- a/expensereport-rust/Makefile +++ b/expensereport-rust/Makefile @@ -1,3 +1,21 @@ +SHELL:=/bin/bash + .PHONY: all -all: +all: test + +.PHONY: test +test: unittest acceptancetest + +.PHONY: build +build: target/debug/expensereport-rust + +target/debug/expensereport-rust: src/main.rs + cargo build + +.PHONY: acceptancetest +acceptancetest: ./target/debug/expensereport-rust + diff <(tail -n +2 gold.txt) <(./target/debug/expensereport-rust | tail -n +2) + +.PHONY: unittest +unittest: cargo test diff --git a/expensereport-rust/gold.txt b/expensereport-rust/gold.txt new file mode 100644 index 00000000..f19edef5 --- /dev/null +++ b/expensereport-rust/gold.txt @@ -0,0 +1,13 @@ +Expense Report 2021-08-20 14:37:24.107318811 UTC +Dinner 1 +Dinner 5000 +Dinner 5001 X +Breakfast 2 +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 8 +Lunch 2000 +Lunch 2001 X +Meal Expenses: 16014 +Total Expenses: 16018 diff --git a/expensereport-rust/src/main.rs b/expensereport-rust/src/main.rs index 9a997628..8816df4b 100644 --- a/expensereport-rust/src/main.rs +++ b/expensereport-rust/src/main.rs @@ -1,51 +1,86 @@ use chrono; -enum ExpenseType { - Dinner, Breakfast, CarRental +struct ExpenseType { + name: &'static str, + limit: i32, + is_meal: bool, } +const DINNER: ExpenseType = ExpenseType {name: "Dinner", limit: 5000, is_meal: true}; +const BREAKFAST: ExpenseType = ExpenseType {name: "Breakfast", limit: 1000, is_meal: true}; +const CAR_RENTAL: ExpenseType = ExpenseType {name: "Car Rental", limit: i32::MAX, is_meal: false}; +const LUNCH: ExpenseType = ExpenseType {name: "Lunch", limit: 2000, is_meal: true}; + struct Expense { type_: ExpenseType, amount: i32, } -fn print_report(expenses: &[Expense]) { - let mut meal_expenses = 0; - let mut total = 0; +impl Expense { + fn get_name(&self) -> &str { + self.type_.name + } + + fn is_over_limit(&self) -> bool { + self.amount > self.type_.limit + } + fn is_meal(&self) -> bool { + self.type_.is_meal + } +} + +fn print_report(expenses: &[Expense]) { println!("Expense Report {}", chrono::offset::Utc::now()); for expense in expenses { - match expense.type_ { - ExpenseType::Dinner => meal_expenses += expense.amount, - ExpenseType::Breakfast => meal_expenses += expense.amount, - _ => (), - } - - let expense_name: &str; - match expense.type_ { - ExpenseType::Dinner => expense_name = "Dinner", - ExpenseType::Breakfast => expense_name = "Breakfast", - ExpenseType::CarRental => expense_name = "Car Rental", - } - - let meal_over_expenses_marker = if matches!(expense.type_, ExpenseType::Dinner) && expense.amount > 5000 || matches!(expense.type_, ExpenseType::Breakfast) && expense.amount > 1000 { "X" } else { " " }; - - println!("{}\t{}\t{}", expense_name, expense.amount, meal_over_expenses_marker); - total += expense.amount; + let expense_name = expense.get_name(); + let expense_over_limit_marker = if expense.is_over_limit() { "X" } else { " " }; + + println!("{}\t{}\t{}", expense_name, expense.amount, expense_over_limit_marker); } - println!("Meal Expenses: {}", meal_expenses); - println!("Total Expenses: {}", total); + println!("Meal Expenses: {}", sum_meal_expenses(expenses)); + println!("Total Expenses: {}", sum_expenses(expenses)); +} + +fn sum_expenses(expenses: &[Expense]) -> i32 { + sum_filtered_expenses(expenses, |_| { true }) +} + +fn sum_meal_expenses(expenses: &[Expense]) -> i32 { + sum_filtered_expenses(expenses, |expense: &&Expense| { expense.is_meal() }) +} + +fn sum_filtered_expenses(expenses: &[Expense], predicate: fn(&&Expense) -> bool) -> i32 { + expenses.iter() + .filter(predicate) + .map(|expense| { expense.amount }) + .sum() } fn main() { + print_report(&[ + Expense{ type_: DINNER, amount: 1}, + Expense{ type_: DINNER, amount: 5000}, + Expense{ type_: DINNER, amount: 5001}, + Expense{ type_: BREAKFAST, amount: 2}, + Expense{ type_: BREAKFAST, amount: 1000}, + Expense{ type_: BREAKFAST, amount: 1001}, + Expense{ type_: CAR_RENTAL, amount: 4}, + Expense{ type_: LUNCH, amount: 8}, + Expense{ type_: LUNCH, amount: 2000}, + Expense{ type_: LUNCH, amount: 2001}, + ]); } #[cfg(test)] mod tests { + // use crate::{print_report, Expense, ExpenseType}; + #[test] fn characterize_print_report() { - + // print_report(&[Expense{ type_: ExpenseType::Dinner, amount: 1000}]); + // assert_eq!() } } diff --git a/expensereport-sql/expesereport.sql b/expensereport-sql/expesereport.sql index de9f774a..de0e394e 100755 --- a/expensereport-sql/expesereport.sql +++ b/expensereport-sql/expesereport.sql @@ -1,23 +1,39 @@ #!/bin/sh sqlite3 < 5000 or type = "breakfast" and amount > 1000) + 1, 1)) - FROM Expenses; +SELECT printf("%s %s %s", name, amount, substr(" X", (expenseLimit IS NOT NULL AND amount > expenseLimit) + 1, 1)) + FROM Expenses JOIN ExpenseTypes ON Expenses.type = ExpenseTypes.id; SELECT printf("Meal expenses: %s", sum(amount)) - FROM Expenses - WHERE type = "dinner" OR type = "breakfast"; + FROM Expenses JOIN ExpenseTypes ON Expenses.type = ExpenseTypes.id + WHERE isMeal; SELECT printf("Total expenses: %s", sum(amount)) FROM Expenses; diff --git a/expensereport-typescript/README.md b/expensereport-typescript/README.md index ef2fec2c..f1e921ce 100644 --- a/expensereport-typescript/README.md +++ b/expensereport-typescript/README.md @@ -14,7 +14,7 @@ The ExpenseReport refactoring example in TypeScript. ```shell ./configure.sh # will use nvm to install and activate the appropriate version of node npm test # will run all the tests -npm bulid # will create a build of the project +npm build # will create a build of the project node build/output.js # will run the compiled output npm run format:check # runs the linter and formatter rules npm run format:fix # applies a fix to the fixable issues diff --git a/expensereport-typescript/package-lock.json b/expensereport-typescript/package-lock.json index 18637eb1..8f6739d1 100644 --- a/expensereport-typescript/package-lock.json +++ b/expensereport-typescript/package-lock.json @@ -79,9 +79,9 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -95,26 +95,19 @@ } }, "node_modules/@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dependencies": { - "@babel/types": "^7.15.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.15.0", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz", @@ -133,43 +126,39 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/types": "^7.14.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -260,20 +249,28 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -300,12 +297,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -377,9 +374,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.0.tgz", - "integrity": "sha512-0v7oNOjr6YT9Z2RAOTv4T9aP+ubfx4Q/OhVtAet7PFDt0t9Oy6Jn+/rfC6b8HJ5zEqrQCiMxJfgtHpmIminmJQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -537,42 +534,108 @@ } }, "node_modules/@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", + "node_modules/@babel/template/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", - "debug": "^4.1.0", + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/template/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/template/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/template/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/template/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/template/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/template/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -580,16 +643,62 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/traverse/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/traverse/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@babel/traverse/node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -598,12 +707,32 @@ "node": ">=4" } }, + "node_modules/@babel/traverse/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -920,6 +1049,58 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz", + "integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz", + "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1014,27 +1195,27 @@ } }, "node_modules/@types/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", + "version": "8.56.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.3.tgz", + "integrity": "sha512-PvSf1wfv2wJpVIFUMSb+i4PvqNYkB9Rkp9ZDO3oaWzq4SKhsQk4mrMBr3ZH06I0hKrVGLBacmgl8JM4WVjb9dg==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "node_modules/@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "node_modules/@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/graceful-fs": { "version": "4.1.5", @@ -1262,133 +1443,133 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -1556,9 +1737,9 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { "node": ">=8" } @@ -1736,25 +1917,34 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "node_modules/browserslist": { - "version": "4.16.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.7.tgz", - "integrity": "sha512-7I4qVwqZltJ7j37wObBe3SoTz+nS8APaNcrBOlgoirb6/HbEU2XxW/LpUDTCngM6iauwFqmRTuOMfyKnFGY5JA==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001248", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.793", - "escalade": "^3.1.1", - "node-releases": "^1.1.73" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/bs-logger": { @@ -1798,13 +1988,23 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001249", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001249.tgz", - "integrity": "sha512-vcX4U8lwVXPdqzPWi6cAJ3FnQaqXbBqy/GZseKNQzRj37J7qZdGcBtxq/QLFNLLlfsoXLUdHw8Iwenri86Tagw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "version": "1.0.30001589", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", + "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] }, "node_modules/chalk": { "version": "4.1.2", @@ -2133,9 +2333,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.798", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.798.tgz", - "integrity": "sha512-fwsr6oXAORoV9a6Ak2vMCdXfmHIpAGgpOGesulS1cbGgJmrMl3H+GicUyRG3t+z9uHTMrIuMTleFDW+EUFYT3g==" + "version": "1.4.681", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.681.tgz", + "integrity": "sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==" }, "node_modules/emittery": { "version": "0.8.1", @@ -2154,9 +2354,9 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enhanced-resolve": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", - "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -2196,9 +2396,9 @@ } }, "node_modules/es-module-lexer": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz", - "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" }, "node_modules/escalade": { "version": "3.1.1", @@ -2858,9 +3058,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/has": { "version": "1.0.3", @@ -3198,9 +3398,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -4091,9 +4291,9 @@ } }, "node_modules/jest-worker": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.6.tgz", - "integrity": "sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -4201,11 +4401,6 @@ "node": ">=4" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -4222,12 +4417,9 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dependencies": { - "minimist": "^1.2.5" - }, + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, @@ -4449,9 +4641,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -4522,9 +4714,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4532,11 +4724,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -4577,9 +4764,9 @@ } }, "node_modules/node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -4771,6 +4958,11 @@ "node": ">=8" } }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -4901,6 +5093,11 @@ "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4971,6 +5168,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -5100,9 +5302,9 @@ } }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -5117,9 +5319,9 @@ } }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5136,9 +5338,9 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=" }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dependencies": { "randombytes": "^2.1.0" } @@ -5213,9 +5415,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -5433,13 +5635,14 @@ } }, "node_modules/terser": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.1.tgz", - "integrity": "sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==", + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz", + "integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==", "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" + "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" @@ -5449,16 +5652,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz", - "integrity": "sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA==", - "dependencies": { - "jest-worker": "^27.0.2", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.0" + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -5469,20 +5671,28 @@ }, "peerDependencies": { "webpack": "^5.1.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.4.0" } }, "node_modules/terser/node_modules/commander": { @@ -5490,14 +5700,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, - "node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "engines": { - "node": ">= 8" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -5527,9 +5729,9 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "node_modules/tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" }, "node_modules/to-fast-properties": { "version": "2.0.0", @@ -5551,13 +5753,14 @@ } }, "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { "node": ">=6" @@ -5703,13 +5906,42 @@ } }, "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "engines": { "node": ">= 4.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5718,6 +5950,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -5772,9 +6013,9 @@ } }, "node_modules/watchpack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", - "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -5792,34 +6033,34 @@ } }, "node_modules/webpack": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.49.0.tgz", - "integrity": "sha512-XarsANVf28A7Q3KPxSnX80EkCcuOer5hTOEJWJNvbskOZ+EK3pobHarGHceyUZMxpsTHBHhlV7hiQyLZzGosYw==", - "dependencies": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.0", - "es-module-lexer": "^0.7.1", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.2.0", - "webpack-sources": "^3.2.0" + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" }, "bin": { "webpack": "bin/webpack.js" @@ -5893,17 +6134,17 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.0.tgz", - "integrity": "sha512-fahN08Et7P9trej8xz/Z7eRu8ltyiygEo/hnRi9KqBUs80KeDcnf96ZJo++ewWd84fEf3xSX9bp4ZS9hbw0OBw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "engines": { "node": ">=10.13.0" } }, "node_modules/webpack/node_modules/acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -5912,9 +6153,9 @@ } }, "node_modules/webpack/node_modules/acorn-import-assertions": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz", - "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "peerDependencies": { "acorn": "^8" } @@ -5965,9 +6206,9 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "engines": { "node": ">=0.10.0" } @@ -6079,17 +6320,6 @@ "engines": { "node": ">=10" } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } }, "dependencies": { @@ -6137,9 +6367,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" }, "source-map": { "version": "0.5.7", @@ -6149,20 +6379,14 @@ } }, "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "requires": { - "@babel/types": "^7.15.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" } }, "@babel/helper-compilation-targets": { @@ -6177,36 +6401,32 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "requires": { - "@babel/types": "^7.14.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -6273,17 +6493,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.22.5" } }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==" + }, "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/helper-validator-option": { "version": "7.14.5", @@ -6301,12 +6526,12 @@ } }, "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -6362,9 +6587,9 @@ } }, "@babel/parser": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.0.tgz", - "integrity": "sha512-0v7oNOjr6YT9Z2RAOTv4T9aP+ubfx4Q/OhVtAet7PFDt0t9Oy6Jn+/rfC6b8HJ5zEqrQCiMxJfgtHpmIminmJQ==" + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==" }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -6471,62 +6696,164 @@ } }, "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" } } } }, "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", - "debug": "^4.1.0", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" } }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } } } }, "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -6775,6 +7102,49 @@ "chalk": "^4.0.0" } }, + "@jridgewell/gen-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz", + "integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz", + "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6857,27 +7227,27 @@ } }, "@types/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", + "version": "8.56.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.3.tgz", + "integrity": "sha512-PvSf1wfv2wJpVIFUMSb+i4PvqNYkB9Rkp9ZDO3oaWzq4SKhsQk4mrMBr3ZH06I0hKrVGLBacmgl8JM4WVjb9dg==", "requires": { "@types/estree": "*", "@types/json-schema": "*" } }, "@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "requires": { "@types/eslint": "*", "@types/estree": "*" } }, "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "@types/graceful-fs": { "version": "4.1.5", @@ -7031,133 +7401,133 @@ } }, "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" }, "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" }, "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" } }, "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" } }, "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "requires": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" } }, @@ -7276,9 +7646,9 @@ } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -7414,15 +7784,14 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "browserslist": { - "version": "4.16.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.7.tgz", - "integrity": "sha512-7I4qVwqZltJ7j37wObBe3SoTz+nS8APaNcrBOlgoirb6/HbEU2XxW/LpUDTCngM6iauwFqmRTuOMfyKnFGY5JA==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "requires": { - "caniuse-lite": "^1.0.30001248", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.793", - "escalade": "^3.1.1", - "node-releases": "^1.1.73" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" } }, "bs-logger": { @@ -7457,9 +7826,9 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { - "version": "1.0.30001249", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001249.tgz", - "integrity": "sha512-vcX4U8lwVXPdqzPWi6cAJ3FnQaqXbBqy/GZseKNQzRj37J7qZdGcBtxq/QLFNLLlfsoXLUdHw8Iwenri86Tagw==" + "version": "1.0.30001589", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", + "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==" }, "chalk": { "version": "4.1.2", @@ -7708,9 +8077,9 @@ } }, "electron-to-chromium": { - "version": "1.3.798", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.798.tgz", - "integrity": "sha512-fwsr6oXAORoV9a6Ak2vMCdXfmHIpAGgpOGesulS1cbGgJmrMl3H+GicUyRG3t+z9uHTMrIuMTleFDW+EUFYT3g==" + "version": "1.4.681", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.681.tgz", + "integrity": "sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==" }, "emittery": { "version": "0.8.1", @@ -7723,9 +8092,9 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "enhanced-resolve": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", - "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -7753,9 +8122,9 @@ } }, "es-module-lexer": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz", - "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" }, "escalade": { "version": "3.1.1", @@ -8232,9 +8601,9 @@ } }, "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "has": { "version": "1.0.3", @@ -8477,9 +8846,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, @@ -9152,9 +9521,9 @@ } }, "jest-worker": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.6.tgz", - "integrity": "sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -9231,11 +9600,6 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -9252,12 +9616,9 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "kind-of": { "version": "6.0.3", @@ -9415,9 +9776,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, @@ -9472,18 +9833,13 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -9515,9 +9871,9 @@ "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" }, "node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "normalize-path": { "version": "3.0.0", @@ -9649,6 +10005,11 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -9745,6 +10106,11 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9786,6 +10152,11 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -9872,9 +10243,9 @@ } }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -9882,9 +10253,9 @@ } }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "requires": { "lru-cache": "^6.0.0" } @@ -9895,9 +10266,9 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=" }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "requires": { "randombytes": "^2.1.0" } @@ -9954,9 +10325,9 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -10117,48 +10488,38 @@ } }, "terser": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.1.tgz", - "integrity": "sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==", + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz", + "integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==", "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" + "source-map-support": "~0.5.20" }, "dependencies": { + "acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" } } }, "terser-webpack-plugin": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz", - "integrity": "sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA==", - "requires": { - "jest-worker": "^27.0.2", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.0" - }, - "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - } + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "requires": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" } }, "test-exclude": { @@ -10187,9 +10548,9 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" }, "to-fast-properties": { "version": "2.0.0", @@ -10205,13 +10566,14 @@ } }, "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" } }, "tr46": { @@ -10295,9 +10657,18 @@ "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==" }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" + }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } }, "uri-js": { "version": "4.4.1", @@ -10307,6 +10678,15 @@ "punycode": "^2.1.0" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -10354,9 +10734,9 @@ } }, "watchpack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", - "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -10368,45 +10748,45 @@ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "webpack": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.49.0.tgz", - "integrity": "sha512-XarsANVf28A7Q3KPxSnX80EkCcuOer5hTOEJWJNvbskOZ+EK3pobHarGHceyUZMxpsTHBHhlV7hiQyLZzGosYw==", - "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.0", - "es-module-lexer": "^0.7.1", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.2.0", - "webpack-sources": "^3.2.0" + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" }, "dependencies": { "acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==" + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" }, "acorn-import-assertions": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz", - "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "requires": {} } } @@ -10441,9 +10821,9 @@ } }, "webpack-sources": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.0.tgz", - "integrity": "sha512-fahN08Et7P9trej8xz/Z7eRu8ltyiygEo/hnRi9KqBUs80KeDcnf96ZJo++ewWd84fEf3xSX9bp4ZS9hbw0OBw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" }, "whatwg-encoding": { "version": "1.0.5", @@ -10482,9 +10862,9 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" }, "wrap-ansi": { "version": "7.0.0", @@ -10561,11 +10941,6 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } } diff --git a/expensereport-typescript/src/ExpenseReport.test.ts b/expensereport-typescript/src/ExpenseReport.test.ts index ad579a3d..2927fa1f 100644 --- a/expensereport-typescript/src/ExpenseReport.test.ts +++ b/expensereport-typescript/src/ExpenseReport.test.ts @@ -1,4 +1,4 @@ -import { printHelloWorld, printReport, sumTwoValues, Expense, ExpenseType } from './ExpenseReport' +import { Expense, ExpenseType, printHelloWorld, printReport, sumTwoValues } from './ExpenseReport' describe(`ExpenseReport`, () => { it(`should keep its original behavior`, () => { @@ -8,10 +8,65 @@ describe(`ExpenseReport`, () => { return true; }) printReport([ - new Expense("dinner", 5001) + new Expense(ExpenseType.DINNER, 5001) ]) expect(interceptedOutput).toEqual("") }) + + it('printReport', () => { + const originalStdoutWrite = process.stdout.write.bind(process.stdout); + + let interceptedOutput = ''; + + process.stdout.write = ( + chunk: any, + encodingOrCallback?: string | ((error?: Error | null) => void), + callback?: (error?: Error | null) => void + ): boolean => { + let content = ''; + + if (typeof chunk === 'string' || Buffer.isBuffer(chunk)) { + content = chunk.toString(typeof encodingOrCallback === 'string' ? encodingOrCallback as BufferEncoding : undefined); + } + + interceptedOutput += content; + + if (typeof encodingOrCallback === 'function') { + encodingOrCallback(); + } else if (typeof callback === 'function') { + callback(); + } + + return true; + }; + + const expenses: Expense[] = [ + new Expense(ExpenseType.BREAKFAST, 1), + new Expense(ExpenseType.BREAKFAST, 1000), + new Expense(ExpenseType.BREAKFAST, 1001), + new Expense(ExpenseType.DINNER, 2), + new Expense(ExpenseType.DINNER, 5000), + new Expense(ExpenseType.DINNER, 5001), + new Expense(ExpenseType.CAR_RENTAL, 4), + ]; + printReport(expenses) + const now = new Date().toISOString() + const expected = `Expenses: ${now}\n` + + "Breakfast\t1\t \n" + + "Breakfast\t1000\t \n" + + "Breakfast\t1001\tX\n" + + "Dinner\t2\t \n" + + "Dinner\t5000\t \n" + + "Dinner\t5001\tX\n" + + "Car Rental\t4\t \n" + + "Meal Expenses: 12005\n" + + "Total Expenses: 12009\n" + + process.stdout.write = originalStdoutWrite; + + expect(expected).toEqual(interceptedOutput) + + }) }) describe(`given I have this test suite`, () => { diff --git a/expensereport-typescript/src/ExpenseReport.ts b/expensereport-typescript/src/ExpenseReport.ts index 4145abff..8da6d6ee 100644 --- a/expensereport-typescript/src/ExpenseReport.ts +++ b/expensereport-typescript/src/ExpenseReport.ts @@ -1,56 +1,93 @@ -const message = 'Hello, World!\n'; +const message = 'Hello, World!\n' const sumTwoValues = (a: number, b: number): number => a + b const printHelloWorld = (): void => { - process.stdout.write(message); + process.stdout.write(message) } -type ExpenseType = "dinner" | "breakfast" | "car-rental" +interface IExpenseType { + name: string; + limit: number; + isMeal: boolean; +} + +enum ExpenseType { + DINNER = 'Dinner', + BREAKFAST = 'Breakfast', + CAR_RENTAL = 'Car Rental', +} + +const ExpenseTypes: Record = { + [ExpenseType.DINNER]: {name: 'Dinner', limit: 5000, isMeal: true}, + [ExpenseType.BREAKFAST]: {name: 'Breakfast', limit: 1000, isMeal: true}, + [ExpenseType.CAR_RENTAL]: {name: 'Car Rental', limit: Number.MAX_SAFE_INTEGER, isMeal: false}, +} class Expense { type: ExpenseType amount: number + constructor(type: ExpenseType, amount: number) { this.type = type this.amount = amount } + + isMeal(): boolean { + return ExpenseTypes[this.type].isMeal + } + + getName(): string { + return ExpenseTypes[this.type].name + } + + isOverLimit(): boolean { + return this.amount > ExpenseTypes[this.type].limit + } + } -function printReport(expenses: Expense[]) { - let totalExpenses: number = 0 - let mealExpenses: number = 0 +function printReport(expenses: Expense[]): void { + printReportOn(new Date().toISOString(), expenses) +} + +function printReportOn(date: string, expenses: Expense[]): void { + printHeader(date) + printDetails(expenses) + printSummary(expenses) +} - process.stdout.write("Expenses: " + new Date().toISOString().substr(0, 10) + "\n") +function printHeader(date: string) { + process.stdout.write('Expenses: ' + date + '\n') +} +function printDetails(expenses: Expense[]) { + for (const expense of expenses) { + process.stdout.write(expense.getName() + '\t' + expense.amount + '\t' + (expense.isOverLimit() ? 'X' : ' ') + '\n') + } +} +function printSummary(expenses: Expense[]) { + process.stdout.write('Meal Expenses: ' + sumMeal(expenses) + '\n') + process.stdout.write('Total Expenses: ' + sumTotal(expenses) + '\n') +} + +function sumMeal(expenses: Expense[]) { + let mealExpenses: number = 0 for (const expense of expenses) { - if (expense.type == "dinner" || expense.type == "breakfast") { + if (expense.isMeal()) { mealExpenses += expense.amount } + } + return mealExpenses +} - let expenseName = "" - switch (expense.type) { - case "dinner": - expenseName = "Dinner" - break - case "breakfast": - expenseName = "Breakfast" - break - case "car-rental": - expenseName = "Car Rental" - break - } - - let mealOverExpensesMarker = expense.type == "dinner" && expense.amount > 5000 || expense.type == "breakfast" && expense.amount > 1000 ? "X" : " " - - process.stdout.write(expenseName + "\t" + expense.amount + "\t" + mealOverExpensesMarker + "\n") - +function sumTotal(expenses: Expense[]) { + let totalExpenses: number = 0 + for (const expense of expenses) { totalExpenses += expense.amount } - - process.stdout.write("Meal Expenses: " + mealExpenses + "\n") - process.stdout.write("Total Expenses: " + totalExpenses + "\n") + return totalExpenses } -export {sumTwoValues, printHelloWorld, printReport, Expense, ExpenseType} +export { sumTwoValues, printHelloWorld, printReport, Expense, ExpenseType } diff --git a/expensereport-xslt/ExpenseReport.xslt b/expensereport-xslt/ExpenseReport.xslt index 72d7e276..cfbee093 100644 --- a/expensereport-xslt/ExpenseReport.xslt +++ b/expensereport-xslt/ExpenseReport.xslt @@ -4,43 +4,59 @@ xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > + + + + + + + + + + + + + + + + + Expenses: - - - - Dinner - - - Breakfast - - - Car Rental - - - - - - - - X - - - - - - - + + + + + + + Meal Expenses: - + - Total: + Total Expenses: + + + + + + + + + X + + + + + + + + diff --git a/expensereport-xslt/Makefile b/expensereport-xslt/Makefile new file mode 100644 index 00000000..6d9a8a52 --- /dev/null +++ b/expensereport-xslt/Makefile @@ -0,0 +1,5 @@ +SHELL:=bash + +.PHONY: all +all: + diff expected.txt <(xsltproc ExpenseReport.xslt expenses.xml) diff --git a/expensereport-xslt/expected.txt b/expensereport-xslt/expected.txt new file mode 100644 index 00000000..d5d69d29 --- /dev/null +++ b/expensereport-xslt/expected.txt @@ -0,0 +1,10 @@ +Expenses: +Dinner 5000 +Dinner 5001 X +Breakfast 1000 +Breakfast 1001 X +Car Rental 4 +Lunch 2000 +Lunch 2001 X +Meal Expenses: 16003 +Total Expenses: 16007 diff --git a/expensereport-xslt/expenseTypes.xml b/expensereport-xslt/expenseTypes.xml new file mode 100644 index 00000000..c8e1aa6a --- /dev/null +++ b/expensereport-xslt/expenseTypes.xml @@ -0,0 +1,6 @@ + + Dinner + Breakfast + Car Rental + Lunch + diff --git a/expensereport-xslt/expenses.xml b/expensereport-xslt/expenses.xml new file mode 100644 index 00000000..53b9ff06 --- /dev/null +++ b/expensereport-xslt/expenses.xml @@ -0,0 +1,9 @@ + + + + + + + + +