Initial Commit
diff --git a/src/JSONLoader.js b/src/JSONLoader.js
new file mode 100644
index 0000000..10e2840
--- /dev/null
+++ b/src/JSONLoader.js
@@ -0,0 +1,26 @@
+module.exports = function JSONLoader(){
+  var self = this;
+
+  function receivedResponse(xhr){
+    return xhr.status==200 && xhr.readyState==4;
+  }
+
+  function handleResponse(xhr,callback){
+    xhr.onreadystatechange = function(){
+      if ( receivedResponse(xhr) ){
+        try{
+          callback(null,JSON.parse(xhr.responseText) );
+        }catch(err){
+          callback(err,null);
+        }    
+      }
+    }
+  }
+
+  self.load = function(location,callback){
+    var xhr = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
+    xhr.open("GET", location, true);
+    handleResponse(xhr,callback);
+    xhr.send();
+  };
+};
\ No newline at end of file
diff --git a/src/SearchStrategies/fuzzy.js b/src/SearchStrategies/fuzzy.js
new file mode 100644
index 0000000..ab26035
--- /dev/null
+++ b/src/SearchStrategies/fuzzy.js
@@ -0,0 +1,15 @@
+module.exports = new FuzzySearchStrategy();
+
+function FuzzySearchStrategy(){
+  var self = this;
+
+  function createFuzzyRegExpFromString(string){
+    return new RegExp( string.split('').join('.*?'), 'gi');
+  }
+
+  self.matches = function(string,crit){
+    if( typeof string !== 'string' ) return false;
+    string = string.trim();
+    return !!string.match(createFuzzyRegExpFromString(crit));
+  };
+};
\ No newline at end of file
diff --git a/src/SearchStrategies/literal.js b/src/SearchStrategies/literal.js
new file mode 100644
index 0000000..9bddbe9
--- /dev/null
+++ b/src/SearchStrategies/literal.js
@@ -0,0 +1,15 @@
+module.exports = new LiteralSearchStrategy();
+
+function LiteralSearchStrategy(){
+  var self = this;
+
+  function doMatch(string,crit){
+    return string.toLowerCase().indexOf(crit.toLowerCase()) >= 0;
+  }
+
+  self.matches = function(string,crit){
+    if( typeof string !== 'string' ) return false;
+    string = string.trim();
+    return doMatch(string,crit);
+  };
+};
\ No newline at end of file
diff --git a/src/Searcher.js b/src/Searcher.js
new file mode 100644
index 0000000..99626dd
--- /dev/null
+++ b/src/Searcher.js
@@ -0,0 +1,42 @@
+module.exports = function Searcher(){
+  var self = this;
+
+  var matches = [];
+
+  var fuzzy = false;
+  var limit = 10;
+
+  var fuzzySearchStrategy = require('./SearchStrategies/fuzzy');
+  var literalSearchStrategy = require('./SearchStrategies/literal');
+
+  self.setFuzzy = function(_fuzzy){ fuzzy = !!_fuzzy; };
+
+  self.setLimit = function(_limit){ limit = parseInt(_limit,10) || limit; };
+
+  self.search = function(data,crit){
+    if( !crit ) return [];
+    matches.length = 0;
+    return findMatches(data,crit,getSearchStrategy());
+  };
+
+  function findMatches(store,crit,strategy){
+    var data = store.get();
+    for(var i = 0; i < data.length && matches.length < limit; i++) {
+      findMatchesInObject(data[i],crit,strategy);
+    }
+    return matches;
+  }
+
+  function findMatchesInObject(obj,crit,strategy){
+    for(var key in obj) {
+      if( strategy.matches(obj[key], crit) ){
+        matches.push(obj);
+        break;
+      }
+    }
+  }
+
+  function getSearchStrategy(){
+    return fuzzy ? fuzzySearchStrategy : literalSearchStrategy;
+  }
+};
\ No newline at end of file
diff --git a/src/Store.js b/src/Store.js
new file mode 100644
index 0000000..af4a060
--- /dev/null
+++ b/src/Store.js
@@ -0,0 +1,40 @@
+module.exports = function Store(_store){
+  var self = this;
+
+  var store = [];
+
+  if( isArray(_store) ){
+    addArray(_store);
+  }
+
+  function isObject(obj){ return !!obj && Object.prototype.toString.call(obj) == '[object Object]'; }
+  function isArray(obj){ return !!obj && Object.prototype.toString.call(obj) == '[object Array]'; }
+
+  function addObject(data){
+    store.push(data);
+    return data;
+  }
+
+  function addArray(data){
+    var added = [];
+    for (var i = 0; i < data.length; i++)
+      if( isObject(data[i]) )
+        added.push(addObject(data[i]));
+    return added;
+  }
+
+  self.clear = function(){
+    store.length = 0;
+    return store;
+  };
+
+  self.get = function(){
+    return store;
+  };
+
+  self.put = function(data){
+    if( isObject(data) ) return addObject(data);
+    if( isArray(data) ) return addArray(data);
+    return undefined;
+  };
+};
\ No newline at end of file
diff --git a/src/Templater.js b/src/Templater.js
new file mode 100644
index 0000000..c3a6580
--- /dev/null
+++ b/src/Templater.js
@@ -0,0 +1,15 @@
+module.exports = function Templater(){
+  var self = this;
+
+  var templatePattern = /\{(.*?)\}/g;
+
+  self.setTemplatePattern = function(newTemplatePattern){
+    templatePattern = newTemplatePattern;
+  };
+
+  self.render = function(t, data){
+    return t.replace(templatePattern, function(match, prop) {
+      return data[prop] || match;
+    });
+  };
+};
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..881a944
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,113 @@
+;(function(window,document,undefined){
+  'use strict';
+  
+  var Searcher = require('./Searcher');
+  var Templater = require('./Templater');
+  var Store = require('./Store');
+  var JSONLoader = require('./JSONLoader');
+
+  var searcher = new Searcher();
+  var templater = new Templater();
+  var store = new Store();
+  var jsonLoader = new JSONLoader();
+
+
+  window.SimpleJekyllSearch = new SimpleJekyllSearch();
+  function SimpleJekyllSearch(){
+    var self = this;
+
+    var requiredOptions = [
+      'searchInput',
+      'resultsContainer',
+      'dataSource',
+    ];
+    var opt = {
+      searchInput: null,
+      resultsContainer: null,
+      dataSource: [],
+      searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
+      noResultsText: 'No results found',
+      limit: 10,
+      fuzzy: false,
+    };
+
+    function initWithJSON(json){
+      store.put(opt.dataSource);
+      registerInput();      
+    }
+
+    function initWithURL(url){
+      jsonLoader.load(url, function gotJSON(err,json){
+        if( !err ) {
+          store.put(json);
+          registerInput();
+        }else{
+          throwError('failed to get JSON (' + url + ')');
+        }
+      });
+    }
+
+    self.init = function(_opt){
+      validateOptions(_opt);
+      assignOptions(_opt);
+      if( isJSON(opt.dataSource) ){
+        initWithJSON(opt.dataSource);
+      }else{
+        initWithURL(opt.dataSource);
+      }
+    };
+
+
+    function throwError(message){ throw new Error('SimpleJekyllSearch --- '+ message); }
+
+    function validateOptions(_opt){
+      for(var i = 0; i < requiredOptions.length; i++){
+        var req = requiredOptions[i];
+        if( !_opt[req] )
+          throwError('You must specify a ' + req);
+      }
+    }
+
+    function assignOptions(_opt){
+      for(var option in opt)
+        opt[option] = _opt[option] || opt[option];
+    }
+
+    function isJSON(json){
+      try{
+        return json instanceof Object && JSON.parse(JSON.stringify(json));
+      }catch(e){
+        return false;
+      }
+    }
+
+    function emptyResultsContainer(){
+      opt.resultsContainer.innerHTML = '';    
+    }
+
+    function appendToResultsContainer(text){
+      opt.resultsContainer.innerHTML += text;
+    }
+
+    function registerInput(){
+      opt.searchInput.addEventListener('keyup', function(e){
+        if( e.target.value.length == 0 ){
+          emptyResultsContainer();
+          return;
+        }
+        render( searcher.search(store, e.target.value) );
+      });
+    }
+
+    function render(results){
+      emptyResultsContainer();
+      if( results.length == 0 ){
+        return appendToResultsContainer(opt.noResultsText);
+      }
+      for (var i = 0; i < results.length; i++) {
+        appendToResultsContainer( templater.render(opt.searchResultTemplate, results[i]) );
+      };
+    }
+
+  };
+})(window,document);