cli.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #!/usr/bin/env node
  2. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  3. var fs = _interopDefault(require('fs'));
  4. var parser = _interopDefault(require('postcss-selector-parser'));
  5. var postcss = _interopDefault(require('postcss'));
  6. const selectorRegExp = /:has/;
  7. var plugin = postcss.plugin('css-has-pseudo', opts => {
  8. const preserve = Boolean('preserve' in Object(opts) ? opts.preserve : true);
  9. return root => {
  10. root.walkRules(selectorRegExp, rule => {
  11. const modifiedSelector = parser(selectors => {
  12. selectors.walkPseudos(selector => {
  13. if (selector.value === ':has' && selector.nodes) {
  14. const isNotHas = checkIfParentIsNot(selector);
  15. selector.value = isNotHas ? ':not-has' : ':has';
  16. const attribute = parser.attribute({
  17. attribute: encodeURIComponent(String(selector)).replace(/%3A/g, ':').replace(/%5B/g, '[').replace(/%5D/g, ']').replace(/%2C/g, ',').replace(/[():%\[\],]/g, '\\$&')
  18. });
  19. if (isNotHas) {
  20. selector.parent.parent.replaceWith(attribute);
  21. } else {
  22. selector.replaceWith(attribute);
  23. }
  24. }
  25. });
  26. }).processSync(rule.selector);
  27. const clone = rule.clone({
  28. selector: modifiedSelector
  29. });
  30. if (preserve) {
  31. rule.before(clone);
  32. } else {
  33. rule.replaceWith(clone);
  34. }
  35. });
  36. };
  37. });
  38. function checkIfParentIsNot(selector) {
  39. return Object(Object(selector.parent).parent).type === 'pseudo' && selector.parent.parent.value === ':not';
  40. }
  41. const fileRegExp = /^[\w\/.]+$/;
  42. const argRegExp = /^--(\w+)=("|')?(.+)\2$/;
  43. const relaxedJsonPropRegExp = /(['"])?([a-z0-9A-Z_]+)(['"])?:/g;
  44. const relaxedJsonValueRegExp = /("[a-z0-9A-Z_]+":\s*)(?!true|false|null|\d+)'?([A-z0-9]+)'?([,}])/g;
  45. const argo = process.argv.slice(2).reduce((object, arg) => {
  46. const argMatch = arg.match(argRegExp);
  47. const fileMatch = arg.match(fileRegExp);
  48. if (argMatch) {
  49. object[argMatch[1]] = argMatch[3];
  50. } else if (fileMatch) {
  51. if (object.from === '<stdin>') {
  52. object.from = arg;
  53. } else if (object.to === '<stdout>') {
  54. object.to = arg;
  55. }
  56. }
  57. return object;
  58. }, {
  59. from: '<stdin>',
  60. to: '<stdout>',
  61. opts: 'null'
  62. }); // get css from command line arguments or stdin
  63. (argo.from === '<stdin>' ? getStdin() : readFile(argo.from)).then(css => {
  64. if (argo.from === '<stdin>' && !css) {
  65. console.log(['CSS Has Pseudo\n', ' Transforms CSS with :has {}\n', 'Usage:\n', ' css-has-pseudo source.css transformed.css', ' css-has-pseudo --from=source.css --to=transformed.css --opts={}', ' echo "body:has(:focus) {}" | css-has-pseudo\n'].join('\n'));
  66. process.exit(0);
  67. }
  68. const pluginOpts = JSON.parse(argo.opts.replace(relaxedJsonPropRegExp, '"$2": ').replace(relaxedJsonValueRegExp, '$1"$2"$3'));
  69. const processOptions = Object.assign({
  70. from: argo.from,
  71. to: argo.to || argo.from
  72. }, argo.map ? {
  73. map: JSON.parse(argo.map)
  74. } : {});
  75. const result = plugin.process(css, processOptions, pluginOpts);
  76. if (argo.to === '<stdout>') {
  77. return result.css;
  78. } else {
  79. return writeFile(argo.to, result.css).then(() => `CSS was written to "${argo.to}"`);
  80. }
  81. }).catch(error => {
  82. if (Object(error).name === 'CssSyntaxError') {
  83. throw new Error(`PostCSS had trouble reading the file (${error.reason} on line ${error.line}, column ${error.column}).`);
  84. }
  85. if (Object(error).errno === -2) {
  86. throw new Error(`Sorry, "${error.path}" could not be read.`);
  87. }
  88. throw error;
  89. }).then(result => {
  90. console.log(result);
  91. process.exit(0);
  92. }, error => {
  93. console.error(Object(error).message || 'Something bad happened and we don’t even know what it was.');
  94. process.exit(1);
  95. });
  96. function readFile(pathname) {
  97. return new Promise((resolve, reject) => {
  98. fs.readFile(pathname, 'utf8', (error, data) => {
  99. if (error) {
  100. reject(error);
  101. } else {
  102. resolve(data);
  103. }
  104. });
  105. });
  106. }
  107. function writeFile(pathname, data) {
  108. return new Promise((resolve, reject) => {
  109. fs.writeFile(pathname, data, (error, content) => {
  110. if (error) {
  111. reject(error);
  112. } else {
  113. resolve(content);
  114. }
  115. });
  116. });
  117. }
  118. function getStdin() {
  119. return new Promise(resolve => {
  120. let data = '';
  121. if (process.stdin.isTTY) {
  122. resolve(data);
  123. } else {
  124. process.stdin.setEncoding('utf8');
  125. process.stdin.on('readable', () => {
  126. let chunk;
  127. while (chunk = process.stdin.read()) {
  128. data += chunk;
  129. }
  130. });
  131. process.stdin.on('end', () => {
  132. resolve(data);
  133. });
  134. }
  135. });
  136. }