commit 4ed2bf2e6076ed66592d3912b3bc2ccb7f6b738a Author: Geoff Doty Date: Sun Mar 11 07:15:42 2018 -0400 converting blog to wintersmith diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..1e815f9 --- /dev/null +++ b/config.json @@ -0,0 +1,27 @@ +{ + "locals": { + "url": "http://blog.negative9.net", + "name": "DEVELOPER LOG", + "owner": "Geoff Doty", + "description": "Inspiration Through Code", + "blerb": "Memory is a fickel thing, and code is read more than it is written, so write about it, write well, and remember it" + }, + "plugins": [ + "./plugins/paginator.coffee" + ], + "require": { + "moment": "moment", + "_": "underscore", + "typogr": "typogr" + }, + "jade": { + "pretty": true + }, + "markdown": { + "smartLists": true, + "smartypants": true + }, + "paginator": { + "perPage": 3 + } +} diff --git a/contents/about.md b/contents/about.md new file mode 100644 index 0000000..6d94ef5 --- /dev/null +++ b/contents/about.md @@ -0,0 +1,21 @@ +--- +view: none +--- + +Wintersmith is made by [Johan Nordberg][1] and licensed under the [MIT-license][2]. +This footer text can be edited in about.md + +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, +quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo +consequat. + +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum +dolore eu fugiat nulla pariatur. + +Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia +deserunt mollit anim id est laborum. + + +[1]: http://johan-nordberg.com +[2]: http://opensource.org/licenses/MIT diff --git a/contents/archive.json b/contents/archive.json new file mode 100644 index 0000000..0fd2fd3 --- /dev/null +++ b/contents/archive.json @@ -0,0 +1,3 @@ +{ + "template": "archive.jade" +} diff --git a/contents/articles/codeigniter-database-migrations/index.md b/contents/articles/codeigniter-database-migrations/index.md new file mode 100644 index 0000000..86e0e17 --- /dev/null +++ b/contents/articles/codeigniter-database-migrations/index.md @@ -0,0 +1,173 @@ +--- +title: Codeigniter Database Migrations +author: geoff-doty +date: 2012-06-28 +template: article.jade +--- + +##Overview + +Migrations are a convenient way for you to alter your database in a structured and organized manner. You **could** edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run them and where they are located. + +Keeping track of which changes need to be run against which deployment environment also posses an equally challenging situation on every deployment. + +Migrations **TRIES** to simplify this process by: + +- Keeping All database changes in the Migrations Folder +- Versioning each Migration +- And allow you to account for environment variations + + +##How it works + +Migrations are specially named CodeIgniter classes saved in the migrations folder… + + application/migrations + +…that allow you to go up or down a migration revision. + +Migrations files are prefixed with a migration revision number, followed by a meaningful name: + + 001_some_feature.php + +The Migration class is named differently, so for the above file the migration class skeleton would look like: + + class Migration_Some_Feature extends MY_Migrations { + + public function up() + { + //code used to update the database + } + + public function down() + { + //code to reverse what was done in the up() method + } + } + +A custome migration class (MY_Migration) could differ from the default implementation by providing an additional *protected* method + + _get_environment() {} + +Added for connivence to organize settings based on environment. The return values should be: + +- development +- testing +- production + +## Example + class Migration_Some_Feature extends MY_Migrations { + + public function up() + { + //global code used to update the database schema + + //environment specific changes + $environment = $this->_get_environment(); + + if($environment) + { + switch($environment) + { + case 'development': + //development settings changes here + break; + + case 'testing': + //testing settings changes here + break; + + case 'production' : + //production settings changes here + break; + default: + log_message('error','unable to determine environment'); + return FALSE; + } + } + + return TRUE; + } + + public function down() + { + //code to reverse what was done in the up() method + } + } + +> __NOTE__ +>> There are a more than few classes that can help with developing migrations: + __dbforge()__ loaded by the migration class, can be used to create and delete tables + - __active record__ can be used to do simple updates/deletes + - __log_message()__ to log changes made to the database (console app in mac to watch) + - ect.. + +## Configuration + +> Migrations are disabled by default + +Migrations are managed via the migrations file: + + application/config/migrations.php + +There are two settings of importance + + $config['migration_enabled'] = FALSE; + $config['migration_version'] = 0; + +**migration_enabled** switches database migrations on or off and the **migration_version** identifies the migration version this installation should use when triggered (see Updates and Rollbacks) + +> While **migration_version** lists the target migration version, the current migration version is managed in the the *migrations.version* field in the database. The Migration system automatically version up() or down() based on the version differences -- **WHEN TRIGGERED**! + +## Updates and Rollbacks + +Check out my custom `migrate` controller, it provides 4 operations + +| Method | Description | +|:--------|:------------| +| migrate/current | brings database to current version listed in config | +| migrate/ | alias for _migrate/current/__ | +| migrate/version/[num] | migrates to [num] revision passed in | +| migrate/latest | ignores config, and updates to latest migration file | + +### Usage + +Usage is _really_ split between to groups, __developers__ and __deployments__. + +##### Developers Workflow + +Developers should _NOT_ modify +​ + config/migrations.php + +Instead, developers should manage their local database via +- _migrate/latest_ to load the latest migration file on their system +- or _migrate/version/[num]_ to go to a migration version to test + +##### Deployment Workflow + +When setting up a deployment, the _config/migrations.php_ would be modified to reflect the version you want that deployment set to: + + $config['migration_version'] = 121; //used migration version + +and then run + + migrate/current +--- + + +> There currently is no UI, however one could be build based on the return values of the _migrate_ methods. + +>> Return values: +- TRUE, If Migration is on the latest version +- FALSE, If the migration failed +- INT, revision number successfully updated to + + +##Additional Resources +- [Migrations CodeIgniter Manual](http://codeigniter.com/user_guide/libraries/migration.html) +- [DBForge Manual](http://codeigniter.com/user_guide/database/forge.html) +- [Active Record Manual](http://codeigniter.com/user_guide/database/active_record.html) + + + diff --git a/contents/articles/fuck-webpack/index.md b/contents/articles/fuck-webpack/index.md new file mode 100644 index 0000000..0fbccf5 --- /dev/null +++ b/contents/articles/fuck-webpack/index.md @@ -0,0 +1,53 @@ +--- +title: ES6 is Evergreen, so Fuck Webpack +author: geoff-doty +date: 2017-12-24 +template: article.jade +--- + +I know. I know. Blasphame, but I really hate [Webpack](https://webpack.js.org/), not the idea of it but what it turned web development into - a bloated tangled mess of **shit you don't need**. + +## Webpack + +[Webpack](https://webpack.js.org/) became the defacto go-to solution to get the latest [es6](http://es6-features.org) javascript language functionality into a developers pipeline via transpilers like [Babeljs](https://babeljs.io/) and support the overly bloated non-standard web component soultion as the [ReactJS](https://reactjs.org/) framework (batteries NOT included). Sure [Webpack](https://webpack.js.org/) does more or can do more, but in the end developers just want to develop with [es6](http://es6-features.org) features. + +This is a web developers handycap and reminds me of those old drug commercials + + I work more, so I can do more drugs, so I can work more... + +it becomes + + I work more, so I can add more configuration, so I can work more... + +**PULL THE FUCKING PLUG!** + +Stop getting distracted by the shiny future that may never come, and focus on the here and now. Here and now we have native es6 language support in all evergreen browsers. + +No webpack. + +No configuration. + +No bullshit. + +### How Does it work + +Everything starts with your `index.html` page, or what ever html page your building, and you turn your `script` includes from this + +```html + +``` +into this +```html + +``` +and those lazy import statements you have that dont include the extension +```javascript + import Code from './src/Code'; +``` +should have the extension +```js + import Code from './src/Code.js'; +``` +and everything is right with the world and we can find peace and happyness for all. + +**FUCK WEBPACK!** \ No newline at end of file diff --git a/contents/articles/future-of-php/index.md b/contents/articles/future-of-php/index.md new file mode 100644 index 0000000..bedd2ef --- /dev/null +++ b/contents/articles/future-of-php/index.md @@ -0,0 +1,196 @@ +--- +title: Future of PHP +author: geoff-doty +date: 2013-12-18 +template: article.jade +--- + +PHP is in a metamorphosis. + +A quickening. + +This is not the result of one thing, but the sum of many smaller things coming together, seemingly, at the same time. + +| SUM OF THREE THINGS | +|:--:| +|PHP Language Improvements (5.3/5.4)| +|PHP Framework Interop Group| +|Composer Dependency Management| + +This digest aims to identify key php trends, so that we can determine if we need to correct our course to stay competitive and relevant in our future coding efforts. + +## PHP Language Improvements + +Abit slow, php is evolving and the most relevant of these changes can be seen in later versions of the php 5.x releases where the focus moved from stability, speed and functionality improvements to languages improvements, specifically designed for framework development -- these changes can drastically redefine how we architect our applications. + +A couple of the biggest game changers, are Namespaces and Closures. + +### Namespaces + +PHP Namespaces remove the need for long, PEAR-style class names (e.g. Slim\_Middleware\_Content\_Types) while still avoiding class name collisions with other libraries. + +```php + namespace ARX\Logging; +``` +Namespaces are a way of encapsulating items to prevent collisions between code you create, and internal PHP classes, functions and constants or the same found in a third-party library. + +It also provides the ability to alias (or shorten) Extra\_Long\_Names improving code readability. + +```php +use ARX\Logging as log; + +$log->error('This is so wrong, its right', 200); +``` +- [Read more about Namespaces](http://php.net/manual/en/language.namespaces.rationale.php) + +### Closures/Anonymous Functions + +Anonymous functions allow the creation of nameless functions which lend themselves great as callback parameters. + +Borrowed from the Functional Programming world, PHP now supports first-class functions, meaning that a function can be assigned to a variable. Both user defined functions and built-in functions can be referenced by a variable and invoked dynamically. + +Functions can be used as arguments to other functions and a function can return other functions as a response. + +```php +//assigned to a variable +$me = function() { + return 'My Name is Fred'; +} + +// as a callback +$app->get('/', function () { + echo "Hello World!"; +}); +``` +Newer PHP frameworks take advantage of Anonymous Functions/Closures for some architecture decisions. While I find use cases limited to callbacks and framework router workflows, this is a powerful construct - especially independent data scope mentioned at the bottom of [this article](http://zuttonet.com/articles/anonymous-functions-and-closure-php/). + +- [Read more about Closures](http://www.vancelucas.com/blog/practical-uses-for-php-5-3-closures/) + +## PHP Framework Interop Group + +The PHP community is large and diverse, composed of innumerable libraries, frameworks, and components. It is common for PHP developers to choose several of these and combine them into a single project. It is important that PHP code adhere (as close as possible) to a common code style to make it easy for developers to mix and match various libraries for their projects. + +The Framework Interop Group (formerly known as the ‘PHP Standards Group’) has proposed and approved a series of style recommendations, known as PSR-0, PSR-1 and PSR-2. You can read more about these standards here: + +- [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) relates to Autoloader Interoperability +- [PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) relates to Basic Coding Style +- [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) expands on the Coding Style + +> These recommendations expand on the previous recommendation. + +These recommendations provide a set of rules that a large majority of php projects/frameworks are adopting at different levels, such as: + +- Laravel 4 will support PSR-0, PSR-1 +- Slim 2 supports PSR-0,PSR-1, PSR-2 +- FuelPHP 2 will support PSR-0, PSR-1 +- CodeIgniter 3 will support PSR-0 +- Symfony 2 supports PSR-0, PSR-1, and PSR-2 +- Zend 2 supports PSR-0 + +Ideally you should write PHP code that adheres to one or more of these standards so that other developers can easily read and work with your code, and applications can consistently extend and interoperate with third-party code. + +### PSR-0 Autoloading Interoperability + +Autoloading in PHP has been a bit of a mess for some time, as every developer has his or her own ways of handling things. Some packages, like Smarty, use their own autoloading, some developers place multiple classes into one file or have lower-case file names – it's all very random. + +PSR-0 is a standard, created by the PHP Standards Group, to calm this mess down; As you will see later, Composer will work with it by default. Composer bundles with a PSR-0 autoloader, which you can include in your project with only a single line: + +```php +include_once './vendor/autoload.php'; +``` +### PSR-1 Basic Code Style + +There’s nothing worse than inheriting an application or needing to make changes to code that requires a lot of energy to decipher... you end up trawling through lines and lines of code that doesn’t make its purpose or intentions clear. + +Whats worse is your source control screams at different character encodings, your editor jumbles code because indentation inconsistently and when you just want to instantiate that new object like everywhere else in your code you cannot because a third-party library defined the class name as myObject rather than MY_Object. + +Having a defined coding style guide, helps insure a project doesn't die from a thousand little cuts. + +PSR-1 is **THAT** very basic style guide, and expands on PSR-0 adding basic code and file formatting rules such as: + +- PHP tags +- Character Encoding +- Namespacing Usage +- Class/Method/Constant Naming + +### PSR-2 Expanded Coding Style + +PSR-2 expands on PSR-1. + +The intent of this PSR-2 is to reduce cognitive friction when scanning code from different authors. It does so by enumerating a shared set of rules and expectations about how to format PHP code. + +The style rules herein are derived from commonalities among the various member projects. When various authors collaborate across multiple projects, it helps to have one set of guidelines to be used among all those projects. Thus, the benefit of this guide is not in the rules themselves, but in the sharing of those rules. + +## Composer Dependency Management + +It is pretty rare to find a developer who actively uses systems like PEAR. Instead, most developers have chosen their favorite framework, which has code specifically written for it to handle various things, like DB interaction, ORM's, OAuth, Amazon S3 integration, etc. + +The downside here, though, is that switching frameworks (or returning to not using a framework at all) can be a nightmare, as it involves relearning everything to use brand new tools – and that is no easy task. + +[Composer](http://getcomposer.org/) sets out to solve this situation by positioning itself as "the glue between all projects" – meaning that packages can be written, developed and shared in a format that other developers can plug into other applications with ease. + +Composer makes it super easy to declare and autoload your project’s dependencies without worrying about writing or registering your own autoloaders. + +And setting up Composer is fairly painless: + +```bash +curl -s http://getcomposer.org/installer | php +``` +With Composer installed we would create a `composer.json` file in the web root that indicates the dependencies you want to install, as an example showing Slim PHP: + +```js +{ + "require": {"slim/slim": "2.*"} +} +``` +This says install the latest 2.x branch of Slim. Then we install our dependencies via composer like this: + + php composer.phar install + +Composer organizes libraries in a `vendor/` folder with subfolders identified in your `composer.json` file. + +For example: + + vendor/ + arx/ + composer/ + slim/ + autoload.php + + +All libraries installed this way are available via one include: + +```php +require 'vendor/autoload.php'; +``` +Composer has a side kick too! Check out [The Packagist](https://packagist.org/) to see all of the libraries and frameworks that support Composer - way more than PEAR!! + +## Where Do We Go From Here? + +Lets start an open discussion on how to get our code working for us, rather than against us. + +Back-End Ideas: + +- Migrations (Time Based) +- Custom Code Generators / [CLI Tool](http://symfony.com/doc/current/components/console/introduction.html) +- Unit Tests +- Build Component-Based Libraries (as Separate Projects) +- Migrate to a Composer-based framework (Laravel 4?) + +Front-End Ideas: + +- HTML5 Boilerplate +- Twitter Bootstramp w/ [LESS](http://lesscss.org/) +- Template inheritance w/ Theme Support ([Twig](http://twig.sensiolabs.org/)?) + +#### Notes +- +Most of this document is a direct plagiarizing of internet news sources, personal blogs, and books. Think of this as a digest of trends facing the future of PHP. + +To give credit where credit is due, here are a few sources that inspired this digest. + +- [PHP The Right Way](http://www.phptherightway.com/) +- [Net Tuts](http://net.tutsplus.com/tutorials/php/easy-package-management-with-composer/) +- [Slim 2](http://www.slimframework.com/news/version-2) +- [FuelPHP History and Future](http://www.prodeveloper.org/fuelphp-history-and-future.html) +- [Laravel 4](http://www.thenerdary.net/post/30859565484/laravel-4) \ No newline at end of file diff --git a/contents/articles/git-ready/index.md b/contents/articles/git-ready/index.md new file mode 100644 index 0000000..2e5ef03 --- /dev/null +++ b/contents/articles/git-ready/index.md @@ -0,0 +1,213 @@ +--- +title: Git Ready +author: geoff-doty +date: 2012-10-18 +template: article.jade +--- + + +Primer on Git source-control management + +## Overview + +Git is a distributed version control system(DVAC) offering a few killer features that sway more developers towards git than any other version control system, for svn these killer features include: + - speed + - local commits + - branching + - merging + +SPEED: Everything in git is local, commits, logs, ignores, settings are all stored locally with in a single `.git` file. This greatly increase the speed at reading logs and reviewing commits. + +LOCAL COMMITS: The ability to commit, branch and stash locally allows the developer to commit without affecting other developers or the main codebase until s/he is ready to do so. This also leads to more commits providing a greater identification of the scope of work being done. + +BRANCHING: Branches are fast, light-weight as they are only registered as diffs from the branch your diverging. All history is local, and they can be deleted with confidence. + +MERGING: Merges are often looked at line-by-line, not file-by-file, so operations tend to be more like: _delete_line X_, and _add_line_X_ operations. This leads to fewer conflicts. Also if no changes have be made locally, git simply fast-forwards the codebase where merging is not required (FAST!). + +> Everything mentioned in this document is how **git** behaves by default. Reading through the document you may ask yourself, _"but can I do X?"_ and the answer is _almost always_ -- _"YES, there is option for that!"_ + + +## 1. Setup + +_setup operations here are one-time set-and-forget_ + +Introduce yourself to git + + git config --global user.name "Your Name" + git config -- global user.email "my.email@assist-rx.com" + +**Line Ending Woes (`auto` | `false`)** + +_Different operating system use different line endings. Text editors can negotiate differences seamlessly, as can your version control system -- if you tell it to!_ + +In mixed environments, `false` maybe the better option, `auto`, however is a safe default`. + + git config --global core.autocrlf false + +There are other settings you may be interested in such as + + git config --global apply.whitespace nowarn + +to ignore whitespace changes. + +### Handful of Commands + +A few git commands your going to be seeing a lot of. + +Command | Description +|:---:|---| +pull | pull commits and merges from a central/shared repo +push | push commits to central/shared repo +commit | commit changes locally +stash | stash code for use later +rebase | set [branch] as the foundation your changes are applied on top of +merge | merge [branch] into the active branch + + +### Remotes + +_Spring Loops has a ssh key management system, you need to add your public key(if you have one) to the repo you want access to first_ + + git remote add [name] ssh://[user]@[server]/[path]/project.git + +> _Common practice is to name your remote 'origin'_ + +The first time you `push`/`pull` changes to a remote (if there is only one), you need to identify where your `push`/`pull` should go via + + git pull origin master + +after that you can just call + + git pull + +### Clone + +If a git or svn repo already exists you can `clone` it which will add a remote automatically for the clone address. + +to clone a SVN repo into git use + + git svn clone @:/// + +_So you have code, now what?_ + +## 2. Git Workflow Loop + +If you just cloned your repo, you can start coding, however normally you want to `pull` the latest code before you start coding. + + git pull + +Again, if you have not previously ran `pull`, you can pass the _[repo]_ and _[branch]_. __Git will let you know!__ As an example: + + git pull origin master + +Develop like only you know how! _Netbeans_, _SublimeText_, _TextMate_ -- doesn't matter. Code as you would. + +> __NOTE__ +>> I find it best to do all code in a `branch`. As it always seems to happen if you get pulled into another project mid-step you can do so without affecting the code that you currently written. You can also `stash` your code. + ee Section 3 below for more information._ + +When your ready to `commit` your changes, initially you'll find it similar to 'SVN'. + +1. Choose the files you want to commit +2. write a `commit` message and +3. call `commit` + +There are a few command-line options you can pass to make this easier. + + git commit -m "[My Commit Message]" + +`-m` stands for message, and this will commit all modified files, however it will not commit new files, to do that you can pass `-a`, like this: + + git commit -am "[My Commit Message]" + +Usually you will code . . . commit . . . code . . . commit, as git lends itself better as a source control system than source backup system, however those commits are local, to share your changes you need(for central setup) to `push` them up to a `master` or central repository. + +> __BEFORE__ `push`, '`pull` the latest code! + + git pull + git push + +Rinse and repeat! + + +## 3. Stash, Branch, Tag, and Merge + +### Stash + +Git expects a clean working directory for most operations (`pull`, `push`, `rebase`), but what happens if you have files your working on, and you are not ready to commit those, but you need to `pull`, `merge`, or `rebase` your code? + +You `stash` them! + + git stash + +Calling the above stashes your changes and brings you to a clean state to call other commands. such as `pull`. + +> _New files are ignored by git until you `add` them_ + +When you want your stashed changes back, call + + git stash pop + +This will apply your stash on top of your current code and delete the saved stash (if successful) +​ +> _Stashes are local only_ + +### Tags + +Tag specific points in history as being important. Generally, people use this functionality to mark release points (v1.0, and so on) or otherwise known stable points in the history. + + git tag [tag_name] + +> Tag names cannot contain spaces, but you can use `.`, `-` or `_`. + +### Branches +Git branches are light weight code deviations (stored as _diffs_), that allow you to quickly jump and build features or try experiments without affecting your main code branch; called `master` in *git* or `trunk` in *svn*. + +Calling `git branch` by itself will list all the branches that exist with an asterisk(*) next to the branch your currently working in. + +To create a new branch you simply call + + git branch [name_of_branch] + +Then checkout your newly created `branch` via + + git checkout [name_of_branch] + +Or you can create a `branch` and `checkout` in one line via + + git checkout -b [name_of_branch] + +> REMEMBER: branches are created from the active branch + +Now that you've built this awesome feature in a _branch_, how do you get it back into the main project, lets say `master` for example? + +That's where merging comes in... + +### Merging + +First we need to be on the `branch` that we want to `merge` changes into: + + git checkout master + +then `merge` in the changes we want with + + git merge [name_of_branch] + +If there were no conflicts, you can now remove the branch and keep you workspace clean with + + git branch -d [name_of_branch] + +The branch will be deleted **ONLY IF** the branch changes have been merged successfully. git is smart enough to only delete changes you have used, but should you want to throw away a branch use the `-D` option instead of the `-d` option to force delete the branch. + +### Squash Merge (advanced) + +Sometimes it is advantageous to squash a series of commits in a branch when merging into another branch (such as master) to accurately convey a group of commits belong to a single feature or bug fix. + + git checkout master + git merge --squash [name_of_branch] + git commit + +## Resources + +- [Git Homepage](http://git-scm.com/) +- [Git Branches](http://gitref.org/branching/#branch) diff --git a/contents/articles/introduction-to-crud/index.md b/contents/articles/introduction-to-crud/index.md new file mode 100644 index 0000000..5fe0089 --- /dev/null +++ b/contents/articles/introduction-to-crud/index.md @@ -0,0 +1,84 @@ +--- +title: Introduction to CRUD +author: geoff-doty +date: 2015-08-10 +template: article.jade +--- + +Understanding CRUD operations, both apparent and implied + +## Introduction + +CRUD is an acronym for **Create**, **Read**, **Update**, and **Delete**. Each letter in the acronym translates to an operation to be performed, such as + +- Create or add new entries +- Read, retrieve, search, or view existing entries +- Update or edit existing entries +- Delete/deactivate existing entries + +The CRUD operations are at the heart of most dynamic websites, especially popularized by those created during the Web 2.0 era. CRUD is often referred to or most relevant when designing user interfaces for most applications. Those interfaces could be a GUI-based interface or as a low-level Application Program Interface (API) + +Without these four CRUD operations, applications cannot be considered complete. Because these operations are so fundamental, they are often documented and described under one comprehensive heading, such as "CRUD operations", "content management" or "contact maintenance". + +**CRUD Operations Translation to REST and SQL** + +As CRUD is so paramount for most applications, most architecture designs and systems supports these operations, but often under different terms, such as: + +|Operation| SQL | HTTP | +|---|---|---| +| Create | INSERT | POST / PUT | +| Read | SELECT | GET | +| Update | UPDATE| PUT /POST | +| Delete | DELETE | DELETE| + +**Other Variations** + +Over the years many variations of CRUD terminology were devised to help explicitly mention other operations that were implied by CRUD, for example the READ operation usually translates to ANY READ operation, including searching for a record to READ, READ a listing of records, and READ the details of a single record. + +Because of these hidden, implied operations terms like + +- SCRUD (Search) +- CRUDL (List) +- SCRUDL (Search and List) +- BREAD (Browse, Read, Edit, Add, Delete) + +were born. + +Once upon a time I used the term DICE, just so I could say "I'll DICE up that interface". DICE stood for Detail, Index, Create, and Edit. In this paradigm + +- Detail reads the details of a record +- Index is the listing of the records +- Create or add a record +- Edit or update a record -- including marking a record as deleted + +## CRUD Development Evolution + +CRUD is an evolutionary process. Below roughly explains how that evolution evolves. + +> NOTE: This is usually called "EVIL-LUTION";) + +### First Pass + +Initially CRUD operations are dumb, that is to say they do what they are suppose to do with out any checks and balances. For example a + +- **CREATE** operations create a records without any field validation and could include any or all of the fields defined in a record. +- **READ** operations read the entire data object, even attributes that would normally be hidden and designed to manage the object such as `is_deleted` or `created_by` that may have no bearing on what is trying to be read. +- **UPDATE** operations, much like CREATE operations have no field validation, no permissions to determine if a record can be updated or how it should be updated. Typically update operations in the first pass perform a replace operation of the record potentially erasing a dozen fields to add one. +- **DELETE** operations delete all traces of the record. This leaves holes in reports and breaks all but the simplest resources because of resource dependencies. For example, if we were building a blogging application add deleted a *user* resource, the *post* resource would have an invalid pointer to a resource id that no longer exists. + +### Second Pass + +At this point we realize our need to add some intelligence to our CRUD implementation so we do not get support calls at 3AM, because someone deleted a user accidentally and now any resource tied to that user is throwing errors. + +- **CREATE** operations are now restricted to a data dictionary, defining required fields, data types, data lengths, and other validation rules so we do not accept junk data. +- **READ** operations now adjust the fields returned to the context of how we want to read that data; Reading a list of records only return a small subset of features to reduce bandwidth and increase the speed to deliver and render the content. We start adding additional functionality such as paging, sorting, and searching for records. +- **UPDATE** operations now obey the validation improvements done for create, and tend to do a true update and not replace operation. This allows a user to change his/her name only +- **DELETE** operations are no longer permanent irreversible operations, but rather a simple hidden flag added to the record to identify that it has been deleted. Like a truly deleted record, it doesn't show up in read requests or search results unless specifically looking for deleted records. + +### Final Pass (Enterprise Class) + +In our final pass, security plays a huge role as to whether an operation can be performed, such as those associated with user permissions. Additionally detailed logging is added to all operations in Activity Logs or Audit Logs. + +## References + +- https://en.wikipedia.org/wiki/Create,_read,_update_and_delete diff --git a/contents/articles/make-windows-suck-less/index.md b/contents/articles/make-windows-suck-less/index.md new file mode 100644 index 0000000..12feb4a --- /dev/null +++ b/contents/articles/make-windows-suck-less/index.md @@ -0,0 +1,67 @@ +--- +title: Make Windows Suck Less +author: geoff-doty +date: 2015-05-14 +template: article.jade +--- + +You ever feel like there is a great divide among developers, like the parting of the ocean where on one side you have Windows developers and on the other -- the rest of the entire development world! + +I did. + +So, I bought a Mac! + +Then, I figured out how to make **Windows Suck Less™** for development. On this short trip over Windows short comings I'm going to provide the perfect recipe to fix one aspect of Windows to achieve my over-arching desire to make **Windows Suck Less™** + +--- +##### Fun FACT: The latest Visual Studio 2015 install ships with Google's Chrome Browser, Git, and NodeJS. +> What ever happened to Internet Explorer, Visual Source Safe, and ASP.NET? + +--- + +### FIX - The Command Prompt + +For the love that all developer hold holy -- kill the Windows command prompt. This 80's throw-back `CMD` keeps making it into the operating system after operating system. Why?! Ok, I know why, but still: *"Developers, Developers, Developers!"* want a better terminal. + +What is a better terminal? It is one that provides + +- Tab completion +- History +- Syntax highlighting +- Multiple tabbed sessions +- And access to tools used on other platforms such as `ssh`, `curl`, `ls` ect... + +So how do we achieve this? + +With two pieces of software: [GIT](http://git-scm.com/) of and [ConEmu](http://conemu.github.io/) + +#### Git + +Git, while best known as the little version control system that could, it can do so much more. It is not the most adopted version control system out there for no reason -- oh the sure POWER! What other version control system lets you build your own version control system using itself? This and other reasons why git is a key ingredient into making Windows Suck Less™ just by install it. + +*No self respecting developer would use anything else;)* + +Where git really shines on Windows is with the inclusion of over 80 command line tools that leverage the playing field between Windows, Mac and Linux. Command line tools like `sh`, `ssh`, `ls`, `mkdir`, `openssl`, `curl`, and 80 more... + +> Git is not *just* version control, it is a micro development eco-system and the *secret sauce* to make **Windows Suck Less™** + +But **git** alone does not complete our _domination_ of the Windows command prompt. We want to add one more nail to the coffin to insure this 80's throw-back-life-sucker stays down. + +> Here comes the **STAKE!** + +#### ConEmu + +ConEmu starts a console program in a hidden console window, in our case the Git Bash , and then provides an alternative customizable GUI window with various features + +- Multiple tabbed window interface +- Ability to hide and show from a keyboard shortcut +- XTerm 256 colors +- Better fonts including support for bold, italic and underline +- Transparency +- Copy and Paste + +Just to mention a few. + +So what are you waiting for, go forth and install these bad boys and make **Windows Suck Less™** + +DISCLAIMER: The solutions provided here were tested on Windows 7/8. Use at your own risk. \ No newline at end of file diff --git a/contents/articles/pragmatic-api-design/index.md b/contents/articles/pragmatic-api-design/index.md new file mode 100644 index 0000000..c1df3dc --- /dev/null +++ b/contents/articles/pragmatic-api-design/index.md @@ -0,0 +1,233 @@ +--- +title: Pragmatic API Design +author: geoff-doty +date: 2016-08-12 +template: article.jade +--- + +*Launch like a start-up, scale like an enterprise* + +## Introduction + +The API design should be organized around REST principles, however to maintain both user and developer sanity it **should** be pragmatic, thus we call it RESTful design, as it may not conform to *all* the full REST architecture design tenets. + +This document summaries *RESTful* design tenets. + +API's should have predictable, resource-oriented URLs and to use HTTP-based response codes to indicate API errors. Leveraging built-in HTTP features, like HTTP authentication and HTTP verbs, allows off-the-shelf HTTP clients to work out-of-the-box. + +> NOTE: `cross-origin` resource sharing only applies to browsers, and allows you to interact API's directly from a client-side web application + +By default, all API responses should return JSON, including errors + +## Authentication / Authorization + +### Basic Authentication + +- username:password should be key:password + +### Case for Bearer Tokens (JWT) + +Authorization based on [RFC 6750]() Bearer Tokens, a subset of the OAuth 2 framework. The bearer token is a [Javascript Web Token (JWT)](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32) + +The Authentication / Authorization process first requires you have authenticated against the `trusts` API resource, then you use that Token to authorize future API requests. + +Example + + POST /tokens + { + "username": "", + "password": "" + } + +This will return the JWT security token that will be used for the remainder of the requests + + { + "token": "mF_9.B5f-4.1JqM" + } + +You authenticate to the API by providing one of your API keys in the request. You can manage your API keys from your account. You can have multiple API keys active at one time. + +Your API keys carry many privileges, so be sure to keep them secret! + +Authorization to the API occurs via a Javascript Web Token (JWT). Provide your API JWT token as the Authorization HTTP Header + + GET /resource HTTP/1.1 + Host: api.assistrxlabs.com + Authorization: Bearer mF_9.B5f-4.1JqM + +All API requests must be made over HTTPS. Calls made over plain HTTP will fail. You must provide the Authorization header for all requests. + +## Authentication + +You authenticate to the API by providing one of your API keys in the request. You can manage your API keys from your account. You can have multiple API keys active at one time. Your API keys carry many privileges, so be sure to keep them secret! + +Authentication to the API occurs via HTTP Basic Auth. Provide your API key as the basic auth username. You do not need to provide a password. + +All API requests must be made over HTTPS. Calls made over plain HTTP will fail. You must authenticate for all requests. + +### Endpoints + +To make the API as explorable as possible, accounts have test-mode API keys as well as live-mode API keys. These keys can be active at the same time. Data created with test-mode credentials will never hit the credit card networks and will never cost anyone money. + +Example Endpoints + +#### Development + + https://api.example.com/v1/ + +#### Production + + https://api.example.com/v1/ + +## RESTful Verbs (pragmatic) + +Core RESOURCES should map HTTP VERBS to CRUD operations + +- GET = read resource +- POST = create resource +- PUT = replace resource +- DELETE = delete resource (or mark as deleted -- never really delete) + +|Resource|GET|PUT|POST|DELETE| +|---|:---|:---|:--|:---|:---| +|.|read| update|create|remove| +|Collection /resources/|List the URIs and perhaps other details of the collection's members.|Replace the entire collection with another collection|Create a new entry in the collection. The new entry's URI is assigned automatically and is usually returned by the operation|Delete the entire collection| +|Element /resources/item|Retrieve a representation of the addressed member of the collection, expressed in an appropriate Internet media type|Replace the addressed member of the collection, or if it doesn't exist, create it|Not generally used. Treat the addressed member as a collection in its own right and create a new entry in it|Delete the addressed member of the collection| + +> NOTE: PATCH could update a resource, however this was once a browser support issue + + +### URL Structure + +- first part of api should be version v1, v2 and not 1.2, or v1.5 ect… +- Additional URLs for + - api.example.com + - developers.example.com + - documents.example.com + +For Example + + https://api.example.com/v1/users + +> NOTE: as your developing an API, you can use '/v0' as your living API version + +### URI Resources + +- no verbs +- plural resource names +- /[api version]/[resource]/[identifier] + - /v1/cases/id/ + - /v1/jobs/id/ + +###### Resource Relationships (optional) + +- resource/id/resource + - /v1/jobs/id/applicants/ + - /v1/jobs/id/applicants/id + +### Actions + +- verbs not nouns + - /convert?from=EUR&to=CNY&amount=100 + - /search?q=??? + - /count + +### Search + + /search?q=item1+item2 + +this can be on a resource: + + /v1/docs/search?q=hash + +or on an item + + /v1/docs/1234/search?q=sample + +### Response Objects + +``` +{ + "data": [] + "pagination": {} +} +``` + +### Error Handling (extended) + +|Code| Description| +|--|---| +|1xx| Informational | +|2xx| Success | +|3xx| Redirection | +|4xx| Client Error| +|5xx| Server Error| + +### Common Codes + +| Code | Result | Summary | +|---|---|---| +| 200 | OK | Success| +| 400 | Bad Request | Often missing a required parameter| +| 401 | Unauthorized | No valid API key provided.| +| 402 | Request Failed | Parameters were valid but request failed. | +| 404 | Not Found | The requested item doesn't exist. +| 500 ||| +| 501 | Not Implemented | something went wrong on API's end. | + +Not all errors map cleanly onto HTTP response codes, but you should not throw out the HTTP responses codes, but rather extend. + +- `{code: , message:, link:}` + - code = extended http error ie 401-10 or 40110 + - message = error message + - link = url to documentation + +standard js error/exception object + +`{name:, message:}` + + +#### Error Suppression + +- suppress error codes per request + - suppress\_response\_code = FALSE + +### Partial Responses + + ?fields=name,date,street + +### Pagination + +All top-level API resources have support for bulk fetches — "list" API methods. For instance you can list people, list places, and list things. These list API methods share a common structure. + +The API utilizes cursor-based pagination, using the parameter `offset`. Pass `offset` to dictate where in the list you would like to begin (see below). + +- limit +- offset + +Examples + + https://api.example.com/v0/users?offset=0&limit=10 + +### Return Data Formats + + ?format=json + +or + + .json + +### Variable Naming + +lowerCamelCase (ie follow javascript style for json api) + +### Reserved Words + +`count` as a reserved word + + /v1/docs/count + +### Fake Response Method + +- ?method=put +- ?method=delete diff --git a/contents/authors/geoff-doty.json b/contents/authors/geoff-doty.json new file mode 100644 index 0000000..b7b38b2 --- /dev/null +++ b/contents/authors/geoff-doty.json @@ -0,0 +1,5 @@ +{ + "name": "Geoff Doty", + "email": "n2geoff@gmail.com", + "bio": "A web technology enthusiast since the dawn of Netscape Navigator" +} diff --git a/contents/css/main.css b/contents/css/main.css new file mode 100644 index 0000000..d95cfeb --- /dev/null +++ b/contents/css/main.css @@ -0,0 +1,460 @@ + +h1, h2, h3, h4, h5, h6, p, body, a, img, ul, ol, blockquote, pre { + margin: 0; padding: 0; border: 0; +} + +body { + font-family: 'Lora', serif; + font-size: 21px; + line-height: 1.52; + background-color: #f8f8f8; + text-rendering: optimizeLegibility; +} + +.content-wrap { + width: 740px; + margin: 0 auto; +} + +a { + color: slategray; +} + +a:hover { + color: steelblue; + text-decoration: underline; +} + +p { + margin-bottom: 1.52em; +} + +pre { + font-size: 0.9em; + overflow: auto; + background: #fff; + border: 1px dashed steelblue; + border-radius: 0.25em; + margin-bottom: 1.8em; + padding: 1em; +} + +h1 { + font-size: 2em; + margin-bottom: 1em; +} + +h2 { + font-size: 1.2em; + font-weight: 400; + line-height: 1.43; + margin-bottom: 1.35em; +} + +h3 { + font-style: italic; + font-weight: 400; + font-size: 1.4em; + margin-top: 1.8em; + margin-bottom: 0.8em; +} + +ol, ul { + margin: 0 1.4em 1.4em 1.4em; +} + +li { + margin-bottom: 0.5em; +} + +blockquote { + margin: 1.2em 3em; + padding-left: 1em; + font-style: italic; +} + +hr { + border: 0; + border-top: 1px solid steelblue; + height: 0; + margin: 1.6em 0; +} + +/* page header */ + +.header { + margin: 3em 0 4em; +} + +.header h1 { + font-size: 2.6em; + text-align: center; + font-weight: 700; + margin: 0; +} + +.header a, .header a:hover { + text-decoration: none; + color: #171717; +} + +.header .author { + font-family: 'Merriweather', serif; + font-variant: small-caps; + text-transform: lowercase; + text-rendering: auto; + text-align: center; + font-weight: 400; + letter-spacing: 1px; +} + +.header .description { + font-size: 1.2em; + font-style: italic; + text-align: center; + margin-top: -0.3em; +} + +.header p.blerb { + font-size: 1em; + border-top: 1px solid steelblue; + border-bottom: 1px solid steelblue; + padding: 1em; + font-style: italic; + text-align: center; + margin-bottom:0; +} + +body.article-detail > header h1 { + font-size: 2.5em; + font-style: italic; + font-weight: 400; + margin-bottom: -0.2em; +} + +body.article-detail > header { + margin-bottom: 3em; +} + +/* page footer */ + +footer { + margin: 3em 0; +} + +footer .nav { + text-align: center; + margin-top: 5em; + margin-bottom: 3.5em; +} + +footer .nav a { + padding: 0 0.5em; + font-size: 1.2em; + text-decoration: none; +} + +footer .about { + border-top: 1px solid steelblue; + padding: 2.2em 3em; + font-size: 0.7em; + -webkit-column-count: 3; + -moz-column-count: 3; + -ms-column-count: 3; + column-count: 3; + -webkit-column-gap: 2em; + -moz-column-gap: 2em; + -ms-column-gap: 2em; + column-gap: 2em; +} + +footer .copy { + text-align: center; + font-size: 0.7em; + font-style: italic; + margin-top: 1em; +} + +footer .copy, footer .copy a { + color: #8e8e8e; +} + +/* article */ + +.article { + margin: 3em 0 4em; +} + +.article header { + border-top: 1px dashed steelblue; +} + +.article header h2 { + font-style: italic; + text-align: center; + font-weight: 400; + margin: 0.8em 0; + font-size: 1.4em; +} + +.article header h2 a { + text-decoration: none; +} + +.article header .date { + text-align: center; + font-size: 0.8em; + margin-top: -0.7em; +} + +.article header .date span { + background-color: #f8f8f8; + padding: 0 0.7em; +} + +.article.intro .content p { + display: inline; +} + +.article.intro .content .more { + text-decoration: underline; + font-weight: 700; + padding-left: 0.3em; +} + +.article .content img { + display: block; + width: 100%; +} + +.more, .date { + font-family: 'Merriweather', serif; + font-variant: small-caps; + text-transform: lowercase; + font-weight: 400; + text-rendering: auto; + letter-spacing: 1px; +} + +/* archive */ + +.archive { + width: 32em; + margin: 5em auto 6em; + padding-left: 2em; +} + +.archive h2 { + font-size: 2em; + margin: 0; + margin-left: 6.1em; + margin-bottom: 0.5em; + font-style: italic; +} + +.archive a, .archive span{ + display: block; + float: left; + margin-bottom: -1px; + text-decoration: none; +} +.archive li:not(:last-child) { + border-bottom: 1px solid #d2d2d2; + margin-bottom: -1px; +} + +.archive a.last, .archive span.last { + border: 0; + margin-bottom: 0; +} + +.archive a { + width: 21em; + text-indent: 1em; + white-space: nowrap; +} + +.archive .year-label, +.archive .month-label{ + width: 4em; + font-family: 'Merriweather', serif; + font-variant: small-caps; + text-transform: lowercase; + font-weight: 400; + text-rendering: auto; + letter-spacing: 1px; + text-align: center; +} + +.archive .month-label { + width: 7em; +} + +.archive ul { + list-style: none; + margin: 0; +} + +.archive ul li { + margin: 0; +} + +table { + margin-bottom: 1em; + width: 100%; + /* border: 1px solid steelblue; */ + border-collapse: collapse; +} +th { + background-color: #EEE; + border-bottom: 1px solid steelblue; +} +td { + border-bottom: 1px solid steelblue; + vertical-align: top; + padding: 2px; +} + +/* code styling */ + +code { + font-family: 'Anonymous Pro', monospace; + font-size: 0.85em; + color: #000; +} + +pre code { + display: block; + line-height: 1.1; +} + +p code { + padding: 0.1em 0.3em 0.2em; + border-radius: 0.3em; + position: relative; + background: #FFF; + + white-space: nowrap; +} + +/* syntax hl stuff */ + +code.lang-markdown { + color: #424242; +} + +code.lang-markdown .header, +code.lang-markdown .strong { + font-weight: bold; +} + +code.lang-markdown .emphasis { + font-style: italic; +} + +code.lang-markdown .horizontal_rule, +code.lang-markdown .link_label, +code.lang-markdown .code, +code.lang-markdown .header, +code.lang-markdown .link_url { + color: #555; +} + +code.lang-markdown .blockquote, +code.lang-markdown .bullet { + color: #bbb; +} + +/* Tomorrow Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +.tomorrow-comment, pre .comment, pre .title { + color: #8e908c; +} + +.tomorrow-red, pre .variable, pre .attribute, pre .tag, pre .regexp, pre .ruby .constant, pre .xml .tag .title, pre .xml .pi, pre .xml .doctype, pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo { + color: #c82829; +} + +.tomorrow-orange, pre .number, pre .preprocessor, pre .built_in, pre .literal, pre .params, pre .constant { + color: #f5871f; +} + +.tomorrow-yellow, pre .class, pre .ruby .class .title, pre .css .rules .attribute { + color: #eab700; +} + +.tomorrow-green, pre .string, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata { + color: #718c00; +} + +.tomorrow-aqua, pre .css .hexcolor { + color: #3e999f; +} + +.tomorrow-blue, pre .function, pre .python .decorator, pre .python .title, pre .ruby .function .title, pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title { + color: #4271ae; +} + +.tomorrow-purple, pre .keyword, pre .javascript .function { + color: #8959a8; +} + +/* media queries */ + +@media (min-width: 1600px) { + body { font-size: 26px; } +} + +@media (max-width: 900px) { + body { font-size: 18px; } +} + +@media (max-width: 690px) { + .content-wrap { + width: auto; + padding: 0 1em; + } + .header { + margin: 1em 0; + } + .header h1 { + font-size: 1.4em; + margin-bottom: 0.6em; + } + .header .description { + font-size: 1em; + } + .article { + margin: 1em 0 2.5em; + } + .archive { + width: 80%; + margin: 0 auto; + } + .archive * { + float: none !important; + line-height: 1.6 !important; + width: auto !important; + height: auto !important; + text-align: left !important; + border: 0 !important; + margin: 0 !important; + } + footer .nav { + margin: 1em 0; + } + footer .about { + padding: 0; + font-size: 0.9em; + padding-top: 1.6em; + -webkit-column-count: 1; + -moz-column-count: 1; + -ms-column-count: 1; + column-count: 1; + } + footer .about p { + margin-bottom: 1em; + } +} diff --git a/contents/feed.json b/contents/feed.json new file mode 100644 index 0000000..9c3d3fd --- /dev/null +++ b/contents/feed.json @@ -0,0 +1,4 @@ +{ + "template": "feed.jade", + "filename": "feed.xml" +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8d8eb67 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,21 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "moment": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.3.1.tgz", + "integrity": "sha1-2nPXD2Jla7W36vA4f3k79hITEuM=" + }, + "typogr": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/typogr/-/typogr-0.5.2.tgz", + "integrity": "sha1-pFdu5+NJagW1YSn5eIV8iRR7ces=" + }, + "underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f7ff426 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "moment": "2.3.x", + "underscore": "1.4.x", + "typogr": "0.5.x" + }, + "private": "true" +} diff --git a/plugins/paginator.coffee b/plugins/paginator.coffee new file mode 100644 index 0000000..a8ce516 --- /dev/null +++ b/plugins/paginator.coffee @@ -0,0 +1,91 @@ + +module.exports = (env, callback) -> + ### Paginator plugin. Defaults can be overridden in config.json + e.g. "paginator": {"perPage": 10} ### + + defaults = + template: 'index.jade' # template that renders pages + articles: 'articles' # directory containing contents to paginate + first: 'index.html' # filename/url for first page + filename: 'page/%d/index.html' # filename for rest of pages + perPage: 2 # number of articles per page + + # assign defaults any option not set in the config file + options = env.config.paginator or {} + for key, value of defaults + options[key] ?= defaults[key] + + getArticles = (contents) -> + # helper that returns a list of articles found in *contents* + # note that each article is assumed to have its own directory in the articles directory + articles = contents[options.articles]._.directories.map (item) -> item.index + # skip articles that does not have a template associated + articles = articles.filter (item) -> item.template isnt 'none' + # sort article by date + articles.sort (a, b) -> b.date - a.date + return articles + + class PaginatorPage extends env.plugins.Page + ### A page has a number and a list of articles ### + + constructor: (@pageNum, @articles) -> + + getFilename: -> + if @pageNum is 1 + options.first + else + options.filename.replace '%d', @pageNum + + getView: -> (env, locals, contents, templates, callback) -> + # simple view to pass articles and pagenum to the paginator template + # note that this function returns a funciton + + # get the pagination template + template = templates[options.template] + if not template? + return callback new Error "unknown paginator template '#{ options.template }'" + + # setup the template context + ctx = {@articles, @pageNum, @prevPage, @nextPage} + + # extend the template context with the enviroment locals + env.utils.extend ctx, locals + + # finally render the template + template.render ctx, callback + + # register a generator, 'paginator' here is the content group generated content will belong to + # i.e. contents._.paginator + env.registerGenerator 'paginator', (contents, callback) -> + + # find all articles + articles = getArticles contents + + # populate pages + numPages = Math.ceil articles.length / options.perPage + pages = [] + for i in [0...numPages] + pageArticles = articles.slice i * options.perPage, (i + 1) * options.perPage + pages.push new PaginatorPage i + 1, pageArticles + + # add references to prev/next to each page + for page, i in pages + page.prevPage = pages[i - 1] + page.nextPage = pages[i + 1] + + # create the object that will be merged with the content tree (contents) + # do _not_ modify the tree directly inside a generator, consider it read-only + rv = {pages:{}} + for page in pages + rv.pages["#{ page.pageNum }.page"] = page # file extension is arbitrary + rv['index.page'] = pages[0] # alias for first page + rv['last.page'] = pages[(numPages-1)] # alias for last page + + # callback with the generated contents + callback null, rv + + # add the article helper to the environment so we can use it later + env.helpers.getArticles = getArticles + + # tell the plugin manager we are done + callback() diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..4d83917 --- /dev/null +++ b/readme.md @@ -0,0 +1,4 @@ + +# BLOG.NEGATIVE9.NET + +__A Wintersmith Blog__ diff --git a/templates/archive.jade b/templates/archive.jade new file mode 100644 index 0000000..a51e365 --- /dev/null +++ b/templates/archive.jade @@ -0,0 +1,32 @@ + +extends layout +//- this logic should be moved to a view at some point + +block content + - var lineHeight = 2.2; + - var archives = _.chain(env.helpers.getArticles(contents)).groupBy(function(item) { + - return item.date.getFullYear(); + - }).value(); + - for (var archive in archives) { + - archives[archive] = _.groupBy(archives[archive], function(item) { return item.date.getMonth(); }); + - } + - var month_names = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + section.archive + h2 Archive + ul + - var yearsK = _.chain(archives).keys().reverse().value(); + each yearK in yearsK + - var months = archives[yearK]; + - var yearHeight = lineHeight * _.reduce(months, function(memo,month) { return memo + month.length; }, 0); + li + span.year-label(style='line-height:' + yearHeight + 'em')= yearK + ul(style='margin-left:4em') + - var monthsK = _.chain(months).keys().reverse().value(); + each monthK in monthsK + - var monthHeight = lineHeight * months[monthK].length; + li + span.month-label(style='line-height:' + monthHeight + 'em')= month_names[monthK] + ul(style='margin-left:7em') + each item in months[monthK] + li(style='height:'+ lineHeight + 'em;line-height:' + lineHeight + 'em') + a(href=item.url)= item.title diff --git a/templates/article.jade b/templates/article.jade new file mode 100644 index 0000000..10b03d1 --- /dev/null +++ b/templates/article.jade @@ -0,0 +1,23 @@ + +extends layout + +block append vars + - bodyclass = 'article-detail' + +block prepend title + | #{ page.title + ' - '} + +block header + include author + h1= page.title + p.author + | #{ 'Written by ' } + mixin author(page.metadata.author) + +block content + article.article + section.content!= typogr(page.html).typogrify() + +block prepend footer + div.nav + a(href=contents.index.url) « Full blog diff --git a/templates/author.jade b/templates/author.jade new file mode 100644 index 0000000..a801ff7 --- /dev/null +++ b/templates/author.jade @@ -0,0 +1,8 @@ + +mixin author(authorName) + - var author = contents.authors[authorName + '.json']; + span.author + if author + a(href='mailto:' + author.metadata.email)= author.metadata.name + else + = authorName diff --git a/templates/feed.jade b/templates/feed.jade new file mode 100644 index 0000000..2fb8314 --- /dev/null +++ b/templates/feed.jade @@ -0,0 +1,25 @@ +doctype xml +rss(version='2.0', + xmlns:content='http://purl.org/rss/1.0/modules/content/', + xmlns:wfw='http://wellformedweb.org/CommentAPI/', + xmlns:dc='http://purl.org/dc/elements/1.1/' + xmlns:atom='http://www.w3.org/2005/Atom') + channel + - var articles = env.helpers.getArticles(contents); + title= locals.name + atom:link(href=locals.url + '/feed.xml', rel='self', type='application/rss+xml') + link= locals.url + description= locals.description + pubDate= articles[0].rfc822date + generator Wintersmith - https://github.com/jnordberg/wintersmith + language en + each article in articles + - var permalink = locals.url + article.url; + item + title= article.title + link= permalink + pubDate= article.rfc822date + guid(isPermaLink='true')= permalink + author= article.author + //- passing locals.url resolves all relative urls to absolute + description= article.getHtml(locals.url) diff --git a/templates/index.jade b/templates/index.jade new file mode 100644 index 0000000..aab0129 --- /dev/null +++ b/templates/index.jade @@ -0,0 +1,27 @@ + +extends layout + +block content + include author + each article in articles + article.article.intro + header + p.date + span= moment.utc(article.date).format('DD. MMMM YYYY') + h2 + a(href=article.url)= article.title + section.content + if article.intro.length > 0 + != typogr(article.intro).typogrify() + if article.hasMore + p.more + a(href=article.url) more + +block prepend footer + div.nav + if prevPage + a(href=prevPage.url) « Newer + else + a(href='/archive.html') « Archives + if nextPage + a(href=nextPage.url) Next page » diff --git a/templates/layout.jade b/templates/layout.jade new file mode 100644 index 0000000..0aa5258 --- /dev/null +++ b/templates/layout.jade @@ -0,0 +1,37 @@ +doctype html +block vars + - var bodyclass = null; +html(lang='en') + head + block head + meta(charset='utf-8') + meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1') + meta(name='viewport', content='width=device-width') + title + block title + = locals.name + link(rel='alternate', href=locals.url+'/feed.xml', type='application/rss+xml', title=locals.description) + link(rel='stylesheet', href='//fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic|Anonymous+Pro:400,700,400italic,700italic|Merriweather:400,700,300') + link(rel='stylesheet', href=contents.css['main.css'].url) + body(class=bodyclass) + header.header + div.content-wrap + block header + div.logo + h1 + a(href=locals.url)= locals.name + p.description= locals.description + p.blerb= locals.blerb + div#content + div.content-wrap + block content + h2 Welcome to zombocom! + footer + div.content-wrap + block footer + section.about + !=contents['about.md'].html + section.copy + p © #{ new Date().getFullYear() } #{ locals.owner } — powered by  + a(href='https://github.com/jnordberg/wintersmith') Wintersmith + //- please leave the "powered by" if you use the design