markdown-magic 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -344,22 +344,24 @@ async function markdownMagic(globOrOpts = {}, options = {}) {
344
344
  name: file,
345
345
  id: file,
346
346
  srcPath: file,
347
- blocks: foundBlocks.blocks
347
+ blocks: foundBlocks.blocks,
348
+ parsedBlocks: foundBlocks
348
349
  }
349
350
  })
350
351
 
351
352
  const blocks = blocksByPath.map((item) => {
352
353
  const dir = path.dirname(item.srcPath)
353
- item.dependencies = []
354
+ const dependencySet = new Set()
354
355
  item.blocks.forEach((block) => {
355
356
  if (block.options && block.options.src) {
356
357
  const resolvedPath = path.resolve(dir, block.options.src)
357
358
  // if (resolvedPath.match(/\.md$/)) {
358
359
  // console.log('resolvedPath', resolvedPath)
359
- item.dependencies = item.dependencies.concat(resolvedPath)
360
+ dependencySet.add(resolvedPath)
360
361
  //}
361
362
  }
362
363
  })
364
+ item.dependencies = Array.from(dependencySet)
363
365
  return item
364
366
  })
365
367
 
@@ -381,23 +383,23 @@ async function markdownMagic(globOrOpts = {}, options = {}) {
381
383
  /** */
382
384
 
383
385
  // Convert items into a format suitable for toposort
384
- const graph = blocks
385
- .filter((item) => item.blocks && item.blocks.length)
386
- .map((item) => {
387
- return [ item.id, ...item.dependencies ]
388
- })
386
+ const { itemsWithBlocks, itemIds, graph } = createDependencyGraph(blocks)
389
387
  // console.log('graph', graph)
390
388
  // Perform the topological sort and reverse for execution order
391
- const sortedIds = toposort(graph).reverse();
389
+ const sortedIds = graph.length ? toposort.array(itemIds, graph).reverse() : itemIds
392
390
 
393
391
  // Reorder items based on sorted ids
394
- const sortedItems = sortedIds.map(id => blocks.find(item => item.id === id)).filter(Boolean);
392
+ const itemById = new Map(itemsWithBlocks.map((item) => [item.id, item]))
393
+ const sortedItems = sortedIds.map((id) => itemById.get(id)).filter(Boolean)
395
394
 
396
395
  // topoSort(blocks)
397
396
  const orderedFiles = sortedItems.map((block) => block.id)
398
397
  // console.log('sortedItems', sortedItems)
399
398
  // console.log('orderedFiles', orderedFiles)
400
399
 
400
+ const fileContentByPath = new Map(files.map((file, i) => [file, fileContents[i]]))
401
+ const parsedBlocksByPath = new Map(blocksByPath.map((item) => [item.id, item.parsedBlocks]))
402
+
401
403
  const processedFiles = []
402
404
  await asyncForEach(orderedFiles, async (file) => {
403
405
  // logger('file', file)
@@ -424,6 +426,8 @@ async function markdownMagic(globOrOpts = {}, options = {}) {
424
426
  // logger('newPath', newPath)
425
427
  const result = await processFile({
426
428
  ...opts,
429
+ content: fileContentByPath.get(file),
430
+ parsedBlocks: parsedBlocksByPath.get(file),
427
431
  patterns,
428
432
  open,
429
433
  close,
@@ -787,6 +791,29 @@ function changedFiles(files) {
787
791
  return files.filter(({ isChanged }) => isChanged)
788
792
  }
789
793
 
794
+ /**
795
+ * Create graph data for deterministic dependency ordering
796
+ * @param {Array<{id: string, blocks: Array<any>, dependencies?: Array<string>}>} blockItems
797
+ * @returns {{itemsWithBlocks: Array<any>, itemIds: Array<string>, graph: Array<[string, string]>}}
798
+ */
799
+ function createDependencyGraph(blockItems = []) {
800
+ const itemsWithBlocks = blockItems.filter((item) => item.blocks && item.blocks.length)
801
+ const itemIds = itemsWithBlocks.map((item) => item.id)
802
+ const itemIdSet = new Set(itemIds)
803
+ const graph = itemsWithBlocks
804
+ .flatMap((item) => {
805
+ return (item.dependencies || [])
806
+ .filter((dependency) => itemIdSet.has(dependency))
807
+ .map((dependency) => [item.id, dependency])
808
+ })
809
+
810
+ return {
811
+ itemsWithBlocks,
812
+ itemIds,
813
+ graph
814
+ }
815
+ }
816
+
790
817
  async function asyncForEach(array, callback) {
791
818
  for (let index = 0; index < array.length; index++) {
792
819
  await callback(array[index], index, array)
@@ -802,5 +829,8 @@ module.exports = {
802
829
  parseMarkdown,
803
830
  blockTransformer,
804
831
  processFile,
805
- stringUtils
832
+ stringUtils,
833
+ __private: {
834
+ createDependencyGraph
835
+ }
806
836
  }
@@ -158,23 +158,29 @@ module.exports = async function CODE(api) {
158
158
 
159
159
  /* Check for Id */
160
160
  if (id) {
161
- const lines = code.split("\n")
162
- const startLineIndex = lines.findIndex(line => line.includes(`CODE_SECTION:${id}:START`));
163
- const startLine = startLineIndex !== -1 ? startLineIndex : 0;
161
+ const lines = code.split('\n')
162
+ const startLineIndex = lines.findIndex((line) => line.includes(`CODE_SECTION:${id}:START`))
163
+ const endLineIndex = lines.findIndex((line) => line.includes(`CODE_SECTION:${id}:END`))
164
+
165
+ if (startLineIndex === -1 || endLineIndex === -1) {
166
+ throw new Error(`Missing ${id} code section from ${codeFilePath}`)
167
+ }
168
+
169
+ if (endLineIndex <= startLineIndex) {
170
+ throw new Error(`Invalid ${id} code section in ${codeFilePath}. End marker must be after start marker`)
171
+ }
164
172
 
165
- const endLineIndex = lines.findIndex(line => line.includes(`CODE_SECTION:${id}:END`));
166
- const endLine = endLineIndex !== -1 ? endLineIndex : lines.length - 1;
167
173
  // console.log('startLine', startLine)
168
174
  // console.log('endLine', endLine)
169
- if (startLine === -1 && endLine === -1) {
170
- throw new Error(`Missing ${id} code section from ${codeFilePath}`)
175
+ const selectedLines = lines.slice(startLineIndex + 1, endLineIndex)
176
+
177
+ if (!selectedLines.length) {
178
+ throw new Error(`Empty ${id} code section in ${codeFilePath}`)
171
179
  }
172
-
173
- const selectedLines = lines.slice(startLine + 1, endLine)
174
-
175
- const firstMatch = selectedLines[0] && selectedLines[0].match(/^(\s*)/);
176
- const trimBy = firstMatch && firstMatch[1] ? firstMatch[1].length : 0;
177
- const newValue = `${selectedLines.map(line => line.substring(trimBy).replace(/^\/\/ CODE_SECTION:INCLUDE /g, "")).join("\n")}`
180
+
181
+ const firstMatch = selectedLines[0] && selectedLines[0].match(/^(\s*)/)
182
+ const trimBy = firstMatch && firstMatch[1] ? firstMatch[1].length : 0
183
+ const newValue = `${selectedLines.map((line) => line.substring(trimBy).replace(/^\/\/ CODE_SECTION:INCLUDE /g, '')).join('\n')}`
178
184
  // console.log('newValue', newValue)
179
185
  code = newValue
180
186
  }
@@ -1,28 +1,52 @@
1
1
  const fetch = require('node-fetch')
2
2
 
3
3
  function formatUrl(url = '') {
4
- return url.match(/^https?:\/\//) ? url : `https://${url}`
4
+ if (typeof url !== 'string') return ''
5
+ const trimmed = url.trim()
6
+ if (!trimmed) return ''
7
+ return trimmed.match(/^https?:\/\//) ? trimmed : `https://${trimmed}`
5
8
  }
6
9
 
7
10
  async function remoteRequest(url, settings = {}, srcPath) {
8
- let body
9
11
  const finalUrl = formatUrl(url)
12
+ const fixText = srcPath ? `\nFix "${url}" value in ${srcPath}` : ''
13
+ if (!finalUrl) {
14
+ const msg = `Invalid URL "${url}"${fixText}`
15
+ if (settings.failOnMissingRemote) {
16
+ throw new Error(msg)
17
+ }
18
+ console.log(msg)
19
+ return
20
+ }
21
+
10
22
  // ignore demo url todo remove one day
11
23
  if (finalUrl === 'http://url-to-raw-md-file.md') {
12
24
  return
13
25
  }
26
+
27
+ let response
14
28
  try {
15
- const res = await fetch(finalUrl)
16
- body = await res.text()
29
+ response = await fetch(finalUrl)
17
30
  } catch (e) {
18
31
  console.log(`⚠️ WARNING: REMOTE URL "${finalUrl}" NOT FOUND`)
19
- const msg = (e.message || '').split('\n')[0] + `\nFix "${url}" value in ${srcPath}`
32
+ const msg = (e.message || '').split('\n')[0] + fixText
20
33
  console.log(msg)
21
34
  if (settings.failOnMissingRemote) {
22
35
  throw new Error(msg)
23
36
  }
37
+ return
38
+ }
39
+
40
+ if (!response.ok) {
41
+ const msg = `Remote request failed with status ${response.status} (${response.statusText}) for "${finalUrl}"${fixText}`
42
+ console.log(`⚠️ WARNING: ${msg}`)
43
+ if (settings.failOnMissingRemote) {
44
+ throw new Error(msg)
45
+ }
46
+ return
24
47
  }
25
- return body
48
+
49
+ return response.text()
26
50
  }
27
51
 
28
52
  module.exports = {
@@ -1,33 +1,3 @@
1
- export function uxParse(_rawArgv?: any[], opts?: {}): {
2
- leadingCommands: string[];
3
- extraParse: {};
4
- mriOptionsOriginal: mri.Argv<{
5
- [x: string]: any;
6
- }>;
7
- globGroups: any[];
8
- rawArgv?: undefined;
9
- mriOptionsClean?: undefined;
10
- mriDiff?: undefined;
11
- yargsParsed?: undefined;
12
- mergedOptions?: undefined;
13
- } | {
14
- rawArgv: string;
15
- leadingCommands: string[];
16
- globGroups: any[];
17
- extraParse: {};
18
- mriOptionsOriginal: mri.Argv<{
19
- [x: string]: any;
20
- }>;
21
- mriOptionsClean: {
22
- [x: string]: any;
23
- } & {
24
- _: string[];
25
- };
26
- mriDiff: boolean;
27
- yargsParsed: string;
28
- mergedOptions: {
29
- _: string[];
30
- };
31
- };
32
- import mri = require("mri");
1
+ export { dxParse };
2
+ import { dxParse } from "@davidwells/dx-args";
33
3
  //# sourceMappingURL=argparse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"argparse.d.ts","sourceRoot":"","sources":["../../../src/argparse/argparse.js"],"names":[],"mappings":"AAoCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2QC"}
1
+ {"version":3,"file":"argparse.d.ts","sourceRoot":"","sources":["../../../src/argparse/argparse.js"],"names":[],"mappings":""}
@@ -1,3 +1,3 @@
1
- export { uxParse };
2
- import { uxParse } from "./argparse";
1
+ export { dxParse };
2
+ import { dxParse } from "./argparse";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,3 @@
1
- export function splitOutsideQuotes(str: any): any[];
1
+ export { splitOutsideQuotes };
2
+ import { splitOutsideQuotes } from "@davidwells/dx-args";
2
3
  //# sourceMappingURL=splitOutsideQuotes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"splitOutsideQuotes.d.ts","sourceRoot":"","sources":["../../../src/argparse/splitOutsideQuotes.js"],"names":[],"mappings":"AAEA,oDAuEC"}
1
+ {"version":3,"file":"splitOutsideQuotes.d.ts","sourceRoot":"","sources":["../../../src/argparse/splitOutsideQuotes.js"],"names":[],"mappings":""}
@@ -1,4 +1,6 @@
1
- import { getGlobGroupsFromArgs } from "./globparse";
2
- export function runCli(options: {}, rawArgv: any): Promise<import("./").MarkdownMagicResult>;
1
+ import { getGlobGroupsFromArgs } from "@davidwells/dx-args";
2
+ export function parseCliArgv(rawArgv?: any[]): any;
3
+ export function normalizeCliOptions(parsed: any): any;
4
+ export function runCli(options: {}, rawArgv: any, deps?: {}): Promise<any>;
3
5
  export { getGlobGroupsFromArgs };
4
6
  //# sourceMappingURL=cli-run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-run.d.ts","sourceRoot":"","sources":["../../src/cli-run.js"],"names":[],"mappings":";AA0GA,6FA4QC"}
1
+ {"version":3,"file":"cli-run.d.ts","sourceRoot":"","sources":["../../src/cli-run.js"],"names":[],"mappings":";AA8OA,mDAIC;AAED,sDAgDC;AAjLD,2EAyHC"}
@@ -1,18 +1,12 @@
1
- export function getGlobGroupsFromArgs(args: any, opts?: {}): {
2
- globGroups: {
3
- key: any;
4
- rawKey: any;
5
- values: any;
6
- }[];
7
- otherOpts: any[];
8
- };
9
- export function isArrayLike(str: any): boolean;
10
- export function stringLooksLikeFile(value: any): boolean;
11
- export function getValue(val: any): any;
12
- export function trimLeadingDashes(value: any): any;
13
- export function removeNodeModules(value?: string): boolean;
14
- export function coerceStringToRegex(str: any): any;
15
- export function convertToArray(str?: string): any;
16
- export function addValue(value: any, currentCollection: any): any;
17
- export function customIsGlob(arg: any): any;
1
+ import { getGlobGroupsFromArgs } from "@davidwells/dx-args";
2
+ import { isArrayLike } from "@davidwells/dx-args";
3
+ import { stringLooksLikeFile } from "@davidwells/dx-args";
4
+ import { getValue } from "@davidwells/dx-args";
5
+ import { trimLeadingDashes } from "@davidwells/dx-args";
6
+ import { removeNodeModules } from "@davidwells/dx-args";
7
+ import { coerceStringToRegex } from "@davidwells/dx-args";
8
+ import { convertToArray } from "@davidwells/dx-args";
9
+ import { addValue } from "@davidwells/dx-args";
10
+ import { customIsGlob } from "@davidwells/dx-args";
11
+ export { getGlobGroupsFromArgs, isArrayLike, stringLooksLikeFile, getValue, trimLeadingDashes, removeNodeModules, coerceStringToRegex, convertToArray, addValue, customIsGlob };
18
12
  //# sourceMappingURL=globparse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"globparse.d.ts","sourceRoot":"","sources":["../../src/globparse.js"],"names":[],"mappings":"AA6EA;;;;;;;EA8EC;AA5ID,+CAGC;AAED,yDAOC;AAED,wCAEC;AAED,mDAEC;AAED,2DAKC;AAED,mDAOC;AAED,kDAGC;AAED,kEASC;AAED,4CAMC"}
1
+ {"version":3,"file":"globparse.d.ts","sourceRoot":"","sources":["../../src/globparse.js"],"names":[],"mappings":""}
@@ -1,18 +1,32 @@
1
+ declare namespace _exports {
2
+ export { SyntaxType, FilePathsOrGlobs, ProcessFileOptions, MarkdownMagicOptions, OutputConfig, MarkdownMagicResult };
3
+ }
4
+ declare namespace _exports {
5
+ export { markdownMagic };
6
+ export { parseMarkdown };
7
+ export { blockTransformer };
8
+ export { processFile };
9
+ export { stringUtils };
10
+ export namespace __private {
11
+ export { createDependencyGraph };
12
+ }
13
+ }
14
+ export = _exports;
1
15
  /**
2
16
  * Allowed file syntaxes
3
17
  */
4
- export type SyntaxType = "md" | "js" | "yml" | "yaml";
18
+ type SyntaxType = "md" | "js" | "yml" | "yaml";
5
19
  /**
6
20
  * Path to file, files or Glob string or Glob Array
7
21
  */
8
- export type FilePathsOrGlobs = string | Array<string>;
9
- export type ProcessFileOptions = import("comment-block-replacer").ProcessFileOptions;
22
+ type FilePathsOrGlobs = string | Array<string>;
23
+ type ProcessFileOptions = import("comment-block-replacer").ProcessFileOptions;
10
24
  /**
11
25
  * Configuration for markdown magic
12
26
  *
13
27
  * Below is the main config for `markdown-magic`
14
28
  */
15
- export type MarkdownMagicOptions = {
29
+ type MarkdownMagicOptions = {
16
30
  /**
17
31
  * - Files to process.
18
32
  */
@@ -81,7 +95,7 @@ export type MarkdownMagicOptions = {
81
95
  /**
82
96
  * Optional output configuration
83
97
  */
84
- export type OutputConfig = {
98
+ type OutputConfig = {
85
99
  /**
86
100
  * - Change output path of new content. Default behavior is replacing the original file
87
101
  */
@@ -102,7 +116,7 @@ export type OutputConfig = {
102
116
  /**
103
117
  * Result of markdown processing
104
118
  */
105
- export type MarkdownMagicResult = {
119
+ type MarkdownMagicResult = {
106
120
  /**
107
121
  * - Any errors encountered.
108
122
  */
@@ -183,12 +197,25 @@ export type MarkdownMagicResult = {
183
197
  console.log(`Processing complete`, result)
184
198
  })
185
199
  */
186
- export function markdownMagic(globOrOpts?: FilePathsOrGlobs | MarkdownMagicOptions, options?: MarkdownMagicOptions): Promise<MarkdownMagicResult>;
200
+ declare function markdownMagic(globOrOpts?: FilePathsOrGlobs | MarkdownMagicOptions, options?: MarkdownMagicOptions): Promise<MarkdownMagicResult>;
187
201
  import { blockTransformer } from "comment-block-transformer";
188
202
  import { processFile } from "comment-block-replacer";
189
- export namespace stringUtils {
203
+ declare namespace stringUtils {
190
204
  export { stringBreak };
191
205
  }
206
+ /**
207
+ * Create graph data for deterministic dependency ordering
208
+ * @param {Array<{id: string, blocks: Array<any>, dependencies?: Array<string>}>} blockItems
209
+ * @returns {{itemsWithBlocks: Array<any>, itemIds: Array<string>, graph: Array<[string, string]>}}
210
+ */
211
+ declare function createDependencyGraph(blockItems?: Array<{
212
+ id: string;
213
+ blocks: Array<any>;
214
+ dependencies?: Array<string>;
215
+ }>): {
216
+ itemsWithBlocks: Array<any>;
217
+ itemIds: Array<string>;
218
+ graph: Array<[string, string]>;
219
+ };
192
220
  import stringBreak = require("./utils/string-break");
193
- export { parseMarkdown, blockTransformer, processFile };
194
221
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":";;;yBAkDa,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM;;;;+BAK5B,MAAM,GAAC,KAAK,CAAC,MAAM,CAAC;iCAIpB,OAAO,wBAAwB,EAAE,kBAAkB;;;;;;;;;;YASlD,gBAAgB;;;;;;;;aAEhB,YAAY,GAAC,MAAM;;;;aACnB,UAAU;;;;WACV,MAAM;;;;YACN,MAAM;;;;UACN,MAAM;;;;oBACN,OAAO;;;;iBACP,OAAO;;;;UACP,OAAO;;;;YACP,OAAO;;;;aACP,OAAO;;;;8BACP,OAAO;;;;8BACP,OAAO;;;;0BACP,OAAO;;;;gBACP,MAAM;;;;;;;;;gBAMN,MAAM;;;;qBACN,OAAO;;;;;;;;8BAEP,OAAO;;;;;;;;;;;;;kBAQP,KAAK,CAAC,MAAM,CAAC;;;;;;AApD3B;;;GAGG;AAEH;;;GAGG;AAEH;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;GAOG;AAEH;;;;;;;GAOG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,2CARW,gBAAgB,GAAC,oBAAoB,YACrC,oBAAoB,GAClB,OAAO,CAAC,mBAAmB,CAAC,CAqmBxC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;kBAkDa,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM;;;;wBAK5B,MAAM,GAAC,KAAK,CAAC,MAAM,CAAC;0BAIpB,OAAO,wBAAwB,EAAE,kBAAkB;;;;;;;;;;YASlD,gBAAgB;;;;;;;;aAEhB,YAAY,GAAC,MAAM;;;;aACnB,UAAU;;;;WACV,MAAM;;;;YACN,MAAM;;;;UACN,MAAM;;;;oBACN,OAAO;;;;iBACP,OAAO;;;;UACP,OAAO;;;;YACP,OAAO;;;;aACP,OAAO;;;;8BACP,OAAO;;;;8BACP,OAAO;;;;0BACP,OAAO;;;;gBACP,MAAM;;;;;;;;;gBAMN,MAAM;;;;qBACN,OAAO;;;;;;;;8BAEP,OAAO;;;;;;;;;;;;;kBAQP,KAAK,CAAC,MAAM,CAAC;;;;;;AApD3B;;;GAGG;AAEH;;;GAGG;AAEH;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;GAOG;AAEH;;;;;;;GAOG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,4CARW,gBAAgB,GAAC,oBAAoB,YACrC,oBAAoB,GAClB,OAAO,CAAC,mBAAmB,CAAC,CA+nBxC;;;;;;AAuCD;;;;GAIG;AACH,oDAHW,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAAC,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAAC,CAAC,GACnE;IAAC,eAAe,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAC,CAkBjG"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/transforms/code/index.js"],"names":[],"mappings":";;;AA+CiB,kDA+JhB;;;;;;;;;SAzLa,MAAM;;;;aACN,MAAM;;;;aACN,MAAM;;;;YACN,MAAM;;;;cACN,MAAM;;;;mBACN,OAAO"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/transforms/code/index.js"],"names":[],"mappings":";;;AA+CiB,kDAqKhB;;;;;;;;;SA/La,MAAM;;;;aACN,MAAM;;;;aACN,MAAM;;;;YACN,MAAM;;;;cACN,MAAM;;;;mBACN,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"remoteRequest.d.ts","sourceRoot":"","sources":["../../../src/utils/remoteRequest.js"],"names":[],"mappings":"AAMA,kFAmBC"}
1
+ {"version":3,"file":"remoteRequest.d.ts","sourceRoot":"","sources":["../../../src/utils/remoteRequest.js"],"names":[],"mappings":"AASA,kFAwCC"}