├── .gitignore ├── tsconfig.json ├── .editorconfig ├── test ├── test.ts └── test-legacy.js ├── .github └── workflows │ └── test.yml ├── LICENSE ├── package.json ├── .eslintrc.js ├── README.md └── src └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /yarn.lock 3 | /dist 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "CommonJS", 5 | "target": "es2015", 6 | "esModuleInterop": true, 7 | "lib": ["esnext"], 8 | "outDir": "dist", 9 | "sourceMap": true, 10 | "declaration": true 11 | }, 12 | "include": ["src/**/*", "test/**/*"], 13 | "exclude": ["node_modules"] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | tab_width = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [{*.json,*.json.example,*.gyp,*.yml,*.yaml,*.workflow}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [{*.py,*.asm}] 17 | indent_style = space 18 | 19 | [*.py] 20 | indent_size = 4 21 | 22 | [*.asm] 23 | indent_size = 8 24 | 25 | [*.md] 26 | trim_trailing_whitespace = false 27 | 28 | # Ideal settings - some plugins might support these. 29 | [*.js] 30 | quote_type = single 31 | 32 | [{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}] 33 | curly_bracket_next_line = false 34 | spaces_around_operators = true 35 | spaces_around_brackets = outside 36 | # close enough to 1TB 37 | indent_brace_style = K&R 38 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import createMode, { Mode } from '../src'; 3 | 4 | describe('stat-mode (TypeScript)', () => { 5 | describe('default export', () => { 6 | it('should be a function named "createMode()"', () => { 7 | assert.equal('function', typeof createMode); 8 | assert.equal('createMode', createMode.name); 9 | }); 10 | it('should create a `Mode` instance without `new`', () => { 11 | const m = createMode(); 12 | assert(m instanceof Mode); 13 | assert(m instanceof createMode); 14 | }); 15 | }); 16 | 17 | describe('Mode', () => { 18 | it('should export the `Mode` constructor', () => { 19 | assert.equal('function', typeof Mode); 20 | assert.equal('Mode', Mode.name); 21 | }); 22 | it('should create a `Mode` instance with `new`', () => { 23 | const m = new Mode(); 24 | assert(m instanceof Mode); 25 | assert(m instanceof createMode); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | name: Test Node.js ${{ matrix.node-version }} on ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macos-latest, windows-latest] 12 | node-version: [6.x, 8.x, 10.x, 12.x] 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Print Node.js Version 25 | run: node --version 26 | 27 | - name: Install Dependencies 28 | run: npm install 29 | env: 30 | CI: true 31 | 32 | - name: Run "build" step 33 | run: npm run build --if-present 34 | env: 35 | CI: true 36 | 37 | - name: Run tests 38 | run: npm test 39 | env: 40 | CI: true 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2016 Nathan Rajlich 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stat-mode", 3 | "version": "1.0.0", 4 | "description": "Offers convenient getters and setters for the stat `mode`", 5 | "main": "dist/src/index", 6 | "typings": "dist/src/index", 7 | "files": [ 8 | "dist/src" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/TooTallNate/stat-mode.git" 13 | }, 14 | "keywords": [ 15 | "stat", 16 | "mode", 17 | "owner", 18 | "group", 19 | "others", 20 | "chmod", 21 | "octal", 22 | "symbolic", 23 | "permissions" 24 | ], 25 | "author": "Nathan Rajlich (http://n8.io/)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/TooTallNate/stat-mode/issues" 29 | }, 30 | "homepage": "https://github.com/TooTallNate/stat-mode", 31 | "devDependencies": { 32 | "@types/escodegen": "^0.0.6", 33 | "@types/esprima": "^4.0.2", 34 | "@types/mocha": "^5.2.7", 35 | "@types/node": "^10.5.3", 36 | "@typescript-eslint/eslint-plugin": "1.6.0", 37 | "@typescript-eslint/parser": "1.1.0", 38 | "cpy-cli": "^2.0.0", 39 | "eslint": "5.16.0", 40 | "eslint-config-airbnb": "17.1.0", 41 | "eslint-config-prettier": "4.1.0", 42 | "eslint-import-resolver-typescript": "1.1.1", 43 | "eslint-plugin-import": "2.16.0", 44 | "eslint-plugin-jsx-a11y": "6.2.1", 45 | "eslint-plugin-react": "7.12.4", 46 | "mocha": "^6.2.0", 47 | "rimraf": "^3.0.0", 48 | "typescript": "^3.5.3" 49 | }, 50 | "engines": { 51 | "node": ">= 6" 52 | }, 53 | "scripts": { 54 | "prebuild": "rimraf dist", 55 | "build": "tsc", 56 | "postbuild": "cpy --parents src test '!**/*.ts' dist", 57 | "test": "mocha --reporter spec dist/test/test*.js", 58 | "test-lint": "eslint src --ext .js,.ts", 59 | "prepublishOnly": "npm run build" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': [ 3 | 'airbnb', 4 | 'prettier' 5 | ], 6 | 'parser': '@typescript-eslint/parser', 7 | 'parserOptions': { 8 | 'ecmaVersion': 2018, 9 | 'sourceType': 'module', 10 | 'modules': true 11 | }, 12 | 'plugins': [ 13 | '@typescript-eslint' 14 | ], 15 | 'settings': { 16 | 'import/resolver': { 17 | 'typescript': { 18 | } 19 | } 20 | }, 21 | 'rules': { 22 | 'quotes': [ 23 | 2, 24 | 'single', 25 | { 26 | 'allowTemplateLiterals': true 27 | } 28 | ], 29 | 'class-methods-use-this': 0, 30 | 'consistent-return': 0, 31 | 'func-names': 0, 32 | 'global-require': 0, 33 | 'guard-for-in': 0, 34 | 'import/no-duplicates': 0, 35 | 'import/no-dynamic-require': 0, 36 | 'import/no-extraneous-dependencies': 0, 37 | 'import/prefer-default-export': 0, 38 | 'lines-between-class-members': 0, 39 | 'no-await-in-loop': 0, 40 | 'no-bitwise': 0, 41 | 'no-console': 0, 42 | 'no-continue': 0, 43 | 'no-control-regex': 0, 44 | 'no-empty': 0, 45 | 'no-loop-func': 0, 46 | 'no-nested-ternary': 0, 47 | 'no-param-reassign': 0, 48 | 'no-plusplus': 0, 49 | 'no-restricted-globals': 0, 50 | 'no-restricted-syntax': 0, 51 | 'no-shadow': 0, 52 | 'no-underscore-dangle': 0, 53 | 'no-use-before-define': 0, 54 | 'prefer-const': 0, 55 | 'prefer-destructuring': 0, 56 | 'camelcase': 0, 57 | 'no-unused-vars': 0, // in favor of '@typescript-eslint/no-unused-vars' 58 | // 'indent': 0 // in favor of '@typescript-eslint/indent' 59 | '@typescript-eslint/no-unused-vars': 'warn', 60 | // '@typescript-eslint/indent': ['error', 2] // this might conflict with a lot ongoing changes 61 | '@typescript-eslint/no-array-constructor': 'error', 62 | '@typescript-eslint/adjacent-overload-signatures': 'error', 63 | '@typescript-eslint/class-name-casing': 'error', 64 | '@typescript-eslint/interface-name-prefix': 'error', 65 | '@typescript-eslint/no-empty-interface': 'error', 66 | '@typescript-eslint/no-inferrable-types': 'error', 67 | '@typescript-eslint/no-misused-new': 'error', 68 | '@typescript-eslint/no-namespace': 'error', 69 | '@typescript-eslint/no-non-null-assertion': 'error', 70 | '@typescript-eslint/no-parameter-properties': 'error', 71 | '@typescript-eslint/no-triple-slash-reference': 'error', 72 | '@typescript-eslint/prefer-namespace-keyword': 'error', 73 | '@typescript-eslint/type-annotation-spacing': 'error', 74 | // '@typescript-eslint/array-type': 'error', 75 | // '@typescript-eslint/ban-types': 'error', 76 | // '@typescript-eslint/explicit-function-return-type': 'warn', 77 | // '@typescript-eslint/explicit-member-accessibility': 'error', 78 | // '@typescript-eslint/member-delimiter-style': 'error', 79 | // '@typescript-eslint/no-angle-bracket-type-assertion': 'error', 80 | // '@typescript-eslint/no-explicit-any': 'warn', 81 | // '@typescript-eslint/no-object-literal-type-assertion': 'error', 82 | // '@typescript-eslint/no-use-before-define': 'error', 83 | // '@typescript-eslint/no-var-requires': 'error', 84 | // '@typescript-eslint/prefer-interface': 'error' 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/test-legacy.js: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | let Mode = require('../src'); 3 | 4 | describe('stat-mode (JavaScript)', function() { 5 | it('should export the `Mode` constructor', function() { 6 | assert.equal('function', typeof Mode); 7 | assert.equal('createMode', Mode.name); 8 | }); 9 | 10 | describe('Mode', function() { 11 | it('should return a `Mode` instance with `new`', function() { 12 | let m = new Mode(); 13 | assert(m instanceof Mode); 14 | }); 15 | 16 | it('should return a `Mode` instance without `new`', function() { 17 | let m = Mode(); 18 | assert(m instanceof Mode); 19 | }); 20 | 21 | [ 22 | { 23 | mode: 33188 /* 0100644 */, 24 | octal: '0644', 25 | string: '-rw-r--r--', 26 | type: 'file' 27 | }, 28 | { 29 | mode: 16877 /* 040755 */, 30 | octal: '0755', 31 | string: 'drwxr-xr-x', 32 | type: 'directory' 33 | }, 34 | { 35 | mode: 16832 /* 040700 */, 36 | octal: '0700', 37 | string: 'drwx------', 38 | type: 'directory' 39 | }, 40 | { 41 | mode: 41325 /* 0120555 */, 42 | octal: '0555', 43 | string: 'lr-xr-xr-x', 44 | type: 'symbolicLink' 45 | }, 46 | { 47 | mode: 8592 /* 020620 */, 48 | octal: '0620', 49 | string: 'crw--w----', 50 | type: 'characterDevice' 51 | }, 52 | { 53 | mode: 24960 /* 060600 */, 54 | octal: '0600', 55 | string: 'brw-------', 56 | type: 'blockDevice' 57 | }, 58 | { 59 | mode: 4516 /* 010644 */, 60 | octal: '0644', 61 | string: 'prw-r--r--', 62 | type: 'FIFO' 63 | } 64 | ].forEach(function(test) { 65 | let m = new Mode(test); 66 | let isFn = 67 | `is${ test.type[0].toUpperCase() }${test.type.substring(1)}`; 68 | let strMode = m.toString(); 69 | let opposite = test.type == 'file' ? 'isDirectory' : 'isFile'; 70 | let first = test.type == 'file' ? 'd' : '-'; 71 | describe(`input: 0${ test.mode.toString(8)}`, function() { 72 | describe('#toString()', function() { 73 | it(`should equal "${ test.string }"`, function() { 74 | assert.equal(m.toString(), test.string); 75 | }); 76 | }); 77 | describe('#toOctal()', function() { 78 | it(`should equal "${ test.octal }"`, function() { 79 | assert.equal(m.toOctal(), test.octal); 80 | }); 81 | }); 82 | describe(`#${ isFn }()`, function() { 83 | it(`should return \`true\` for #${ isFn }()`, function() { 84 | assert.ok(m[isFn]()); 85 | }); 86 | it( 87 | `should remain "${ 88 | strMode 89 | }" after #${ 90 | isFn 91 | }(true) (gh-2)`, 92 | function() { 93 | assert.equal(true, m[isFn](true)); 94 | assert.equal(strMode, m.toString()); 95 | } 96 | ); 97 | }); 98 | describe(`#${ opposite }(true)`, function() { 99 | it( 100 | `should return \`false\` for \`#${ opposite }(true)\``, 101 | function() { 102 | assert.equal(false, m[opposite](true)); 103 | } 104 | ); 105 | it( 106 | `should be "${ 107 | first 108 | }${m.toString().substring(1) 109 | }" after #${ 110 | opposite 111 | }(true) (gh-2)`, 112 | function() { 113 | assert.equal( 114 | first + m.toString().substring(1), 115 | m.toString() 116 | ); 117 | } 118 | ); 119 | }); 120 | }); 121 | }); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | stat-mode 2 | ========= 3 | ### Offers convenient getters and setters for the stat `mode` 4 | [![Build Status](https://github.com/TooTallNate/stat-mode/workflows/Node%20CI/badge.svg)](https://github.com/TooTallNate/stat-mode/actions?workflow=Node+CI) 5 | 6 | You know that `mode` property on the `fs.Stat` object that you probably 7 | usually just ignore? Well there's acutally a lot of information packed 8 | into that number. 9 | 10 | The specific information includes: 11 | 12 | * What the ["file type"](http://en.wikipedia.org/wiki/Unix_file_types) of file it is 13 | * Whether or not the [`setuid` and `setgid` bits](http://en.wikipedia.org/wiki/Setuid) are set 14 | * Whether or not the [`sticky` bit](http://en.wikipedia.org/wiki/Sticky_bit) is set 15 | * The [_read_, _write_, and _execute_ permissions for the _owner_, _group_ and _others_](http://en.wikipedia.org/wiki/File_system_permissions) 16 | 17 | This module helps you extract that information. 18 | 19 | All the getters are also setters, which change the `mode` property 20 | appropriately. This is useful for when you have to build up your 21 | own `fs.Stat` object for whatever reason (like when implementing a 22 | FUSE filesystem. 23 | 24 | 25 | Installation 26 | ------------ 27 | 28 | ``` bash 29 | $ npm install stat-mode 30 | ``` 31 | 32 | 33 | Example 34 | ------- 35 | 36 | So given some arbitrary file (let's say `/bin/echo`): 37 | 38 | ``` bash 39 | $ ls -l /bin/echo 40 | -rwxr-xr-x 1 root wheel 14128 Aug 11 2013 /bin/echo 41 | ``` 42 | 43 | We can inspect it using the `fs.stat()` call and creating a `Mode` instance 44 | on top of it. 45 | 46 | ``` javascript 47 | var fs = require('fs'); 48 | var Mode = require('stat-mode'); 49 | 50 | fs.stat('/bin/echo', function (err, stat) { 51 | if (err) throw err; 52 | 53 | // create a "Mode" instance on top of the `stat` object 54 | var mode = new Mode(stat); 55 | 56 | // you can check what kind of file it is: 57 | mode.isDirectory(); 58 | // false 59 | 60 | mode.isFIFO(); 61 | // false 62 | 63 | mode.isFile(); 64 | // true 65 | 66 | 67 | // and you can also check individual owner, group and others permissions 68 | mode.owner.read; 69 | // true 70 | 71 | mode.owner.write; 72 | // true 73 | 74 | mode.owner.execute; 75 | // true 76 | 77 | mode.group.read; 78 | // true 79 | 80 | mode.group.write; 81 | // false 82 | 83 | mode.group.execute; 84 | // true 85 | 86 | mode.others.read; 87 | // true 88 | 89 | mode.others.write; 90 | // false 91 | 92 | mode.others.execute; 93 | // true 94 | 95 | 96 | // the `toString()` output resembes the `ls -l` output: 97 | mode.toString(); 98 | // '-rwxr-xr-x' 99 | }); 100 | ``` 101 | 102 | 103 | API 104 | --- 105 | 106 | ### new Mode(Object stat) → Mode 107 | 108 | You must pass in "stat" object to the `Mode` constructor. The "stat" 109 | object can be a real `fs.Stat` instance, or really any Object with a 110 | `mode` property. 111 | 112 | #### mode.isDirectory([Boolean set]) → Boolean 113 | 114 | Returns `true` if the mode's file type is "directory", `false` otherwise. 115 | If you pass `true` to the function, then the mode will be set to "directory". 116 | 117 | #### mode.isFile([Boolean set]) → Boolean 118 | 119 | Returns `true` if the mode's file type is "file", `false` otherwise. 120 | If you pass `true` to the function, then the mode will be set to "file". 121 | 122 | #### mode.isBlockDevice([Boolean set]) → Boolean 123 | 124 | Returns `true` if the mode's file type is "block device", `false` otherwise. 125 | If you pass `true` to the function, then the mode will be set to "block device". 126 | 127 | #### mode.isCharacterDevice([Boolean set]) → Boolean 128 | 129 | Returns `true` if the mode's file type is "character device", `false` otherwise. 130 | If you pass `true` to the function, then the mode will be set to "character 131 | device". 132 | 133 | #### mode.isSymbolicLink([Boolean set]) → Boolean 134 | 135 | Returns `true` if the mode's file type is "symbolic link", `false` otherwise. 136 | If you pass `true` to the function, then the mode will be set to "symbolic link". 137 | 138 | #### mode.isFIFO([Boolean set]) → Boolean 139 | 140 | Returns `true` if the mode's file type is "FIFO", `false` otherwise. 141 | If you pass `true` to the function, then the mode will be set to "FIFO". 142 | 143 | #### mode.isSocket([Boolean set]) → Boolean 144 | 145 | Returns `true` if the mode's file type is "socket", `false` otherwise. 146 | If you pass `true` to the function, then the mode will be set to "socket". 147 | 148 | #### mode.owner.read → Boolean [Getter/Setter] 149 | 150 | `true` if the mode is "owner read" rights, `false` otherwise. 151 | 152 | #### mode.owner.write → Boolean [Getter/Setter] 153 | 154 | `true` if the mode is "owner write" rights, `false` otherwise. 155 | 156 | #### mode.owner.execute → Boolean [Getter/Setter] 157 | 158 | `true` if the mode is "owner execute" rights, `false` otherwise. 159 | 160 | #### mode.group.read → Boolean [Getter/Setter] 161 | 162 | `true` if the mode is "group read" rights, `false` otherwise. 163 | 164 | #### mode.group.write → Boolean [Getter/Setter] 165 | 166 | `true` if the mode is "group write" rights, `false` otherwise. 167 | 168 | #### mode.group.execute → Boolean [Getter/Setter] 169 | 170 | `true` if the mode is "group execute" rights, `false` otherwise. 171 | 172 | #### mode.others.read → Boolean [Getter/Setter] 173 | 174 | `true` if the mode is "others read" rights, `false` otherwise. 175 | 176 | #### mode.others.write → Boolean [Getter/Setter] 177 | 178 | `true` if the mode is "others write" rights, `false` otherwise. 179 | 180 | #### mode.others.execute → Boolean [Getter/Setter] 181 | 182 | `true` if the mode is "others execute" rights, `false` otherwise. 183 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Stats } from 'fs'; 2 | 3 | /** 4 | * Constants (defined in `stat.h`). 5 | */ 6 | const S_IFMT = 61440; /* 0170000 type of file */ 7 | const S_IFIFO = 4096; /* 0010000 named pipe (fifo) */ 8 | const S_IFCHR = 8192; /* 0020000 character special */ 9 | const S_IFDIR = 16384; /* 0040000 directory */ 10 | const S_IFBLK = 24576; /* 0060000 block special */ 11 | const S_IFREG = 32768; /* 0100000 regular */ 12 | const S_IFLNK = 40960; /* 0120000 symbolic link */ 13 | const S_IFSOCK = 49152; /* 0140000 socket */ 14 | const S_IFWHT = 57344; /* 0160000 whiteout */ 15 | const S_ISUID = 2048; /* 0004000 set user id on execution */ 16 | const S_ISGID = 1024; /* 0002000 set group id on execution */ 17 | const S_ISVTX = 512; /* 0001000 save swapped text even after use */ 18 | const S_IRUSR = 256; /* 0000400 read permission, owner */ 19 | const S_IWUSR = 128; /* 0000200 write permission, owner */ 20 | const S_IXUSR = 64; /* 0000100 execute/search permission, owner */ 21 | const S_IRGRP = 32; /* 0000040 read permission, group */ 22 | const S_IWGRP = 16; /* 0000020 write permission, group */ 23 | const S_IXGRP = 8; /* 0000010 execute/search permission, group */ 24 | const S_IROTH = 4; /* 0000004 read permission, others */ 25 | const S_IWOTH = 2; /* 0000002 write permission, others */ 26 | const S_IXOTH = 1; /* 0000001 execute/search permission, others */ 27 | 28 | function createMode(stat?: number | createMode.StatsMode) { 29 | return new createMode.Mode(stat); 30 | } 31 | 32 | namespace createMode { 33 | export type StatsMode = Pick; 34 | 35 | export function isStatsMode(v: any): v is StatsMode { 36 | return v && typeof v.mode === 'number'; 37 | } 38 | 39 | export class RWX { 40 | protected static r: number; 41 | protected static w: number; 42 | protected static x: number; 43 | private stat: StatsMode; 44 | 45 | constructor(stat: StatsMode) { 46 | this.stat = stat; 47 | } 48 | 49 | public get read(): boolean { 50 | return Boolean(this.stat.mode & (this.constructor as typeof RWX).r); 51 | } 52 | public set read(v: boolean) { 53 | if (v) { 54 | this.stat.mode |= (this.constructor as typeof RWX).r; 55 | } else { 56 | this.stat.mode &= ~(this.constructor as typeof RWX).r; 57 | } 58 | } 59 | 60 | public get write(): boolean { 61 | return Boolean(this.stat.mode & (this.constructor as typeof RWX).w); 62 | } 63 | public set write(v: boolean) { 64 | if (v) { 65 | this.stat.mode |= (this.constructor as typeof RWX).w; 66 | } else { 67 | this.stat.mode &= ~(this.constructor as typeof RWX).w; 68 | } 69 | } 70 | 71 | public get execute(): boolean { 72 | return Boolean(this.stat.mode & (this.constructor as typeof RWX).x); 73 | } 74 | public set execute(v: boolean) { 75 | if (v) { 76 | this.stat.mode |= (this.constructor as typeof RWX).x; 77 | } else { 78 | this.stat.mode &= ~(this.constructor as typeof RWX).x; 79 | } 80 | } 81 | } 82 | 83 | export class Owner extends RWX { 84 | protected static r = S_IRUSR; 85 | protected static w = S_IWUSR; 86 | protected static x = S_IXUSR; 87 | } 88 | 89 | export class Group extends RWX { 90 | protected static r = S_IRGRP; 91 | protected static w = S_IWGRP; 92 | protected static x = S_IXGRP; 93 | } 94 | 95 | export class Others extends RWX { 96 | protected static r = S_IROTH; 97 | protected static w = S_IWOTH; 98 | protected static x = S_IXOTH; 99 | } 100 | 101 | export class Mode { 102 | public owner: Owner; 103 | public group: Group; 104 | public others: Others; 105 | private stat: StatsMode; 106 | 107 | constructor(stat?: number | StatsMode) { 108 | if (typeof stat === 'number') { 109 | this.stat = { mode: stat }; 110 | } else if (isStatsMode(stat)) { 111 | this.stat = stat; 112 | } else { 113 | this.stat = { mode: 0 }; 114 | } 115 | this.owner = new Owner(this.stat); 116 | this.group = new Group(this.stat); 117 | this.others = new Others(this.stat); 118 | } 119 | 120 | private checkModeProperty(property: number, set?: boolean) { 121 | const { mode } = this.stat; 122 | if (set) { 123 | this.stat.mode = ((mode | S_IFMT) & property) | (mode & ~S_IFMT); 124 | } 125 | return (mode & S_IFMT) === property; 126 | } 127 | 128 | public isDirectory(v?: boolean) { 129 | return this.checkModeProperty(S_IFDIR, v); 130 | } 131 | 132 | public isFile(v?: boolean) { 133 | return this.checkModeProperty(S_IFREG, v); 134 | } 135 | 136 | public isBlockDevice(v?: boolean) { 137 | return this.checkModeProperty(S_IFBLK, v); 138 | } 139 | 140 | public isCharacterDevice(v?: boolean) { 141 | return this.checkModeProperty(S_IFCHR, v); 142 | } 143 | 144 | public isSymbolicLink(v?: boolean) { 145 | return this.checkModeProperty(S_IFLNK, v); 146 | } 147 | 148 | public isFIFO(v?: boolean) { 149 | return this.checkModeProperty(S_IFIFO, v); 150 | } 151 | 152 | public isSocket(v?: boolean) { 153 | return this.checkModeProperty(S_IFSOCK, v); 154 | } 155 | 156 | /** 157 | * Returns an octal representation of the `mode`, eg. "0754". 158 | * 159 | * http://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation 160 | * 161 | * @return {String} 162 | * @api public 163 | */ 164 | public toOctal(): string { 165 | const octal = this.stat.mode & 4095 /* 07777 */; 166 | return `0000${octal.toString(8)}`.slice(-4); 167 | } 168 | 169 | /** 170 | * Returns a String representation of the `mode`. 171 | * The output resembles something similar to what `ls -l` would output. 172 | * 173 | * http://en.wikipedia.org/wiki/Unix_file_types 174 | * 175 | * @return {String} 176 | * @api public 177 | */ 178 | public toString(): string { 179 | const str = []; 180 | 181 | // file type 182 | if (this.isDirectory()) { 183 | str.push('d'); 184 | } else if (this.isFile()) { 185 | str.push('-'); 186 | } else if (this.isBlockDevice()) { 187 | str.push('b'); 188 | } else if (this.isCharacterDevice()) { 189 | str.push('c'); 190 | } else if (this.isSymbolicLink()) { 191 | str.push('l'); 192 | } else if (this.isFIFO()) { 193 | str.push('p'); 194 | } else if (this.isSocket()) { 195 | str.push('s'); 196 | } else { 197 | const mode = this.valueOf(); 198 | const err = new TypeError(`Unexpected "file type": mode=${ mode}`); 199 | //err.stat = this.stat; 200 | //err.mode = mode; 201 | throw err; 202 | } 203 | 204 | // owner read, write, execute 205 | str.push(this.owner.read ? 'r' : '-'); 206 | str.push(this.owner.write ? 'w' : '-'); 207 | if (this.setuid) { 208 | str.push(this.owner.execute ? 's' : 'S'); 209 | } else { 210 | str.push(this.owner.execute ? 'x' : '-'); 211 | } 212 | 213 | // group read, write, execute 214 | str.push(this.group.read ? 'r' : '-'); 215 | str.push(this.group.write ? 'w' : '-'); 216 | if (this.setgid) { 217 | str.push(this.group.execute ? 's' : 'S'); 218 | } else { 219 | str.push(this.group.execute ? 'x' : '-'); 220 | } 221 | 222 | // others read, write, execute 223 | str.push(this.others.read ? 'r' : '-'); 224 | str.push(this.others.write ? 'w' : '-'); 225 | if (this.sticky) { 226 | str.push(this.others.execute ? 't' : 'T'); 227 | } else { 228 | str.push(this.others.execute ? 'x' : '-'); 229 | } 230 | 231 | return str.join(''); 232 | } 233 | 234 | public valueOf(): number { 235 | return this.stat.mode; 236 | } 237 | 238 | get setuid(): boolean { 239 | return Boolean(this.stat.mode & S_ISUID); 240 | } 241 | set setuid(v: boolean) { 242 | if (v) { 243 | this.stat.mode |= S_ISUID; 244 | } else { 245 | this.stat.mode &= ~S_ISUID; 246 | } 247 | } 248 | 249 | get setgid(): boolean { 250 | return Boolean(this.stat.mode & S_ISGID); 251 | } 252 | set setgid(v: boolean) { 253 | if (v) { 254 | this.stat.mode |= S_ISGID; 255 | } else { 256 | this.stat.mode &= ~S_ISGID; 257 | } 258 | } 259 | 260 | get sticky(): boolean { 261 | return Boolean(this.stat.mode & S_ISVTX); 262 | } 263 | set sticky(v: boolean) { 264 | if (v) { 265 | this.stat.mode |= S_ISVTX; 266 | } else { 267 | this.stat.mode &= ~S_ISVTX; 268 | } 269 | } 270 | } 271 | 272 | // So that `instanceof` checks work as expected 273 | createMode.prototype = Mode.prototype; 274 | } 275 | 276 | export = createMode; 277 | --------------------------------------------------------------------------------