First commit
diff --git a/genmanifest/genmanifest.go b/genmanifest/genmanifest.go
new file mode 100644
index 0000000..958928f
--- /dev/null
+++ b/genmanifest/genmanifest.go
@@ -0,0 +1,121 @@
+// Utility to generate web extension manifest files from GJSON (Generic JSON)
+// templates, given some dependencies.
+package main
+
+import (
+	"bufio"
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"regexp"
+	"strings"
+)
+
+var beginningOfIfStatement = regexp.MustCompile(`^\s*#if defined\(([^\(\)]*)\)\s*$`)
+var endOfIfStatement = regexp.MustCompile(`^\s*#endif\s*$`)
+
+var (
+  quietMode = flag.Bool("quiet", false, "Quiet mode")
+	templateFile = flag.String("template", "", "Template file")
+	destFile = flag.String("dest", "", "Destination file")
+)
+
+// findWithCaseFolding returns whether there is some element of slice which is
+// equal to val under Unicode case-folding.
+func findWithCaseFolding(slice []string, val string) bool {
+	for _, item := range slice {
+		if strings.EqualFold(item, val) {
+			return true
+		}
+	}
+	return false
+}
+
+// Given a Generic JSON (GJSON) template and the dependencies met,
+// WriteManifest will write at dest the manifest file generated from the
+// template according to the dependencies.
+func WriteManifest(template io.Reader, dest io.Writer, dependencies []string) error {
+	level := 0
+	activeLevel := 0
+	scanner := bufio.NewScanner(template)
+	for scanner.Scan() {
+		line := scanner.Text()
+		considerLine := false
+		if m := beginningOfIfStatement.FindStringSubmatch(line); m != nil {
+			if level == activeLevel {
+				statementDeps := m[1]
+				deps := strings.Split(statementDeps, "||")
+				for _, dep := range deps {
+					dep = strings.TrimSpace(dep)
+					if findWithCaseFolding(dependencies, dep) {
+						activeLevel++
+						break
+					}
+				}
+			}
+			level++
+		} else if m := endOfIfStatement.MatchString(line); m {
+			if activeLevel == level {
+				activeLevel--
+			}
+			level--
+		} else {
+			considerLine = level == activeLevel
+		}
+
+		if considerLine {
+			_, err := io.WriteString(dest, line+"\n")
+			if err != nil {
+				return fmt.Errorf("Can't write manifest: %v", err)
+			}
+		}
+	}
+
+	return nil
+}
+
+// WriteManifestFileNames is the same as WriteManifest, but instead of receiving
+// read and write handlers for each file, this function receives the file names
+// as strings and reads/writes from/to those files.
+func WriteManifestFileNames(templateFile string, destFile string, dependencies []string) error {
+	template, err := os.Open(templateFile)
+	if err != nil {
+		return fmt.Errorf("Couldn't open file %v: %v", templateFile, err)
+	}
+	defer template.Close()
+
+	dest, err := os.Create(destFile)
+	if err != nil {
+		return fmt.Errorf("Couldn't create file %v: %v", destFile, err)
+	}
+	defer dest.Close()
+
+	return WriteManifest(template, dest, dependencies)
+}
+
+func main() {
+	log.SetPrefix("generatemanifest: ")
+  log.SetFlags(0)
+
+  flag.Parse()
+  dependencies := flag.Args()
+
+  if len(dependencies) == 0 {
+    log.Fatalf("Pass the dependencies as arguments (for instance, run `go run generateManifest.go CHROMIUM`).")
+  }
+	if (*templateFile == "") {
+		log.Fatalf("Pass the template file name via the -template flag.")
+	}
+	if (*destFile == "") {
+		log.Fatalf("Pass the destination file name via the -dest flag.")
+	}
+
+	err := WriteManifestFileNames(*templateFile, *destFile, dependencies)
+  if err != nil {
+    log.Fatalf("%v", err)
+  } else if !*quietMode {
+    log.Println("Manifest has been generated successfully")
+  }
+}
diff --git a/genmanifest/genmanifest_test.go b/genmanifest/genmanifest_test.go
new file mode 100644
index 0000000..81b102f
--- /dev/null
+++ b/genmanifest/genmanifest_test.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"encoding/hex"
+	"io"
+	"os"
+	"testing"
+)
+
+type Manifest struct {
+	name         string
+	templateFile string
+	expectedFile string
+	dependencies []string
+}
+
+func TestManifestConversions(t *testing.T) {
+	testManifests := []Manifest{
+		{
+			name:         "ManifestSmall",
+			templateFile: "testdata/manifest_small1.gjson",
+			expectedFile: "testdata/manifest_small1_expected.json",
+			dependencies: []string{"AAA", "BBB", "D"},
+		},
+		{
+			name:         "ManifestFrozenChromium",
+			templateFile: "testdata/manifest_frozen.gjson",
+			expectedFile: "testdata/manifest_frozen_chromium_expected.json",
+			dependencies: []string{"CHROMIUM"},
+		},
+		{
+			name:         "ManifestFrozenGecko",
+			templateFile: "testdata/manifest_frozen.gjson",
+			expectedFile: "testdata/manifest_frozen_gecko_expected.json",
+			dependencies: []string{"GECKO"},
+		},
+	}
+
+	for _, m := range testManifests {
+		t.Run(m.name, func(t *testing.T) {
+			template, err := os.Open(m.templateFile)
+			if err != nil {
+				t.Fatalf("Can't open test file: %v", err)
+			}
+			defer template.Close()
+
+			expected, err := os.Open(m.expectedFile)
+			if err != nil {
+				t.Fatalf("Can't open expected file: %v", err)
+			}
+			defer expected.Close()
+
+			dest := bytes.NewBufferString("")
+
+			WriteManifest(template, dest, m.dependencies)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			h1 := sha256.New()
+			if _, err := io.Copy(h1, dest); err != nil {
+				t.Fatalf("Can't prepare sha256 sum of the result: %v", err)
+			}
+
+			h2 := sha256.New()
+			if _, err := io.Copy(h2, expected); err != nil {
+				t.Fatalf("Can't prepare sha256 sum of the expected file: %v", err)
+			}
+
+			sum1 := h1.Sum(nil)
+			sum2 := h2.Sum(nil)
+			if bytes.Compare(sum1, sum2) != 0 {
+				t.Fatalf("The expected manifest file (sha256: %v) differs from what we got (sha256: %v).", hex.EncodeToString(sum2), hex.EncodeToString(sum1))
+			}
+		})
+	}
+}
diff --git a/genmanifest/testdata/manifest_frozen.gjson b/genmanifest/testdata/manifest_frozen.gjson
new file mode 100644
index 0000000..8d93b2e
--- /dev/null
+++ b/genmanifest/testdata/manifest_frozen.gjson
@@ -0,0 +1,89 @@
+{
+  "manifest_version": 2,
+  "name": "__MSG_appName__",
+  "version": "0",
+#if defined(CHROMIUM)
+  "version_name": "dirty",
+#endif
+  "description": "__MSG_appDescription__",
+  "icons": {
+    "512": "icons/512.png",
+    "128": "icons/128.png"
+  },
+  "content_scripts": [
+    {
+      "matches": ["https://support.google.com/s/community*"],
+      "js": ["common/content_scripts.js", "content_scripts/console_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/s/community*"],
+      "js": ["common/content_scripts.js", "content_scripts/console_inject_start.js"],
+      "css": ["common/console.css"],
+      "run_at": "document_start"
+    },
+    {
+      "matches": ["https://support.google.com/*/threads*"],
+      "js": ["content_scripts/forum_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/*/thread/*"],
+      "exclude_matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/new*"],
+      "js": ["common/content_scripts.js", "content_scripts/thread_inject.js"],
+      "run_at": "document_end"
+    },
+    {
+      "matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/*"],
+      "exclude_matches": ["https://support.google.com/*/thread/new*"],
+      "js": ["common/content_scripts.js", "common/cs_event_listener.js", "content_scripts/profileindicator_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/*/profile/*"],
+      "js": ["common/content_scripts.js", "content_scripts/profile_inject.js"],
+      "css": ["common/forum.css"]
+    }
+  ],
+  "permissions": [
+    "https://support.google.com/s/community*",
+    "https://support.google.com/*/threads*",
+    "https://support.google.com/*/thread/*",
+    "storage"
+  ],
+  "web_accessible_resources": [
+    "injections/profileindicator_inject.js",
+    "injections/profileindicator_inject.css",
+    "injections/ccdarktheme.css",
+    "injections/batchlock_inject.js"
+  ],
+  "browser_action": {},
+#if defined(CHROMIUM)
+  "options_page": "options.html",
+#endif
+  "options_ui": {
+    "page": "options.html",
+#if defined(CHROMIUM)
+    "chrome_style": true,
+#endif
+#if defined(GECKO)
+    "browser_style": true,
+#endif
+    "open_in_tab": false
+  },
+  "background": {
+#if defined(CHROMIUM)
+    "persistent": false,
+#endif
+    "scripts": [
+      "common/common.js",
+      "background.js"
+    ]
+  },
+#if defined(GECKO)
+  "browser_specific_settings": {
+    "gecko": {
+      "id": "twpowertools@avm99963.com",
+      "strict_min_version": "57.0"
+    }
+  },
+#endif
+  "default_locale": "en"
+}
diff --git a/genmanifest/testdata/manifest_frozen_chromium_expected.json b/genmanifest/testdata/manifest_frozen_chromium_expected.json
new file mode 100644
index 0000000..2bd184b
--- /dev/null
+++ b/genmanifest/testdata/manifest_frozen_chromium_expected.json
@@ -0,0 +1,70 @@
+{
+  "manifest_version": 2,
+  "name": "__MSG_appName__",
+  "version": "0",
+  "version_name": "dirty",
+  "description": "__MSG_appDescription__",
+  "icons": {
+    "512": "icons/512.png",
+    "128": "icons/128.png"
+  },
+  "content_scripts": [
+    {
+      "matches": ["https://support.google.com/s/community*"],
+      "js": ["common/content_scripts.js", "content_scripts/console_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/s/community*"],
+      "js": ["common/content_scripts.js", "content_scripts/console_inject_start.js"],
+      "css": ["common/console.css"],
+      "run_at": "document_start"
+    },
+    {
+      "matches": ["https://support.google.com/*/threads*"],
+      "js": ["content_scripts/forum_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/*/thread/*"],
+      "exclude_matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/new*"],
+      "js": ["common/content_scripts.js", "content_scripts/thread_inject.js"],
+      "run_at": "document_end"
+    },
+    {
+      "matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/*"],
+      "exclude_matches": ["https://support.google.com/*/thread/new*"],
+      "js": ["common/content_scripts.js", "common/cs_event_listener.js", "content_scripts/profileindicator_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/*/profile/*"],
+      "js": ["common/content_scripts.js", "content_scripts/profile_inject.js"],
+      "css": ["common/forum.css"]
+    }
+  ],
+  "permissions": [
+    "https://support.google.com/s/community*",
+    "https://support.google.com/*/threads*",
+    "https://support.google.com/*/thread/*",
+    "storage"
+  ],
+  "web_accessible_resources": [
+    "injections/profileindicator_inject.js",
+    "injections/profileindicator_inject.css",
+    "injections/ccdarktheme.css",
+    "injections/batchlock_inject.js"
+  ],
+  "browser_action": {},
+  "options_page": "options.html",
+  "options_ui": {
+    "page": "options.html",
+    "chrome_style": true,
+    "open_in_tab": false
+  },
+  "background": {
+    "persistent": false,
+    "scripts": [
+      "common/common.js",
+      "background.js"
+    ]
+  },
+  "default_locale": "en"
+}
diff --git a/genmanifest/testdata/manifest_frozen_gecko_expected.json b/genmanifest/testdata/manifest_frozen_gecko_expected.json
new file mode 100644
index 0000000..49792d5
--- /dev/null
+++ b/genmanifest/testdata/manifest_frozen_gecko_expected.json
@@ -0,0 +1,73 @@
+{
+  "manifest_version": 2,
+  "name": "__MSG_appName__",
+  "version": "0",
+  "description": "__MSG_appDescription__",
+  "icons": {
+    "512": "icons/512.png",
+    "128": "icons/128.png"
+  },
+  "content_scripts": [
+    {
+      "matches": ["https://support.google.com/s/community*"],
+      "js": ["common/content_scripts.js", "content_scripts/console_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/s/community*"],
+      "js": ["common/content_scripts.js", "content_scripts/console_inject_start.js"],
+      "css": ["common/console.css"],
+      "run_at": "document_start"
+    },
+    {
+      "matches": ["https://support.google.com/*/threads*"],
+      "js": ["content_scripts/forum_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/*/thread/*"],
+      "exclude_matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/new*"],
+      "js": ["common/content_scripts.js", "content_scripts/thread_inject.js"],
+      "run_at": "document_end"
+    },
+    {
+      "matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/*"],
+      "exclude_matches": ["https://support.google.com/*/thread/new*"],
+      "js": ["common/content_scripts.js", "common/cs_event_listener.js", "content_scripts/profileindicator_inject.js"]
+    },
+    {
+      "matches": ["https://support.google.com/*/profile/*"],
+      "js": ["common/content_scripts.js", "content_scripts/profile_inject.js"],
+      "css": ["common/forum.css"]
+    }
+  ],
+  "permissions": [
+    "https://support.google.com/s/community*",
+    "https://support.google.com/*/threads*",
+    "https://support.google.com/*/thread/*",
+    "storage"
+  ],
+  "web_accessible_resources": [
+    "injections/profileindicator_inject.js",
+    "injections/profileindicator_inject.css",
+    "injections/ccdarktheme.css",
+    "injections/batchlock_inject.js"
+  ],
+  "browser_action": {},
+  "options_ui": {
+    "page": "options.html",
+    "browser_style": true,
+    "open_in_tab": false
+  },
+  "background": {
+    "scripts": [
+      "common/common.js",
+      "background.js"
+    ]
+  },
+  "browser_specific_settings": {
+    "gecko": {
+      "id": "twpowertools@avm99963.com",
+      "strict_min_version": "57.0"
+    }
+  },
+  "default_locale": "en"
+}
diff --git a/genmanifest/testdata/manifest_small1.gjson b/genmanifest/testdata/manifest_small1.gjson
new file mode 100644
index 0000000..5027087
--- /dev/null
+++ b/genmanifest/testdata/manifest_small1.gjson
@@ -0,0 +1,26 @@
+{
+  "foo": "option",
+#if defined(AAA)
+  "bar": 1,
+    #endif                   
+#if defined(    BBB )
+  "count": 2,
+  #if defined(B || C)    
+  "mmm": {},
+#endif
+#if defined(C)
+  #if defined(D)  
+  "ok": true,
+#endif
+#endif
+#if defined(D)
+#if defined(C)
+  "ok2": false,
+#endif
+#endif
+#endif
+#if defined(CDE || AAA)
+  "shouldBe": true,
+#endif
+  "so": 3.14
+}
diff --git a/genmanifest/testdata/manifest_small1_expected.json b/genmanifest/testdata/manifest_small1_expected.json
new file mode 100644
index 0000000..0e32581
--- /dev/null
+++ b/genmanifest/testdata/manifest_small1_expected.json
@@ -0,0 +1,7 @@
+{
+  "foo": "option",
+  "bar": 1,
+  "count": 2,
+  "shouldBe": true,
+  "so": 3.14
+}