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], [])