diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5d3169fe016..efe9d7da187 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -279,6 +279,15 @@ jobs: run: | npx prettier --write --minify `echo "${{steps.changed.outputs.changed}}" | tr " " "\n"` || true + - name: Detect possible code duplications + working-directory: specifyweb/frontend/js_src + run: | + npx tsx lib/tests/jscpd.ts + + echo "If code duplications were found, you have the following options:" + echo "1. Refactor the code to deduplicate the code" + echo "2. If you believe having the duplication is better for readability/simplicity, then you can ignore the duplication. Instructions: https://github.com/kucherenko/jscpd/tree/master/apps/jscpd#ignored-blocks" + - name: Commit linted files (if made any changes) working-directory: specifyweb/frontend/js_src # Creates a new commit. Amending an existing commit is a bad idea diff --git a/.gitignore b/.gitignore index c392ed07bab..a9be54faca8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,45 @@ +# Setup +/seed-database/ +*.env +.env +docker-compose.yml + +# Python +/venv/ +/ve/ *.pyc -*~ -.vagrant + +# OS +.DS_Store + +# Editors +/.vscode/ +/.idea/ .emacs.desktop* -.directory -testing -specifypanel/ve -specifyweb/virtenv -venv/ -ve/ -seed-database -*.env + +# Back-end /specifyweb/settings/local_settings.py /specifyweb/settings/local_specify_settings.py /specifyweb/settings/local_logging_settings.py /specifyweb/settings/debug.py /specifyweb/settings/secret_key.py /specifyweb/settings/ldap_settings.py + +# Build artifacts /.mypy_cache /specifyweb/frontend/locale/**/*.mo /specifyweb/settings/build_version.py -/specifyweb/frontend/static/js -/specifyweb/frontend/static/css +/specifyweb/frontend/static/js/ +/specifyweb/frontend/static/css/ /specifyweb/frontend/static/manifest.json -node_modules -/specifyweb/frontend/js_src/testBuild -/specifyweb/frontend/js_src/docs -/specifyweb/frontend/js_src/coverage +/specifyweb/frontend/js_src/node_modules +/specifyweb/frontend/js_src/testBuild/ +/specifyweb/frontend/js_src/docs/ +/specifyweb/frontend/js_src/coverage/ /local_specifyweb_apache.conf /specifyweb/frontend/js_src/stats.json -/specifyweb/frontend/js_src/dist -.env -docker-compose.yml +/specifyweb/frontend/js_src/dist/ + +# Misc +*~ +testing/ diff --git a/specifyweb/context/collection_resources.py b/specifyweb/context/collection_resources.py index ac2aa8916bd..eecedd87184 100644 --- a/specifyweb/context/collection_resources.py +++ b/specifyweb/context/collection_resources.py @@ -2,76 +2,13 @@ from specifyweb.specify.models import Spappresource, Spappresourcedir from specifyweb.specify.views import openapi from specifyweb.context.resources import Resource, Resources +from specifyweb.context.user_resources import get_resources_endpoint_schema, get_resource_endpoint_schema -collection_resources = openapi(schema={ - "get": { - "responses": { - "200": { - "description": "Returns list of public app resources in the logged in collection.", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { "type": "integer", "description": "The appresource id." }, - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - }, - 'required': ['id', 'name', 'mimetype', 'metadata'], - 'additionalProperties': False - } - } - } - } - } - } - }, - "post": { - "requestBody": { - "required": True, - "description": "Creates appresource in the logged in collection owned by the logged in user.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - "data": { "type": "string", "description": "The data to be stored in the appresource." }, - }, - 'required': ['name', 'mimetype', 'metadata', 'data'], - 'additionalProperties': False - } - } - } - }, - "responses": { - "201": { - "description": "The user resource was created.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { "type": "integer", "description": "The appresource id." }, - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - "data": { "type": "string", "description": "The data to be stored in the appresource." }, - }, - 'required': ['name', 'mimetype', 'metadata', 'data'], - 'additionalProperties': False - } - } - } - } - } - } -})(Resources.as_view(_spappresourcedirfilter= lambda request: { +collection_resources = openapi(schema=get_resources_endpoint_schema( + description_get="Returns list of public app resources in the logged in collection.", + description_create="Creates appresource in the logged in collection.", + description_created="The collection resource was created.", +))(Resources.as_view(_spappresourcedirfilter= lambda request: { 'ispersonal': False, 'specifyuser__isnull': True, 'usertype__isnull': True, @@ -86,60 +23,10 @@ })) -collection_resource = openapi(schema={ - "get": { - "responses": { - "200": { - "description": "The public app resource of the given id in the logged in collection ", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { "type": "integer", "description": "The appresource id." }, - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - "data": { "type": "string", "description": "The data to be stored in the appresource." }, - }, - 'required': ['id', 'name', 'mimetype', 'metadata', 'data'], - 'additionalProperties': False - } - } - } - } - } - }, - "put": { - "requestBody": { - "required": True, - "description": "Updates the appresource with the given id in the logged in collection", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - "data": { "type": "string", "description": "The data to be stored in the appresource." }, - }, - 'required': ['name', 'mimetype', 'metadata', 'data'], - 'additionalProperties': False - } - } - } - }, - "responses": { - "204": { "description": "The resource was updated.", }, - } - }, - "delete": { - "responses": { - "204": {"description": "The resource was deleted.",} - } - } -})(Resource.as_view(_spappresourcefilter= lambda request: { +collection_resource = openapi(schema=get_resource_endpoint_schema( + description_get="The public app resource of the given id in the logged in collection", + description_update="Updates the appresource with the given id in the logged in collection" +))(Resource.as_view(_spappresourcefilter= lambda request: { 'spappresourcedir__ispersonal': False, })) diff --git a/specifyweb/context/user_resources.py b/specifyweb/context/user_resources.py index 6944ca666b5..40bfef9fa78 100644 --- a/specifyweb/context/user_resources.py +++ b/specifyweb/context/user_resources.py @@ -4,81 +4,84 @@ from specifyweb.context.resources import Resource, Resources from specifyweb.specify.models import Spappresource, Spappresourcedir -user_resource_dir_filter_gen = lambda request: { - 'specifyuser': request.specify_user, - 'ispersonal': True, - 'usertype': get_usertype(request.specify_user), +app_resource_data_schema = { + "id": { "type": "integer", "description": "The appresource id." }, + "name": { "type": "string", "description": "The appresource name." }, + "mimetype": { "type": "string" }, + "metadata": { "type": "string" }, + "data": { "type": "string", "description": "The data to be stored in the appresource." }, } -user_resources = openapi(schema={ - "get": { - "responses": { - "200": { - "description": "Returns list of app resources owned by the logged in user in the logged in collection.", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { "type": "integer", "description": "The appresource id." }, - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - }, - 'required': ['id', 'name', 'mimetype', 'metadata'], - 'additionalProperties': False +def get_resources_endpoint_schema( + description_get, + description_create, + description_created, +): + return { + "get": { + "responses": { + "200": { + "description": description_get, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {key:value for key,value in app_resource_data_schema.items() if key != "data"}, + 'required': ['id', 'name', 'mimetype', 'metadata'], + 'additionalProperties': False + } } } } } } - } - }, - "post": { - "requestBody": { - "required": True, - "description": "Creates appresource in the logged in collection owned by the logged in user.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - "data": { "type": "string", "description": "The data to be stored in the appresource." }, - }, - 'required': ['name', 'mimetype', 'metadata', 'data'], - 'additionalProperties': False - } - } - } }, - "responses": { - "201": { - "description": "The user resource was created.", + "post": { + "requestBody": { + "required": True, + "description": description_create, "content": { "application/json": { "schema": { "type": "object", - "properties": { - "id": { "type": "integer", "description": "The appresource id." }, - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - "data": { "type": "string", "description": "The data to be stored in the appresource." }, - }, + "properties": {key:value for key,value in app_resource_data_schema.items() if key != "id"}, 'required': ['name', 'mimetype', 'metadata', 'data'], 'additionalProperties': False } } } + }, + "responses": { + "201": { + "description": description_created, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": app_resource_data_schema, + 'required': ['name', 'mimetype', 'metadata', 'data'], + 'additionalProperties': False + } + } + } + } } } } -})(Resources.as_view(_spappresourcedirfilter= user_resource_dir_filter_gen, + +user_resource_dir_filter_gen = lambda request: { + 'specifyuser': request.specify_user, + 'ispersonal': True, + 'usertype': get_usertype(request.specify_user), +} + +user_resources = openapi(schema=get_resources_endpoint_schema( + description_get="Returns list of app resources owned by the logged in user in the logged in collection.", + description_create="Creates appresource in the logged in collection owned by the logged in user.", + description_created="The user resource was created.", +))(Resources.as_view(_spappresourcedirfilter= user_resource_dir_filter_gen, _spappresourcefilter= lambda request: { 'spappresourcedir__specifyuser': request.specify_user, 'spappresourcedir__ispersonal':True @@ -87,65 +90,58 @@ },_spappresourcedircreate=user_resource_dir_filter_gen )) - -user_resource = openapi(schema={ - "get": { - "responses": { - "200": { - "description": "The app resource of the given id owned by the logged in user in the logged in collection.", +def get_resource_endpoint_schema( + description_get, + description_update, +): + return { + "get": { + "responses": { + "200": { + "description": description_get, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": app_resource_data_schema, + 'required': ['id', 'name', 'mimetype', 'metadata', 'data'], + 'additionalProperties': False + } + } + } + } + } + }, + "put": { + "requestBody": { + "required": True, + "description": description_update, "content": { "application/json": { "schema": { "type": "object", - "properties": { - "id": { "type": "integer", "description": "The appresource id." }, - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - "data": { "type": "string", "description": "The data to be stored in the appresource." }, - }, - 'required': ['id', 'name', 'mimetype', 'metadata', 'data'], + "properties": {key:value for key,value in app_resource_data_schema.items() if key != "id"}, + 'required': ['name', 'mimetype', 'metadata', 'data'], 'additionalProperties': False } } } - } - } - }, - "put": { - "requestBody": { - "required": True, - "description": "Updates the appresource with the given id in the logged in collection owned by the logged in user.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { "type": "string", "description": "The appresource name." }, - "mimetype": { "type": "string" }, - "metadata": { "type": "string" }, - "data": { "type": "string", "description": "The data to be stored in the appresource." }, - }, - 'required': ['name', 'mimetype', 'metadata', 'data'], - 'additionalProperties': False - } - } + }, + "responses": { + "204": { "description": "The resource was updated.", }, } }, - "responses": { - "204": { "description": "The resource was updated.", }, - } - }, - "delete": { - "responses": { - "204": {"description": "The resource was deleted.",} + "delete": { + "responses": { + "204": {"description": "The resource was deleted.",} + } } } -})(Resource.as_view(_spappresourcefilter= lambda request: { + +user_resource = openapi(schema=get_resource_endpoint_schema( + description_get="The app resource of the given id owned by the logged in user in the logged in collection.", + description_update="Updates the appresource with the given id in the logged in collection owned by the logged in user." +))(Resource.as_view(_spappresourcefilter= lambda request: { 'spappresourcedir__specifyuser': request.specify_user, 'spappresourcedir__ispersonal': True, })) - - - - diff --git a/specifyweb/frontend/js_src/lib/components/AppResources/Tabs.tsx b/specifyweb/frontend/js_src/lib/components/AppResources/Tabs.tsx index 174d1accb6c..1290994b034 100644 --- a/specifyweb/frontend/js_src/lib/components/AppResources/Tabs.tsx +++ b/specifyweb/frontend/js_src/lib/components/AppResources/Tabs.tsx @@ -1,10 +1,9 @@ -import { Tab } from '@headlessui/react'; import React from 'react'; import type { LocalizedString } from 'typesafe-i18n'; import { resourcesText } from '../../localization/resources'; import { f } from '../../utils/functools'; -import type { GetSet, IR, RA, RR } from '../../utils/types'; +import type { GetSet, RA, RR } from '../../utils/types'; import { filterArray } from '../../utils/types'; import { WarningMessage } from '../Atoms'; import { toResource } from '../DataModel/helpers'; @@ -20,7 +19,6 @@ import type { import { ErrorBoundary } from '../Errors/ErrorBoundary'; import { Dialog, dialogClassNames } from '../Molecules/Dialog'; import { appResourceIcon } from './EditorComponents'; -import { radioButtonClassName } from './Filters'; import { getAppResourceType, getResourceType } from './filtersHelpers'; import type { AppResourceEditorType, @@ -164,50 +162,3 @@ function OtherCollectionWarning({ {resourcesText.wrongScopeWarning()} ) : null; } - -export function Tabs({ - tabs, - index: [currentIndex, handleChange], -}: { - readonly tabs: IR; - readonly index: GetSet; -}): JSX.Element { - return ( - - - {Object.keys(tabs).map((label, index) => ( - handleChange(index) - : undefined - } - > - {label} - - ))} - - - {Object.values(tabs).map((element, index) => ( - - {element} - - ))} - - - ); -} diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx b/specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx index c04af2ee868..9f912b5cd21 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx +++ b/specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx @@ -29,6 +29,7 @@ export const legacyNonJsxIcons = { link: ``, printer: ``, } as const; +/* jscpd:ignore-start */ // eslint-disable-next-line capitalized-comments // prettier-ignore export const icons = { @@ -135,6 +136,7 @@ export const icons = { viewList: , x: , } as const; +/* jscpd:ignore-end */ export const dialogIcons: RR< keyof typeof dialogIconTriggers, diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/legacyTypes.ts b/specifyweb/frontend/js_src/lib/components/DataModel/legacyTypes.ts index 22b4bb1413b..653262aa2de 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/legacyTypes.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/legacyTypes.ts @@ -14,6 +14,25 @@ import type { } from './helperTypes'; import type { Collection, SpecifyTable } from './specifyTable'; +type SchemaFields = + | keyof CommonFields + | keyof SCHEMA['fields'] + | keyof SCHEMA['toManyDependent'] + | keyof SCHEMA['toManyIndependent'] + | keyof SCHEMA['toOneDependent'] + | keyof SCHEMA['toOneIndependent']; + +type SchemaFieldValue< + SCHEMA extends AnySchema, + FIELD_NAME extends SchemaFields +> = (CommonFields & + IR & + SCHEMA['fields'] & + SCHEMA['toManyDependent'] & + SCHEMA['toManyIndependent'] & + SCHEMA['toOneDependent'] & + SCHEMA['toOneIndependent'])[FIELD_NAME]; + /* * FEATURE: need to improve the typing to handle the following: * Dynamic references @@ -43,32 +62,20 @@ export type SpecifyResource = { */ /* eslint-disable @typescript-eslint/method-signature-style */ get< - FIELD_NAME extends - | keyof CommonFields - | keyof SCHEMA['fields'] - | keyof SCHEMA['toManyDependent'] - | keyof SCHEMA['toManyIndependent'] - | keyof SCHEMA['toOneDependent'] - | keyof SCHEMA['toOneIndependent'], - VALUE extends (CommonFields & - IR & - SCHEMA['fields'] & - SCHEMA['toManyDependent'] & - SCHEMA['toManyIndependent'] & - SCHEMA['toOneDependent'] & - SCHEMA['toOneIndependent'])[FIELD_NAME], + FIELD_NAME extends SchemaFields, + VALUE extends SchemaFieldValue >( fieldName: FIELD_NAME // eslint-disable-next-line functional/prefer-readonly-type ): [VALUE] extends [never] ? never : VALUE extends AnySchema - ? VALUE extends null - ? string | null - : string - : VALUE extends RA - ? string - : VALUE; + ? VALUE extends null + ? string | null + : string + : VALUE extends RA + ? string + : VALUE; // Case-insensitive fetch of a -to-one resource rgetPromise< FIELD_NAME extends @@ -76,7 +83,7 @@ export type SpecifyResource = { | keyof SCHEMA['toOneIndependent'], VALUE = (IR & SCHEMA['toOneDependent'] & - SCHEMA['toOneIndependent'])[FIELD_NAME], + SCHEMA['toOneIndependent'])[FIELD_NAME] >( fieldName: FIELD_NAME, prePopulate?: boolean @@ -93,7 +100,7 @@ export type SpecifyResource = { | keyof SCHEMA['toOneIndependent'], VALUE = (IR & SCHEMA['toOneDependent'] & - SCHEMA['toOneIndependent'])[FIELD_NAME], + SCHEMA['toOneIndependent'])[FIELD_NAME] >( fieldName: FIELD_NAME, options?: { @@ -112,26 +119,14 @@ export type SpecifyResource = { FIELD_NAME extends keyof (SCHEMA['toManyDependent'] & SCHEMA['toManyIndependent']), VALUE extends (SCHEMA['toManyDependent'] & - SCHEMA['toManyIndependent'])[FIELD_NAME], + SCHEMA['toManyIndependent'])[FIELD_NAME] >( fieldName: FIELD_NAME, filters?: CollectionFetchFilters ): Promise>; set< - FIELD_NAME extends - | keyof CommonFields - | keyof SCHEMA['fields'] - | keyof SCHEMA['toManyDependent'] - | keyof SCHEMA['toManyIndependent'] - | keyof SCHEMA['toOneDependent'] - | keyof SCHEMA['toOneIndependent'], - VALUE extends (CommonFields & - IR & - SCHEMA['fields'] & - SCHEMA['toManyDependent'] & - SCHEMA['toManyIndependent'] & - SCHEMA['toOneDependent'] & - SCHEMA['toOneIndependent'])[FIELD_NAME], + FIELD_NAME extends SchemaFields, + VALUE extends SchemaFieldValue >( fieldName: FIELD_NAME, value: readonly [VALUE] extends readonly [never] @@ -149,11 +144,11 @@ export type SpecifyResource = { | RA> | RA> : null extends VALUE - ? - | SerializedResource> - | SpecifyResource> - | null - : SerializedResource | SpecifyResource), + ? + | SerializedResource> + | SpecifyResource> + | null + : SerializedResource | SpecifyResource), options?: { readonly silent: boolean } ): SpecifyResource; // Not type safe diff --git a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx index 940be5f197a..8626aaf4df6 100644 --- a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx @@ -5,7 +5,6 @@ import { useId } from '../../hooks/useId'; import { useInfiniteScroll } from '../../hooks/useInfiniteScroll'; import { commonText } from '../../localization/common'; import { formsText } from '../../localization/forms'; -import { f } from '../../utils/functools'; import type { IR, RA } from '../../utils/types'; import { sortFunction } from '../../utils/utils'; import { Button } from '../Atoms/Button'; @@ -25,8 +24,8 @@ import { FormMeta } from '../FormMeta'; import type { FormCellDefinition, SubViewSortField } from '../FormParse/cells'; import { attachmentView } from '../FormParse/webOnlyViews'; import { SpecifyForm } from '../Forms/SpecifyForm'; -import { SubViewContext } from '../Forms/SubView'; import { propsToFormMode, useViewDefinition } from '../Forms/useViewDefinition'; +import { useRenderedResourceId } from '../FormSliders/useRenderedResourceId'; import { loadingGif } from '../Molecules'; import { Dialog } from '../Molecules/Dialog'; import type { SortConfig } from '../Molecules/Sorting'; @@ -210,25 +209,7 @@ export function FormTable({ onSelected: handleAddResources, }); - const subviewContext = React.useContext(SubViewContext); - const parentContext = React.useMemo( - () => subviewContext?.parentContext ?? [], - [subviewContext?.parentContext] - ); - - const renderedResourceId = React.useMemo( - () => - parentContext.length === 0 || relationship.isDependent() - ? undefined - : f.maybe( - parentContext.find( - ({ relationship: parentRelationship }) => - parentRelationship === relationship.getReverse() - ), - ({ parentResource: { id } }) => id - ), - [parentContext, relationship] - ); + const renderedResourceId = useRenderedResourceId(relationship); const children = collapsedViewDefinition === undefined ? ( diff --git a/specifyweb/frontend/js_src/lib/components/FormParse/plugins.ts b/specifyweb/frontend/js_src/lib/components/FormParse/plugins.ts index da403b0031d..0d5dad464ef 100644 --- a/specifyweb/frontend/js_src/lib/components/FormParse/plugins.ts +++ b/specifyweb/frontend/js_src/lib/components/FormParse/plugins.ts @@ -170,7 +170,7 @@ const processUiPlugin: { const relationship = getProperty('relName'); if (relationship === undefined) { console.error( - "Can't display CollectionRelOneToManyPlugin because initialize.relname is not set" + "Can't display ColRelTypePlugin because initialize.relname is not set" ); return { type: 'Blank' }; } else if ( diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx index 30cff8f1029..5771b44c4be 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx @@ -25,7 +25,6 @@ import type { FormType } from '../FormParse'; import type { SubViewSortField } from '../FormParse/cells'; import { augmentMode, ResourceView } from '../Forms/ResourceView'; import { useFirstFocus } from '../Forms/SpecifyForm'; -import { SubViewContext } from '../Forms/SubView'; import type { InteractionWithPreps } from '../Interactions/helpers'; import { interactionPrepTables } from '../Interactions/helpers'; import { InteractionDialog } from '../Interactions/InteractionDialog'; @@ -33,9 +32,9 @@ import { hasTablePermission } from '../Permissions/helpers'; import { relationshipIsToMany } from '../WbPlanView/mappingHelpers'; import { AttachmentsCollection } from './AttachmentsCollection'; import { RecordSelectorFromCollection } from './RecordSelectorFromCollection'; +import { useRenderedResourceId } from './useRenderedResourceId'; /** A wrapper for RecordSelector to integrate with Backbone.Collection */ - export function IntegratedRecordSelector({ urlParameter, viewName, @@ -136,25 +135,7 @@ export function IntegratedRecordSelector({ const isAttachmentTable = collection.table.specifyTable.name.includes('Attachment'); - const subviewContext = React.useContext(SubViewContext); - const parentContext = React.useMemo( - () => subviewContext?.parentContext ?? [], - [subviewContext?.parentContext] - ); - - const renderedResourceId = React.useMemo( - () => - parentContext.length === 0 || relationship.isDependent() - ? undefined - : f.maybe( - parentContext.find( - ({ relationship: parentRelationship }) => - parentRelationship === relationship.getReverse() - ), - ({ parentResource: { id } }) => id - ), - [parentContext, relationship] - ); + const renderedResourceId = useRenderedResourceId(relationship); const isCOJO = relationship.relatedTable.name === 'CollectionObjectGroupJoin' && diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/useRenderedResourceId.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/useRenderedResourceId.tsx new file mode 100644 index 00000000000..fd8b7032d98 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/useRenderedResourceId.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { f } from '../../utils/functools'; +import type { Relationship } from '../DataModel/specifyField'; +import { SubViewContext } from '../Forms/SubView'; + +export function useRenderedResourceId( + relationship: Relationship +): number | undefined { + const subviewContext = React.useContext(SubViewContext); + const parentContext = React.useMemo( + () => subviewContext?.parentContext ?? [], + [subviewContext?.parentContext] + ); + + return React.useMemo( + () => + parentContext.length === 0 || relationship.isDependent() + ? undefined + : f.maybe( + parentContext.find( + ({ relationship: parentRelationship }) => + parentRelationship === relationship.getReverse() + ), + ({ parentResource: { id } }) => id + ), + [parentContext, relationship] + ); +} diff --git a/specifyweb/frontend/js_src/lib/components/Formatters/Types.tsx b/specifyweb/frontend/js_src/lib/components/Formatters/Types.tsx index 3bc6b2fd0bc..d68fa03736b 100644 --- a/specifyweb/frontend/js_src/lib/components/Formatters/Types.tsx +++ b/specifyweb/frontend/js_src/lib/components/Formatters/Types.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { commonText } from '../../localization/common'; import { schemaText } from '../../localization/schema'; import type { GetOrSet, RA } from '../../utils/types'; -import { Tabs } from '../AppResources/Tabs'; +import { Tabs } from '../Molecules/Tabs'; import { NotFoundView } from '../Router/NotFoundView'; import { SafeOutlet } from '../Router/RouterUtils'; import { useRoutePart } from '../Router/useRoutePart'; @@ -61,6 +61,7 @@ export function FormatterTypes(): JSX.Element { count: parsed.aggregators.length, })]: child, }} + variant="combined" /> ); } diff --git a/specifyweb/frontend/js_src/lib/components/Formatters/spec.ts b/specifyweb/frontend/js_src/lib/components/Formatters/spec.ts index fc1651d82a3..41fd9b1163b 100644 --- a/specifyweb/frontend/js_src/lib/components/Formatters/spec.ts +++ b/specifyweb/frontend/js_src/lib/components/Formatters/spec.ts @@ -64,6 +64,8 @@ export type Aggregator = SpecToJson< ReturnType >['aggregators'][number]; +// eslint-disable-next-line capitalized-comments +/* jscpd:ignore-start */ const formatterSpec = f.store(() => createXmlSpec({ name: pipe( @@ -87,6 +89,8 @@ const formatterSpec = f.store(() => ), }) ); +// eslint-disable-next-line capitalized-comments +/* jscpd:ignore-end */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const switchSpec = ({ table }: SpecToJson>) => diff --git a/specifyweb/frontend/js_src/lib/components/Molecules/Tabs.tsx b/specifyweb/frontend/js_src/lib/components/Molecules/Tabs.tsx index ed0bda607de..0620d04846e 100644 --- a/specifyweb/frontend/js_src/lib/components/Molecules/Tabs.tsx +++ b/specifyweb/frontend/js_src/lib/components/Molecules/Tabs.tsx @@ -2,27 +2,33 @@ import { Tab } from '@headlessui/react'; import React from 'react'; import type { GetSet, IR } from '../../utils/types'; -import { className } from '../Atoms/className'; +import { radioButtonClassName } from '../AppResources/Filters'; import { ErrorBoundary } from '../Errors/ErrorBoundary'; export function Tabs({ tabs, index: [currentIndex, handleChange], + variant = 'separated', }: { readonly tabs: IR; readonly index: GetSet; + readonly variant?: 'combined' | 'separated'; }): JSX.Element { return ( {Object.keys(tabs).map((label, index) => ( handleChange(index) : undefined + currentIndex === index + ? (): void => handleChange(index) + : undefined } > {label} diff --git a/specifyweb/frontend/js_src/lib/components/RouterCommands/SwitchCollection.tsx b/specifyweb/frontend/js_src/lib/components/RouterCommands/SwitchCollection.tsx index aeb54a2407e..ef201ec09d8 100644 --- a/specifyweb/frontend/js_src/lib/components/RouterCommands/SwitchCollection.tsx +++ b/specifyweb/frontend/js_src/lib/components/RouterCommands/SwitchCollection.tsx @@ -42,8 +42,8 @@ export function SwitchCollectionCommand(): null { body: collectionId!.toString(), errorMode: 'dismissible', }) - .then(clearAllCache) - .then(() => globalThis.location.replace(nextUrl)), + .then(clearAllCache) + .then(() => globalThis.location.replace(nextUrl)), [collectionId, nextUrl] ), true diff --git a/specifyweb/frontend/js_src/lib/components/SchemaViewer/Fields.tsx b/specifyweb/frontend/js_src/lib/components/SchemaViewer/Fields.tsx index fa995a174b9..cf9c88ac679 100644 --- a/specifyweb/frontend/js_src/lib/components/SchemaViewer/Fields.tsx +++ b/specifyweb/frontend/js_src/lib/components/SchemaViewer/Fields.tsx @@ -51,16 +51,20 @@ export function SchemaViewerFields({ ); } +export const commonFieldColumns = f.store(() => ({ + name: getField(tables.SpLocaleContainerItem, 'name').label, + label: reportsText.labels(), + description: schemaText.description(), + isHidden: getField(tables.SpLocaleContainerItem, 'isHidden').label, + isReadOnly: schemaText.readOnly(), + isRequired: getField(tables.SpLocaleContainerItem, 'isRequired').label, + type: getField(tables.SpLocaleContainerItem, 'type').label, +})); + const fieldColumns = f.store( () => ({ - name: getField(tables.SpLocaleContainerItem, 'name').label, - label: reportsText.labels(), - description: schemaText.description(), - isHidden: getField(tables.SpLocaleContainerItem, 'isHidden').label, - isReadOnly: schemaText.readOnly(), - isRequired: getField(tables.SpLocaleContainerItem, 'isRequired').label, - type: getField(tables.SpLocaleContainerItem, 'type').label, + ...commonFieldColumns(), length: schemaText.fieldLength(), databaseColumn: schemaText.databaseColumn(), }) as const diff --git a/specifyweb/frontend/js_src/lib/components/SchemaViewer/Relationships.tsx b/specifyweb/frontend/js_src/lib/components/SchemaViewer/Relationships.tsx index 9829502969b..a658ffc46ee 100644 --- a/specifyweb/frontend/js_src/lib/components/SchemaViewer/Relationships.tsx +++ b/specifyweb/frontend/js_src/lib/components/SchemaViewer/Relationships.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { reportsText } from '../../localization/report'; import { schemaText } from '../../localization/schema'; import { f } from '../../utils/functools'; import { booleanFormatter } from '../../utils/parser/parse'; @@ -8,11 +7,10 @@ import type { RA, RR } from '../../utils/types'; import { ensure } from '../../utils/types'; import { H3 } from '../Atoms'; import { Button } from '../Atoms/Button'; -import { getField } from '../DataModel/helpers'; import type { SpecifyTable } from '../DataModel/specifyTable'; -import { tables } from '../DataModel/tables'; import { TableIcon } from '../Molecules/TableIcon'; import { localizedRelationshipTypes } from '../SchemaConfig/helpers'; +import { commonFieldColumns } from './Fields'; import type { SchemaViewerRow, SchemaViewerValue } from './helpers'; import { SchemaViewerTableList } from './TableList'; @@ -91,13 +89,7 @@ export function SchemaViewerRelationships({ const relationshipColumns = f.store( () => ({ - name: getField(tables.SpLocaleContainerItem, 'name').label, - label: reportsText.labels(), - description: schemaText.description(), - isHidden: getField(tables.SpLocaleContainerItem, 'isHidden').label, - isReadOnly: schemaText.readOnly(), - isRequired: getField(tables.SpLocaleContainerItem, 'isRequired').label, - type: getField(tables.SpLocaleContainerItem, 'type').label, + ...commonFieldColumns(), databaseColumn: schemaText.databaseColumn(), relatedTable: schemaText.relatedTable(), otherSideName: schemaText.otherSideName(), diff --git a/specifyweb/frontend/js_src/lib/tests/gitignoreToGlob.ts b/specifyweb/frontend/js_src/lib/tests/gitignoreToGlob.ts new file mode 100644 index 00000000000..1ec45a0139a --- /dev/null +++ b/specifyweb/frontend/js_src/lib/tests/gitignoreToGlob.ts @@ -0,0 +1,36 @@ +import { readFileSync } from 'node:fs'; + +import type { RA } from '../utils/types'; + +/** + * Convert .gitignore file to glob patterns. + * + * I tried gitignore-to-glob, but it has several issues + * (i.e https://github.com/EE/gitignore-to-glob/issues/8), and doesn't quite + * fit our use case. + */ +export const gitIgnoreToGlob = (gitIgnorePath: string): RA => + readFileSync(gitIgnorePath, { encoding: 'utf8' }) + .split('\n') + // Filter out empty lines and comments. + .filter((pattern) => pattern && !pattern.startsWith('#')) + // If pattern doesn't start with /, prepend ** + .map((pattern) => + pattern.startsWith('/') || pattern.startsWith('!/') + ? pattern + : `${pattern.startsWith('!') ? '!' : ''}**/${pattern}` + ) + .reduce>((result, pattern) => { + // Convert / into /** + if (pattern.endsWith('/')) return [...result, `${pattern}**`]; + // Leave "file.js" as it + // eslint-disable-next-line unicorn/prefer-ternary + if ( + pattern.includes('*') || + pattern.includes('{') || + pattern.split('/').at(-1)?.includes('.') === true + ) + return [...result, pattern]; + // Convert "file" into "file" and "file/**" + else return [...result, pattern, `${pattern}/**`]; + }, []); diff --git a/specifyweb/frontend/js_src/lib/tests/jscpd.ts b/specifyweb/frontend/js_src/lib/tests/jscpd.ts new file mode 100644 index 00000000000..3164fb0750c --- /dev/null +++ b/specifyweb/frontend/js_src/lib/tests/jscpd.ts @@ -0,0 +1,75 @@ +import { join } from 'node:path/posix'; +import { fileURLToPath } from 'node:url'; + +import { getModeHandler } from '@jscpd/core'; +import { detectClones } from 'jscpd'; + +import { gitIgnoreToGlob } from './gitignoreToGlob'; + +const repositoryRoot = join( + // @ts-expect-error REFACTOR: modify tsconfig.json to allow modern features + fileURLToPath(import.meta.url), + '../../../../../..' +); +// Make relative file paths be relative to repository root +process.chdir(repositoryRoot); + +// Workaround for https://github.com/kucherenko/jscpd/issues/651 +const gitIgnore = gitIgnoreToGlob(join(repositoryRoot, '.gitignore')); +const fixRelative = (pattern: string): string => + pattern.startsWith('/') + ? join(repositoryRoot, pattern) + : pattern.startsWith('!/') + ? `!${join(repositoryRoot, pattern.slice(1))}` + : pattern; +const allIgnore = [ + ...gitIgnore, + '/.git/**', + '/specifyweb/context/data/**', + '/specifyweb/specify/migrations/**', + '/specifyweb/frontend/static/**', + '/specifyweb/frontend/locale/**', + '/specifyweb/frontend/js_src/lib/tests/fixtures/**', + '/specifyweb/frontend/js_src/lib/tests/ajax/static/**', + '/specifyweb/frontend/js_src/lib/**/__tests__/**', + '/specifyweb/frontend/js_src/lib/tests/*.js', + '/specifyweb/**/tests/**', + '/specifyweb/**/tests.py', +].map(fixRelative); + +void detectClones({ + path: [repositoryRoot], + absolute: false, + ignoreCase: true, + minLines: 8, + minTokens: 80, + /* + * These reporters create duplication, but we need both if we want to see + * both the code snippets and the summary table + * Filed a feature request: https://github.com/kucherenko/jscpd/issues/652 + */ + reporters: ['console', 'consoleFull'], + ignore: allIgnore, + format: [ + 'javascript', + 'typescript', + 'python', + 'bash', + 'css', + 'docker', + 'django', + 'html', + 'markdown', + 'yaml', + 'tsx', + ], + gitignore: true, + mode: getModeHandler('weak'), +}) + // eslint-disable-next-line unicorn/no-process-exit + .then(({ length }) => process.exit(length > 0 ? 1 : 0)) + .catch((error) => { + console.error(error); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); + }); diff --git a/specifyweb/frontend/js_src/package-lock.json b/specifyweb/frontend/js_src/package-lock.json index 4343491a49a..dc441fd061d 100644 --- a/specifyweb/frontend/js_src/package-lock.json +++ b/specifyweb/frontend/js_src/package-lock.json @@ -89,6 +89,7 @@ "jest-fail-on-console": "^2.4.2", "jest-silent-reporter": "^0.5.0", "jest-skipped-reporter": "^0.0.5", + "jscpd": "^3.5.3", "jsdom-global": "^3.0.2", "loader-utils": "^3.2.0", "node-notifier": "^10.0.1", @@ -1961,6 +1962,16 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -3256,6 +3267,56 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@jscpd/core": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@jscpd/core/-/core-3.5.10.tgz", + "integrity": "sha512-k8owgREEI6bUHtVq+PM3+Gp5GgLgwMklWabJzZdtNXpHeC22EzviAW1Of7cifPLmdMJ3xfkoHl5No9/gg6y5FA==", + "dev": true, + "dependencies": { + "eventemitter3": "^5.0.1" + } + }, + "node_modules/@jscpd/finder": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@jscpd/finder/-/finder-3.5.10.tgz", + "integrity": "sha512-zyafUsTB2xDySho3wKMSoZuiMw2XvkdYW72kI7bLdz9M+ERyTMRfUnX1RgoGldXyylyZPF1FSflmfsppkV4Wuw==", + "dev": true, + "dependencies": { + "@jscpd/core": "^3.5.4", + "@jscpd/tokenizer": "^3.5.4", + "blamer": "^1.0.4", + "bytes": "^3.1.0", + "cli-table3": "^0.6.0", + "colors": "1.4.0", + "fast-glob": "^3.2.2", + "fs-extra": "^9.0.0", + "markdown-table": "^2.0.0", + "pug": "^3.0.1" + } + }, + "node_modules/@jscpd/html-reporter": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@jscpd/html-reporter/-/html-reporter-3.5.10.tgz", + "integrity": "sha512-EPee/YSy/12UboUOoOD+vFvU8/Bi/CAKnjTDnAdPnJlSh68jog/BNhHKrXiiJKZS2CLn1WzjUaefJrn8VcWo+A==", + "dev": true, + "dependencies": { + "@jscpd/finder": "^3.5.10", + "colors": "1.4.0", + "fs-extra": "^9.0.1", + "pug": "^3.0.2" + } + }, + "node_modules/@jscpd/tokenizer": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@jscpd/tokenizer/-/tokenizer-3.5.4.tgz", + "integrity": "sha512-qvGbHNFaGXqMqgw0cujRqSM2cuPBAR9EOtZNUx4eGQ6IeuJhsS+aI0ijEUOE1OdVBStdu5xYdyytuLkkmtJzHA==", + "dev": true, + "dependencies": { + "@jscpd/core": "^3.5.4", + "reprism": "^0.0.11", + "spark-md5": "^3.0.1" + } + }, "node_modules/@lezer/common": { "version": "1.0.0", "license": "MIT" @@ -5361,6 +5422,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==", + "dev": true + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -5382,6 +5455,15 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -5620,6 +5702,18 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/backbone": { "version": "0.9.10", "dependencies": { @@ -5704,6 +5798,66 @@ "node": ">=8" } }, + "node_modules/blamer": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/blamer/-/blamer-1.0.6.tgz", + "integrity": "sha512-fv7QToPS87oD1m1bDDTf29zC/bVKJxj2Nqh1r/v4NhMtbnzDIbWOHBYIfxCjlmkVGu3FGOjKgdNG3SFm7TkvBQ==", + "dev": true, + "dependencies": { + "execa": "^4.0.0", + "which": "^2.0.2" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/blamer/node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/blamer/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/blamer/node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -5807,6 +5961,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -6002,6 +6165,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "dependencies": { + "is-regex": "^1.0.3" + } + }, "node_modules/character-reference-invalid": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", @@ -6194,6 +6366,21 @@ "node": ">=4" } }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -6317,6 +6504,15 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6369,6 +6565,16 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -7231,6 +7437,12 @@ "node": ">=6.0.0" } }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "dev": true + }, "node_modules/dom-accessibility-api": { "version": "0.5.14", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", @@ -7302,6 +7514,15 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.10.0", "dev": true, @@ -8504,6 +8725,12 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "node_modules/events": { "version": "3.3.0", "dev": true, @@ -8968,6 +9195,30 @@ "node": ">=0.10.0" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "dev": true, @@ -9150,6 +9401,15 @@ } ] }, + "node_modules/gitignore-to-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gitignore-to-glob/-/gitignore-to-glob-0.3.0.tgz", + "integrity": "sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA==", + "dev": true, + "engines": { + "node": ">=4.4 <5 || >=6.9" + } + }, "node_modules/glob": { "version": "7.2.0", "dev": true, @@ -9938,6 +10198,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, "node_modules/is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", @@ -10067,6 +10337,12 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -11473,6 +11749,12 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "dev": true + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -11489,6 +11771,34 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jscpd": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-3.5.10.tgz", + "integrity": "sha512-lsFBSqtmGocMtF8NuCh867VRhjeSvPlGit7FYA9a9bX3GFoV65djHnWMbIWfHLb/6wCCwllmXaHuMgeMuhqo3Q==", + "dev": true, + "dependencies": { + "@jscpd/core": "^3.5.4", + "@jscpd/finder": "^3.5.10", + "@jscpd/html-reporter": "^3.5.10", + "@jscpd/tokenizer": "^3.5.4", + "colors": "1.4.0", + "commander": "^5.0.0", + "fs-extra": "^9.1.0", + "gitignore-to-glob": "^0.3.0" + }, + "bin": { + "jscpd": "bin/jscpd" + } + }, + "node_modules/jscpd/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/jsdoctypeparser": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", @@ -11611,6 +11921,37 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "dev": true, + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", @@ -11880,6 +12221,19 @@ "node": ">=0.10.0" } }, + "node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/marked": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz", @@ -13793,6 +14147,15 @@ "node": ">= 0.6.0" } }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "dependencies": { + "asap": "~2.0.3" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -13826,6 +14189,140 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, + "node_modules/pug": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz", + "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==", + "dev": true, + "dependencies": { + "pug-code-gen": "^3.0.3", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dev": true, + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz", + "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==", + "dev": true, + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "dev": true + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dev": true, + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dev": true, + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dev": true, + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "dev": true + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dev": true, + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.1.1", "dev": true, @@ -14274,6 +14771,12 @@ "node": ">=0.10" } }, + "node_modules/reprism": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/reprism/-/reprism-0.0.11.tgz", + "integrity": "sha512-VsxDR5QxZo08M/3nRypNlScw5r3rKeSOPdU/QhDmu3Ai3BJxHn/qgfXGWQp/tAxUtzwYNo9W6997JZR0tPLZsA==", + "dev": true + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -14960,6 +15463,12 @@ "deprecated": "See https://github.com/lydell/source-map-url#deprecated", "dev": true }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "dev": true + }, "node_modules/spdx-correct": { "version": "3.1.1", "dev": true, @@ -15779,6 +16288,12 @@ "ret": "~0.1.10" } }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "dev": true + }, "node_modules/too-wordy": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/too-wordy/-/too-wordy-0.3.4.tgz", @@ -16392,6 +16907,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -16834,6 +17358,21 @@ "dev": true, "license": "MIT" }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/wmf": { "version": "1.0.2", "license": "Apache-2.0", @@ -18389,6 +18928,13 @@ "w3c-keyname": "^2.2.4" } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -19173,6 +19719,56 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@jscpd/core": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@jscpd/core/-/core-3.5.10.tgz", + "integrity": "sha512-k8owgREEI6bUHtVq+PM3+Gp5GgLgwMklWabJzZdtNXpHeC22EzviAW1Of7cifPLmdMJ3xfkoHl5No9/gg6y5FA==", + "dev": true, + "requires": { + "eventemitter3": "^5.0.1" + } + }, + "@jscpd/finder": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@jscpd/finder/-/finder-3.5.10.tgz", + "integrity": "sha512-zyafUsTB2xDySho3wKMSoZuiMw2XvkdYW72kI7bLdz9M+ERyTMRfUnX1RgoGldXyylyZPF1FSflmfsppkV4Wuw==", + "dev": true, + "requires": { + "@jscpd/core": "^3.5.4", + "@jscpd/tokenizer": "^3.5.4", + "blamer": "^1.0.4", + "bytes": "^3.1.0", + "cli-table3": "^0.6.0", + "colors": "1.4.0", + "fast-glob": "^3.2.2", + "fs-extra": "^9.0.0", + "markdown-table": "^2.0.0", + "pug": "^3.0.1" + } + }, + "@jscpd/html-reporter": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@jscpd/html-reporter/-/html-reporter-3.5.10.tgz", + "integrity": "sha512-EPee/YSy/12UboUOoOD+vFvU8/Bi/CAKnjTDnAdPnJlSh68jog/BNhHKrXiiJKZS2CLn1WzjUaefJrn8VcWo+A==", + "dev": true, + "requires": { + "@jscpd/finder": "^3.5.10", + "colors": "1.4.0", + "fs-extra": "^9.0.1", + "pug": "^3.0.2" + } + }, + "@jscpd/tokenizer": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@jscpd/tokenizer/-/tokenizer-3.5.4.tgz", + "integrity": "sha512-qvGbHNFaGXqMqgw0cujRqSM2cuPBAR9EOtZNUx4eGQ6IeuJhsS+aI0ijEUOE1OdVBStdu5xYdyytuLkkmtJzHA==", + "dev": true, + "requires": { + "@jscpd/core": "^3.5.4", + "reprism": "^0.0.11", + "spark-md5": "^3.0.1" + } + }, "@lezer/common": { "version": "1.0.0" }, @@ -20780,6 +21376,18 @@ "is-shared-array-buffer": "^1.0.2" } }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -20798,6 +21406,12 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -20972,6 +21586,15 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, + "babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dev": true, + "requires": { + "@babel/types": "^7.9.6" + } + }, "backbone": { "version": "0.9.10", "requires": { @@ -21025,6 +21648,50 @@ "version": "2.2.0", "dev": true }, + "blamer": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/blamer/-/blamer-1.0.6.tgz", + "integrity": "sha512-fv7QToPS87oD1m1bDDTf29zC/bVKJxj2Nqh1r/v4NhMtbnzDIbWOHBYIfxCjlmkVGu3FGOjKgdNG3SFm7TkvBQ==", + "dev": true, + "requires": { + "execa": "^4.0.0", + "which": "^2.0.2" + }, + "dependencies": { + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + } + } + }, "brace-expansion": { "version": "1.1.11", "dev": true, @@ -21085,6 +21752,12 @@ "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -21212,6 +21885,15 @@ "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", "dev": true }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "requires": { + "is-regex": "^1.0.3" + } + }, "character-reference-invalid": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", @@ -21354,6 +22036,16 @@ "escape-string-regexp": "^1.0.5" } }, + "cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -21453,6 +22145,12 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -21494,6 +22192,16 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, + "constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dev": true, + "requires": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -22058,6 +22766,12 @@ "esutils": "^2.0.2" } }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "dev": true + }, "dom-accessibility-api": { "version": "0.5.14", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", @@ -22117,6 +22831,15 @@ "iconv-lite": "^0.6.2" } }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, "enhanced-resolve": { "version": "5.10.0", "dev": true, @@ -22967,6 +23690,12 @@ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true }, + "eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "events": { "version": "3.3.0", "dev": true @@ -23308,6 +24037,26 @@ "map-cache": "^0.2.2" } }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + } + } + }, "fs.realpath": { "version": "1.0.0", "dev": true @@ -23424,6 +24173,12 @@ } } }, + "gitignore-to-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gitignore-to-glob/-/gitignore-to-glob-0.3.0.tgz", + "integrity": "sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA==", + "dev": true + }, "glob": { "version": "7.2.0", "dev": true, @@ -23965,6 +24720,16 @@ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true }, + "is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", @@ -24049,6 +24814,12 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -25134,6 +25905,12 @@ "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", "dev": true }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "dev": true + }, "js-tokens": { "version": "4.0.0" }, @@ -25146,6 +25923,30 @@ "argparse": "^2.0.1" } }, + "jscpd": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-3.5.10.tgz", + "integrity": "sha512-lsFBSqtmGocMtF8NuCh867VRhjeSvPlGit7FYA9a9bX3GFoV65djHnWMbIWfHLb/6wCCwllmXaHuMgeMuhqo3Q==", + "dev": true, + "requires": { + "@jscpd/core": "^3.5.4", + "@jscpd/finder": "^3.5.10", + "@jscpd/html-reporter": "^3.5.10", + "@jscpd/tokenizer": "^3.5.4", + "colors": "1.4.0", + "commander": "^5.0.0", + "fs-extra": "^9.1.0", + "gitignore-to-glob": "^0.3.0" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + } + } + }, "jsdoctypeparser": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", @@ -25228,6 +26029,34 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + } + } + }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "dev": true, + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, "jsx-ast-utils": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", @@ -25425,6 +26254,15 @@ "object-visit": "^1.0.0" } }, + "markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "requires": { + "repeat-string": "^1.0.0" + } + }, "marked": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz", @@ -26512,6 +27350,15 @@ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "requires": { + "asap": "~2.0.3" + } + }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -26543,6 +27390,140 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, + "pug": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz", + "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==", + "dev": true, + "requires": { + "pug-code-gen": "^3.0.3", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dev": true, + "requires": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "pug-code-gen": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz", + "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==", + "dev": true, + "requires": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "dev": true + }, + "pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dev": true, + "requires": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "requires": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dev": true, + "requires": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dev": true, + "requires": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "dev": true + }, + "pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dev": true, + "requires": { + "pug-error": "^2.0.0" + } + }, + "pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.1.1", "dev": true @@ -26855,6 +27836,12 @@ "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", "dev": true }, + "reprism": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/reprism/-/reprism-0.0.11.tgz", + "integrity": "sha512-VsxDR5QxZo08M/3nRypNlScw5r3rKeSOPdU/QhDmu3Ai3BJxHn/qgfXGWQp/tAxUtzwYNo9W6997JZR0tPLZsA==", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -27377,6 +28364,12 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, + "spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "dev": true + }, "spdx-correct": { "version": "3.1.1", "dev": true, @@ -27967,6 +28960,12 @@ "is-number": "^7.0.0" } }, + "token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "dev": true + }, "too-wordy": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/too-wordy/-/too-wordy-0.3.4.tgz", @@ -28395,6 +29394,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "dev": true + }, "vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -28686,6 +29691,18 @@ "version": "2.0.0", "dev": true }, + "with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + } + }, "wmf": { "version": "1.0.2" }, diff --git a/specifyweb/frontend/js_src/package.json b/specifyweb/frontend/js_src/package.json index 7ea5fcc4e1c..13a8f0146da 100644 --- a/specifyweb/frontend/js_src/package.json +++ b/specifyweb/frontend/js_src/package.json @@ -109,6 +109,7 @@ "jest-fail-on-console": "^2.4.2", "jest-silent-reporter": "^0.5.0", "jest-skipped-reporter": "^0.0.5", + "jscpd": "^3.5.3", "jsdom-global": "^3.0.2", "loader-utils": "^3.2.0", "node-notifier": "^10.0.1", diff --git a/specifyweb/specify/build_models.py b/specifyweb/specify/build_models.py index 69256a9fbb8..805e3f5ca7b 100644 --- a/specifyweb/specify/build_models.py +++ b/specifyweb/specify/build_models.py @@ -2,30 +2,12 @@ from django.db.models.signals import pre_delete from specifyweb.businessrules.exceptions import AbortSave +from specifyweb.specify.config import orderings from . import model_extras from .model_timestamp import save_auto_timestamp_field_with_override appname = __name__.split('.')[-2] -# REFACTOR: generate this on the fly based on presence of -# ordinal/ordernumber/position etc field in a table. This way it will -# automatically work for any newly added table -orderings = { - 'Picklistitem': ('ordinal', ), - 'Recordsetitem': ('recordid', ), - 'Spqueryfield': ('position', ), - 'Determination': ('-iscurrent',), - 'Author': ('ordernumber',), - 'Collector': ('ordernumber',), - 'AgentSpecialty': ('ordernumber',), - 'Determiner': ('ordernumber',), - 'Extractor': ('ordernumber',), - 'FieldNotebookPageSet': ('ordernumber',), - 'FundingAgent': ('ordernumber',), - 'GroupPerson': ('ordernumber',), - 'PcrPerson': ('ordernumber',), -} - def make_model(module, table, datamodel): """Returns a Django model class based on the definition of a Specify table. diff --git a/specifyweb/specify/config.py b/specifyweb/specify/config.py new file mode 100644 index 00000000000..a5d693062e7 --- /dev/null +++ b/specifyweb/specify/config.py @@ -0,0 +1,154 @@ +sp6_dependent_fields = { + 'Accession.accessionagents', + 'Accession.accessionauthorizations', + 'Accession.addressofrecord', + 'Agent.addresses', + 'Agent.agentgeographies', + 'Agent.agentspecialties', + 'Agent.groups', + 'Agent.identifiers', + 'Agent.variants', + 'Borrow.addressofrecord', + 'Borrow.borrowagents', + 'Borrow.borrowmaterials', + 'Borrow.shipments', + 'Borrowmaterial.borrowreturnmaterials', + 'Collectingevent.collectingeventattribute', + 'Collectingevent.collectingeventattrs', + 'Collectingevent.collectingeventauthorizations', + 'Collectingevent.collectors', + 'Collectingtrip.collectingtripattribute', + 'Collectingtrip.collectingtripauthorizations', + 'Collectingtrip.fundingagents', + 'Collectionobject.collectionobjectattribute', + 'Collectionobject.collectionobjectattrs', + 'Collectionobject.collectionobjectcitations', + 'Collectionobject.conservdescriptions', + 'Collectionobject.determinations', + 'Collectionobject.dnasequences', + 'Collectionobject.exsiccataitems', + 'CollectionObject.leftsiderels', + 'Collectionobject.otheridentifiers', + 'Collectionobject.preparations', + 'Collectionobject.collectionobjectproperties', + 'CollectionObject.rightsiderels', + 'Collectionobject.treatmentevents', + 'Collectionobject.voucherrelationships', + 'Commonnametx.citations', + 'Conservdescription.events', + 'Deaccession.deaccessionagents', + 'Determination.determinationcitations', + 'Determination.determiners', + 'Disposal.disposalagents', + 'Disposal.disposalpreparations', + 'Dnasequence.dnasequencingruns', + 'Dnasequencingrun.citations', + 'Exchangein.exchangeinpreps', + 'Exchangein.addressofrecord', + 'Exchangeout.exchangeoutpreps', + 'Exchangeout.addressofrecord', + 'Exsiccata.exsiccataitems', + 'Fieldnotebook.pagesets', + 'Fieldnotebookpageset.pages', + 'Geographytreedef.treedefitems', + 'Geologictimeperiodtreedef.treedefitems', + 'Gift.addressofrecord', + 'Gift.giftagents', + 'Gift.giftpreparations', + 'Gift.shipments', + 'Latlonpolygon.points', + 'lithostrattreedef.treedefitems', + 'Loan.addressofrecord', + 'Loan.loanagents', + 'Loan.loanpreparations', + 'Loan.shipments', + 'Loanpreparation.loanreturnpreparations', + 'Locality.geocoorddetails', + 'Locality.latlonpolygons', + 'Locality.localitycitations', + 'Locality.localitydetails', + 'Locality.localitynamealiass', + 'Materialsample.dnasequences', + 'Picklist.picklistitems', + 'Preparation.materialsamples', + 'Preparation.preparationattribute', + 'Preparation.preparationattrs', + 'Preparation.preparationproperties', + 'Preptype.attributedefs', + 'Referencework.authors', + 'Repositoryagreement.addressofrecord', + 'Repositoryagreement.repositoryagreementagents', + 'Repositoryagreement.repositoryagreementauthorizations', + 'Spquery.fields', + 'Storagetreedef.treedefitems', + 'Taxon.commonnames', + 'Taxon.taxoncitations', + 'Taxon.taxonattribute', + 'Taxontreedef.treedefitems', + 'Workbench.workbenchtemplate', + 'Workbenchtemplate.workbenchtemplatemappingitems', +} + + +sp6_system_tables = { + 'Attachment', + 'Attachmentimageattribute', + 'Attachmentmetadata', + 'Attachmenttag', + 'Attributedef', + 'Autonumberingscheme', + 'Datatype', + 'Morphbankview', + 'Picklist', + 'Picklistitem', + 'Recordset', + 'Recordsetitem', + 'Spappresource', + 'Spappresourcedata', + 'Spappresourcedir', + 'Spauditlogfield', + 'Spexportschema', + 'Spexportschemaitem', + 'Spexportschemaitemmapping', + 'Spexportschemamapping', + 'Spfieldvaluedefault', + 'Splocalecontainer', + 'Splocalecontaineritem', + 'Splocaleitemstr', + 'Sppermission', + 'Spprincipal', + 'Spquery', + 'Spqueryfield', + 'Spreport', + 'Sptasksemaphore', + 'Spversion', + 'Spviewsetobj', + 'Spvisualquery', + 'Specifyuser', + 'Workbench', + 'Workbenchdataitem', + 'Workbenchrow', + 'Workbenchrowexportedrelationship', + 'Workbenchrowimage', + 'Workbenchtemplate', + 'Workbenchtemplatemappingitem', +} + +# REFACTOR: generate this on the fly based on presence of +# ordinal/ordernumber/position etc field in a table. This way it will +# automatically work for any newly added table +orderings = { + 'Picklistitem': ('ordinal', ), + 'Recordsetitem': ('recordid', ), + 'Spqueryfield': ('position', ), + 'Determination': ('-iscurrent',), + 'Author': ('ordernumber',), + 'Collector': ('ordernumber',), + 'AgentSpecialty': ('ordernumber',), + 'Determiner': ('ordernumber',), + 'Extractor': ('ordernumber',), + 'FieldNotebookPageSet': ('ordernumber',), + 'FundingAgent': ('ordernumber',), + 'GroupPerson': ('ordernumber',), + 'PcrPerson': ('ordernumber',), +} \ No newline at end of file diff --git a/specifyweb/specify/load_datamodel.py b/specifyweb/specify/load_datamodel.py index 421aea8dbec..ea06708bb66 100644 --- a/specifyweb/specify/load_datamodel.py +++ b/specifyweb/specify/load_datamodel.py @@ -8,6 +8,8 @@ from django.conf import settings # type: ignore from django.utils.translation import gettext as _ +from specifyweb.specify.config import sp6_dependent_fields, sp6_system_tables + class DoesNotExistError(Exception): pass @@ -386,140 +388,4 @@ def flag_system_tables(datamodel: Datamodel) -> None: if table.is_attachment_jointable: table.system = True if table.name.endswith('treedef') or table.name.endswith('treedefitem'): - table.system = True - -sp6_dependent_fields = { - 'Accession.accessionagents', - 'Accession.accessionauthorizations', - 'Accession.addressofrecord', - 'Agent.addresses', - 'Agent.agentgeographies', - 'Agent.agentspecialties', - 'Agent.groups', - 'Agent.identifiers', - 'Agent.variants', - 'Borrow.addressofrecord', - 'Borrow.borrowagents', - 'Borrow.borrowmaterials', - 'Borrow.shipments', - 'Borrowmaterial.borrowreturnmaterials', - 'Collectingevent.collectingeventattribute', - 'Collectingevent.collectingeventattrs', - 'Collectingevent.collectingeventauthorizations', - 'Collectingevent.collectors', - 'Collectingtrip.collectingtripattribute', - 'Collectingtrip.collectingtripauthorizations', - 'Collectingtrip.fundingagents', - 'Collectionobject.collectionobjectattribute', - 'Collectionobject.collectionobjectattrs', - 'Collectionobject.collectionobjectcitations', - 'Collectionobject.conservdescriptions', - 'Collectionobject.determinations', - 'Collectionobject.dnasequences', - 'Collectionobject.exsiccataitems', - 'CollectionObject.leftsiderels', - 'Collectionobject.otheridentifiers', - 'Collectionobject.preparations', - 'Collectionobject.collectionobjectproperties', - 'CollectionObject.rightsiderels', - 'Collectionobject.treatmentevents', - 'Collectionobject.voucherrelationships', - 'Commonnametx.citations', - 'Conservdescription.events', - 'Deaccession.deaccessionagents', - 'Determination.determinationcitations', - 'Determination.determiners', - 'Disposal.disposalagents', - 'Disposal.disposalpreparations', - 'Dnasequence.dnasequencingruns', - 'Dnasequencingrun.citations', - 'Exchangein.exchangeinpreps', - 'Exchangein.addressofrecord', - 'Exchangeout.exchangeoutpreps', - 'Exchangeout.addressofrecord', - 'Exsiccata.exsiccataitems', - 'Fieldnotebook.pagesets', - 'Fieldnotebookpageset.pages', - 'Geographytreedef.treedefitems', - 'Geologictimeperiodtreedef.treedefitems', - 'Gift.addressofrecord', - 'Gift.giftagents', - 'Gift.giftpreparations', - 'Gift.shipments', - 'Latlonpolygon.points', - 'lithostrattreedef.treedefitems', - 'Loan.addressofrecord', - 'Loan.loanagents', - 'Loan.loanpreparations', - 'Loan.shipments', - 'Loanpreparation.loanreturnpreparations', - 'Locality.geocoorddetails', - 'Locality.latlonpolygons', - 'Locality.localitycitations', - 'Locality.localitydetails', - 'Locality.localitynamealiass', - 'Materialsample.dnasequences', - 'Picklist.picklistitems', - 'Preparation.materialsamples', - 'Preparation.preparationattribute', - 'Preparation.preparationattrs', - 'Preparation.preparationproperties', - 'Preptype.attributedefs', - 'Referencework.authors', - 'Repositoryagreement.addressofrecord', - 'Repositoryagreement.repositoryagreementagents', - 'Repositoryagreement.repositoryagreementauthorizations', - 'Spquery.fields', - 'Storagetreedef.treedefitems', - 'Taxon.commonnames', - 'Taxon.taxoncitations', - 'Taxon.taxonattribute', - 'Taxontreedef.treedefitems', - 'Workbench.workbenchtemplate', - 'Workbenchtemplate.workbenchtemplatemappingitems', -} - - -sp6_system_tables = { - 'Attachment', - 'Attachmentimageattribute', - 'Attachmentmetadata', - 'Attachmenttag', - 'Attributedef', - 'Autonumberingscheme', - 'Datatype', - 'Morphbankview', - 'Picklist', - 'Picklistitem', - 'Recordset', - 'Recordsetitem', - 'Spappresource', - 'Spappresourcedata', - 'Spappresourcedir', - 'Spauditlogfield', - 'Spexportschema', - 'Spexportschemaitem', - 'Spexportschemaitemmapping', - 'Spexportschemamapping', - 'Spfieldvaluedefault', - 'Splocalecontainer', - 'Splocalecontaineritem', - 'Splocaleitemstr', - 'Sppermission', - 'Spprincipal', - 'Spquery', - 'Spqueryfield', - 'Spreport', - 'Sptasksemaphore', - 'Spversion', - 'Spviewsetobj', - 'Spvisualquery', - 'Specifyuser', - 'Workbench', - 'Workbenchdataitem', - 'Workbenchrow', - 'Workbenchrowexportedrelationship', - 'Workbenchrowimage', - 'Workbenchtemplate', - 'Workbenchtemplatemappingitem', -} \ No newline at end of file + table.system = True \ No newline at end of file diff --git a/specifyweb/specify/record_merging.py b/specifyweb/specify/record_merging.py index f9313d9b322..b5bf945c028 100644 --- a/specifyweb/specify/record_merging.py +++ b/specifyweb/specify/record_merging.py @@ -16,7 +16,7 @@ from specifyweb.celery_tasks import LogErrorsTask, app from specifyweb.specify import models as spmodels from specifyweb.specify.api import uri_for_model, delete_obj, is_dependent_field, put_resource -from specifyweb.specify.build_models import orderings +from specifyweb.specify.config import orderings from specifyweb.specify.load_datamodel import Table, FieldDoesNotExistError from celery.utils.log import get_task_logger # type: ignore from specifyweb.specify.utils import get_app_model diff --git a/specifyweb/specify/sp7_build_models.py b/specifyweb/specify/sp7_build_models.py index f53915b1f9d..346a80e9985 100644 --- a/specifyweb/specify/sp7_build_models.py +++ b/specifyweb/specify/sp7_build_models.py @@ -1,5 +1,6 @@ import logging from . import model_extras +from specifyweb.specify.config import orderings appname = __name__.split('.')[-2] logger = logging.getLogger(__name__) @@ -55,27 +56,11 @@ 'models.BooleanField': 'TINYINT(1)', } -ORDERINGS = { - 'Picklistitem': ('ordinal', ), - 'Recordsetitem': ('recordid', ), - 'Spqueryfield': ('position', ), - 'Determination': ('-iscurrent',), - 'Author': ('ordernumber',), - 'Collector': ('ordernumber',), - 'AgentSpecialty': ('ordernumber',), - 'Determiner': ('ordernumber',), - 'Extractor': ('ordernumber',), - 'FieldNotebookPageSet': ('ordernumber',), - 'FundingAgent': ('ordernumber',), - 'GroupPerson': ('ordernumber',), - 'PcrPerson': ('ordernumber',), -} - def meta_class_code(table, attrs, indexes=[]): db_table = table.table ordering = tuple() - if table.django_name in ORDERINGS: - ordering += ORDERINGS[table.django_name] + if table.django_name in orderings: + ordering += orderings[table.django_name] if 'rankid' in attrs: ordering += ('rankid', ) # _meta diff --git a/specifyweb/specify/tree_extras.py b/specifyweb/specify/tree_extras.py index 173dfa3aeca..88e2f5d9e08 100644 --- a/specifyweb/specify/tree_extras.py +++ b/specifyweb/specify/tree_extras.py @@ -78,20 +78,8 @@ def save(): "Tree node's parent has rank greater than itself", {"tree" : self.__class__.__name__, "localizationKey" : "nodeParentInvalidRank", - "node" : { - "id" : self.id, - "rankid" : self.rankid, - "fullName" : self.fullname, - "parentid": self.parent.id, - "children": list(self.children.values('id', 'fullname')) - }, - "parent" : { - "id": self.parent.id, - "rankid" : self.parent.rankid, - "fullName": self.parent.fullname, - "parentid": self.parent.parent.id, - "children": list(self.parent.children.values('id', 'fullname')) - } + "node" : format_tree_node(self), + "parent" : format_tree_node(self.parent) }) if model.objects.filter(parent=self, parent__rankid__gte=F('rankid')).count() > 0: @@ -100,10 +88,7 @@ def save(): {"tree" : self.__class__.__name__, "localizationKey" : "nodeChildrenInvalidRank", "node" : { - "id" : self.id, - "rankid" : self.rankid, - "fullName" : self.fullname, - "parentid": self.parent.id, + **format_tree_node(self), "children": list(self.children.values('id', 'rankid', 'fullname').filter(parent=self, parent__rankid__gte=F('rankid'))) }}) @@ -216,24 +201,22 @@ def adding_node(node): {"tree" : "Taxon", "localizationKey" : "nodeOperationToSynonymizedParent", "operaton" : "Adding", - "node" : { - "id" : node.id, - "rankid" : node.rankid, - "fullName" : node.fullname, - "parentid": node.parent.id, - "children": list(node.children.values('id', 'fullname')) - }, - "parent" : { - "id" : parent.id, - "rankid" : parent.rankid, - "fullName" : parent.fullname, - "parentid": parent.parent.id, - "children": list(parent.children.values('id', 'fullname')) - }}) + "node" : format_tree_node(node), + "parent" : format_tree_node(parent) + }) insertion_point = open_interval(model, parent.nodenumber, 1) node.highestchildnodenumber = node.nodenumber = insertion_point +def format_tree_node(node): + return { + "id" : node.id, + "rankid" : node.rankid, + "fullName" : node.fullname, + "parentid": node.parent.id, + "children": list(node.children.values('id', 'fullname')) + } + def moving_node(to_save): logger.info('moving node %s', to_save) model = type(to_save) @@ -246,20 +229,8 @@ def moving_node(to_save): {"tree" : "Taxon", "localizationKey" : "nodeOperationToSynonymizedParent", "operation" : "Moving", - "node" : { - "id" : to_save.id, - "rankid" : to_save.rankid, - "fullName" : to_save.fullname, - "parentid": to_save.parent.id, - "children": list(to_save.children.values('id', 'fullname')) - }, - "parent" : { - "id" : new_parent.id, - "rankid" : new_parent.rankid, - "fullName" : new_parent.fullname, - "parentid": new_parent.parent.id, - "children": list(new_parent.children.values('id', 'fullname')) - }}) + "node" : format_tree_node(to_save), + "parent" : format_tree_node(new_parent)}) insertion_point = open_interval(model, new_parent.nodenumber, size) # node interval will have moved if it is to the right of the insertion point @@ -295,20 +266,9 @@ def merge(node, into, agent): {"tree" : "Taxon", "localizationKey" : "nodeOperationToSynonymizedParent", "operation" : "Merging", - "node" : { - "id" : node.id, - "rankid" : node.rankid, - "fullName" : node.fullname, - "parentid": node.parent.id, - "children": list(node.children.values('id', 'fullname')) - }, - "synonymized" : { - "id" : into.id, - "rankid" : into.rankid, - "fullName" : into.fullname, - "parentid": into.parent.id, - "children": list(into.children.values('id', 'fullname')) - }}) + "node" : format_tree_node(node), + "synonymized" : format_tree_node(into) + }) target_children = target.children.select_for_update() for child in node.children.select_for_update(): matched = [target_child for target_child in target_children @@ -374,20 +334,8 @@ def synonymize(node, into, agent): 'Synonymizing "{node.fullname}" to synonymized node "{into.fullname}"'.format(node=node, into=into), {"tree" : "Taxon", "localizationKey" : "nodeSynonymizeToSynonymized", - "node" : { - "id" : node.id, - "rankid" : node.rankid, - "fullName" : node.fullname, - "parentid": node.parent.id, - "children": list(node.children.values('id', 'fullname')) - }, - "synonymized" : { - "id" : into.id, - "rankid" : into.rankid, - "fullName" : into.fullname, - "parentid": into.parent.id, - "children": list(into.children.values('id', 'fullname')) - }}) + "node" : format_tree_node(to_save), + "parent" : format_tree_node(into)}) node.accepted_id = target.id node.isaccepted = False node.save() @@ -401,19 +349,8 @@ def synonymize(node, into, agent): 'Synonymizing node "{node.fullname}" which has children'.format(node=node), {"tree" : "Taxon", "localizationKey" : "nodeSynonimizeWithChildren", - "node" : { - "id" : node.id, - "rankid" : node.rankid, - "fullName" : node.fullname, - "children": list(node.children.values('id', 'fullname')) - }, - "parent" : { - "id" : into.id, - "rankid" : into.rankid, - "fullName" : into.fullname, - "parentid": into.parent.id, - "children": list(into.children.values('id', 'fullname')) - }}) + "node" : format_tree_node(to_save), + "parent" : format_tree_node(into)}) node.acceptedchildren.update(**{node.accepted_id_attr().replace('_id', ''): target}) #assuming synonym can't be synonymized mutation_log(TREE_SYNONYMIZE, node, agent, node.parent, diff --git a/specifyweb/stored_queries/build_models.py b/specifyweb/stored_queries/build_models.py index 2c0d7c36b1c..3f0c43bfd4e 100644 --- a/specifyweb/stored_queries/build_models.py +++ b/specifyweb/stored_queries/build_models.py @@ -2,6 +2,7 @@ from specifyweb.specify.load_datamodel import Datamodel, Table, Field, Relationship from sqlalchemy import Table as Table_Sqlalchemy, Column, ForeignKey, types, orm, MetaData from sqlalchemy.dialects.mysql import BIT as mysql_bit_type +from specifyweb.stored_queries.sp7_build_models import make_foreign_key metadata = MetaData() # Custom BIT type to handle both BIT(1) and TINYINT @@ -27,18 +28,6 @@ def make_table(datamodel: Datamodel, tabledef: Table): return Table_Sqlalchemy(tabledef.table, metadata, *columns) -def make_foreign_key(datamodel: Datamodel, reldef: Relationship): - remote_tabledef = datamodel.get_table(reldef.relatedModelName) # TODO: this could be a method of relationship - if remote_tabledef is None: - return - - fk_target = '.'.join((remote_tabledef.table, remote_tabledef.idColumn)) - - return Column(reldef.column, - ForeignKey(fk_target), - nullable = not reldef.required, - unique = reldef.type == 'one_to_one') - def make_column(flddef: Field): field_type = field_type_map[ flddef.type ] diff --git a/specifyweb/workbench/upload/upload_table.py b/specifyweb/workbench/upload/upload_table.py index e361ebb478f..9a24c8b0f32 100644 --- a/specifyweb/workbench/upload/upload_table.py +++ b/specifyweb/workbench/upload/upload_table.py @@ -37,6 +37,7 @@ def get_cols(self) -> Set[str]: | set(col for u in self.toOne.values() for col in u.get_cols()) \ | set(col for rs in self.toMany.values() for r in rs for col in r.get_cols()) + # jscpd:ignore-start def _to_json(self) -> Dict: result = dict( wbcols={k: v.to_json() for k,v in self.wbcols.items()}, @@ -51,6 +52,7 @@ def _to_json(self) -> Dict: for key, to_manys in self.toMany.items() } return result + # jscpd:ignore-end def to_json(self) -> Dict: return { 'uploadTable': self._to_json() } @@ -103,10 +105,12 @@ def apply_scoping(self, collection, defer: bool = True) -> Union["ScopedUploadTa return apply_scoping_to_uploadtable(self, collection) else: return self + # jscpd:ignore-start def get_cols(self) -> Set[str]: return set(cd.column for cd in self.wbcols.values()) \ | set(col for u in self.toOne.values() for col in u.get_cols()) \ | set(col for rs in self.toMany.values() for r in rs for col in r.get_cols()) + # jscpd:ignore-end """ @@ -238,6 +242,7 @@ def get_treedefs(self) -> Set: ) + # jscpd:ignore-start def bind(self, collection, row: Row, uploadingAgentId: int, auditor: Auditor, cache: Optional[Dict]=None, row_index: Optional[int] = None ) -> Union["BoundUploadTable", ParseFailures]: parsedFields, parseFails = parse_many(collection, self.name, self.wbcols, row) @@ -276,6 +281,7 @@ def bind(self, collection, row: Row, uploadingAgentId: int, auditor: Auditor, ca auditor=auditor, cache=cache, ) + # jscpd:ignore-end class OneToOneTable(UploadTable): def apply_scoping(self, collection) -> "ScopedOneToOneTable": @@ -335,6 +341,7 @@ def filter_on(self, path: str) -> FilterPack: for fieldname_, value in parsedField.filter_on.items() } + # jscpd:ignore-start for toOneField, toOneTable in self.toOne.items(): fs, es = toOneTable.filter_on(path + '__' + toOneField) for f in fs: @@ -347,6 +354,7 @@ def filter_on(self, path: str) -> FilterPack: (path + '__' + fieldname): value for fieldname, value in {**self.scopingAttrs, **self.static}.items() }) + # jscpd:ignore-end return FilterPack([filters], [])