From 6083e70ea089fa2683741a1118be0e4e6b76f858 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 8 Jan 2018 21:54:16 +0900
Subject: separate setting actions and reducers

---
 src/background/components/background.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/background/components/background.js')

diff --git a/src/background/components/background.js b/src/background/components/background.js
index 2d94310..22c6693 100644
--- a/src/background/components/background.js
+++ b/src/background/components/background.js
@@ -1,6 +1,6 @@
 import messages from 'shared/messages';
 import * as operationActions from 'background/actions/operation';
-import * as settingsActions from 'settings/actions/setting';
+import * as settingActions from 'background/actions/setting';
 import * as tabActions from 'background/actions/tab';
 import * as commands from 'shared/commands';
 
@@ -46,7 +46,7 @@ export default class BackgroundComponent {
     case messages.CONSOLE_QUERY_COMPLETIONS:
       return commands.complete(message.text, settings.value);
     case messages.SETTINGS_RELOAD:
-      this.store.dispatch(settingsActions.load());
+      this.store.dispatch(settingActions.load());
       return this.broadcastSettingsChanged();
     }
   }
-- 
cgit v1.2.3


From dda4e7475cdd092d00441c7cd0ceb194ee5dee3d Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Thu, 11 Jan 2018 20:07:25 +0900
Subject: move commands to background action

---
 src/background/actions/command.js       | 62 ++++++++++++++++++++++++
 src/background/components/background.js |  3 +-
 src/shared/commands/exec.js             | 85 ---------------------------------
 src/shared/commands/index.js            |  3 +-
 src/shared/commands/parsers.js          | 59 +++++++++++++++++++++++
 src/shared/commands/properties.js       | 31 ------------
 test/shared/commands/parsers.test.js    | 78 ++++++++++++++++++++++++++++++
 test/shared/commands/property.test.js   | 41 ----------------
 8 files changed, 202 insertions(+), 160 deletions(-)
 create mode 100644 src/background/actions/command.js
 delete mode 100644 src/shared/commands/exec.js
 create mode 100644 src/shared/commands/parsers.js
 delete mode 100644 src/shared/commands/properties.js
 create mode 100644 test/shared/commands/parsers.test.js
 delete mode 100644 test/shared/commands/property.test.js

(limited to 'src/background/components/background.js')

diff --git a/src/background/actions/command.js b/src/background/actions/command.js
new file mode 100644
index 0000000..f11c61b
--- /dev/null
+++ b/src/background/actions/command.js
@@ -0,0 +1,62 @@
+import * as tabs from 'background/tabs';
+import * as parsers from 'shared/commands/parsers';
+
+const openCommand = (url) => {
+  return browser.tabs.query({
+    active: true, currentWindow: true
+  }).then((gotTabs) => {
+    if (gotTabs.length > 0) {
+      return browser.tabs.update(gotTabs[0].id, { url: url });
+    }
+  });
+};
+
+const tabopenCommand = (url) => {
+  return browser.tabs.create({ url: url });
+};
+
+const winopenCommand = (url) => {
+  return browser.windows.create({ url });
+};
+
+const bufferCommand = (keywords) => {
+  if (keywords.length === 0) {
+    return Promise.resolve([]);
+  }
+  let keywordsStr = keywords.join(' ');
+  return browser.tabs.query({
+    active: true, currentWindow: true
+  }).then((gotTabs) => {
+    if (gotTabs.length > 0) {
+      if (isNaN(keywordsStr)) {
+        return tabs.selectByKeyword(gotTabs[0], keywordsStr);
+      }
+      let index = parseInt(keywordsStr, 10) - 1;
+      return tabs.selectAt(index);
+    }
+  });
+};
+
+const exec = (line, settings) => {
+  let [name, args] = parsers.parseCommandLine(line);
+
+  switch (name) {
+  case 'o':
+  case 'open':
+    return openCommand(parsers.normalizeUrl(args, settings.search));
+  case 't':
+  case 'tabopen':
+    return tabopenCommand(parsers.normalizeUrl(args, settings.search));
+  case 'w':
+  case 'winopen':
+    return winopenCommand(parsers.normalizeUrl(args, settings.search));
+  case 'b':
+  case 'buffer':
+    return bufferCommand(args);
+  case '':
+    return Promise.resolve();
+  }
+  throw new Error(name + ' command is not defined');
+};
+
+export { exec };
diff --git a/src/background/components/background.js b/src/background/components/background.js
index 22c6693..19bf27f 100644
--- a/src/background/components/background.js
+++ b/src/background/components/background.js
@@ -1,5 +1,6 @@
 import messages from 'shared/messages';
 import * as operationActions from 'background/actions/operation';
+import * as commandActions from 'background/actions/command';
 import * as settingActions from 'background/actions/setting';
 import * as tabActions from 'background/actions/tab';
 import * as commands from 'shared/commands';
@@ -35,7 +36,7 @@ export default class BackgroundComponent {
       return this.store.dispatch(
         tabActions.openToTab(message.url, sender.tab), sender);
     case messages.CONSOLE_ENTER_COMMAND:
-      return commands.exec(message.text, settings.value).catch((e) => {
+      return commandActions.exec(message.text, settings.value).catch((e) => {
         return browser.tabs.sendMessage(sender.tab.id, {
           type: messages.CONSOLE_SHOW_ERROR,
           text: e.message,
diff --git a/src/shared/commands/exec.js b/src/shared/commands/exec.js
deleted file mode 100644
index 7248827..0000000
--- a/src/shared/commands/exec.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as tabs from 'background/tabs';
-import * as histories from 'background/histories';
-
-const normalizeUrl = (args, searchConfig) => {
-  let concat = args.join(' ');
-  try {
-    return new URL(concat).href;
-  } catch (e) {
-    if (concat.includes('.') && !concat.includes(' ')) {
-      return 'http://' + concat;
-    }
-    let query = concat;
-    let template = searchConfig.engines[
-      searchConfig.default
-    ];
-    for (let key in searchConfig.engines) {
-      if (args[0] === key) {
-        query = args.slice(1).join(' ');
-        template = searchConfig.engines[key];
-      }
-    }
-    return template.replace('{}', encodeURIComponent(query));
-  }
-};
-
-const openCommand = (url) => {
-  return browser.tabs.query({
-    active: true, currentWindow: true
-  }).then((gotTabs) => {
-    if (gotTabs.length > 0) {
-      return browser.tabs.update(gotTabs[0].id, { url: url });
-    }
-  });
-};
-
-const tabopenCommand = (url) => {
-  return browser.tabs.create({ url: url });
-};
-
-const winopenCommand = (url) => {
-  return browser.windows.create({ url });
-};
-
-const bufferCommand = (keywords) => {
-  if (keywords.length === 0) {
-    return Promise.resolve([]);
-  }
-  let keywordsStr = keywords.join(' ');
-  return browser.tabs.query({
-    active: true, currentWindow: true
-  }).then((gotTabs) => {
-    if (gotTabs.length > 0) {
-      if (isNaN(keywordsStr)) {
-        return tabs.selectByKeyword(gotTabs[0], keywordsStr);
-      }
-      let index = parseInt(keywordsStr, 10) - 1;
-      return tabs.selectAt(index);
-    }
-  });
-};
-
-const exec = (line, settings) => {
-  let words = line.trim().split(/ +/);
-  let name = words.shift();
-
-  switch (name) {
-  case 'o':
-  case 'open':
-    return openCommand(normalizeUrl(words, settings.search));
-  case 't':
-  case 'tabopen':
-    return tabopenCommand(normalizeUrl(words, settings.search));
-  case 'w':
-  case 'winopen':
-    return winopenCommand(normalizeUrl(words, settings.search));
-  case 'b':
-  case 'buffer':
-    return bufferCommand(words);
-  case '':
-    return Promise.resolve();
-  }
-  throw new Error(name + ' command is not defined');
-};
-
-export default exec;
diff --git a/src/shared/commands/index.js b/src/shared/commands/index.js
index c2cea3e..78cb4df 100644
--- a/src/shared/commands/index.js
+++ b/src/shared/commands/index.js
@@ -1,4 +1,3 @@
-import exec from './exec';
 import complete from './complete';
 
-export { exec, complete };
+export { complete };
diff --git a/src/shared/commands/parsers.js b/src/shared/commands/parsers.js
new file mode 100644
index 0000000..af51338
--- /dev/null
+++ b/src/shared/commands/parsers.js
@@ -0,0 +1,59 @@
+const normalizeUrl = (args, searchConfig) => {
+  let concat = args.join(' ');
+  try {
+    return new URL(concat).href;
+  } catch (e) {
+    if (concat.includes('.') && !concat.includes(' ')) {
+      return 'http://' + concat;
+    }
+    let query = concat;
+    let template = searchConfig.engines[
+      searchConfig.default
+    ];
+    for (let key in searchConfig.engines) {
+      if (args[0] === key) {
+        query = args.slice(1).join(' ');
+        template = searchConfig.engines[key];
+      }
+    }
+    return template.replace('{}', encodeURIComponent(query));
+  }
+};
+
+const mustNumber = (v) => {
+  let num = Number(v);
+  if (isNaN(num)) {
+    throw new Error('Not number: ' + v);
+  }
+  return num;
+};
+
+const parseSetOption = (word, types) => {
+  let [key, value] = word.split('=');
+  if (!value) {
+    value = !key.startsWith('no');
+    key = value ? key : key.slice(2);
+  }
+  let type = types[key];
+  if (!type) {
+    throw new Error('Unknown property: ' + key);
+  }
+  if (type === 'boolean' && typeof value !== 'boolean' ||
+       type !== 'boolean' && typeof value === 'boolean') {
+    throw new Error('Invalid argument: ' + word);
+  }
+
+  switch (type) {
+  case 'string': return [key, value];
+  case 'number': return [key, mustNumber(value)];
+  case 'boolean': return [key, value];
+  }
+};
+
+const parseCommandLine = (line) => {
+  let words = line.trim().split(/ +/);
+  let name = words.shift();
+  return [name, words];
+};
+
+export { normalizeUrl, parseCommandLine, parseSetOption };
diff --git a/src/shared/commands/properties.js b/src/shared/commands/properties.js
deleted file mode 100644
index 8a3213d..0000000
--- a/src/shared/commands/properties.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const mustNumber = (v) => {
-  let num = Number(v);
-  if (isNaN(num)) {
-    throw new Error('Not number: ' + v);
-  }
-  return num;
-};
-
-const parseProperty = (word, types) => {
-  let [key, value] = word.split('=');
-  if (!value) {
-    value = !key.startsWith('no');
-    key = value ? key : key.slice(2);
-  }
-  let type = types[key];
-  if (!type) {
-    throw new Error('Unknown property: ' + key);
-  }
-  if (type === 'boolean' && typeof value !== 'boolean' ||
-       type !== 'boolean' && typeof value === 'boolean') {
-    throw new Error('Invalid argument: ' + word);
-  }
-
-  switch (type) {
-  case 'string': return [key, value];
-  case 'number': return [key, mustNumber(value)];
-  case 'boolean': return [key, value];
-  }
-};
-
-export { parseProperty };
diff --git a/test/shared/commands/parsers.test.js b/test/shared/commands/parsers.test.js
new file mode 100644
index 0000000..200323c
--- /dev/null
+++ b/test/shared/commands/parsers.test.js
@@ -0,0 +1,78 @@
+import { expect } from "chai";
+import * as parsers from 'shared/commands/parsers';
+
+describe("shared/commands/parsers", () => {
+  describe("#parsers.parseSetOption", () => {
+    it('parse set string', () => {
+      let [key, value] = parsers.parseSetOption('encoding=utf-8', { encoding: 'string' });
+      expect(key).to.equal('encoding');
+      expect(value).to.equal('utf-8');
+    });
+
+    it('parse set string', () => {
+      let [key, value] = parsers.parseSetOption('history=50', { history: 'number' });
+      expect(key).to.equal('history');
+      expect(value).to.equal(50);
+    });
+
+    it('parse set boolean', () => {
+      let [key, value] = parsers.parseSetOption('paste', { paste: 'boolean' });
+      expect(key).to.equal('paste');
+      expect(value).to.be.true;
+
+      [key, value] = parsers.parseSetOption('nopaste', { paste: 'boolean' });
+      expect(key).to.equal('paste');
+      expect(value).to.be.false;
+    });
+
+    it('throws error on unknown property', () => {
+      expect(() => parsers.parseSetOption('charset=utf-8', {})).to.throw(Error, 'Unknown');
+      expect(() => parsers.parseSetOption('smoothscroll', {})).to.throw(Error, 'Unknown');
+      expect(() => parsers.parseSetOption('nosmoothscroll', {})).to.throw(Error, 'Unknown');
+    })
+
+    it('throws error on invalid property', () => {
+      expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'number' })).to.throw(Error, 'Not number');
+      expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'boolean' })).to.throw(Error, 'Invalid');
+      expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'string' })).to.throw(Error, 'Invalid');
+      expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'number' })).to.throw(Error, 'Invalid');
+    })
+  });
+
+  describe('#normalizeUrl', () => {
+    const config = {
+      default: 'google',
+      engines: {
+        google: 'https://google.com/search?q={}',
+        yahoo: 'https://yahoo.com/search?q={}',
+      }
+    };
+
+    it('convertes search url', () => {
+      expect(parsers.normalizeUrl(['google', 'apple'], config))
+        .to.equal('https://google.com/search?q=apple');
+      expect(parsers.normalizeUrl(['yahoo', 'apple'], config))
+        .to.equal('https://yahoo.com/search?q=apple');
+      expect(parsers.normalizeUrl(['google', 'apple', 'banana'], config))
+        .to.equal('https://google.com/search?q=apple%20banana');
+      expect(parsers.normalizeUrl(['yahoo', 'C++CLI'], config))
+        .to.equal('https://yahoo.com/search?q=C%2B%2BCLI');
+    });
+
+    it('user default  search engine', () => {
+      expect(parsers.normalizeUrl(['apple', 'banana'], config))
+        .to.equal('https://google.com/search?q=apple%20banana');
+    });
+  });
+
+  describe('#parseCommandLine', () => {
+    it('parse command line as name and args', () => {
+      expect(parsers.parseCommandLine('open google apple')).to.deep.equal(['open', ['google', 'apple']]);
+      expect(parsers.parseCommandLine('  open  google  apple  ')).to.deep.equal(['open', ['google', 'apple']]);
+      expect(parsers.parseCommandLine('')).to.deep.equal(['', []]);
+      expect(parsers.parseCommandLine('  ')).to.deep.equal(['', []]);
+      expect(parsers.parseCommandLine('exit')).to.deep.equal(['exit', []]);
+      expect(parsers.parseCommandLine('  exit  ')).to.deep.equal(['exit', []]);
+    });
+  });
+});
diff --git a/test/shared/commands/property.test.js b/test/shared/commands/property.test.js
deleted file mode 100644
index d949482..0000000
--- a/test/shared/commands/property.test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { expect } from "chai";
-import { parseProperty } from 'shared/commands/properties';
-
-describe("shared/commands/properties", () => {
-  describe("#parseProperty", () => {
-    it('parse set string', () => {
-      let [key, value] = parseProperty('encoding=utf-8', { encoding: 'string' });
-      expect(key).to.equal('encoding');
-      expect(value).to.equal('utf-8');
-    });
-
-    it('parse set string', () => {
-      let [key, value] = parseProperty('history=50', { history: 'number' });
-      expect(key).to.equal('history');
-      expect(value).to.equal(50);
-    });
-
-    it('parse set boolean', () => {
-      let [key, value] = parseProperty('paste', { paste: 'boolean' });
-      expect(key).to.equal('paste');
-      expect(value).to.be.true;
-
-      [key, value] = parseProperty('nopaste', { paste: 'boolean' });
-      expect(key).to.equal('paste');
-      expect(value).to.be.false;
-    });
-
-    it('throws error on unknown property', () => {
-      expect(() => parseProperty('charset=utf-8', {})).to.throw(Error, 'Unknown');
-      expect(() => parseProperty('smoothscroll', {})).to.throw(Error, 'Unknown');
-      expect(() => parseProperty('nosmoothscroll', {})).to.throw(Error, 'Unknown');
-    })
-
-    it('throws error on invalid property', () => {
-      expect(() => parseProperty('charset=utf-8', { charset: 'number' })).to.throw(Error, 'Not number');
-      expect(() => parseProperty('charset=utf-8', { charset: 'boolean' })).to.throw(Error, 'Invalid');
-      expect(() => parseProperty('smoothscroll', { smoothscroll: 'string' })).to.throw(Error, 'Invalid');
-      expect(() => parseProperty('smoothscroll', { smoothscroll: 'number' })).to.throw(Error, 'Invalid');
-    })
-  });
-});
-- 
cgit v1.2.3


From fad8f96a663d83792138cc986474ec4228b6c6c9 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Thu, 11 Jan 2018 20:22:49 +0900
Subject: implement set option

---
 src/background/actions/command.js       | 17 +++++++++++++++++
 src/background/components/background.js | 11 +++++------
 2 files changed, 22 insertions(+), 6 deletions(-)

(limited to 'src/background/components/background.js')

diff --git a/src/background/actions/command.js b/src/background/actions/command.js
index f11c61b..4c52bca 100644
--- a/src/background/actions/command.js
+++ b/src/background/actions/command.js
@@ -1,5 +1,7 @@
+import actions from '../actions';
 import * as tabs from 'background/tabs';
 import * as parsers from 'shared/commands/parsers';
+import * as properties from 'shared/settings/properties';
 
 const openCommand = (url) => {
   return browser.tabs.query({
@@ -37,6 +39,19 @@ const bufferCommand = (keywords) => {
   });
 };
 
+const setCommand = (args) => {
+  if (!args[0]) {
+    return Promise.resolve();
+  }
+
+  let [name, value] = parsers.parseSetOption(args[0], properties.types);
+  return {
+    type: actions.SETTING_SET_PROPERTY,
+    name,
+    value
+  };
+};
+
 const exec = (line, settings) => {
   let [name, args] = parsers.parseCommandLine(line);
 
@@ -53,6 +68,8 @@ const exec = (line, settings) => {
   case 'b':
   case 'buffer':
     return bufferCommand(args);
+  case 'set':
+    return setCommand(args);
   case '':
     return Promise.resolve();
   }
diff --git a/src/background/components/background.js b/src/background/components/background.js
index 19bf27f..9578e78 100644
--- a/src/background/components/background.js
+++ b/src/background/components/background.js
@@ -36,12 +36,11 @@ export default class BackgroundComponent {
       return this.store.dispatch(
         tabActions.openToTab(message.url, sender.tab), sender);
     case messages.CONSOLE_ENTER_COMMAND:
-      return commandActions.exec(message.text, settings.value).catch((e) => {
-        return browser.tabs.sendMessage(sender.tab.id, {
-          type: messages.CONSOLE_SHOW_ERROR,
-          text: e.message,
-        });
-      });
+      this.store.dispatch(
+        commandActions.exec(message.text, settings.value),
+        sender
+      );
+      return this.broadcastSettingsChanged();
     case messages.SETTINGS_QUERY:
       return Promise.resolve(this.store.getState().setting.value);
     case messages.CONSOLE_QUERY_COMPLETIONS:
-- 
cgit v1.2.3