├── .gitignore ├── scripts ├── build └── deploy ├── images ├── icon-16.ico ├── icon-32.ico └── unknown_user.png ├── Gemfile ├── .gitmodules ├── _test.sh ├── _config.yml ├── _config_prod.yml ├── .travis.yml ├── _plugins ├── code_block.rb ├── sample_markdown.rb └── samples_generator.rb ├── LICENSE.txt ├── _sass ├── _reset.scss ├── _syntax.scss └── _layout.scss ├── README.md ├── _includes └── sample.html ├── index.html ├── css └── screen.scss └── _layouts └── default.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.lock 2 | /pkg 3 | 4 | _site 5 | .sass-cache 6 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | jekyll build --trace --config _config_prod.yml 2 | -------------------------------------------------------------------------------- /images/icon-16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sftrabbit/CppPatterns/HEAD/images/icon-16.ico -------------------------------------------------------------------------------- /images/icon-32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sftrabbit/CppPatterns/HEAD/images/icon-32.ico -------------------------------------------------------------------------------- /images/unknown_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sftrabbit/CppPatterns/HEAD/images/unknown_user.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'jekyll', '~> 3.6.2' 4 | gem 'pygments.rb', '~> 1.2.0' 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "samples"] 2 | path = samples 3 | url = https://github.com/sftrabbit/CppPatterns-Patterns.git 4 | branch = release 5 | -------------------------------------------------------------------------------- /_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | find _samples/ -iname '*.cpp' -print0 | xargs -0 -I "{}" clang++-3.5 -std=c++1y -stdlib=libc++ {} 4 | rm a.out 5 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | environment: development 2 | baseurl: 3 | highlighter: pygments 4 | exclude: ['README.md', 'LICENSE.txt', 'Gemfile', 'scripts', 'samples'] 5 | -------------------------------------------------------------------------------- /_config_prod.yml: -------------------------------------------------------------------------------- 1 | environment: production 2 | baseurl: https://cpppatterns.com 3 | highlighter: pygments 4 | exclude: ['README.md', 'LICENSE.txt', 'Gemfile', 'scripts', 'samples'] 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.1 4 | branches: 5 | only: 6 | - release 7 | env: 8 | - secure: "hzsoLvfsveyRFbyykjIfDyLZzMLM72Ar6AOEUxRwhpxMBoXeX9Y9T0Cl9j3jwTN3FAmA8zuqpSIe42geFJIRjnYzKDLcpik3sM8Avc5kqJbYhwkBKfYJYzoQ/aYYbe0yAZ3/USOyPU9TbKH8jvQqkvCu9c/EQPvTSb/aXaLE6MA=" 9 | script: ./scripts/build 10 | after_success: ./scripts/deploy 11 | -------------------------------------------------------------------------------- /scripts/deploy: -------------------------------------------------------------------------------- 1 | git clone https://github.com/sftrabbit/CppPatterns-BUILD.git build 2 | cd build 3 | 4 | git config user.name $GIT_NAME 5 | git config user.email $GIT_EMAIL 6 | 7 | rm -r * 8 | cp -r ../_site/* . 9 | echo "cpppatterns.com" > CNAME 10 | 11 | git add -A 12 | timestamp="$(date +"%Y%m%d%H%M%S")" 13 | git commit -m "Build $timestamp" 14 | 15 | git push -q "https://${GH_TOKEN}@github.com/sftrabbit/CppPatterns-BUILD.git" gh-pages 16 | -------------------------------------------------------------------------------- /_plugins/code_block.rb: -------------------------------------------------------------------------------- 1 | module CppSamples 2 | class CodeBlock < Jekyll::Tags::HighlightBlock 3 | def add_code_tag(code) 4 | code = code.sub(/
\n*/,'').sub(/\n*<\/pre><\/div>/,'')
 5 |       code.strip!
 6 | 
 7 |       line_num = 0
 8 |       line_nums = ''
 9 |       code_lines = code.split("\n")
10 |       code_lines.map! do |line|
11 |         line_num += 1
12 |         line_nums += "#{line_num}"
13 |         "#{line}"
14 |       end
15 |       code = code_lines.join("\n")
16 | 
17 |       output = ''
18 |       output += ""
19 |       output += ""
20 |       output += '
#{line_nums}#{code}
' 21 | end 22 | end 23 | end 24 | 25 | Liquid::Template.register_tag('codeblock', CppSamples::CodeBlock) 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Joseph Mansfield 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /_sass/_reset.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { 7 | margin: 0; 8 | padding: 0; 9 | border: 0; 10 | font-size: 100%; 11 | font: inherit; 12 | vertical-align: baseline; 13 | } 14 | 15 | /* HTML5 display-role reset for older browsers */ 16 | 17 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { 18 | display: block; 19 | } 20 | 21 | body { 22 | line-height: 1; 23 | } 24 | 25 | ol, ul { 26 | list-style: none; 27 | } 28 | 29 | blockquote, q { 30 | quotes: none; 31 | } 32 | 33 | blockquote { 34 | &:before, &:after { 35 | content: ''; 36 | content: none; 37 | } 38 | } 39 | 40 | q { 41 | &:before, &:after { 42 | content: ''; 43 | content: none; 44 | } 45 | } 46 | 47 | table { 48 | border-collapse: collapse; 49 | border-spacing: 0; 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Patterns 2 | 3 | [C++ Patterns](https://cpppatterns.com) is a repository of code patterns 4 | illustrating a modern 5 | and idiomatic approach to writing C++. The aim is to provide 6 | beginner to intermediate C++ developers a reference for solving common 7 | problems in C++. As the C++ language and library evolve, which they 8 | have been doing rapidly since the release of C++11, these patterns 9 | will be updated to match the current state-of-the-art in idiomatic C++ 10 | development. 11 | 12 | This repository contains the source for the web front-end, providing a 13 | simple web interface for browsing the patterns. It is a static website 14 | built with [Jekyll](http://jekyllrb.com/) and uses custom Jekyll 15 | plugins to generate the pattern pages from their source. 16 | 17 | ## Contributing 18 | 19 | If you wish to contribute to the front-end itself, please fork this 20 | repository on GitHub. If you'd like to provide some patterns or edit 21 | existing patterns, please take a look at the 22 | [patterns repository](https://github.com/sftrabbit/CppPatterns-Patterns). 23 | 24 | ## Patterns 25 | 26 | Pattern pages are generated from a collection of `.cpp` files. These 27 | files are kept in a separate repository, namely 28 | [sftrabbit/CppPatterns-Patterns](https://github.com/sftrabbit/CppPatterns-Patterns). 29 | To build this website, you will need to clone this repository into 30 | the `_samples` subdirectory: 31 | 32 | $ git clone https://github.com/sftrabbit/CppPatterns-Patterns.git _samples 33 | 34 | ## Build 35 | 36 | After cloning the pattern sources into `_samples`, the site can be 37 | built with: 38 | 39 | $ jekyll build 40 | 41 | The site will be built to the `_site` directory. 42 | -------------------------------------------------------------------------------- /_plugins/sample_markdown.rb: -------------------------------------------------------------------------------- 1 | require 'jekyll/filters' 2 | 3 | module CppSamples 4 | def self.generate_lineref_html(capital, line_start, line_end = nil) 5 | if line_end 6 | text = "lines #{line_start}–#{line_end}" 7 | end_attribute = "data-line-end=\"#{line_end}\"" 8 | else 9 | text = "line #{line_start}" 10 | end_attribute = "" 11 | end 12 | 13 | text.capitalize! if capital 14 | 15 | return "#{text}" 16 | end 17 | 18 | module SampleFilters 19 | include Jekyll::Filters 20 | 21 | def sample_markdown(text, code_offset) 22 | text.gsub!(/\[(\d+)\]/) do |match| 23 | line_num = $1.to_i - code_offset 24 | CppSamples::generate_lineref_html(false, line_num) 25 | end 26 | 27 | text.gsub!(/\[!(\d+)\]/) do |match| 28 | line_num = $1.to_i - code_offset 29 | CppSamples::generate_lineref_html(true, line_num) 30 | end 31 | 32 | text.gsub!(/\[(\d+)\-(\d+)\]/) do |match| 33 | line_num_start = $1.to_i - code_offset 34 | line_num_end = $2.to_i - code_offset 35 | CppSamples::generate_lineref_html(false, line_num_start, line_num_end) 36 | end 37 | 38 | text.gsub!(/\[!(\d+)\-(\d+)\]/) do |match| 39 | line_num_start = $1.to_i - code_offset 40 | line_num_end = $2.to_i - code_offset 41 | CppSamples::generate_lineref_html(true, line_num_start, line_num_end) 42 | end 43 | 44 | text.gsub!(/\[(.+?)\]\((c(pp)?\/.+?)\)/) do |match| 45 | "#{$1}" 46 | end 47 | 48 | markdownify(text) 49 | end 50 | 51 | def sample_intent(sample) 52 | sample_markdown(sample['intent'], sample['code_offset']) 53 | end 54 | 55 | def sample_description(sample) 56 | sample_markdown(sample['description'], sample['code_offset']) 57 | end 58 | end 59 | end 60 | 61 | Liquid::Template.register_filter(CppSamples::SampleFilters) 62 | -------------------------------------------------------------------------------- /_sass/_syntax.scss: -------------------------------------------------------------------------------- 1 | .highlight .c { color: #999988; font-style: italic } /* Comment */ 2 | .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 3 | .highlight .k { color: #C3B134; } /* Keyword */ 4 | .highlight .o { } /* Operator */ 5 | .highlight .cm { color: #999988; } /* Comment.Multiline */ 6 | .highlight .cp { color: #6b6b6b; } /* Comment.Preproc */ 7 | .highlight .c1 { color: #999988; } /* Comment.Single */ 8 | .highlight .cs { color: #6b6b6b; } /* Comment.Special */ 9 | .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 10 | .highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ 11 | .highlight .ge { font-style: italic } /* Generic.Emph */ 12 | .highlight .gr { color: #aa0000 } /* Generic.Error */ 13 | .highlight .gh { color: #6b6b6b } /* Generic.Heading */ 14 | .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 15 | .highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ 16 | .highlight .go { color: #888888 } /* Generic.Output */ 17 | .highlight .gp { color: #555555 } /* Generic.Prompt */ 18 | .highlight .gs { } /* Generic.Strong */ 19 | .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ 20 | .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 21 | .highlight .kc { } /* Keyword.Constant */ 22 | .highlight .kd { } /* Keyword.Declaration */ 23 | .highlight .kp { } /* Keyword.Pseudo */ 24 | .highlight .kr { } /* Keyword.Reserved */ 25 | .highlight .kt { color: #54B5F0; } /* Keyword.Type */ 26 | .highlight .m { color: #009999 } /* Literal.Number */ 27 | .highlight .s { color: #DF8329 } /* Literal.String */ 28 | .highlight .na { color: #008080 } /* Name.Attribute */ 29 | .highlight .nb { color: #DF8329 } /* Name.Builtin */ 30 | .highlight .nc { } /* Name.Class */ 31 | .highlight .no { color: #008080 } /* Name.Constant */ 32 | .highlight .ni { color: #800080 } /* Name.Entity */ 33 | .highlight .ne { color: #4288D8; } /* Name.Exception */ 34 | .highlight .nf { } /* Name.Function */ 35 | .highlight .nn { color: #555555 } /* Name.Namespace */ 36 | .highlight .nt { color: #000080 } /* Name.Tag */ 37 | .highlight .nv { color: #008080 } /* Name.Variable */ 38 | .highlight .ow { } /* Operator.Word */ 39 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 40 | .highlight .mf { color: #DF8329 } /* Literal.Number.Float */ 41 | .highlight .mh { color: #DF8329 } /* Literal.Number.Hex */ 42 | .highlight .mi { color: #DF8329 } /* Literal.Number.Integer */ 43 | .highlight .mo { color: #DF8329 } /* Literal.Number.Oct */ 44 | .highlight .sb { color: #DF8329 } /* Literal.String.Backtick */ 45 | .highlight .sc { color: #DF8329 } /* Literal.String.Char */ 46 | .highlight .sd { color: #DF8329 } /* Literal.String.Doc */ 47 | .highlight .s2 { color: #DF8329 } /* Literal.String.Double */ 48 | .highlight .se { color: #91602F } /* Literal.String.Escape */ 49 | .highlight .sh { color: #DF8329 } /* Literal.String.Heredoc */ 50 | .highlight .si { color: #DF8329 } /* Literal.String.Interpol */ 51 | .highlight .sx { color: #DF8329 } /* Literal.String.Other */ 52 | .highlight .sr { color: #009926 } /* Literal.String.Regex */ 53 | .highlight .s1 { color: #CB6B1A } /* Literal.String.Single */ 54 | .highlight .ss { color: #990073 } /* Literal.String.Symbol */ 55 | .highlight .bp { color: #6b6b6b } /* Name.Builtin.Pseudo */ 56 | .highlight .vc { color: #008080 } /* Name.Variable.Class */ 57 | .highlight .vg { color: #008080 } /* Name.Variable.Global */ 58 | .highlight .vi { color: #008080 } /* Name.Variable.Instance */ 59 | .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ 60 | -------------------------------------------------------------------------------- /_sass/_layout.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | $size-names: normal, thin, flow, tiny; 10 | $size-upper-limit: 10000px, 1140px, 880px, 680px; 11 | $size-page-width: 1135px, 865px, auto, auto; 12 | $size-column-width: 250px, 190px, 100%; 13 | $size-gutter-width: 45px, 35px, 0px; 14 | 15 | @function upper-limit($size) { 16 | @return nth($size-upper-limit, index($size-names, $size)); 17 | } 18 | 19 | @function page-width($size) { 20 | @return nth($size-page-width, index($size-names, $size)); 21 | } 22 | 23 | @function column-width($size) { 24 | @return nth($size-column-width, index($size-names, $size)); 25 | } 26 | 27 | @function gutter-width($size) { 28 | @return nth($size-gutter-width, index($size-names, $size)); 29 | } 30 | 31 | @function column-width-n($size, $columns: 1) { 32 | @if $size == flow { 33 | @return column_width($size); 34 | } @else { 35 | @return column_width($size) + (column-width($size) + gutter-width($size)) 36 | * ($columns - 1); 37 | } 38 | } 39 | 40 | @function column-offset($size, $column) { 41 | @return gutter-width($size) + (column-width($size) + gutter-width($size)) 42 | * $column; 43 | } 44 | 45 | .row { 46 | @include clearfix; 47 | margin-left: (-(gutter-width(normal))); 48 | padding-bottom: 2em; 49 | 50 | @media (max-width: upper-limit(thin)) { 51 | margin-left: -(gutter-width(thin)); 52 | } 53 | @media (max-width: upper-limit(flow)) { 54 | margin-left: -(gutter-width(flow)); 55 | } 56 | @media (max-width: upper-limit(tiny)) { 57 | padding-bottom: 2em; 58 | } 59 | 60 | &:first-child { 61 | padding-top: 2em; 62 | 63 | @media (max-width: upper-limit(tiny)) { 64 | padding-top: 2em; 65 | } 66 | } 67 | } 68 | 69 | .subrow { 70 | @include clearfix; 71 | margin-left: (-(gutter-width(normal))); 72 | 73 | @media (max-width: upper-limit(thin)) { 74 | margin-left: -(gutter-width(thin)); 75 | } 76 | @media (max-width: upper-limit(flow)) { 77 | margin-left: -(gutter-width(flow)); 78 | } 79 | } 80 | 81 | @mixin column { 82 | float: left; 83 | margin-left: gutter-width(normal); 84 | 85 | @media (max-width: upper-limit(thin)) { 86 | margin-left: gutter-width(thin); 87 | } 88 | @media (max-width: upper-limit(flow)) { 89 | margin-left: gutter-width(flow); 90 | } 91 | } 92 | 93 | @for $i from 1 through 4 { 94 | .column-#{$i} { 95 | @include column; 96 | width: column-width-n(normal, $i); 97 | 98 | @media (max-width: upper-limit(thin)) { 99 | width: column-width-n(thin, $i); 100 | } 101 | 102 | @media (max-width: upper-limit(flow)) { 103 | @include clearfix; 104 | margin-bottom: 1.5em; 105 | width: column-width-n(flow, $i); 106 | 107 | &:last-child { 108 | margin-bottom: 0; 109 | } 110 | } 111 | } 112 | } 113 | 114 | @for $i from 1 through 3 { 115 | .offset-#{$i} { 116 | margin-left: column-offset(normal, $i); 117 | 118 | @media (max-width: upper-limit(thin)) { 119 | margin-left: column-offset(thin, $i); 120 | } 121 | @media (max-width: upper-limit(flow)) { 122 | margin-left: 0; 123 | } 124 | } 125 | } 126 | 127 | @mixin center { 128 | margin-left: auto; 129 | margin-right: auto; 130 | } 131 | 132 | .container { 133 | @include center; 134 | width: page-width(normal); 135 | 136 | @media (max-width: upper-limit(thin)) { 137 | width: page-width(thin); 138 | } 139 | @media (max-width: upper-limit(flow)) { 140 | width: page-width(flow); 141 | margin: 0 30px; 142 | } 143 | } 144 | 145 | .flow-hide { 146 | @media (max-width: upper-limit(flow)) { 147 | display: none; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /_includes/sample.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 61 | {% assign variation = page.sample.variations[page.spec] %} 62 |
63 |
64 |
65 | ← Patterns 66 |

{{ variation.title }}

67 |
68 |
69 |
70 |
71 | {% codeblock cpp %}{{ variation.code }}{% endcodeblock %} 72 |

This pattern is licensed under the CC0 Public Domain Dedication.

73 |
74 |
75 |
76 | Requires 77 | {{ page.spec }} 78 | {% if page.spec != 'experimental' %}or newer{% endif %}. 79 | {% if page.sample.variations.size > 1 %} 80 | Other versions: 81 |
    82 | {% for variation_pairs in page.sample.variations %} 83 | {% assign spec = variation_pairs[0] %} 84 | {% if spec != page.spec %} 85 | {% if spec == page.sample.primary_spec %} 86 |
  • {{ spec }}
  • 87 | {% else %} 88 |
  • {{ spec }}
  • 89 | {% endif %} 90 | {% endif %} 91 | {% endfor %} 92 |
93 | {% endif %} 94 |
95 |

Intent

96 | {{ variation | sample_intent }} 97 |

Description

98 | {{ variation | sample_description }} 99 |
100 |
101 |

Contributors

102 |
    103 | {% for contributor in variation.contributors %} 104 |
  • {{ contributor.name }}
  • 105 | {% endfor %} 106 |
107 |

Last Updated

108 |

{{ variation.modified_date | date_to_long_string }}

109 |

Source

110 |

Fork this pattern on GitHub

111 |
112 |
113 |

Share

114 |

115 |

116 | 117 |
118 |
119 |
120 |
121 |
122 | {% if site.environment == 'production' %} 123 |
124 |
125 |
126 | 135 | 136 |
137 |
138 | {% endif %} 139 |
140 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | description: A repository of modern and idiomatic C++ code patterns curated by the community. 4 | --- 5 | 6 | 36 | {% if site.environment == 'production' %} 37 |
38 |
39 | 40 |
41 |
42 | {% endif %} 43 |
44 |
45 |

All patterns

46 |
47 | {% for category in page.sample_categories %} 48 | {% assign groupname = forloop.index %} 49 |
50 |
51 |

{{ category[0].title }}

52 |
53 |
54 | {% for sample in category[1] %} 55 |
56 |

{{ sample.primary.title }}

57 | {% assign min_spec = sample.min_spec %} 58 |
59 | Requires 60 | {{ min_spec }} 61 | {% if min_spec != 'experimental' %}or newer{% endif %} 62 |
63 | {{ sample.primary | sample_intent }} 64 |
65 | {% cycle groupname: '', '', '', '
' %} 66 | {% endfor %} 67 |
68 |
69 | {% endfor %} 70 |
71 |
72 |
73 |
74 | ← All patterns 75 |

Search Results

76 |
77 |
78 |
79 |
80 | 81 | 179 | -------------------------------------------------------------------------------- /css/screen.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import 'layout'; 5 | @import 'reset'; 6 | @import 'syntax'; 7 | 8 | body { 9 | font-family: 'Roboto'; font-size: 15px; 10 | line-height: 1.4em; 11 | } 12 | 13 | body > header { 14 | background-color: #2a90d8; 15 | color: #fff; 16 | 17 | a { 18 | color: #fff; 19 | 20 | &:visited { 21 | color: #fff; 22 | } 23 | } 24 | 25 | .row { 26 | padding-bottom: 10px; 27 | 28 | &:first-child { 29 | padding-top: 10px; 30 | } 31 | 32 | .column-1 { 33 | @media (max-width: upper-limit(flow)) { 34 | margin-bottom: 0; 35 | } 36 | } 37 | } 38 | 39 | svg { 40 | fill: #fff; 41 | pointer-events: none; 42 | } 43 | } 44 | 45 | body > footer { 46 | border-top: 1px solid #d8d8d8; 47 | } 48 | 49 | body > header h1 { 50 | margin: 4px 0; 51 | font-weight: normal; 52 | font-size: 32px; 53 | line-height: 1em; 54 | text-transform: lowercase; 55 | } 56 | body > header p { 57 | margin: 12px 0 0 0; 58 | } 59 | body > header h1 span { 60 | font-weight: 100; 61 | } 62 | 63 | .search { 64 | position: relative; 65 | 66 | @media (max-width: upper-limit(flow)) { 67 | margin-top: 8px; 68 | margin-bottom: 0; 69 | } 70 | 71 | svg { 72 | float: left; 73 | position: absolute; 74 | top: 9px; 75 | left: 10px; 76 | width: 24px; 77 | } 78 | 79 | input { 80 | box-sizing: border-box; 81 | width: 100%; 82 | height: 40px; 83 | padding: 0 12px 0 40px; 84 | background-color: #4fa6e4; 85 | border: 0; 86 | border-radius: 2px; 87 | outline: 0; 88 | color: #fff; 89 | font-size: 14px; 90 | } 91 | } 92 | 93 | .social { 94 | text-align: right; 95 | 96 | a { 97 | display: inline-block; 98 | margin-left: 8px; 99 | } 100 | 101 | svg { 102 | margin-top: 5px; 103 | width: 28px; 104 | height: 28px; 105 | } 106 | } 107 | 108 | section h1 { 109 | font-family: 'Roboto Slab'; 110 | font-size: 26px; 111 | font-weight: normal; 112 | line-height: 1.3em; 113 | } 114 | section section h1 { 115 | font-family: 'Roboto'; 116 | font-size: 20px; 117 | border-left: none; 118 | padding: 0; 119 | margin-bottom: 16px; 120 | } 121 | 122 | .samples-index, .search-results { 123 | .row { 124 | padding-bottom: 1em 0; 125 | } 126 | 127 | section .row { 128 | padding: 0; 129 | } 130 | 131 | .samples-section { 132 | margin-bottom: 1.5em; 133 | } 134 | 135 | .samples-section-header { 136 | margin-bottom: 0.2em; 137 | } 138 | 139 | .index-item { 140 | margin-bottom: 1.2em; 141 | 142 | h1 { 143 | font-family: 'Roboto'; 144 | font-size: 14px; 145 | border-bottom: none; 146 | margin-bottom: 5px; 147 | } 148 | 149 | p { 150 | margin: 4px 0; 151 | font-size: 14px; 152 | color: #777; 153 | } 154 | } 155 | 156 | .min-spec span { 157 | margin-bottom: 2px; 158 | } 159 | } 160 | 161 | .search-results { 162 | display: none; 163 | } 164 | 165 | .min-spec { 166 | color: #c3c3c3; 167 | } 168 | 169 | .tag { 170 | display: inline-block; 171 | margin: 0 2px 0 0; 172 | background-color: #c3c5c0; 173 | border-radius: 2px; 174 | 175 | &.experimental { 176 | background-color: #d24848; 177 | } 178 | 179 | &.c\+\+17 { 180 | background-color: #73b322; 181 | } 182 | 183 | &.c\+\+14 { 184 | background-color: #97c751; 185 | } 186 | 187 | &.c\+\+11 { 188 | background-color: #b5d488; 189 | } 190 | 191 | a { 192 | padding: 3px 4px; 193 | color: #fff; 194 | font-size: 13px; 195 | line-height: 1em; 196 | } 197 | } 198 | 199 | a { 200 | color: #2A90D8; 201 | text-decoration: none; 202 | } 203 | 204 | code { 205 | font-family: 'Ubuntu Mono'; 206 | } 207 | 208 | .codeblock { 209 | display: block; 210 | background-color: #2c2c2c; 211 | color: #fff; 212 | line-height: 20px; 213 | overflow-x: auto; 214 | 215 | .linenums { 216 | position: absolute; 217 | padding: 16px 8px 16px 12px; 218 | text-align: right; 219 | color: #6b6b6b; 220 | background-color: #2c2c2c; 221 | border-right: 1px solid #3f3f3f; 222 | 223 | span { 224 | display: block; 225 | } 226 | } 227 | .code { 228 | width: 100%; 229 | padding: 16px 16px 16px 48px; 230 | 231 | .codeline { 232 | display: block; 233 | width: 100%; 234 | white-space: pre; 235 | tab-size: 2; 236 | -moz-tab-size: 2; 237 | 238 | &:before { 239 | content: "\200B"; 240 | } 241 | } 242 | } 243 | } 244 | 245 | a.lineref { 246 | border-bottom: 1px dotted #000; 247 | color: inherit; 248 | } 249 | 250 | p { 251 | margin-bottom: 1em; 252 | } 253 | 254 | em, i { 255 | font-style: italic; 256 | } 257 | 258 | strong, b { 259 | font-weight: bold; 260 | } 261 | 262 | sup { 263 | font-size: 10px; 264 | vertical-align: baseline; 265 | position: relative; 266 | top: -0.6em; 267 | } 268 | 269 | ul, ol { 270 | padding-left: 1.5em; 271 | margin-bottom: 1em; 272 | } 273 | 274 | ul { 275 | list-style-type: disc; 276 | } 277 | 278 | ol { 279 | list-style-type: decimal; 280 | } 281 | 282 | .sample { 283 | h2 { 284 | margin-bottom: 6px; 285 | text-transform: uppercase; 286 | font-size: 12px; 287 | color: #666; 288 | } 289 | 290 | .min-spec { 291 | margin-bottom: 13px; 292 | } 293 | 294 | .specs { 295 | display: inline-block; 296 | list-style-type: none; 297 | padding: 0; 298 | margin: 0; 299 | 300 | li { 301 | display: inline-block; 302 | margin-left: 2px; 303 | } 304 | } 305 | 306 | .contributors { 307 | padding: 0; 308 | margin-bottom: 14px; 309 | 310 | li { 311 | display: inline-block; 312 | 313 | a img { 314 | border-radius: 50%; 315 | width: 36px; 316 | height: 36px; 317 | } 318 | } 319 | } 320 | 321 | .adsbygoogle { 322 | margin-top: 32px; 323 | } 324 | 325 | .license { 326 | margin: 1em 0; 327 | color: #666; 328 | } 329 | } 330 | 331 | .about { 332 | img { 333 | float: left; 334 | margin-right: 16px; 335 | width: 70px; 336 | } 337 | 338 | p { 339 | margin-top: 16px; 340 | text-transform: uppercase; 341 | font-size: 12px; 342 | color: #666; 343 | 344 | a { 345 | display: block; 346 | font-size: 17px; 347 | text-transform: none; 348 | } 349 | } 350 | } 351 | 352 | .featured { 353 | .codeblock { 354 | max-height: 230px; 355 | overflow: hidden; 356 | position: relative; 357 | text-align: center; 358 | 359 | .linenums { 360 | margin: 0; 361 | display: none; 362 | } 363 | 364 | .code { 365 | padding-left: 24px; 366 | text-align: left; 367 | } 368 | 369 | &:before { 370 | content: ' '; 371 | display: block; 372 | position: absolute; 373 | width: 100%; 374 | height: 230px; 375 | background-color: transparent; 376 | background-image: linear-gradient(rgba(44, 44, 44, 0) 60%, rgba(44, 44, 44, 1) 98%); 377 | } 378 | } 379 | 380 | .lineref { 381 | border: none; 382 | cursor: default; 383 | } 384 | 385 | .code-overlay { 386 | width: 100%; 387 | text-align: center; 388 | 389 | a { 390 | position: relative; 391 | top: -3em; 392 | padding: 6px 10px; 393 | background-color: #424242; 394 | border-radius: 2px; 395 | color: #fff; 396 | box-shadow: 0px 0px 12px 2px #222; 397 | } 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% if page.title %}{{ page.title }} - {% endif %}C++ Patterns 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |

C++Patterns

31 |
32 | {% if page.name == 'index.html' %} 33 | 37 | 49 |
50 |
51 |
52 | {{ content }} 53 |
54 |
55 |
56 |
57 |

Feel like contributing? This website is generated from a git repository. If you have something to add or have noticed a mistake, please fork the project on GitHub.

58 |

59 |

60 | 61 |
62 |
63 |
64 | 65 |

C++ Patterns created by Joseph Mansfield

66 |
67 |
68 |
69 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /_plugins/samples_generator.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'uri' 3 | require 'yaml' 4 | 5 | module CppSamples 6 | DEFAULT_SAMPLES_DIR = 'samples' 7 | COMMENT_REGEX = /^\/\/\s*(.+)?$/ 8 | SPECS = ['c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'experimental'] 9 | 10 | def self.sort_specs(specs) 11 | specs.sort {|spec1, spec2| SPECS.find_index(spec1) <=> SPECS.find_index(spec2)} 12 | end 13 | 14 | class SamplesGenerator < Jekyll::Generator 15 | def generate(site) 16 | index = site.pages.detect { |page| page.name == 'index.html' } 17 | 18 | samples_dir = site.config['samples_dir'] || DEFAULT_SAMPLES_DIR 19 | samples_tree = CppSamples::build_samples_tree(site, samples_dir) 20 | 21 | index.data['specs'] = SPECS 22 | index.data['sample_categories'] = samples_tree 23 | index.data['random_sample'] = CppSamples::get_random_sample(samples_tree) 24 | 25 | samples_tree.each do |category, samples| 26 | samples.each do |sample| 27 | sample.variations.keys.each do |spec| 28 | site.pages << SamplePage.new(site, sample, spec) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | 35 | GithubUser = Struct.new(:username, :avatar, :profile) 36 | 37 | class GithubUserCache 38 | def initialize 39 | @cache = {} 40 | @usernames_cache = {} 41 | 42 | @github_token = ENV['GH_TOKEN'] 43 | end 44 | 45 | def get_user_by_email(email) 46 | if @cache.has_key?(email) 47 | return @cache[email] 48 | end 49 | 50 | attempts = 0 51 | 52 | while attempts < 3 53 | search_uri = URI.parse("https://api.github.com/search/users?q=#{email}+in:email&per_page=1") 54 | 55 | if @github_token.nil? or @github_token.empty? 56 | search_response = Net::HTTP.get_response(search_uri) 57 | else 58 | search_request = Net::HTTP::Get.new(search_uri) 59 | search_request.basic_auth(ENV['GH_TOKEN'], 'x-oauth-basic') 60 | search_response = Net::HTTP.start(search_uri.hostname, 61 | search_uri.port, 62 | :use_ssl => true) do |http| 63 | http.request(search_request) 64 | end 65 | end 66 | 67 | search_result = JSON.parse(search_response.body) 68 | 69 | break if search_result.has_key?('items') 70 | 71 | attempts += 1 72 | 73 | rate_limit_reset_timestamp = search_response['X-RateLimit-Reset'].to_i 74 | while Time.now.to_i < rate_limit_reset_timestamp 75 | sleep(30) 76 | end 77 | end 78 | 79 | if search_result['items'].empty? 80 | user = GithubUser.new(nil, '/images/unknown_user.png', nil) 81 | else 82 | user_item = search_result['items'][0] 83 | user = GithubUser.new(user_item['login'], user_item['avatar_url'] + '&size=36', user_item['html_url']) 84 | end 85 | 86 | @cache[email] = user 87 | if !user.username.nil? 88 | @usernames_cache[user.username] = user 89 | end 90 | 91 | user 92 | end 93 | 94 | def get_user_by_username(username) 95 | if @usernames_cache.has_key?(username) 96 | return @usernames_cache[username] 97 | end 98 | 99 | attempts = 0 100 | 101 | while attempts < 3 102 | user_uri = URI.parse("https://api.github.com/users/#{username}") 103 | 104 | if @github_token.nil? or @github_token.empty? 105 | user_response = Net::HTTP.get_response(user_uri) 106 | else 107 | user_request = Net::HTTP::Get.new(user_uri) 108 | user_request.basic_auth(ENV['GH_TOKEN'], 'x-oauth-basic') 109 | user_response = Net::HTTP.start(user_uri.hostname, 110 | user_uri.port, 111 | :use_ssl => true) do |http| 112 | http.request(user_request) 113 | end 114 | end 115 | 116 | user_result = JSON.parse(user_response.body) 117 | 118 | break if user_result.has_key?('login') or 119 | user_result['message'] == "Not Found" 120 | 121 | attempts += 1 122 | 123 | rate_limit_reset_timestamp = user_response['X-RateLimit-Reset'].to_i 124 | while Time.now.to_i < rate_limit_reset_timestamp 125 | sleep(30) 126 | end 127 | end 128 | 129 | if user_result.has_key?('login') 130 | user = GithubUser.new(username, user_result['avatar_url'] + '&size=36', user_result['html_url']) 131 | else 132 | user = GithubUser.new(username, '/images/unknown_user.png', nil) 133 | end 134 | 135 | @usernames_cache[username] = user 136 | if user_result.has_key?('email') 137 | @cache[user_result['email']] = user 138 | end 139 | end 140 | end 141 | 142 | class DummyUserCache 143 | def get_user_by_email(email) 144 | GithubUser.new(nil, '/images/unknown_user.png', nil) 145 | end 146 | 147 | def get_user_by_username(username) 148 | GithubUser.new(nil, '/images/unknown_user.png', nil) 149 | end 150 | end 151 | 152 | class SamplePage < Jekyll::Page 153 | def initialize(site, sample, spec) 154 | @site = site 155 | @base = site.source 156 | @dir = "patterns" 157 | 158 | is_primary = spec == sample.primary_spec 159 | 160 | if is_primary 161 | @name = "#{sample.name}.html" 162 | else 163 | @name = "#{sample.name}.#{spec}.html" 164 | end 165 | 166 | process(@name) 167 | read_yaml(File.join(@base, '_includes', ''), 'sample.html') 168 | 169 | variation = sample.variations[spec] 170 | 171 | self.data['sample'] = sample 172 | self.data['spec'] = spec 173 | self.data['title'] = variation.title 174 | self.data['description'] = variation.intent 175 | end 176 | end 177 | 178 | class Section 179 | attr_accessor :title 180 | 181 | def initialize(title) 182 | @title = title 183 | end 184 | 185 | def to_liquid 186 | return {'title' => @title} 187 | end 188 | end 189 | 190 | class Sample 191 | attr_accessor :name, :variations 192 | 193 | def initialize(samples_dir, sample_name, user_cache, contributors_list) 194 | @name = File.basename(sample_name) 195 | @user_cache = user_cache 196 | 197 | @variations = {} 198 | 199 | sample_paths = Dir.glob("#{samples_dir}/#{sample_name}*.cpp") 200 | sample_paths.each do |sample_path| 201 | spec_match = /^.+\.(?.+)\.cpp$/.match(sample_path) 202 | has_spec = !spec_match.nil? 203 | 204 | variation = Variation.new(sample_path, user_cache, contributors_list) 205 | 206 | spec = if has_spec 207 | spec_match['spec'] 208 | else 209 | variation.tags[0] || 'c++98' 210 | end 211 | 212 | @variations[spec] = variation 213 | end 214 | end 215 | 216 | def primary 217 | @variations[primary_spec] 218 | end 219 | 220 | def min_spec 221 | CppSamples::sort_specs(@variations.keys).first 222 | end 223 | 224 | def primary_spec 225 | specs = CppSamples::sort_specs(@variations.keys) 226 | i = specs.rindex { |spec| spec.start_with?('c++') } 227 | specs[i] if i else specs.last 228 | end 229 | 230 | def to_liquid 231 | { 232 | 'name' => @name, 233 | 'primary' => primary.to_liquid, 234 | 'min_spec' => min_spec, 235 | 'primary_spec' => primary_spec, 236 | 'variations' => @variations.each_with_object({}) do |(spec, variation), variations| 237 | variations[spec] = variation.to_liquid 238 | variations 239 | end 240 | } 241 | end 242 | end 243 | 244 | class Variation 245 | attr_accessor :file_name, :path, :code, :code_offset, :title, :intent, :tags, :description 246 | 247 | def initialize(sample_path, user_cache, contributors_list) 248 | @user_cache = user_cache 249 | 250 | sample_file = File.new(sample_path, 'r') 251 | 252 | @file_name = get_file_name(sample_path) 253 | @path = file_name_to_path(sample_path) 254 | 255 | sample_contents = strip_blank_lines(sample_file.readlines) 256 | 257 | @title = extract_title(sample_contents) 258 | @tags = extract_tags(sample_contents) 259 | 260 | code_start = 1 261 | code_start = 2 unless @tags.empty? 262 | 263 | body_lines, body_start = extract_body(sample_contents) 264 | @intent, @description = extract_body_parts(body_lines) 265 | 266 | code_lines = strip_blank_lines(sample_contents[code_start..body_start-1]) 267 | @code = code_lines.join 268 | @code_offset = sample_contents.index(code_lines[0]) 269 | 270 | @contributors = get_contributors(sample_path, contributors_list) 271 | @modified_date = get_modified_date(sample_path) 272 | end 273 | 274 | def to_liquid 275 | { 276 | 'title' => @title, 277 | 'tags' => @tags, 278 | 'code' => @code, 279 | 'code_offset' => @code_offset, 280 | 'description' => @description, 281 | 'intent' => @intent, 282 | 'contributors' => @contributors, 283 | 'modified_date' => @modified_date, 284 | 'path' => @path, 285 | 'file_name' => @file_name 286 | } 287 | end 288 | 289 | private def get_file_name(full_file_name) 290 | file_name_parts = full_file_name.split('/')[-3..-1] 291 | file_name_parts.join('/') 292 | end 293 | 294 | private def file_name_to_path(file_name) 295 | file_name_parts = file_name.split('/')[-3..-1] 296 | file_name_parts[2] = File.basename(file_name_parts[2], '.*') 297 | file_name_parts[0].slice!(/^\d+\-/) 298 | file_name_parts.delete_at(1) 299 | file_name_parts.join('/') 300 | end 301 | 302 | private def extract_title(lines) 303 | header = lines[0] 304 | header_match = COMMENT_REGEX.match(header) 305 | 306 | unless header_match 307 | raise "invalid header line in sample" 308 | end 309 | 310 | header_match[1] 311 | end 312 | 313 | private def extract_tags(lines) 314 | tags_line = lines[1] 315 | tags_line_match = COMMENT_REGEX.match(tags_line) 316 | 317 | unless tags_line_match 318 | return [] 319 | end 320 | 321 | tags_text = tags_line_match[1] 322 | tags = tags_text.split(/\s*,\s*/) 323 | tags.collect! {|tag| tag.strip.downcase} 324 | 325 | return tags 326 | end 327 | 328 | private def extract_body(lines) 329 | body = [] 330 | line_index = lines.length - 1 331 | 332 | while not COMMENT_REGEX.match(lines[line_index]) 333 | line_index -= 1 334 | end 335 | 336 | while match = COMMENT_REGEX.match(lines[line_index]) 337 | body.unshift("#{match[1]}\n") 338 | line_index -= 1 339 | end 340 | 341 | return body, line_index 342 | end 343 | 344 | private def extract_body_parts(body_lines) 345 | blank_line_index = body_lines.index {|line| /^[\t ]*\n$/ =~ line} 346 | intent = body_lines[0..blank_line_index].join() 347 | description = body_lines[blank_line_index+1..-1].join() 348 | return intent, description 349 | end 350 | 351 | private def strip_blank_lines(lines) 352 | lines.join("").strip.split("\n").map {|line| "#{line}\n" } 353 | end 354 | 355 | private def get_contributors(file_name, contributors_list) 356 | real_path = file_name.split('/')[1..-1].join('/') 357 | 358 | committers = nil 359 | Dir.chdir('samples') do 360 | gitlog_output = `git log --follow --simplify-merges --format="format:%ae %an" -- #{real_path}` 361 | committer_strings = gitlog_output.split("\n") 362 | committers = committer_strings.inject([]) do |committers, committer_string| 363 | split_committer_string = committer_string.split(/\s/,2) 364 | committers << {'email' => split_committer_string[0], 'name' => split_committer_string[1]} 365 | end 366 | end 367 | 368 | committers.uniq! {|committer| committer['email'] } 369 | 370 | contributors = [] 371 | 372 | committers.each do |committer| 373 | contributors_item = contributors_list.detect do |contributors_item| 374 | contributors_item['name'] == committer['name'] or 375 | contributors_item['email'] == committer['email'] 376 | end 377 | 378 | unless contributors_item.nil? 379 | user = @user_cache.get_user_by_username(contributors_item['github_username']) 380 | end 381 | 382 | if user.nil? or user.username.nil? 383 | user = @user_cache.get_user_by_email(committer['email']) 384 | end 385 | 386 | contributors << { 387 | 'name' => committer['name'], 388 | 'image' => user.avatar, 389 | 'url' => user.profile 390 | } 391 | end 392 | 393 | contributors 394 | end 395 | 396 | private def get_modified_date(file_name) 397 | real_path = file_name.split('/')[-3..-1].join('/') 398 | Dir.chdir('samples') do 399 | `git log -1 --format="format:%ad" -- #{real_path}`.strip 400 | end 401 | end 402 | end 403 | 404 | def self.build_samples_tree(site, samples_dir) 405 | if site.config['environment'] == "production" 406 | user_cache = GithubUserCache.new 407 | else 408 | user_cache = DummyUserCache.new 409 | end 410 | 411 | contributors_list = [] 412 | File.open("#{samples_dir}/CONTRIBUTORS.txt", 'r').each_line do |line| 413 | match = /^\- ([^<>]*) (<(.*)> )?\((.*)\)\s*$/.match(line) 414 | contributors_list << { 415 | 'name' => match[1], 416 | 'email' => match[3], 417 | 'github_username' => match[4] 418 | } 419 | end 420 | 421 | contents = YAML.load_file("#{samples_dir}/contents.yml") 422 | 423 | contents['categories'].each_with_object({}) do |category, tree| 424 | tree[Section.new(category['title'])] = category['samples'].map do |sample_path| 425 | Sample.new(samples_dir, sample_path, user_cache, contributors_list) 426 | end 427 | 428 | tree 429 | end 430 | end 431 | 432 | def self.get_random_sample(samples_tree) 433 | all_samples = [] 434 | 435 | samples_tree.each do |_, samples| 436 | all_samples.concat(samples) 437 | end 438 | 439 | seed = Time.now.strftime("%U%Y").to_i 440 | all_samples.sample(random: Random.new(seed)) 441 | end 442 | end 443 | --------------------------------------------------------------------------------