├── .gitignore ├── images ├── icon.png └── icon.svg ├── .vscodeignore ├── tsconfig.json ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── appveyor.yml ├── src ├── test │ ├── extension.test.ts │ └── index.ts └── extension.ts ├── package.nls.ja.json ├── LICENSE.txt ├── package.nls.json ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | package-lock.json 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpubsppop01/vscode-auto-timestamp/HEAD/images/icon.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | out/**/*.map 5 | src/** 6 | .gitignore 7 | tsconfig.json 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src" 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "cSpell.words": [ 10 | "luxon" 11 | ] 12 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | nodejs_version: "" # Empty means latest version 3 | 4 | install: 5 | - ps: Install-Product node $env:nodejs_version 6 | - npm install 7 | 8 | # cf: https://help.appveyor.com/discussions/questions/1954-getting-version-from-packagejson 9 | - ps: $env:package_version = (Get-Content -Raw -Path package.json | ConvertFrom-Json).version 10 | - ps: Update-AppveyorBuild -Version "$env:package_version-$env:APPVEYOR_BUILD_NUMBER" 11 | 12 | build_script: 13 | - npm run compile 14 | - npm run vsce-package 15 | 16 | artifacts: 17 | - path: vscode-auto-timestamp-$(package_version).vsix 18 | -------------------------------------------------------------------------------- /src/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /package.nls.ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "filenamePattern.description": "この正規表現に名称がマッチするファイルを処理対象とします。", 3 | "lineLimit.description": "ファイルの先頭(プラスの場合)または末尾(マイナスの場合)からここで指定した数の行を処理対象とします。", 4 | "birthTimeStart.description": "この正規表現にマッチする箇所から後を作成日時フィールドと見なします。", 5 | "birthTimeEnd.description": "この正規表現にマッチする箇所から前を作成日時フィールドと見なします。", 6 | "modifiedTimeStart.description": "この正規表現にマッチする箇所から後を更新日時フィールドと見なします。", 7 | "modifiedTimeEnd.description": "この正規表現にマッチする箇所から前を更新日時フィールドと見なします。", 8 | "luxonFormat.description": "作成および更新日時に用いる Luxon フォーマット文字列です。参照ください: https://moment.github.io/luxon/#/formatting?id=table-of-tokens", 9 | "luxonTimezone.description": "作成および更新日時に用いる Luxon タイムゾーン文字列です。参照ください: https://moment.github.io/luxon/#/zones?id=specifying-a-zone", 10 | "momentFormat.description": "作成および更新日時に用いる Moment フォーマット文字列です。Luxon の設定がある場合こちらは使用されません。" 11 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | Copyright (c) 2017- lpubsppop01 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source 22 | distribution. 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ "${workspaceRoot}/out/**/*.js" ], 14 | "preLaunchTask": "npm: watch" 15 | }, 16 | { 17 | "name": "Extension Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], 25 | "preLaunchTask": "npm: watch" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "filenamePattern.description": "Configure regular expression that matched files will be processed.", 3 | "lineLimit.description": "The number of lines from head (if plus) or tail (if minus) of file that will be processed.", 4 | "birthTimeStart.description": "Configure regular expression that following of matched part will be treated as birth time field.", 5 | "birthTimeEnd.description": "Configure regular expression that preceding of matched part will be treated as birth time field.", 6 | "modifiedTimeStart.description": "Configure regular expression that following of matched part will be treated as modified time field.", 7 | "modifiedTimeEnd.description": "Configure regular expression that preceding of matched part will be treated as modified time field.", 8 | "luxonFormat.description": "Luxon format string for birth time and modified time. See: https://moment.github.io/luxon/#/formatting?id=table-of-tokens", 9 | "luxonTimezone.description": "Luxon timezone string for birth time and modified time. See: https://moment.github.io/luxon/#/zones?id=specifying-a-zone", 10 | "momentFormat.description": "Moment format string for birth time and modified time. This is not used if Luxon settings are set." 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Time Stamp 2 | [![Build status](https://ci.appveyor.com/api/projects/status/8jhbugo5d2ejuylh?svg=true)](https://ci.appveyor.com/project/lpubsppop01/vscode-auto-timestamp) 3 | 4 | A Visual Studio Code extension that update time stamp in file content when saving document. 5 | 6 | ## Features 7 | When saving document: 8 | - Update last modified time field 9 | - Fill birth time field by file time stamp if it is empty 10 | 11 | Each of the fields will be detected with condition of settings. 12 | By default settings, lines like the following will be detected: 13 | ``` 14 | // Created: 2018/02/04 12:24:41 15 | // Last modified: 2018/02/09 11:41:41 16 | ``` 17 | 18 | If this extension does not work, please check the followings: 19 | - The Line Limit setting is `5` lines from the beginning of the file by default, so it may be too small for your file. 20 | - There is also the setting of the target file name (Filename Pattern), and `.vscode/settings.json` is ignored by default. 21 | - The default value changed in version 0.0.5. If you have been using 0.0.4 or earlier and have changed this setting, please check it. 22 | - This extension does not work if your VS Code Auto Save setting is `afterDelay`. 23 | 24 | You can change the time stamp format and timezone with the following settings: 25 | - The time stamp format can be changed with `luxonFormat`. 26 | - See: [Formatting (Luxon)](https://moment.github.io/luxon/#/formatting?id=table-of-tokens) 27 | - Migrated from Moment to Luxon in version 0.0.8. `momentFormat` can also be used, but `luxonFormat` is recommended for newly setup environments and projects. 28 | - The timezone can be set with `luxonTimezone`. 29 | - See: [Time zones and offsets (Luxon)](https://moment.github.io/luxon/#/zones?id=specifying-a-zone) 30 | - The default value is `system` which means the system's local timezone. 31 | 32 | If you try to handle many situations with only setting values, the patterns may become complicated. 33 | VS Code's standard features allow you to change the settings per project and per language. 34 | Please use them together. 35 | 36 | ## Download 37 | [vscode-auto-timestamp Latest build - GitHub Releases](https://github.com/lpubsppop01/vscode-auto-timestamp/releases/latest/download/vscode-auto-timestamp.vsix) 38 | 39 | Depending on the situation, this extension seems not to be found in the VS Marketplace search. 40 | In that case, please install from the above. 41 | 42 | If you need a version specified URL, please check in [the releases page](https://github.com/lpubsppop01/vscode-auto-timestamp/releases). 43 | 44 | ## Author 45 | [lpubsppop01](https://github.com/lpubsppop01) 46 | 47 | ## License 48 | [zlib License](https://github.com/lpubsppop01/vscode-auto-timestamp/raw/master/LICENSE.txt) 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-auto-timestamp", 3 | "displayName": "Auto Time Stamp", 4 | "description": "Update timestamp in file content on save", 5 | "version": "0.0.8", 6 | "publisher": "lpubsppop01", 7 | "license": "Zlib", 8 | "engines": { 9 | "vscode": "^1.42.0" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/lpubsppop01/vscode-auto-timestamp.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/lpubsppop01/vscode-auto-timestamp/issues", 17 | "email": "lpubsppop01@gmail.com" 18 | }, 19 | "categories": [ 20 | "Other" 21 | ], 22 | "icon": "images/icon.png", 23 | "activationEvents": [ 24 | "*" 25 | ], 26 | "main": "./out/extension", 27 | "contributes": { 28 | "configuration": [ 29 | { 30 | "title": "Auto Time Stamp", 31 | "type": "object", 32 | "properties": { 33 | "lpubsppop01.autoTimeStamp.filenamePattern": { 34 | "description": "%filenamePattern.description%", 35 | "type": "string", 36 | "scope": "language-overridable", 37 | "default": "^(?!.*[/\\\\]\\.vscode[/\\\\]settings.json$)" 38 | }, 39 | "lpubsppop01.autoTimeStamp.lineLimit": { 40 | "description": "%lineLimit.description%", 41 | "type": "integer", 42 | "scope": "language-overridable", 43 | "default": 5 44 | }, 45 | "lpubsppop01.autoTimeStamp.birthTimeStart": { 46 | "description": "%birthTimeStart.description%", 47 | "type": "string", 48 | "scope": "language-overridable", 49 | "default": "[cC]reated *: " 50 | }, 51 | "lpubsppop01.autoTimeStamp.birthTimeEnd": { 52 | "description": "%birthTimeEnd.description%", 53 | "type": "string", 54 | "scope": "language-overridable", 55 | "default": "$" 56 | }, 57 | "lpubsppop01.autoTimeStamp.modifiedTimeStart": { 58 | "description": "%modifiedTimeStart.description%", 59 | "type": "string", 60 | "scope": "language-overridable", 61 | "default": "[lL]ast[ -][mM]odified *: " 62 | }, 63 | "lpubsppop01.autoTimeStamp.modifiedTimeEnd": { 64 | "description": "%modifiedTimeEnd.description%", 65 | "type": "string", 66 | "scope": "language-overridable", 67 | "default": "$" 68 | }, 69 | "lpubsppop01.autoTimeStamp.luxonFormat": { 70 | "description": "%luxonFormat.description%", 71 | "type": "string", 72 | "scope": "language-overridable", 73 | "default": "yyyy/LL/dd HH:mm:ss" 74 | }, 75 | "lpubsppop01.autoTimeStamp.luxonTimezone": { 76 | "description": "%luxonTimezone.description%", 77 | "type": "string", 78 | "scope": "language-overridable", 79 | "default": "system" 80 | }, 81 | "lpubsppop01.autoTimeStamp.momentFormat": { 82 | "description": "%momentFormat.description%", 83 | "type": "string", 84 | "scope": "language-overridable", 85 | "default": "YYYY/MM/DD HH:mm:ss" 86 | } 87 | } 88 | } 89 | ] 90 | }, 91 | "scripts": { 92 | "vscode:prepublish": "npm run compile", 93 | "compile": "tsc -p ./", 94 | "watch": "tsc -watch -p ./", 95 | "test": "npm run compile && node ./out/test/runTest.js", 96 | "vsce-package": "vsce package" 97 | }, 98 | "devDependencies": { 99 | "@types/mocha": "^10.0.1", 100 | "@types/node": "^18.16.3", 101 | "@types/vscode": "^1.42.0", 102 | "@vscode/test-electron": "^2.3.0", 103 | "@vscode/vsce": "^2.19.0", 104 | "azure-devops-node-api": "^12.0.0", 105 | "typescript": "^5.0.4" 106 | }, 107 | "dependencies": { 108 | "luxon": "^3.3.0", 109 | "moment": "^2.29.4" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import * as fs from 'fs'; 5 | import * as moment from 'moment'; 6 | import { DateTime } from 'luxon'; 7 | 8 | export function activate(context: vscode.ExtensionContext) { 9 | const core = new ExtensionCore(); 10 | context.subscriptions.push(vscode.workspace.onWillSaveTextDocument(e => { 11 | core.onWillSaveTextDocument(e); 12 | })); 13 | } 14 | 15 | export function deactivate() { 16 | } 17 | 18 | class ExtensionCore { 19 | 20 | public onWillSaveTextDocument(e: vscode.TextDocumentWillSaveEvent) { 21 | const config = new ExtensionConfiguration(e.document); 22 | if (!e.document.fileName.match(config.fileNamePattern)) return; 23 | if (e.reason == vscode.TextDocumentSaveReason.AfterDelay) return; 24 | var edits: vscode.TextEdit[] = []; 25 | const lineIndices = this.getIndexRangeUntil(config.lineLimit, e.document.lineCount); 26 | for (const iLine of lineIndices) { 27 | const line = e.document.lineAt(iLine); 28 | 29 | const birthTimeRange = this.getTextRangeBetween(line, 30 | config.birthTimeStart, config.birthTimeEnd); 31 | if (birthTimeRange != null && birthTimeRange.isEmpty) { 32 | const stats = fs.statSync(e.document.fileName); 33 | const timeStr = this.formatTime(config, stats.birthtime) 34 | edits.push(vscode.TextEdit.replace(birthTimeRange, timeStr)); 35 | } 36 | 37 | const modifiedTimeRange = this.getTextRangeBetween(line, 38 | config.modifiedTimeStart, config.modifiedTimeEnd); 39 | if (modifiedTimeRange != null) { 40 | const timeStr = this.formatTime(config); 41 | edits.push(vscode.TextEdit.replace(modifiedTimeRange, timeStr)); 42 | } 43 | } 44 | e.waitUntil(Promise.resolve(edits)); 45 | } 46 | 47 | private getIndexRangeUntil(limit: number, count: number): number[] { 48 | const indices: number[] = []; 49 | if (limit > 0) { 50 | let i = 0; 51 | const iMax = Math.min(limit, count); 52 | while (i < iMax) { 53 | indices.push(i++); 54 | } 55 | } else { 56 | let i = count - 1; 57 | const iMin = Math.max(count + limit, 0); 58 | while (i >= iMin) { 59 | indices.push(i--); 60 | } 61 | } 62 | return indices; 63 | } 64 | 65 | private getTextRangeBetween(line: vscode.TextLine, startPattern: RegExp, endPattern: RegExp): vscode.Range { 66 | const startResult = line.text.match(startPattern); 67 | if (startResult == null) return null; 68 | const iRangeStart = startResult.index + startResult[0].length; 69 | const endResult = line.text.substring(iRangeStart).match(endPattern); 70 | if (endResult == null) return null; 71 | const iRangeEnd = iRangeStart + endResult.index; 72 | const startPos = new vscode.Position(line.lineNumber, iRangeStart); 73 | var endPos = new vscode.Position(line.lineNumber, iRangeEnd); 74 | return new vscode.Range(startPos, endPos); 75 | } 76 | 77 | private formatTime(config: ExtensionConfiguration, birthtime?: Date): string { 78 | if (birthtime) { 79 | if (config.hasMomentFormat && !config.hasLuxonFormatOrTimezone) { 80 | return moment(birthtime).format(config.momentFormat); 81 | } 82 | return DateTime.fromJSDate(birthtime).setZone(config.luxonTimezone).toFormat(config.luxonFormat); 83 | } else { 84 | if (config.hasMomentFormat && !config.hasLuxonFormatOrTimezone) { 85 | return moment().format(config.momentFormat); 86 | } 87 | return DateTime.now().setZone(config.luxonTimezone).toFormat(config.luxonFormat); 88 | } 89 | } 90 | 91 | } 92 | 93 | class ExtensionConfiguration { 94 | 95 | private m_config: vscode.WorkspaceConfiguration; 96 | 97 | public constructor(document: vscode.TextDocument) { 98 | this.m_config = vscode.workspace.getConfiguration("lpubsppop01.autoTimeStamp", document); 99 | } 100 | 101 | private getValue(propertyName: string, defaultValue: T): T { 102 | if (this.m_config == null) return defaultValue; 103 | const value = this.m_config.get(propertyName); 104 | return value != null ? value : defaultValue; 105 | } 106 | 107 | private hasNonDefaultValue(propertyName: string): boolean { 108 | if (this.m_config == null) return false; 109 | const inspected = this.m_config.inspect(propertyName); 110 | for (const [key] of Object.entries(inspected)) { 111 | if (["defaultValue", "key"].includes(key)) continue; 112 | if (inspected[key]) { 113 | return true; 114 | } 115 | } 116 | return false; 117 | } 118 | 119 | private m_fileNamePattern: RegExp; 120 | public get fileNamePattern(): RegExp { 121 | if (this.m_fileNamePattern == null) { 122 | this.m_fileNamePattern = new RegExp(this.getValue("filenamePattern", "^(?!.*[/\\\\]\\.vscode[/\\\\]settings.json$)")); 123 | } 124 | return this.m_fileNamePattern; 125 | } 126 | 127 | public get lineLimit(): number { 128 | return this.getValue("lineLimit", 5); 129 | } 130 | 131 | private m_birthTimeStart: RegExp; 132 | public get birthTimeStart(): RegExp { 133 | if (this.m_birthTimeStart == null) { 134 | this.m_birthTimeStart = new RegExp(this.getValue("birthTimeStart", "[cC]reated *: ")); 135 | } 136 | return this.m_birthTimeStart; 137 | } 138 | 139 | private m_birthTimeEnd: RegExp; 140 | public get birthTimeEnd(): RegExp { 141 | if (this.m_birthTimeEnd == null) { 142 | this.m_birthTimeEnd = new RegExp(this.getValue("birthTimeEnd", "$")); 143 | } 144 | return this.m_birthTimeEnd; 145 | } 146 | 147 | private m_modifiedTimeStart: RegExp; 148 | public get modifiedTimeStart(): RegExp { 149 | if (this.m_modifiedTimeStart == null) { 150 | this.m_modifiedTimeStart = new RegExp(this.getValue("modifiedTimeStart", "[lL]ast[ -][mM]odified *: ")); 151 | } 152 | return this.m_modifiedTimeStart; 153 | } 154 | 155 | private m_modifiedTimeEnd: RegExp; 156 | public get modifiedTimeEnd(): RegExp { 157 | if (this.m_modifiedTimeEnd == null) { 158 | this.m_modifiedTimeEnd = new RegExp(this.getValue("modifiedTimeEnd", "$")); 159 | } 160 | return this.m_modifiedTimeEnd; 161 | } 162 | 163 | public get luxonFormat(): string { 164 | return this.getValue("luxonFormat", "yyyy/LL/dd HH:mm:ss"); 165 | } 166 | 167 | public get luxonTimezone(): string { 168 | return this.getValue("luxonTimezone", "system"); 169 | } 170 | 171 | public get hasLuxonFormatOrTimezone(): boolean { 172 | return this.hasNonDefaultValue('luxonFormat') || this.hasNonDefaultValue('luxonTimezone'); 173 | } 174 | 175 | public get momentFormat(): string { 176 | return this.getValue("momentFormat", "YYYY/MM/DD HH:mm:ss"); 177 | } 178 | 179 | public get hasMomentFormat(): boolean { 180 | return this.hasNonDefaultValue('momentFormat'); 181 | } 182 | 183 | } -------------------------------------------------------------------------------- /images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 40 | 43 | 44 | 46 | 55 | 64 | 73 | 82 | 91 | 100 | 109 | 112 | 119 | 124 | 125 | 128 | 133 | 134 | 137 | 142 | 143 | 146 | 151 | 152 | 155 | 160 | 161 | 164 | 169 | 170 | 173 | 178 | 179 | 182 | 186 | 191 | 192 | 201 | 204 | 209 | 210 | 211 | 215 | 226 | 231 | 232 | 236 | 241 | 242 | 243 | --------------------------------------------------------------------------------