├── .gitignore ├── .graphqlrc.yml ├── .prettierrc ├── .vscode └── settings.json ├── .github ├── dependabot.yml └── workflows │ ├── tests.yml │ └── build-check.yml ├── jest.config.js ├── src ├── requests │ ├── index.ts │ ├── files-content.ts │ ├── commit-files.ts │ └── pull-request-data.ts ├── validators.ts ├── validate.ts ├── templates │ ├── domain.scs │ └── concept.scs ├── types.ts ├── generate.ts ├── main.ts └── replacements.ts ├── action.yml ├── tests ├── ostis_automation.domain.yaml ├── main.test.ts ├── scs_automation.concept.yaml └── expect │ ├── domain_ostis_automation.scs │ └── concept_scs_automation.scs ├── tsconfig.json ├── LICENSE ├── dist └── templates │ ├── domain.scs │ └── concept.scs ├── package.json ├── schemas ├── domain.schema.json ├── base.schema.json └── concept.schema.json ├── .eslintrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | schema.docs.graphql 3 | tests/.actual/ 4 | -------------------------------------------------------------------------------- /.graphqlrc.yml: -------------------------------------------------------------------------------- 1 | schema: 'schema.docs.graphql' 2 | documents: 'src/**/*.{graphql,js,ts,jsx,tsx}' 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid" 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "https://unpkg.com/graphql-config/config-schema.json": "graphqlrc.yml", 4 | "schemas/concept.schema.json": "*.concept.yaml", 5 | "schemas/domain.schema.json": "*.domain.yaml" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: weekly 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | clearMocks: true, 4 | moduleFileExtensions: ['js', 'ts'], 5 | testMatch: ['**/*.test.ts'], 6 | transform: { 7 | '^.+\\.ts$': 'ts-jest' 8 | }, 9 | verbose: true, 10 | } 11 | -------------------------------------------------------------------------------- /src/requests/index.ts: -------------------------------------------------------------------------------- 1 | import { GitHub } from '@actions/github/lib/utils' 2 | export type Octokit = InstanceType 3 | 4 | export { commitFiles } from './commit-files' 5 | export { getFilesContent } from './files-content' 6 | export { getPullRequestData } from './pull-request-data' 7 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | # rebuild any PRs and main branch changes 4 | on: 5 | pull_request: 6 | types: [opened, synchronize] 7 | 8 | jobs: 9 | # make sure build/ci work properly 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: npm install 15 | - run: npm run build 16 | - run: npm run test 17 | -------------------------------------------------------------------------------- /src/validators.ts: -------------------------------------------------------------------------------- 1 | import Ajv from 'ajv' 2 | 3 | import * as baseSchema from '../schemas/base.schema.json' 4 | import * as domainSchema from '../schemas/domain.schema.json' 5 | import * as conceptSchema from '../schemas/concept.schema.json' 6 | 7 | const ajv = new Ajv({ allErrors: true }) 8 | 9 | ajv.addSchema(baseSchema, 'base.schema.json') 10 | ajv.addSchema(domainSchema, 'domain.schema.json') 11 | ajv.addSchema(conceptSchema, 'concept.schema.json') 12 | 13 | export { ajv } 14 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: SCs Automation 2 | author: Artur Sharapov 3 | description: Generate and update scs files 4 | 5 | inputs: 6 | github_token: 7 | description: A GitHub token. 8 | required: false 9 | default: ${{ github.token }} 10 | branch: 11 | description: A branch name to run action from. 12 | required: false 13 | ignore_empty: 14 | description: Ignore empty files [never|always]. 15 | required: false 16 | default: never 17 | ignore_invalid: 18 | description: Ignore invalid files [never|always]. 19 | required: false 20 | default: never 21 | 22 | runs: 23 | using: node16 24 | main: dist/index.js 25 | -------------------------------------------------------------------------------- /tests/ostis_automation.domain.yaml: -------------------------------------------------------------------------------- 1 | ru: автоматизации разработки ostis-систем 2 | en: ostis-system development automation 3 | 4 | parent: ostis_system_development 5 | 6 | children: | 7 | personal_ostis_assistent_development_automation_tools 8 | sc_agent_development_automation_tools 9 | 10 | max: | 11 | ostis_system_development_automation 12 | 13 | concepts: | 14 | ostis_system_developement_automation_tool 15 | personal_ostis_assistent_development_automation_tool 16 | sc_agent_development_automation_tool 17 | ostis_system_development_operation 18 | ostis_archops_tool 19 | ostis_cicd_tool 20 | ostis_cicd_pipeline 21 | ostis_automation_action 22 | scs_automation 23 | 24 | nrels: | 25 | ostis_automation_next_step 26 | 27 | rrels: | 28 | ostis_automation_entrypoint 29 | -------------------------------------------------------------------------------- /src/requests/files-content.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '.' 2 | 3 | export const getFilesContent = async ( 4 | octokit: Octokit, 5 | variables: { owner: string; repo: string }, 6 | payload: { branch: string; fileNames: string[] } 7 | ): Promise => 8 | payload.fileNames.length 9 | ? Object.values( 10 | ( 11 | await octokit.graphql<{ repository: Record<`file${number}`, { text: string } | null> }>( 12 | /* GraphQL-Factory */ ` 13 | query ($owner: String!, $repo: String!) { 14 | repository(owner: $owner, name: $repo) { 15 | ${payload.fileNames.map( 16 | (fileName, index) => ` 17 | file${index}: object(expression: "${payload.branch}:${fileName}") { 18 | ... on Blob { 19 | text 20 | } 21 | } 22 | ` 23 | )} 24 | } 25 | } 26 | `, 27 | variables 28 | ) 29 | ).repository 30 | ).map(node => node?.text ?? '') 31 | : [] 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 4 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 5 | "outDir": "./lib", /* Redirect output structure to the directory. */ 6 | "strict": true, /* Enable all strict type-checking options. */ 7 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 8 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 9 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 10 | "resolveJsonModule": true 11 | }, 12 | "exclude": ["node_modules"], 13 | "include": ["src", "tests"] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Artur Sharapov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/requests/commit-files.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '.' 2 | 3 | export const commitFiles = async ( 4 | octokit: Octokit, 5 | variables: { repo: string; branch: string; oid: string }, 6 | payload: { files: { name: string; content: string }[] } 7 | ): Promise => 8 | ( 9 | await octokit.graphql<{ createCommitOnBranch: { commit: { commitUrl: string } } }>( 10 | /* GraphQL-Factory */ ` 11 | mutation ($repo: String!, $branch: String!, $oid: GitObjectID!) { 12 | createCommitOnBranch( 13 | input: { 14 | branch: { repositoryNameWithOwner: $repo, branchName: $branch } 15 | message: { headline: "Update scs files" } 16 | fileChanges: { 17 | additions: [ 18 | ${payload.files.map(file => `{ path: "${file.name}", contents: "${file.content}" }`)} 19 | ] 20 | } 21 | expectedHeadOid: $oid 22 | } 23 | ) { 24 | commit { 25 | commitUrl 26 | } 27 | } 28 | } 29 | `, 30 | variables 31 | ) 32 | ).createCommitOnBranch.commit.commitUrl 33 | -------------------------------------------------------------------------------- /src/validate.ts: -------------------------------------------------------------------------------- 1 | import { load } from 'js-yaml' 2 | import { ajv } from './validators' 3 | import { Config } from './types' 4 | 5 | export const parse = (text: string, fileName: string): [config: Config, path: string] => { 6 | const config = load(text) 7 | if (isConfigLike(config)) { 8 | type Match = [string, string, string, Config['configType']] 9 | const [, path, system, configType] = fileName.match(/^(.+\/)?(.+)\.(.+)\.yaml$/) as Match 10 | const validate = ajv.getSchema(`${configType}.schema.json`) 11 | if (!validate) { 12 | throw new Error('Invalid config type') 13 | } 14 | if (!validate(config)) { 15 | throw new Error( 16 | `[${system}.${configType}.yaml]\n${ajv.errorsText(validate.errors, { 17 | dataVar: configType, 18 | separator: '\n' 19 | })}` 20 | ) 21 | } 22 | config.system = system 23 | config.configType = configType 24 | return [config, path ?? ''] 25 | } 26 | throw new Error('Config is not valid') 27 | } 28 | 29 | export function isConfigLike(config: unknown): config is Config { 30 | if (typeof config !== 'object') { 31 | return false 32 | } 33 | return true 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/build-check.yml: -------------------------------------------------------------------------------- 1 | name: Build check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | 7 | 8 | jobs: 9 | check-dist: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set Node.js 16.x 16 | uses: actions/setup-node@v3.6.0 17 | with: 18 | node-version: 16.x 19 | 20 | - name: Install dependencies 21 | run: npm ci 22 | 23 | - name: Rebuild the dist/ directory 24 | run: | 25 | npm run build 26 | 27 | - name: Compare the expected and the actual dist/ directories 28 | run: | 29 | if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then 30 | echo "Detected uncommitted changes after build. See status below:" 31 | git diff 32 | exit 1 33 | fi 34 | id: diff 35 | 36 | # If index.js was different than expected, upload the expected version as an artifact 37 | - uses: actions/upload-artifact@v3 38 | if: ${{ failure() && steps.diff.conclusion == 'failure' }} 39 | with: 40 | name: dist 41 | path: dist/ 42 | -------------------------------------------------------------------------------- /tests/main.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from '@jest/globals' 2 | import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs' 3 | import { join } from 'path' 4 | import { generateScsFile } from '../src/generate' 5 | import { parse } from '../src/validate' 6 | 7 | rmSync(`${__dirname}/.actual`, { recursive: true, force: true }) 8 | mkdirSync(`${__dirname}/.actual`) 9 | 10 | const testScsGeneration = (fileName: string): void => { 11 | const fullPath = join(`${__dirname}`, fileName) 12 | const config = readFileSync(fullPath, { encoding: 'utf8' }) 13 | try { 14 | const { name, content } = generateScsFile(...parse(config, fullPath)) 15 | const scs = Buffer.from(content, 'base64').toString('utf-8') 16 | writeFileSync(name.replace(/([^/]+)$/, '.actual/$1'), scs, { encoding: 'utf8' }) 17 | if (process.argv[2] !== 'dev') { 18 | const expected = readFileSync(name.replace(/([^/]+)$/, 'expect/$1'), { encoding: 'utf8' }) 19 | expect(scs).toBe(expected) 20 | } 21 | } catch (e) { 22 | throw e 23 | } 24 | } 25 | 26 | test('Concept Template', async () => { 27 | testScsGeneration('scs_automation.concept.yaml') 28 | }) 29 | 30 | test('Domain Template', async () => { 31 | testScsGeneration('ostis_automation.domain.yaml') 32 | }) 33 | -------------------------------------------------------------------------------- /dist/templates/domain.scs: -------------------------------------------------------------------------------- 1 | section_subject_domain_of_#SYSTEM# 2 | 3 | <- #ATOMIC#_section; 4 | 5 | => nrel_main_idtf: 6 | [Раздел. Предметная область #RU#] (* <- lang_ru;; *); 7 | [Section. Subject domain of #EN#] (* <- lang_en;; *); 8 | 9 | ? <= nrel_section_decomposition: { 10 | - section_subject_domain_of_#CHILDREN# 11 | ? }; 12 | 13 | <- rrel_key_sc_element: 14 | subject_domain_of_#SYSTEM#; 15 | - concept_#MAX#; 16 | - concept_#CONCEPTS#; 17 | - nrel_#NRELS#; 18 | - rrel_#RRELS#; 19 | - #END#;; 20 | 21 | section_subject_domain_of_#SYSTEM#=[* 22 | subject_domain_of_#SYSTEM# 23 | <- sc_node_struct; 24 | <- subject_domain; 25 | => nrel_main_idtf: 26 | [Предметная область #RU#] (* <- lang_ru;; *); 27 | [Subject Domain of #EN#] (* <- lang_en;; *); 28 | 29 | -> rrel_maximum_studied_object_class: 30 | - concept_#MAX#; 31 | 32 | ? -> rrel_not_maximum_studied_object_class: 33 | - concept_#CONCEPTS#; 34 | 35 | ? -> rrel_explored_relation: 36 | - nrel_#NRELS#; 37 | - rrel_#RRELS#; 38 | 39 | <= nrel_private_subject_domain: 40 | subject_domain_of_#PARENT#; 41 | 42 | ? => nrel_private_subject_domain: 43 | - subject_domain_of_#CHILDREN#; 44 | 45 | - #END#;; 46 | *];; 47 | 48 | ? sc_node_class -> 49 | - concept_#MAX#; 50 | - concept_#CONCEPTS#; 51 | - #END#;; 52 | 53 | ? sc_node_norole_relation -> 54 | - nrel_#NRELS#; 55 | - #END#;; 56 | 57 | ? sc_node_role_relation -> 58 | - rrel_#RRELS#; 59 | - #END#;; 60 | -------------------------------------------------------------------------------- /src/templates/domain.scs: -------------------------------------------------------------------------------- 1 | section_subject_domain_of_#SYSTEM# 2 | 3 | <- #ATOMIC#_section; 4 | 5 | => nrel_main_idtf: 6 | [Раздел. Предметная область #RU#] (* <- lang_ru;; *); 7 | [Section. Subject domain of #EN#] (* <- lang_en;; *); 8 | 9 | ? <= nrel_section_decomposition: { 10 | - section_subject_domain_of_#CHILDREN# 11 | ? }; 12 | 13 | <- rrel_key_sc_element: 14 | subject_domain_of_#SYSTEM#; 15 | - concept_#MAX#; 16 | - concept_#CONCEPTS#; 17 | - nrel_#NRELS#; 18 | - rrel_#RRELS#; 19 | - #END#;; 20 | 21 | section_subject_domain_of_#SYSTEM#=[* 22 | subject_domain_of_#SYSTEM# 23 | <- sc_node_struct; 24 | <- subject_domain; 25 | => nrel_main_idtf: 26 | [Предметная область #RU#] (* <- lang_ru;; *); 27 | [Subject Domain of #EN#] (* <- lang_en;; *); 28 | 29 | -> rrel_maximum_studied_object_class: 30 | - concept_#MAX#; 31 | 32 | ? -> rrel_not_maximum_studied_object_class: 33 | - concept_#CONCEPTS#; 34 | 35 | ? -> rrel_explored_relation: 36 | - nrel_#NRELS#; 37 | - rrel_#RRELS#; 38 | 39 | <= nrel_private_subject_domain: 40 | subject_domain_of_#PARENT#; 41 | 42 | ? => nrel_private_subject_domain: 43 | - subject_domain_of_#CHILDREN#; 44 | 45 | - #END#;; 46 | *];; 47 | 48 | ? sc_node_class -> 49 | - concept_#MAX#; 50 | - concept_#CONCEPTS#; 51 | - #END#;; 52 | 53 | ? sc_node_norole_relation -> 54 | - nrel_#NRELS#; 55 | - #END#;; 56 | 57 | ? sc_node_role_relation -> 58 | - rrel_#RRELS#; 59 | - #END#;; 60 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type Config = DomainConfig | ConceptConfig | NrelConfig 2 | export type Subconfig = StatementConfig | PartitionConfig 3 | 4 | interface Identifiers { 5 | ru: T 6 | en: T 7 | } 8 | 9 | interface Nodes { 10 | concepts?: string // multiline 11 | nrels?: string // multiline 12 | rrels?: string // multiline 13 | } 14 | 15 | interface DomainConfig extends Identifiers, Nodes { 16 | configType: 'domain' 17 | system: string 18 | parent: string 19 | children?: string // multiline 20 | max: string // multiline 21 | } 22 | 23 | interface NeighbourhoodConfig extends Identifiers { 24 | system: string 25 | definition: Identifiers & { 26 | using: Nodes 27 | } 28 | statement?: { 29 | [system: string]: Omit 30 | } 31 | } 32 | 33 | interface ConceptConfig extends NeighbourhoodConfig { 34 | configType: 'concept' 35 | subclass?: string[] 36 | } 37 | 38 | interface NrelConfig extends NeighbourhoodConfig { 39 | configType: 'nrel' 40 | arity: 2 41 | domains: `${string} -> ${string}` 42 | properties: { 43 | symmetric: boolean 44 | reflexive: boolean 45 | transitive: boolean 46 | } 47 | } 48 | 49 | interface StatementConfig extends Identifiers { 50 | configType: 'statement' 51 | system: string 52 | title: Identifiers 53 | using: Nodes 54 | } 55 | 56 | interface PartitionConfig { 57 | configType: 'partition' 58 | partition: string // multiline 59 | } 60 | 61 | export const configTypes: Config['configType'][] = ['concept', 'domain'] 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scs-action", 3 | "version": "4.2.5", 4 | "private": false, 5 | "author": "Artur Sharapov", 6 | "license": "MIT", 7 | "description": "Automate custom and preset scs files creation with GitHub Actions", 8 | "main": "dist/index.js", 9 | "assets": [ 10 | "schemas" 11 | ], 12 | "scripts": { 13 | "format": "prettier --write '**/*.ts'", 14 | "format-check": "prettier --check '**/*.ts'", 15 | "lint": "eslint src/**/*.ts", 16 | "build": "ncc build src/main.ts && npm run copy-assets", 17 | "copy-assets": "rm -r ./dist/templates; cp -r ./src/templates ./dist/templates", 18 | "test": "jest", 19 | "start": "nodemon -x 'clear; jest dev' -e 'ts scs yaml'", 20 | "all": "npm run format && npm run lint && npm run build && npm test" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/qaip/scs-action.git" 25 | }, 26 | "keywords": [ 27 | "actions", 28 | "node", 29 | "setup" 30 | ], 31 | "dependencies": { 32 | "@actions/core": "^1.10.0", 33 | "@actions/github": "^5.1.1", 34 | "@octokit/webhooks-definitions": "^3.67.3", 35 | "ajv": "^8.12.0", 36 | "js-yaml": "^4.1.0" 37 | }, 38 | "devDependencies": { 39 | "@types/jest": "^29.5.2", 40 | "@types/js-yaml": "^4.0.5", 41 | "@types/node": "^20.3.3", 42 | "@typescript-eslint/parser": "^5.59.5", 43 | "@vercel/ncc": "^0.36.1", 44 | "eslint": "^8.40.0", 45 | "eslint-plugin-github": "^4.8.0", 46 | "eslint-plugin-jest": "^27.2.1", 47 | "jest": "^29.5.0", 48 | "nodemon": "^2.0.22", 49 | "prettier": "^2.8.8", 50 | "ts-jest": "^29.1.0", 51 | "typescript": "^5.0.4" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/requests/pull-request-data.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '.' 2 | import { configTypes } from '../types' 3 | 4 | export const getPullRequestData = async ( 5 | octokit: Octokit, 6 | variables: { owner: string; repo: string; pullRequestNumber: number } 7 | ): Promise<{ fileNames: string[]; commitOid: string }> => { 8 | const query = /* GraphQL */ ` 9 | query ($owner: String!, $repo: String!, $pullRequestNumber: Int!) { 10 | repository(owner: $owner, name: $repo) { 11 | pullRequest(number: $pullRequestNumber) { 12 | files(first: 100) { 13 | nodes { 14 | path 15 | } 16 | } 17 | headRef { 18 | target { 19 | ... on Commit { 20 | history(first: 1) { 21 | nodes { 22 | oid 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | ` 32 | type QueryResponse = { 33 | repository: { 34 | pullRequest: { 35 | files: { nodes: { path: string }[] } 36 | headRef: { 37 | target: { history: { nodes: [{ oid: string }] } } 38 | } 39 | } 40 | } 41 | } 42 | const { repository } = await octokit.graphql(query, variables) 43 | 44 | const fileNames = repository.pullRequest.files.nodes 45 | .map(node => node.path) 46 | .filter(path => new RegExp(`^.+\\.(${configTypes.join('|')})\\.yaml$`).test(path)) 47 | 48 | const commitOid = repository.pullRequest.headRef.target.history.nodes.at(0)?.oid 49 | if (!commitOid) { 50 | throw new Error('Commit Oid is not found') 51 | } 52 | 53 | return { fileNames, commitOid } 54 | } 55 | -------------------------------------------------------------------------------- /tests/scs_automation.concept.yaml: -------------------------------------------------------------------------------- 1 | ru: Автоматизация SCs 2 | en: SCs Automation | scs-automation | scs action 3 | 4 | definition: 5 | ru: 6 | Инструмент автоматизации и оптимизации создания scs-файлов с использованием 7 | GitHub Actions, а также инструмент разработки базы знаний 8 | en: 9 | - A tool for automation and optimization scs files creation using GitHub 10 | Actions 11 | - A knowledge base development tool 12 | using: 13 | concepts: | 14 | knowledge_base 15 | development_tool 16 | scs_file 17 | nrels: | 18 | automation 19 | optimization 20 | 21 | statement: 22 | scs_automation_motivation: 23 | title: 24 | ru: Мотивация Автоматизации SCs 25 | en: SCs Automation Motivation 26 | ru: 27 | Автоматизация SCs позволяет разработчикам описывать базу знаний без 28 | какого-либо дублирования или написания шаблонного кода 29 | en: 30 | SCs Automation lets developers describe a knowledge base without any 31 | duplicates and boilerplate code 32 | using: 33 | concepts: | 34 | developer 35 | knowledge_base 36 | duplicate 37 | boilerplate_code 38 | 39 | scs_automation_and_code_style_problem: 40 | title: 41 | ru: Автоматизация SCs и проблема стандарта оформления кода 42 | en: SCs Automation and Code Style Problem 43 | ru: Автоматизация SCs решает проблему стандарта оформления кода 44 | en: SCs Automation solves the code style problem 45 | using: 46 | concepts: | 47 | code_style_problem 48 | nrels: | 49 | solution 50 | 51 | subclass: 52 | - subclass_a 53 | - | 54 | subclass_b 55 | subclass_c 56 | subclass_d 57 | - | 58 | subclass_d 59 | subclass_e 60 | -------------------------------------------------------------------------------- /dist/templates/concept.scs: -------------------------------------------------------------------------------- 1 | concept_#SYSTEM# 2 | => nrel_main_idtf: 3 | [#RU#] (* <- lang_ru;; *); 4 | [#EN#] (* <- lang_en;; *); 5 | 6 | ? => nrel_idtf: 7 | - [#RU_ALT#] (* <- lang_ru;; *); 8 | - [#EN_ALT#] (* <- lang_en;; *); 9 | 10 | + /* #PARTITIONS# */ 11 | + => nrel_subdividing: { 12 | + - concept_#PARTITION#; 13 | + - #END# 14 | + }; 15 | 16 | ? => nrel_inclusion: 17 | - concept_#SUBSETS#; 18 | 19 | - #END#;; 20 | 21 | definition -> definition_of_#SYSTEM# (* 22 | -> rrel_key_sc_element: concept_#SYSTEM#;; 23 | 24 | <= nrel_sc_text_translation: ... (* 25 | - -> [#DEFINITION_RU#] (* <- lang_ru;; *);; 26 | - -> [#DEFINITION_EN#] (* <- lang_en;; *);; 27 | *);; 28 | 29 | => nrel_main_idtf: 30 | [Опр. (#RU#)] (* <- lang_ru;; *); 31 | [Def. (#EN#)] (* <- lang_en;; *);; 32 | 33 | => nrel_using_constants: { 34 | - concept_#DEFINITION_CONCEPTS#; 35 | - rrel_#DEFINITION_RRELS#; 36 | - nrel_#DEFINITION_NRELS#; 37 | - #END# 38 | };; 39 | *);; 40 | 41 | + /* #STATEMENT# */ 42 | + statement -> statement_of_#STATEMENT_SYSTEM# (* 43 | + -> rrel_key_sc_element: concept_#SYSTEM#;; 44 | + 45 | + <= nrel_sc_text_translation: ... (* 46 | + - -> [#STATEMENT_RU#] (* <- lang_ru;; *);; 47 | + - -> [#STATEMENT_EN#] (* <- lang_en;; *);; 48 | + *);; 49 | + 50 | + => nrel_main_idtf: 51 | + [Утв. (#STATEMENT_TITLE_RU#)] (* <- lang_ru;; *); 52 | + [Stat. (#STATEMENT_TITLE_EN#)] (* <- lang_en;; *);; 53 | + 54 | + => nrel_using_constants: { 55 | + - concept_#STATEMENT_CONCEPTS#; 56 | + - rrel_#STATEMENT_RRELS#; 57 | + - nrel_#STATEMENT_NRELS#; 58 | + - #END# 59 | + };; 60 | + *);; 61 | 62 | ? sc_node_class -> 63 | - concept_#DEFINITION_CONCEPTS#; 64 | - concept_#STATEMENT_CONCEPTS_ALL#; 65 | - #END#;; 66 | 67 | ? sc_node_norole_relation -> 68 | - nrel_#DEFINITION_NRELS#; 69 | - nrel_#STATEMENT_NRELS_ALL#; 70 | - #END#;; 71 | 72 | ? sc_node_role_relation -> 73 | - rrel_#DEFINITION_RRELS#; 74 | - rrel_#STATEMENT_RRELS_ALL#; 75 | - #END#;; 76 | -------------------------------------------------------------------------------- /src/templates/concept.scs: -------------------------------------------------------------------------------- 1 | concept_#SYSTEM# 2 | => nrel_main_idtf: 3 | [#RU#] (* <- lang_ru;; *); 4 | [#EN#] (* <- lang_en;; *); 5 | 6 | ? => nrel_idtf: 7 | - [#RU_ALT#] (* <- lang_ru;; *); 8 | - [#EN_ALT#] (* <- lang_en;; *); 9 | 10 | + /* #PARTITIONS# */ 11 | + => nrel_subdividing: { 12 | + - concept_#PARTITION#; 13 | + - #END# 14 | + }; 15 | 16 | ? => nrel_inclusion: 17 | - concept_#SUBSETS#; 18 | 19 | - #END#;; 20 | 21 | definition -> definition_of_#SYSTEM# (* 22 | -> rrel_key_sc_element: concept_#SYSTEM#;; 23 | 24 | <= nrel_sc_text_translation: ... (* 25 | - -> [#DEFINITION_RU#] (* <- lang_ru;; *);; 26 | - -> [#DEFINITION_EN#] (* <- lang_en;; *);; 27 | *);; 28 | 29 | => nrel_main_idtf: 30 | [Опр. (#RU#)] (* <- lang_ru;; *); 31 | [Def. (#EN#)] (* <- lang_en;; *);; 32 | 33 | => nrel_using_constants: { 34 | - concept_#DEFINITION_CONCEPTS#; 35 | - rrel_#DEFINITION_RRELS#; 36 | - nrel_#DEFINITION_NRELS#; 37 | - #END# 38 | };; 39 | *);; 40 | 41 | + /* #STATEMENT# */ 42 | + statement -> statement_of_#STATEMENT_SYSTEM# (* 43 | + -> rrel_key_sc_element: concept_#SYSTEM#;; 44 | + 45 | + <= nrel_sc_text_translation: ... (* 46 | + - -> [#STATEMENT_RU#] (* <- lang_ru;; *);; 47 | + - -> [#STATEMENT_EN#] (* <- lang_en;; *);; 48 | + *);; 49 | + 50 | + => nrel_main_idtf: 51 | + [Утв. (#STATEMENT_TITLE_RU#)] (* <- lang_ru;; *); 52 | + [Stat. (#STATEMENT_TITLE_EN#)] (* <- lang_en;; *);; 53 | + 54 | + => nrel_using_constants: { 55 | + - concept_#STATEMENT_CONCEPTS#; 56 | + - rrel_#STATEMENT_RRELS#; 57 | + - nrel_#STATEMENT_NRELS#; 58 | + - #END# 59 | + };; 60 | + *);; 61 | 62 | ? sc_node_class -> 63 | - concept_#DEFINITION_CONCEPTS#; 64 | - concept_#STATEMENT_CONCEPTS_ALL#; 65 | - #END#;; 66 | 67 | ? sc_node_norole_relation -> 68 | - nrel_#DEFINITION_NRELS#; 69 | - nrel_#STATEMENT_NRELS_ALL#; 70 | - #END#;; 71 | 72 | ? sc_node_role_relation -> 73 | - rrel_#DEFINITION_RRELS#; 74 | - rrel_#STATEMENT_RRELS_ALL#; 75 | - #END#;; 76 | -------------------------------------------------------------------------------- /schemas/domain.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Subject Domain", 3 | "description": "SCs Automation", 4 | "type": "object", 5 | "properties": { 6 | "ru": { 7 | "$ref": "base.schema.json#/$defs/ru", 8 | "description": "Russian idenfifier of the subject domain. Must be in the genitive case\n[ Предметная область ... ]", 9 | "examples": ["название области в родительском падеже"] 10 | }, 11 | "en": { 12 | "$ref": "base.schema.json#/$defs/en", 13 | "description": "English idenfifier of the subject domain\n[ Subject domain of ... ]", 14 | "examples": ["domain name"] 15 | }, 16 | "parent": { 17 | "$ref": "base.schema.json#/$defs/system", 18 | "description": "Parent subject domain of the describing subject domain", 19 | "title": "Parent subject domain", 20 | "examples": ["parent_subject_domain"] 21 | }, 22 | "children": { 23 | "$ref": "base.schema.json#/$defs/system-list", 24 | "title": "Child subject domains", 25 | "description": "List of child subject domains of the describing subject domain" 26 | }, 27 | "max": { 28 | "$ref": "base.schema.json#/$defs/system-list", 29 | "title": "Maximum classes of explored objects", 30 | "description": "List of maximum classes of explored objects of the subject domain" 31 | }, 32 | "concepts": { 33 | "$ref": "base.schema.json#/$defs/system-list", 34 | "title": "Non-maximum classes of explored objects", 35 | "description": "List of non-maximum classes of explored objects of the subject domain" 36 | }, 37 | "rrels": { 38 | "$ref": "base.schema.json#/$defs/system-list", 39 | "title": "Explored role relations", 40 | "description": "List of explored role relations of the subject domain" 41 | }, 42 | "nrels": { 43 | "$ref": "base.schema.json#/$defs/system-list", 44 | "title": "Explored norole relations", 45 | "description": "List of explored norole relations of the subject domain" 46 | } 47 | }, 48 | "required": ["ru", "en", "parent", "max", "concepts"], 49 | "additionalProperties": false 50 | } 51 | -------------------------------------------------------------------------------- /src/generate.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import { join } from 'path' 3 | import { getReplacements } from './replacements' 4 | import { Config, Subconfig } from './types' 5 | 6 | const TEMPLATES = new Map() 7 | 8 | export const generateScsFile = (config: Config, path: string): { name: string; content: string } => { 9 | if (!TEMPLATES.has(config.configType)) { 10 | TEMPLATES.set( 11 | config.configType, 12 | readFileSync(join(__dirname, `templates/${config.configType}.scs`), { encoding: 'utf8' }) 13 | ) 14 | } 15 | const template = TEMPLATES.get(config.configType) ?? '' 16 | 17 | const scs = replace(template, config) 18 | return { 19 | name: `${path}${config.configType}_${config.system}.scs`, 20 | content: Buffer.from(scs, 'utf-8').toString('base64') 21 | } 22 | } 23 | 24 | export const replace = (template: string, config: Config | Subconfig): string => { 25 | const replacements = getReplacements(config) 26 | const blockReplacer = (match: string, variable: `#${string}#`, block: string): string => { 27 | const replacement = replacements[variable] ?? match 28 | const subtemplate = block.replace(/^\+ /gm, '') 29 | return typeof replacement === 'function' ? replacement(subtemplate) : replacement 30 | } 31 | const lineReplacer = (match: string, prefix: string, variable: `#${string}#`, postfix: string): string => { 32 | if (variable === '#END#') return match 33 | const replacement = replacements[variable] ?? variable 34 | return typeof replacement === 'function' ? replacement(prefix, postfix) : replacement 35 | } 36 | const replacer = (_match: string, indent: string, variable: `#${string}#`): string => { 37 | const replacement = replacements[variable] ?? variable 38 | return typeof replacement === 'function' ? replacement(indent) : indent + replacement 39 | } 40 | return template 41 | .replace(/^\+ \/\* (#\w+#) \*\/\n((\+ [^\n]*\n)+)/gms, blockReplacer) 42 | .replace(/^- (.*)(#\w+#)(.*)\n/gm, lineReplacer) 43 | .replace(/(\t*)(#\w+#)/gm, replacer) 44 | .replace(/(^\n(\? .*\n)+(- .*\n)?($|\*))|(^\? )/gm, '$4') 45 | .replace(/;*\n*- \t*#END#/g, '') 46 | } 47 | 48 | export const list = 49 | (values: string | undefined) => 50 | (prefix: string, postfix = ''): string => { 51 | return values && values.trim() 52 | ? `${values 53 | .split('\n') 54 | .filter(Boolean) 55 | .map(value => prefix + value + postfix) 56 | .join(`${postfix ? '' : ';'}\n`)}\n` 57 | : '' 58 | } 59 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["jest", "@typescript-eslint"], 3 | "extends": ["plugin:github/recommended"], 4 | "parser": "@typescript-eslint/parser", 5 | "parserOptions": { 6 | "ecmaVersion": 9, 7 | "sourceType": "module", 8 | "project": "./tsconfig.json" 9 | }, 10 | "rules": { 11 | "i18n-text/no-en": "off", 12 | "eslint-comments/no-use": "off", 13 | "import/no-namespace": "off", 14 | "no-unused-vars": "off", 15 | "@typescript-eslint/no-unused-vars": "error", 16 | "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}], 17 | "@typescript-eslint/no-require-imports": "error", 18 | "@typescript-eslint/array-type": "error", 19 | "@typescript-eslint/await-thenable": "error", 20 | "@typescript-eslint/ban-ts-comment": "error", 21 | "camelcase": "off", 22 | "@typescript-eslint/consistent-type-assertions": "error", 23 | "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}], 24 | "@typescript-eslint/func-call-spacing": ["error", "never"], 25 | "@typescript-eslint/no-array-constructor": "error", 26 | "@typescript-eslint/no-empty-interface": "error", 27 | "@typescript-eslint/no-explicit-any": "error", 28 | "@typescript-eslint/no-extraneous-class": "error", 29 | "@typescript-eslint/no-for-in-array": "error", 30 | "@typescript-eslint/no-inferrable-types": "error", 31 | "@typescript-eslint/no-misused-new": "error", 32 | "@typescript-eslint/no-namespace": "error", 33 | "@typescript-eslint/no-non-null-assertion": "warn", 34 | "@typescript-eslint/no-unnecessary-qualifier": "error", 35 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 36 | "@typescript-eslint/no-useless-constructor": "error", 37 | "@typescript-eslint/no-var-requires": "error", 38 | "@typescript-eslint/prefer-for-of": "warn", 39 | "@typescript-eslint/prefer-function-type": "warn", 40 | "@typescript-eslint/prefer-includes": "error", 41 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 42 | "@typescript-eslint/promise-function-async": "error", 43 | "@typescript-eslint/require-array-sort-compare": "error", 44 | "@typescript-eslint/restrict-plus-operands": "error", 45 | "semi": "off", 46 | "@typescript-eslint/semi": ["error", "never"], 47 | "@typescript-eslint/type-annotation-spacing": "error", 48 | "@typescript-eslint/unbound-method": "error" 49 | }, 50 | "env": { 51 | "node": true, 52 | "es6": true, 53 | "jest/globals": true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as github from '@actions/github' 3 | // eslint-disable-next-line import/no-unresolved 4 | import { PullRequestEvent } from '@octokit/webhooks-definitions/schema' 5 | import { generateScsFile } from './generate' 6 | import { commitFiles, getFilesContent, getPullRequestData } from './requests/' 7 | import { parse } from './validate' 8 | 9 | async function run(): Promise { 10 | const githubToken = core.getInput('github_token') 11 | const octokit = github.getOctokit(githubToken) 12 | const payload = github.context.payload as PullRequestEvent 13 | 14 | // Get the names of changed sc-yaml files and the oid of the last commit 15 | const { fileNames, commitOid } = await getPullRequestData(octokit, { 16 | ...github.context.repo, 17 | pullRequestNumber: payload.pull_request.number 18 | }) 19 | 20 | // Exit if no files are changed 21 | if (!fileNames.length) { 22 | core.info(`No changes detected`) 23 | return 24 | } 25 | 26 | core.info(`Detected new changes in files: { ${fileNames.join(',')} }`) 27 | 28 | // Get the content of changed files 29 | let filesContent = await getFilesContent(octokit, github.context.repo, { 30 | fileNames, 31 | branch: payload.pull_request.head.ref 32 | }) 33 | 34 | // Exclude empty files if ignore_empty input is set 35 | const ignoreEmpty = core.getInput('ignore_empty') 36 | if (ignoreEmpty === 'always') { 37 | filesContent = filesContent.filter(content => content.trim()) 38 | } 39 | 40 | // Generate scs files 41 | const ignoreInvalid = core.getInput('ignore_invalid') 42 | const nonNullable = (value: T): value is NonNullable => value !== undefined 43 | const files = filesContent 44 | .map((content, index) => { 45 | try { 46 | return generateScsFile(...parse(content, fileNames[index])) 47 | } catch (e) { 48 | if (e instanceof Error) { 49 | core.error(e.message) 50 | if (ignoreInvalid === 'always') { 51 | return undefined 52 | } 53 | process.exit(1) 54 | } else { 55 | throw e 56 | } 57 | } 58 | }) 59 | .filter(nonNullable) 60 | 61 | // Commit and push generated scs files 62 | const commitUrl = await commitFiles( 63 | octokit, 64 | { 65 | repo: payload.repository.full_name, 66 | branch: payload.pull_request.head.ref, 67 | oid: commitOid 68 | }, 69 | { files } 70 | ) 71 | 72 | core.info(`Successfully made a commit: ${commitUrl}`) 73 | } 74 | 75 | try { 76 | run() 77 | } catch (error) { 78 | core.setFailed((error as Error).message) 79 | } 80 | -------------------------------------------------------------------------------- /tests/expect/domain_ostis_automation.scs: -------------------------------------------------------------------------------- 1 | section_subject_domain_of_ostis_automation 2 | 3 | <- non_atomic_section; 4 | 5 | => nrel_main_idtf: 6 | [Раздел. Предметная область автоматизации разработки ostis-систем] (* <- lang_ru;; *); 7 | [Section. Subject domain of ostis-system development automation] (* <- lang_en;; *); 8 | 9 | <= nrel_section_decomposition: { 10 | section_subject_domain_of_personal_ostis_assistent_development_automation_tools; 11 | section_subject_domain_of_sc_agent_development_automation_tools 12 | }; 13 | 14 | <- rrel_key_sc_element: 15 | subject_domain_of_ostis_automation; 16 | concept_ostis_system_development_automation; 17 | concept_ostis_system_developement_automation_tool; 18 | concept_personal_ostis_assistent_development_automation_tool; 19 | concept_sc_agent_development_automation_tool; 20 | concept_ostis_system_development_operation; 21 | concept_ostis_archops_tool; 22 | concept_ostis_cicd_tool; 23 | concept_ostis_cicd_pipeline; 24 | concept_ostis_automation_action; 25 | concept_scs_automation; 26 | nrel_ostis_automation_next_step; 27 | rrel_ostis_automation_entrypoint;; 28 | 29 | section_subject_domain_of_ostis_automation=[* 30 | subject_domain_of_ostis_automation 31 | <- sc_node_struct; 32 | <- subject_domain; 33 | => nrel_main_idtf: 34 | [Предметная область автоматизации разработки ostis-систем] (* <- lang_ru;; *); 35 | [Subject Domain of ostis-system development automation] (* <- lang_en;; *); 36 | 37 | -> rrel_maximum_studied_object_class: 38 | concept_ostis_system_development_automation; 39 | 40 | -> rrel_not_maximum_studied_object_class: 41 | concept_ostis_system_developement_automation_tool; 42 | concept_personal_ostis_assistent_development_automation_tool; 43 | concept_sc_agent_development_automation_tool; 44 | concept_ostis_system_development_operation; 45 | concept_ostis_archops_tool; 46 | concept_ostis_cicd_tool; 47 | concept_ostis_cicd_pipeline; 48 | concept_ostis_automation_action; 49 | concept_scs_automation; 50 | 51 | -> rrel_explored_relation: 52 | nrel_ostis_automation_next_step; 53 | rrel_ostis_automation_entrypoint; 54 | 55 | <= nrel_private_subject_domain: 56 | subject_domain_of_ostis_system_development; 57 | 58 | => nrel_private_subject_domain: 59 | subject_domain_of_personal_ostis_assistent_development_automation_tools; 60 | subject_domain_of_sc_agent_development_automation_tools;; 61 | *];; 62 | 63 | sc_node_class -> 64 | concept_ostis_system_development_automation; 65 | concept_ostis_system_developement_automation_tool; 66 | concept_personal_ostis_assistent_development_automation_tool; 67 | concept_sc_agent_development_automation_tool; 68 | concept_ostis_system_development_operation; 69 | concept_ostis_archops_tool; 70 | concept_ostis_cicd_tool; 71 | concept_ostis_cicd_pipeline; 72 | concept_ostis_automation_action; 73 | concept_scs_automation;; 74 | 75 | sc_node_norole_relation -> 76 | nrel_ostis_automation_next_step;; 77 | 78 | sc_node_role_relation -> 79 | rrel_ostis_automation_entrypoint;; 80 | -------------------------------------------------------------------------------- /tests/expect/concept_scs_automation.scs: -------------------------------------------------------------------------------- 1 | concept_scs_automation 2 | => nrel_main_idtf: 3 | [Автоматизация SCs] (* <- lang_ru;; *); 4 | [SCs Automation] (* <- lang_en;; *); 5 | 6 | => nrel_idtf: 7 | [scs-automation] (* <- lang_en;; *); 8 | [scs action] (* <- lang_en;; *); 9 | 10 | => nrel_subdividing: { 11 | concept_subclass_b; 12 | concept_subclass_c; 13 | concept_subclass_d 14 | }; 15 | 16 | => nrel_subdividing: { 17 | concept_subclass_d; 18 | concept_subclass_e 19 | }; 20 | 21 | => nrel_inclusion: 22 | concept_subclass_a;; 23 | 24 | definition -> definition_of_scs_automation (* 25 | -> rrel_key_sc_element: concept_scs_automation;; 26 | 27 | <= nrel_sc_text_translation: ... (* 28 | -> [Инструмент автоматизации и оптимизации создания scs-файлов с использованием GitHub Actions, а также инструмент разработки базы знаний] (* <- lang_ru;; *);; 29 | -> [A tool for automation and optimization scs files creation using GitHub Actions] (* <- lang_en;; *);; 30 | -> [A knowledge base development tool] (* <- lang_en;; *);; 31 | *);; 32 | 33 | => nrel_main_idtf: 34 | [Опр. (Автоматизация SCs)] (* <- lang_ru;; *); 35 | [Def. (SCs Automation)] (* <- lang_en;; *);; 36 | 37 | => nrel_using_constants: { 38 | concept_knowledge_base; 39 | concept_development_tool; 40 | concept_scs_file; 41 | nrel_automation; 42 | nrel_optimization 43 | };; 44 | *);; 45 | 46 | statement -> statement_of_scs_automation_motivation (* 47 | -> rrel_key_sc_element: concept_scs_automation;; 48 | 49 | <= nrel_sc_text_translation: ... (* 50 | -> [Автоматизация SCs позволяет разработчикам описывать базу знаний без какого-либо дублирования или написания шаблонного кода] (* <- lang_ru;; *);; 51 | -> [SCs Automation lets developers describe a knowledge base without any duplicates and boilerplate code] (* <- lang_en;; *);; 52 | *);; 53 | 54 | => nrel_main_idtf: 55 | [Утв. (Мотивация Автоматизации SCs)] (* <- lang_ru;; *); 56 | [Stat. (SCs Automation Motivation)] (* <- lang_en;; *);; 57 | 58 | => nrel_using_constants: { 59 | concept_developer; 60 | concept_knowledge_base; 61 | concept_duplicate; 62 | concept_boilerplate_code 63 | };; 64 | *);; 65 | 66 | statement -> statement_of_scs_automation_and_code_style_problem (* 67 | -> rrel_key_sc_element: concept_scs_automation;; 68 | 69 | <= nrel_sc_text_translation: ... (* 70 | -> [Автоматизация SCs решает проблему стандарта оформления кода] (* <- lang_ru;; *);; 71 | -> [SCs Automation solves the code style problem] (* <- lang_en;; *);; 72 | *);; 73 | 74 | => nrel_main_idtf: 75 | [Утв. (Автоматизация SCs и проблема стандарта оформления кода)] (* <- lang_ru;; *); 76 | [Stat. (SCs Automation and Code Style Problem)] (* <- lang_en;; *);; 77 | 78 | => nrel_using_constants: { 79 | concept_code_style_problem; 80 | nrel_solution 81 | };; 82 | *);; 83 | 84 | sc_node_class -> 85 | concept_knowledge_base; 86 | concept_development_tool; 87 | concept_scs_file; 88 | concept_developer; 89 | concept_knowledge_base; 90 | concept_duplicate; 91 | concept_boilerplate_code; 92 | concept_code_style_problem;; 93 | 94 | sc_node_norole_relation -> 95 | nrel_automation; 96 | nrel_optimization; 97 | nrel_solution;; 98 | -------------------------------------------------------------------------------- /src/replacements.ts: -------------------------------------------------------------------------------- 1 | import { list, replace } from './generate' 2 | import { Config, Subconfig } from './types' 3 | 4 | type Replacements = Record<`#${string}#`, string | ((prefix: string, postfix?: string) => string)> 5 | export const getReplacements = (config: Config | Subconfig): Replacements => { 6 | const toArray = (value: string | string[]): string[] => (typeof value === 'string' ? value.split(' | ') : value) 7 | switch (config.configType) { 8 | case 'domain': { 9 | return { 10 | '#SYSTEM#': config.system, 11 | '#RU#': config.ru, 12 | '#EN#': config.en, 13 | '#PARENT#': config.parent, 14 | '#CHILDREN#': list(config.children), 15 | '#MAX#': list(config.max), 16 | '#CONCEPTS#': list(config.concepts), 17 | '#NRELS#': list(config.nrels), 18 | '#RRELS#': list(config.rrels), 19 | '#ATOMIC#': config.children ? 'non_atomic' : 'atomic' 20 | } 21 | } 22 | case 'concept': 23 | case 'nrel': { 24 | const statements = config.statement ? Object.entries(config.statement) : [] 25 | const nbhd: Replacements = { 26 | '#SYSTEM#': config.system, 27 | '#RU#': toArray(config.ru)[0], 28 | '#EN#': toArray(config.en)[0], 29 | '#RU_ALT#': list(toArray(config.ru).slice(1).join('\n')), 30 | '#EN_ALT#': list(toArray(config.en).slice(1).join('\n')), 31 | '#DEFINITION_RU#': list(toArray(config.definition.ru).join('\n')), 32 | '#DEFINITION_EN#': list(toArray(config.definition.en).join('\n')), 33 | '#DEFINITION_CONCEPTS#': list(config.definition.using.concepts), 34 | '#DEFINITION_NRELS#': list(config.definition.using.nrels), 35 | '#DEFINITION_RRELS#': list(config.definition.using.rrels), 36 | '#STATEMENT#': template => 37 | config.statement 38 | ? statements 39 | .map(([system, variables]) => replace(template, { configType: 'statement', system, ...variables })) 40 | .join('\n') 41 | : '', 42 | '#STATEMENT_CONCEPTS_ALL#': list(statements.map(statement => statement[1].using.concepts).join('\n')), 43 | '#STATEMENT_NRELS_ALL#': list(statements.map(statement => statement[1].using.nrels).join('\n')), 44 | '#STATEMENT_RRELS_ALL#': list(statements.map(statement => statement[1].using.rrels).join('\n')) 45 | } 46 | return config.configType === 'concept' 47 | ? { 48 | ...nbhd, 49 | '#PARTITIONS#': template => 50 | config.subclass 51 | ? config.subclass 52 | .filter(partition => partition.includes('\n')) 53 | .map(partition => replace(template, { configType: 'partition', partition })) 54 | .join('\n') 55 | : '', 56 | '#SUBSETS#': config.subclass 57 | ? list(config.subclass.filter(partition => !partition.includes('\n')).join('\n')) 58 | : '' 59 | } 60 | : { ...nbhd } 61 | } 62 | case 'statement': 63 | return { 64 | '#STATEMENT_SYSTEM#': config.system, 65 | '#STATEMENT_RU#': list(toArray(config.ru).join('\n')), 66 | '#STATEMENT_EN#': list(toArray(config.en).join('\n')), 67 | '#STATEMENT_TITLE_RU#': config.title.ru, 68 | '#STATEMENT_TITLE_EN#': config.title.en, 69 | '#STATEMENT_CONCEPTS#': list(config.using.concepts), 70 | '#STATEMENT_NRELS#': list(config.using.nrels), 71 | '#STATEMENT_RRELS#': list(config.using.rrels) 72 | } 73 | case 'partition': 74 | return { 75 | '#PARTITION#': list(config.partition) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /schemas/base.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$defs": { 3 | "system-list": { 4 | "type": "string", 5 | "pattern": "^([a-z][a-z0-9]*(_[a-z0-9]+)*\\n)+$" 6 | }, 7 | "system": { 8 | "type": "string", 9 | "pattern": "^[a-z][a-z0-9]*(_[a-z0-9]+)*$" 10 | }, 11 | "system-2": { 12 | "type": "string", 13 | "pattern": "^[a-z][a-z0-9]*(_[a-z0-9]+)*\\n([a-z][a-z0-9]*(_[a-z0-9]+)*\\n)+$" 14 | }, 15 | "ru": { 16 | "type": "string", 17 | "pattern": "^(([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?))(-([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?)))*)( ([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?))(-([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?)))*)*$", 18 | "examples": ["название"] 19 | }, 20 | "en": { 21 | "type": "string", 22 | "pattern": "^(([A-z]+|([0-9]+(\\.[0-9]+)?))(-([A-z]+|([0-9]+(\\.[0-9]+)?)))*)( ([A-z]+|([0-9]+(\\.[0-9]+)?))(-([A-z]+|([0-9]+(\\.[0-9]+)?)))*)*$", 23 | "examples": ["name"] 24 | }, 25 | "ru-text": { 26 | "type": "string", 27 | "pattern": "^(([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?))((( [-+^*/] )|-)([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?)))*)([.,;?!]?( |\\(| \\()([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?))((( [-+^*/] )|-)([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?)))*[)]?)*$", 28 | "examples": ["текст"] 29 | }, 30 | "en-text": { 31 | "type": "string", 32 | "pattern": "^(([A-z]+|([0-9]+(\\.[0-9]+)?))((( [-+^*/] )|-)([A-z]+|([0-9]+(\\.[0-9]+)?)))*)([.,;?!]?( |\\(| \\()([A-z]+|([0-9]+(\\.[0-9]+)?))((( [-+^*/] )|-)([A-z]+|([0-9]+(\\.[0-9]+)?)))*[)]?)*$", 33 | "examples": ["text"] 34 | }, 35 | "ru-set": { 36 | "type": "string", 37 | "pattern": "^((([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?))(-([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?)))*)( ([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?))(-([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?)))*)*)( \\| (([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?))(-([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?)))*)( ([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?))(-([А-яЁёA-z]+|([0-9]+(\\.[0-9]+)?)))*)*)*$", 38 | "examples": ["название | синоним"] 39 | }, 40 | "en-set": { 41 | "type": "string", 42 | "pattern": "^((([A-z]+|([0-9]+(\\.[0-9]+)?))(-([A-z]+|([0-9]+(\\.[0-9]+)?)))*)( ([A-z]+|([0-9]+(\\.[0-9]+)?))(-([A-z]+|([0-9]+(\\.[0-9]+)?)))*)*)( \\| (([A-z]+|([0-9]+(\\.[0-9]+)?))(-([A-z]+|([0-9]+(\\.[0-9]+)?)))*)( ([A-z]+|([0-9]+(\\.[0-9]+)?))(-([A-z]+|([0-9]+(\\.[0-9]+)?)))*)*)*$", 43 | "examples": ["name | synonym"] 44 | }, 45 | "ru-array": { 46 | "type": "array", 47 | "items": { 48 | "$ref": "#/$defs/ru" 49 | }, 50 | "examples": [["название", "синоним"]] 51 | }, 52 | "en-array": { 53 | "type": "array", 54 | "items": { 55 | "$ref": "#/$defs/ru" 56 | }, 57 | "examples": [["name", "synonym"]] 58 | }, 59 | "ru-text-array": { 60 | "type": "array", 61 | "items": { 62 | "$ref": "#/$defs/ru-text" 63 | }, 64 | "examples": [["текст", "текст"]] 65 | }, 66 | "en-text-array": { 67 | "type": "array", 68 | "items": { 69 | "$ref": "#/$defs/ru-text" 70 | }, 71 | "examples": [["text", "text"]] 72 | }, 73 | "ru-set+array": { 74 | "oneOf": [{ "$ref": "#/$defs/ru-set" }, { "$ref": "#/$defs/ru-array" }] 75 | }, 76 | "en-set+array": { 77 | "oneOf": [{ "$ref": "#/$defs/en-set" }, { "$ref": "#/$defs/en-array" }] 78 | }, 79 | "ru-text+array": { 80 | "oneOf": [{ "$ref": "#/$defs/ru-text" }, { "$ref": "#/$defs/ru-text-array", "type": "array", "minItems": 2 }] 81 | }, 82 | "en-text+array": { 83 | "oneOf": [{ "$ref": "#/$defs/en-text" }, { "$ref": "#/$defs/en-text-array", "type": "array", "minItems": 2 }] 84 | }, 85 | "nodes": { 86 | "type": "object", 87 | "properties": { 88 | "concepts": { 89 | "type": "string", 90 | "$ref": "#/$defs/system-list", 91 | "title": "List of concepts", 92 | "description": "List of concepts" 93 | }, 94 | "nrels": { 95 | "type": "string", 96 | "$ref": "#/$defs/system-list", 97 | "title": "List of norole relations", 98 | "description": "List of norole relations" 99 | }, 100 | "rrels": { 101 | "type": "string", 102 | "$ref": "#/$defs/system-list", 103 | "title": "List of role relations", 104 | "description": "List of role relations" 105 | } 106 | }, 107 | "additionalProperties": false, 108 | "examples": [ 109 | { 110 | "concepts": "|\n used_concept", 111 | "nrels": "|\n used_nrel", 112 | "rrels": "|\n used_rrel" 113 | } 114 | ] 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or advances of any kind 22 | * Trolling, insulting or derogatory comments, and personal or political attacks 23 | * Public or private harassment 24 | * Publishing others' private information, such as a physical or email address, without their explicit permission 25 | * Contacting individual members, contributors, or leaders privately, outside designated community mechanisms, without their explicit permission 26 | * Other conduct which could reasonably be considered inappropriate in a professional setting 27 | 28 | ## Enforcement Responsibilities 29 | 30 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 31 | 32 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 33 | 34 | ## Scope 35 | 36 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 37 | 38 | ## Enforcement 39 | 40 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at opensource@github.com. All complaints will be reviewed and investigated promptly and fairly. 41 | 42 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 43 | 44 | ## Enforcement Guidelines 45 | 46 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 47 | 48 | ### 1. Correction 49 | 50 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 51 | 52 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 53 | 54 | ### 2. Warning 55 | 56 | **Community Impact**: A violation through a single incident or series of actions. 57 | 58 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 59 | 60 | ### 3. Temporary Ban 61 | 62 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 63 | 64 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 65 | 66 | ### 4. Permanent Ban 67 | 68 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 69 | 70 | **Consequence**: A permanent ban from any sort of public interaction within the community. 71 | 72 | ## Attribution 73 | 74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at . 75 | 76 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 77 | 78 | [homepage]: https://www.contributor-covenant.org 79 | 80 | For answers to common questions about this code of conduct, see the FAQ at . Translations are available at . 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to scs-action 2 | 👋 Hi there! Thanks for coming here and contributing to scs-action, your help is very valuable! 3 | 4 | ## How do I contribute 5 | - Create an issue for bug or feature request. 6 | - Comment an existing issue to say that you are tackling it. 7 | - Create a pull request fixing a bug or implementing a feature described in an issue. 8 | - ⭐ Support the project by giving it a star. 9 | 10 | ## Development 11 | - Make sure you have **node 16 or higher** installed. 12 | - Install the dependencies by `npm i`. 13 | - Make sure to run `npm run all` before pushing changes to a pull request. 14 | 15 | ## Adding a new scs template 16 | 1. Create an scs file in `./src/templates` using `#VAR_NAME#` as variables. 17 | 2. Create an interface for the new template in `./src/types.ts` and describe the yaml configuration file for the new template. 18 | 3. Describe the replacement rules in `./src/replacements.ts`. 19 | 20 | ## Template syntax 21 | ### Variables 22 | Template scs files have 3 types of variables: 23 | 24 | 1. Single variable: 25 | ```scs 26 | concept_#SINGLE# -> ...; 27 | ⇓ ⇓ ⇓ 28 | concept_scs_automation -> ...; 29 | ``` 30 | 31 | 2. Line variable: 32 | ```scs 33 | - concept_#LINE# -> ...; 34 | ⇓ ⇓ ⇓ 35 | concept_one -> ...; 36 | concept_two -> ...; 37 | concept_three -> ...; 38 | ``` 39 | 40 | 3. Block variable: 41 | ```scs 42 | + /* #BLOCK# */ 43 | + concept_#SYSTEM# 44 | + => nrel_main_idtf: 45 | + [#RU#] (* <- lang_ru;; *); 46 | + [#EN#] (* <- lang_en;; *);; 47 | 48 | ⇓ ⇓ ⇓ 49 | 50 | concept_one 51 | => nrel_main_idtf: 52 | [Один] (* <- lang_ru;; *); 53 | [One] (* <- lang_en;; *);; 54 | 55 | concept_two 56 | => nrel_main_idtf: 57 | [Два] (* <- lang_ru;; *); 58 | [Two] (* <- lang_en;; *);; 59 | 60 | concept_three 61 | => nrel_main_idtf: 62 | [Три] (* <- lang_ru;; *); 63 | [Three] (* <- lang_en;; *);; 64 | ``` 65 | 66 | Note that the additional syntax constructions of scs template files do not break the scs syntax highlight provided by SCs language server. 67 | 68 | ### Optional blocks 69 | Optional blocks are used when the variables defined inside the blocks may not be specified by users. In this case, the entire block should be removed. Here is an example demonstrating the problem: 70 | ```scs 71 | -> rrel_explored_relation: 72 | - nrel_#NRELS#; 73 | - rrel_#RRELS#; 74 | 75 | sc_node_class -> concept_hello 76 | 77 | ⇓ ⇓ ⇓ 78 | 79 | -> rrel_explored_relation: 80 | sc_node_class -> concept_hello 81 | ``` 82 | In the above example, having no `NRELS` and `RRELS` specified will lead to invalid scs syntax construction. That's where optional blocks come. Optional blocks are defined similarly to line or block variables: 83 | ```scs 84 | ? -> rrel_explored_relation: 85 | - nrel_#NRELS#; 86 | - rrel_#RRELS#; 87 | 88 | sc_node_class -> concept_hello 89 | 90 | ⇓ ⇓ ⇓ 91 | 92 | sc_node_class -> concept_hello 93 | ``` 94 | A more complex example may look like: 95 | ```scs 96 | + /* #STATEMENT# */ 97 | + statement -> statement_of_#STATEMENT_SYSTEM# (* 98 | + -> rrel_key_sc_element: concept_#SYSTEM#;; 99 | + 100 | + ? <- concept_has_translation;; 101 | + ? <= nrel_sc_text_translation: ... (* 102 | + - -> [#STATEMENT_RU#] (* <- lang_ru;; *);; 103 | + - -> [#STATEMENT_EN#] (* <- lang_en;; *);; 104 | + ? *);; 105 | + *);; 106 | ``` 107 | Note that optional blocks can be of any size, and therefore, they **must** begin and end with an empty line. The only exception is if the block ends with a line that starts with a closing bracket (see example above). 108 | 109 | ### Semicolons 110 | You may notice that in case multiple line-variables (lists) are passed to a block, in different situations we may or may not want to append an extra semicolon to the last element of the last list: 111 | ```scs 112 | concept_fruit -> 113 | - concept_#RED#; 114 | - concept_#GREEN#; <- Extra semicolon is not needed 115 | concept_lemon;; 116 | 117 | concept_dog -> 118 | - concept_#DOGS#;; <- Extra semicolon is needed, but for the last element only 119 | 120 | concept_car -> 121 | - concept_#CARS#; <- Append semicolon to the last element of this list if FREIGHT_CARS is empty 122 | - concept_#FREIGHT_CARS#;; 123 | 124 | my_set -> { 125 | - concept_#ELEMENTS#; <- Now we need to delete an extra semicolon of the last element 126 | } 127 | ``` 128 | 129 | For this reason, we introduce a special keyword `#END#` that indicates the end of the list and the need to set a custom number of semicolons to the last element of the last list (if such exists): 130 | ```scs 131 | concept_fruit -> 132 | - concept_#RED#; 133 | - concept_#GREEN#; 134 | concept_lemon;; 135 | 136 | concept_dog -> 137 | - concept_#DOGS#; 138 | - #END#;; 139 | 140 | concept_car -> 141 | - concept_#CARS#; 142 | - concept_#FREIGHT_CARS#; 143 | - #END#;; 144 | 145 | my_set -> { 146 | - concept_#ELEMENTS#; 147 | - #END# 148 | } 149 | ``` 150 | 151 | ### Floating semicolons 152 | The floating semicolons for list variables are defined in the same way as usual semicolons with the only difference: there must be an empty new line before the `#END#` keyword. The difference between normal semicolons and floaing ones is that the floating semicolon is moved to another existing block if the previous one got deleted: 153 | ``` 154 | subject_domain_of_#SYSTEM# 155 | <= nrel_private_subject_domain: 156 | subject_domain_of_#PARENTS#; 157 | 158 | ? => nrel_private_subject_domain: 159 | - subject_domain_of_#CHILDREN#; 160 | 161 | - #END#;; 162 | ``` 163 | In the example above, the extra semicolon will be added to the last element of the `CHILDREN` list in case it exists, and to the last element of the `PARENTS` list otherwise. 164 | 165 | 166 | 167 |
168 | 169 | **Happy hacking! ✌️** 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scs-action 2 | 3 | [![Tests](https://github.com/qaip/scs-action/actions/workflows/tests.yml/badge.svg)](https://github.com/qaip/scs-action/actions/workflows/tests.yml) 4 | [![Build check](https://github.com/qaip/scs-action/actions/workflows/build-check.yml/badge.svg)](https://github.com/qaip/scs-action/actions/workflows/build-check.yml) 5 | 6 | 7 | Say no to manual writing assembly-like SC-code! Generate *formatted and neat* SCs on the fly. 8 | 9 | 10 | ## Motivation 11 | Many of the scs files actually contain quite a lot of boilerplate code with many duplicates. In addition to this, there are extremely many ways in which one can describe a concept or a subject domain. There is no formatter, nor a linter for scs files. **This project solves all these problems at once:** 12 | - The minimum possible amount of code. 13 | - Easy to read. 14 | - Focus on what you need, do not get distracted by complex syntax and semantics. 15 | - Get neat and uniformly formatted ready-to-use scs files generated on the fly. 16 | 17 | Starting `v3.2` we have introduced [strongly typed schemas](#schemas) for interactive highlights and annotations, code snippets, autocompletion and extremely strict validation! This further enhances the benefits offered by this project, as it: 18 | - Significantly improves the developer experience 19 | - Eliminates the possibility of any syntax or language error during the build process 20 | 21 | ## Usage 22 | ### Minimal example 23 | In your GitHub project, create a file `.github/workflows/update-scs.yml`: 24 | ```yaml 25 | name: SCs Automation 26 | 27 | on: pull_request 28 | 29 | jobs: 30 | update: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: qaip/scs-action@v4 34 | ``` 35 | Now whenever configuration `.yaml` files are pushed or changed within a pull request, the corresponding `.scs` files will be generated or updated accordingly. 36 | 37 | ### About configuration yaml files 38 | Source files describing fragments of a knowledge base must have the `.yaml` extension, where `` defines the type of the knowledge base fragment. See [tempaltes](#tempaltes) for more information about available types. 39 | 40 | The name of the configuration file is used as a part of the main system identifier of the knowledge base fragment. 41 | 42 | Each configuration file type has a different set of fields defining the knowledge base fragment, however, there are certain common value types that are used among all configuration files and are also mentioned in annotations: 43 | 44 | - Pipe-separated string (called `Set`): 45 | 46 | ```yaml 47 | en: First | Second | Third 48 | ``` 49 | 50 | - Multiline string (called `List`): 51 | 52 | ```yaml 53 | en: | 54 | First 55 | Second 56 | Third 57 | ``` 58 | 59 | - Collection (called `Array`): 60 | ```yaml 61 | en: 62 | - First 63 | - Second 64 | - Third 65 | ``` 66 | 67 | 68 | ### Additional options 69 | There are several additional options available for scs-action: 70 | - `github_token` — custom GitHub token (by default `github.token` is used). 71 | - `ignore_empty` — whether to ignore empty files (either `never` or `always`, default is `never`). 72 | - `ignore_invalid` — whether to ignore invalid files (either `never` or `always`, default is `never`).\ 73 | When set to `always`, if an error in a configuration file occurs, the action will log the error, skip the file, and continue its job. 74 | 75 | Example: 76 | ```yaml 77 | - uses: qaip/scs-action@v2 78 | with: 79 | ignore_empty: always 80 | ignore_invalid: always 81 | ``` 82 | 83 | 84 | 85 | ## Templates 86 | The currently supported knowledge base fragment types (called templates) are described below. If you need a new template, please leave the corresponding request by creating an issue (see [contributing](#contributing)). 87 | 88 | ### Subject Domain 89 | Matching extension: `.domain.yaml` 90 | ```rb 91 | en: String 92 | ru: String 93 | parent: String 94 | children: List # optional 95 | max: List 96 | concepts: List # optional 97 | rrels: List # optional 98 | nrels: List # optional 99 | ``` 100 | **Example**: 101 | [**Input**](tests/ostis_automation.domain.yaml) 102 | -> 103 | [**Output**](tests/expect/domain_ostis_automation.scs) 104 | 105 | 106 | ### Semantic Neighbourhood of Concept 107 | Matching extension: `.concept.yaml` 108 | ```rb 109 | ru: Set | Array 110 | en: Set | Array 111 | definition: 112 | ru: String | Array 113 | en: String | Array 114 | using: 115 | concepts: List # optional 116 | nrels: List # optional 117 | rrels: List # optional 118 | statement: 119 | one: 120 | title: 121 | ru: String 122 | en: String 123 | ru: String | Array 124 | en: String | Array 125 | using: 126 | concepts: List # optional 127 | nrels: List # optional 128 | rrels: List # optional 129 | two: 130 | ... 131 | subclass: Array 132 | ``` 133 | **Example:** 134 | [**Input**](tests/scs_automation.concept.yaml) 135 | -> 136 | [**Output**](tests/expect/concept_scs_automation.scs) 137 | 138 | 139 | ## Schemas 140 | ### VSCode 141 | 1. Make sure you have [YAML](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) extension installed. 142 | 2. Go to vscode settings, search for `yaml.schemas`, click "Edit in settings.json" and paste the following lines: 143 | ```json 144 | "yaml.schemas": { 145 | "https://raw.githubusercontent.com/qaip/scs-action/v4/schemas/concept.schema.json": "*.concept.yaml", 146 | "https://raw.githubusercontent.com/qaip/scs-action/v4/schemas/domain.schema.json": "*.domain.yaml" 147 | } 148 | ``` 149 | 150 | ### PyCharm 151 | 1. Go to `Settings` > `Languages & Frameworks` > `Schemas and DTDs` > `JSON Schema Mappings`. 152 | 2. Set up the same mappings as for vscode (see above). 153 | 154 | ## Contributing 155 | - Feel free to open issues and request new templates and features. 156 | - Any contributions you make are truly appreciated. 157 | - Check out our [contribution guidelines](CONTRIBUTING.md) for more information. 158 | 159 | 160 | ## License 161 | This project is licensed under the [MIT License](LICENSE). 162 | -------------------------------------------------------------------------------- /schemas/concept.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Concept", 3 | "description": "SCs Automation", 4 | "type": "object", 5 | "properties": { 6 | "ru": { 7 | "$ref": "base.schema.json#/$defs/ru-set+array", 8 | "title": "Russian identifiers", 9 | "description": "Set or array of russian names (identifiers) of the concept", 10 | "default": "название | синоним" 11 | }, 12 | "en": { 13 | "$ref": "base.schema.json#/$defs/en-set+array", 14 | "title": "English identifiers", 15 | "description": "Set or array of english names (identifiers) of the concept", 16 | "default": "name | synonym" 17 | }, 18 | "definition": { 19 | "title": "Definition", 20 | "description": "Definition of the concept", 21 | "type": "object", 22 | "properties": { 23 | "ru": { 24 | "$ref": "base.schema.json#/$defs/ru-text+array", 25 | "title": "Russian definition text", 26 | "description": "Set or array of russian definitions of the concept", 27 | "default": "Текст определения" 28 | }, 29 | "en": { 30 | "$ref": "base.schema.json#/$defs/en-text+array", 31 | "title": "English definition text", 32 | "description": "Set or array of english definitions of the concept", 33 | "default": "Definition text" 34 | }, 35 | "using": { 36 | "$ref": "base.schema.json#/$defs/nodes", 37 | "title": "Used concepts and relations", 38 | "description": "Lists of concepts and role/norole relations used in the concept definition" 39 | } 40 | }, 41 | "required": ["ru", "en", "using"], 42 | "additionalProperties": false, 43 | "examples": [ 44 | { 45 | "ru": "Текст определения", 46 | "en": "Definition text", 47 | "using": { 48 | "concepts": "|\n used_concept", 49 | "nrels": "|\n used_nrel", 50 | "rrels": "|\n used_rrel" 51 | } 52 | } 53 | ] 54 | }, 55 | "statement": { 56 | "title": "Statements", 57 | "description": "Statements about the concept, represented as a key-value object, where key is the system identifier of the statement", 58 | "type": "object", 59 | "patternProperties": { 60 | "^[a-z]+(_[a-z]+)*$": { 61 | "type": "object", 62 | "properties": { 63 | "ru": { 64 | "$ref": "base.schema.json#/$defs/ru-text+array", 65 | "title": "Russian statement", 66 | "description": "Russian statement text" 67 | }, 68 | "en": { 69 | "$ref": "base.schema.json#/$defs/en-text+array", 70 | "title": "English statement", 71 | "description": "English statement text" 72 | }, 73 | "title": { 74 | "title": "Statement title", 75 | "description": "Statement title describing what the statement is about", 76 | "type": "object", 77 | "properties": { 78 | "ru": { 79 | "$ref": "base.schema.json#/$defs/ru", 80 | "title": "Russian statement title", 81 | "description": "Russian statement title describing what the statement is about" 82 | }, 83 | "en": { 84 | "$ref": "base.schema.json#/$defs/en", 85 | "title": "English statement title", 86 | "description": "English statement title describing what the statement is about" 87 | } 88 | }, 89 | "required": ["ru", "en"], 90 | "additionalProperties": false 91 | }, 92 | "using": { 93 | "$ref": "base.schema.json#/$defs/nodes", 94 | "title": "Used concepts and relations", 95 | "description": "Lists of concepts and role/norole relations used in the concept statement" 96 | } 97 | }, 98 | "required": ["ru", "en", "title", "using"], 99 | "additionalProperties": false 100 | } 101 | }, 102 | "additionalProperties": false, 103 | "examples": [ 104 | { 105 | "statement_title": { 106 | "title": { 107 | "ru": "Название высказывания", 108 | "en": "Statement title" 109 | }, 110 | "ru": "Текст высказывания", 111 | "en": "Statement text", 112 | "using": { 113 | "concepts": "|\n used_concept", 114 | "nrels": "|\n used_nrel", 115 | "rrels": "|\n used_rrel" 116 | } 117 | } 118 | } 119 | ] 120 | }, 121 | "subclass": { 122 | "type": "array", 123 | "items": { 124 | "oneOf": [ 125 | { 126 | "title": "Subclass", 127 | "description": "A system identifier interpreted as a subclass of the concept and associated with the inclusion relation", 128 | "$ref": "base.schema.json#/$defs/system" 129 | }, 130 | { 131 | "title": "Partition", 132 | "description": "A list of system identifiers interpreted as a partition of the concept and associated with the 'partition of a set' relation (also known as subdividing relation)", 133 | "$ref": "base.schema.json#/$defs/system-2" 134 | } 135 | ] 136 | }, 137 | "title": "Subclasses", 138 | "description": "Array of the concept subclasses, the classes the concept can be divided into. Array element can be either a single system identifier or a list of system identifiers. A single system identifier is interpreted as a subclass associated with the inclusion relation, and a list of system identifiers is interpreted as a partition of the concept and is associated with the subdividing relation", 139 | "examples": [["subclass", "|\n first_partition\n second_partition\n third_partition"]] 140 | } 141 | }, 142 | "required": ["ru", "en", "definition"], 143 | "additionalProperties": false 144 | } 145 | --------------------------------------------------------------------------------