26 |
27 |
28 |
29 | ## Table of Contents
30 |
31 | * [About the Project](#about-the-project)
32 | * [Built With](#built-with)
33 | * [Rules](#rules)
34 | * [Usage](#usage)
35 | * [Automated Test](#autoamted-test)
36 | * [Video Presentation](#video-presentation)
37 | * [Contributors](#contributors)
38 | * [Contributing](#contributing)
39 | * [Acknowledgements](#acknowledgements)
40 | * [License](#license)
41 |
42 |
43 | ## About The Project
44 |
45 | This is the **Ruby Capstone Project** required at the end of the **Ruby** module.
46 | Specifically it is a **CSS Linter** with the purpose to check for spacing and format errors.
47 | The goal of this project was to apply all the ruby knowledge learned in one small and simple project, the requirements were provided
48 | by a fictional client, I received what the client wanted and translated that to code. In this case I was required to create a linter,
49 | I proceeded to create one specifically for the CSS language.
50 |
51 | ## Rules
52 |
53 | **Line Format**
54 | * One line checking
55 |
56 | bad code:
57 | ```css
58 | h1{font-size: 35px;font-weight:normal;margin-top:5px;}
59 | ```
60 |
61 | good code:
62 | ```css
63 | h1 {
64 | font-size: 35px;
65 | font-weight: normal;
66 | margin-top: 5px;
67 | }
68 | ```
69 |
70 | **Spacing**
71 | * Checking for missing spacing after **:** or **,**
72 | * Checking for missing spacing before **{**
73 | * Checking for line break after **{** or **}** and after each property declaration
74 |
75 | bad code:
76 |
77 | ```css
78 | body{margin:25px;
79 | background-color:rgb ( 240,240,240 ) ;
80 | font-family:arial,sans-serif;
81 | font-size:14px;}
82 | ```
83 |
84 | good code:
85 | ```css
86 | body {
87 | margin: 25px;
88 | background-color: rgb(240, 240, 240);
89 | font-family: arial, sans-serif;
90 | font-size: 14px;
91 | }
92 | ```
93 |
94 | **EOF newline**
95 | * Checks whether a newline at the end of the file exists.
96 |
97 | bad code:
98 |
99 | ```css
100 | #someid {
101 | color: green;
102 | } /* Last line */
103 | ```
104 |
105 | good code:
106 | ```css
107 | #someid {
108 | color: green;
109 | }
110 | /* Last line */
111 | ```
112 |
113 | ## Usage
114 |
115 | The **_CSS Linter_** checks for basic syntax errors, if it finds any it will return an error with a
116 | text to the user indicating in which line and what error it detected as well as a count of
117 | the errors found.
118 |
119 | **Examples**
120 |
121 | - [0] "On line 1 missing space before '{' opening curly bracket",
122 | - [1] "On line 1 missing newline after '{' opening curly bracket",
123 | - [2] "On line 2 a(n) '(' opening parentheses should not have space(s) surrounding it",
124 | - [3] "On line 2 a(n) ')' closing parentheses should not have space(s) surrounding it",
125 | - [4] "On line 5 missing newline after '}' closing curly bracket",
126 | - [5] "On line 7 missing space after ':' colon",
127 | - [6] "On line 8 missing newline after ';' semi-colon",
128 | - [7] "On line 17 missing newline after '}' closing curly bracket"
129 |
130 | To test out **CSS Linter** you need to:
131 | * have **Ruby** installed on your computer
132 | * Clone this repo:
133 | - Clone with SSH:
134 | ```
135 | git@github.com:e71az/Ruby-capstone.git
136 | ```
137 | - Clone with HTTPS:
138 | ```
139 | https://github.com/e71az/Ruby-capstone.git
140 | ```
141 | * Navigate to root directory of the repo and run:
142 | ```
143 | $ bin/main
144 |
145 | Provide a file to check, otherwise the sample.css will be used: path_to_file.css
146 |
147 | ```
148 | 
149 |
150 | **Automated Test**
151 | * Run the command and see the output
152 | ```
153 | $ rspec
154 | ```
155 | 
156 |
157 | ### Built With
158 | This project was built using these technologies.
159 | * Ruby
160 | * Rspec
161 | * Rubocop
162 | * Strscan
163 | * Awesome print
164 |
165 | ## Video Presentation
166 |
167 | Check out full presentation [here](https://www.loom.com/share/a1e55d78be5e403dbcaa80cee442394d)
168 |
169 | ## Potential future features
170 | - Add more advanced linting rules
171 |
172 | ## Contributors
173 |
174 | 👤 **Elias Castañeda**
175 |
176 | - Github: [@e71az](https://github.com/e71az)
177 | - Linkedin: [@e71az](https://www.linkedin.com/in/e71az/)
178 | - Twitter: [@e71az](https://twitter.com/e71az)
179 |
180 | ## :handshake: Contributing
181 | Contributions, issues and feature requests are welcome!
182 | Feel free to check the [issues page](https://github.com/e71az/Ruby-capstone/issues)
183 |
184 | ## Show your support
185 | Give a :star: if you like this project!
186 |
187 |
188 |
189 | [contributors-shield]: https://img.shields.io/github/contributors/e71az/Ruby-capstone.svg?style=flat-square
190 | [contributors-url]: https://github.com/e71az/Ruby-capstone/graphs/contributors
191 | [forks-shield]: https://img.shields.io/github/forks/e71az/Ruby-capstone
192 | [forks-url]: https://github.com/e71az/Ruby-capstone/network/members
193 | [stars-shield]: https://img.shields.io/github/stars/e71az/Ruby-capstone
194 | [stars-url]: https://github.com/e71az/Ruby-capstone/stargazers
195 | [issues-shield]: https://img.shields.io/github/issues/e71az/Ruby-capstone.svg?style=flat-square
196 | [issues-url]: https://github.com/e71az/Ruby-capstone/issues
197 |
198 | ## 📝 License
199 |
200 | This project is [MIT](https://opensource.org/licenses/MIT) licensed.
201 |
--------------------------------------------------------------------------------
/bin/main:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # rubocop:disable Style/MixinUsage
3 | require_relative '../lib/file_loader.rb'
4 | require_relative '../lib/linter_rules.rb'
5 |
6 | include LinterRules
7 |
8 | print 'Provide a file to check, otherwise the sample.css will be used: '
9 |
10 | file_to_open = gets.chomp
11 |
12 | file_to_open = './sample.css' if file_to_open.empty?
13 | file = FileLoader.new(file_to_open)
14 |
15 | linter(file.content)
16 |
17 | # rubocop:enable Style/MixinUsage
18 |
--------------------------------------------------------------------------------
/images/linter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/rspec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eliasecasta/CSS-Linter/16c12ae2d2a5e38003c3d34eae8879b92a4748d9/images/rspec.png
--------------------------------------------------------------------------------
/images/test_run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eliasecasta/CSS-Linter/16c12ae2d2a5e38003c3d34eae8879b92a4748d9/images/test_run.png
--------------------------------------------------------------------------------
/lib/file_loader.rb:
--------------------------------------------------------------------------------
1 | # rubocop:disable Lint/UselessAssignment
2 | class FileLoader
3 | attr_reader :content, :file_path
4 |
5 | def initialize(file_path)
6 | @file_path = file_path
7 | @content = reader(@file_path)
8 | end
9 |
10 | def reader(file_path)
11 | read_file = File.read(file_path)
12 | content = StringScanner.new(read_file)
13 | end
14 | end
15 | # rubocop:enable Lint/UselessAssignment
16 |
--------------------------------------------------------------------------------
/lib/linter_rules.rb:
--------------------------------------------------------------------------------
1 | # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
2 | require 'strscan'
3 | require 'awesome_print'
4 |
5 | module LinterRules
6 | def linter(content)
7 | arr = content.string.split('')
8 | err_arr = []
9 | line_counter = 1
10 | arr.each_with_index do |v, i|
11 | line_counter += 1 if v == "\n"
12 | if v.include?('{') || v.include?('}')
13 | err_arr << err_msg(0, line_counter, v, 'bf', 'spc') if arr[i - 1] != ' ' && v == '{'
14 | err_arr << err_msg(0, line_counter, v, 'af', 'nl') if arr[i + 1] != "\n" && v == '{'
15 | err_arr << err_msg(0, line_counter, v, 'bf', 'nl') if arr[i - 1] != "\n" && v == '}'
16 | err_arr << err_msg(0, line_counter, v, 'af', 'nl') if arr[i + 1] != "\n" and v == '}'
17 | elsif v.include?('(') || v.include?(')')
18 | if (arr[i - 1] == ' ' && v == '(') || (arr[i + 1] == ' ' && v == '(')
19 | err_arr << err_msg(1, line_counter, v, 'surr')
20 | end
21 | if (arr[i - 1] == ' ' && v == ')') || (arr[i + 1] == ' ' && v == ')')
22 | err_arr << err_msg(1, line_counter, v, 'surr')
23 | end
24 | elsif v == ':'
25 | err_arr << err_msg(1, line_counter, v, 'bf') if arr[i - 1] == ' ' && v == ':'
26 | err_arr << err_msg(0, line_counter, v, 'af', 'spc') if arr[i + 1] != ' ' && v == ':'
27 | elsif v == ';'
28 | err_arr << err_msg(1, line_counter, v, 'bf') if arr[i - 1] == ' ' && v == ';'
29 | err_arr << err_msg(0, line_counter, v, 'af', 'nl') if arr[i + 1] != "\n" && v == ';'
30 | elsif v == ','
31 | err_arr << err_msg(0, line_counter, v, 'af', 'spc') if arr[i + 1] != ' ' && v == ','
32 | end
33 | end
34 | err_arr << "EOF Error: 'The file is missing a newline at the end of the file.'" if arr[-1] != "\n"
35 | error_printer(err_arr)
36 | end
37 |
38 | def error_printer(err_arr)
39 | if err_arr.empty? == false
40 | ap err_arr
41 | elsif err_arr == []
42 | ap 'The file does not present any linting errors'
43 | end
44 | end
45 |
46 | def err_msg(option, line_numb, value, bf_af_surr, spc_nl = nil)
47 | sym_name = { '{' => 'opening curly bracket', '}' => 'closing curly bracket',
48 | '(' => 'opening parentheses', ')' => 'closing parentheses',
49 | ':' => 'colon', ';' => 'semi-colon',
50 | 'spc' => 'space', 'nl' => 'newline',
51 | 'bf' => 'before', 'af' => 'after',
52 | 'surr' => 'surrounding' }
53 | if option.zero?
54 | "On line #{line_numb} missing #{sym_name[spc_nl]} #{sym_name[bf_af_surr]} '#{value}' #{sym_name[value]}"
55 | else "On line #{line_numb} a(n) '#{value}' #{sym_name[value]} should not have space(s) #{sym_name[bf_af_surr]} it"
56 | end
57 | end
58 | end
59 | # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
60 |
--------------------------------------------------------------------------------
/sample.css:
--------------------------------------------------------------------------------
1 | body{ margin:25px ;background-color: rgb( 240, 240, 240 );
2 | font-family: arial, sans-serif;
3 | font-size: 14px;
4 | }
5 | h1 {
6 | font-size: 35px;
7 | font-weight: normal;
8 | margin-top: 5px;
9 | }
10 | .someclass {
11 | color: red;
12 | }
13 | #someid {
14 | color: green;
15 | }
--------------------------------------------------------------------------------
/spec/main_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/linter_rules.rb'
2 | require_relative '../lib/file_loader.rb'
3 |
4 | describe LinterRules do
5 | include LinterRules
6 | let(:spacing_arr) do
7 | ["On line 1 missing space before '{' opening curly bracket",
8 | "On line 2 missing space after ':' colon",
9 | "On line 2 a(n) ';' semi-colon should not have space(s) before it",
10 | "On line 3 a(n) '(' opening parentheses should not have space(s) surrounding it",
11 | "On line 3 missing space after ',' ",
12 | "On line 3 a(n) ')' closing parentheses should not have space(s) surrounding it"]
13 | end
14 | let(:eof_arr) do
15 | ["On line 17 missing newline after '}' closing curly bracket",
16 | "EOF Error: 'The file is missing a newline at the end of the file.'"]
17 | end
18 | let(:err_string) { "On line 5 a(n) '}' closing curly bracket should not have space(s) before it" }
19 | let(:nl_arr) do
20 | [
21 | "On line 1 missing newline after '{' opening curly bracket",
22 | "On line 1 missing newline after ';' semi-colon",
23 | "On line 1 missing newline after ';' semi-colon",
24 | "On line 1 missing newline after ';' semi-colon",
25 | "On line 1 missing newline after ';' semi-colon",
26 | "On line 1 missing newline before '}' closing curly bracket",
27 | "On line 1 missing newline after '}' closing curly bracket",
28 | "On line 1 missing newline after '{' opening curly bracket",
29 | "On line 1 missing newline after ';' semi-colon",
30 | "On line 1 missing newline after ';' semi-colon",
31 | "On line 1 missing newline after ';' semi-colon",
32 | "On line 1 missing newline before '}' closing curly bracket",
33 | "On line 1 missing newline after '}' closing curly bracket",
34 | "On line 1 missing newline after '{' opening curly bracket",
35 | "On line 1 missing newline after ';' semi-colon",
36 | "On line 1 missing newline before '}' closing curly bracket",
37 | "On line 1 missing newline after '}' closing curly bracket",
38 | "On line 1 missing newline after '{' opening curly bracket",
39 | "On line 1 missing newline after ';' semi-colon",
40 | "On line 1 missing newline before '}' closing curly bracket"
41 | ]
42 | end
43 | describe '#EOF check' do
44 | it 'Returns an error text which specifies that the file is missing an empty newline at the end of the file' do
45 | file_to_open = 'spec/test_files/eof.css'
46 | file = FileLoader.new(file_to_open)
47 | expect(linter(file.content)).to eql(eof_arr)
48 | end
49 | end
50 |
51 | describe '#err_msg' do
52 | it 'Returns an error message created with the given values' do
53 | expect(err_msg(1, 5, '}', 'bf')).to eql(err_string)
54 | end
55 | end
56 |
57 | describe '#Spacing check' do
58 | it 'Returns an array with all the spacing errors found' do
59 | file_to_open = 'spec/test_files/spacing.css'
60 | file = FileLoader.new(file_to_open)
61 | expect(linter(file.content)).to eql(spacing_arr)
62 | end
63 | end
64 |
65 | describe '#Spacing check' do
66 | it 'Returns an array with all the newline errors found' do
67 | file_to_open = 'spec/test_files/newline.css'
68 | file = FileLoader.new(file_to_open)
69 | expect(linter(file.content)).to eql(nl_arr)
70 | end
71 | end
72 |
73 | describe '#Error Printer' do
74 | it 'Return no linting errors found' do
75 | expect(error_printer([])).to eql('The file does not present any linting errors')
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # rubocop:disable Style/BlockComments
2 | # This file was generated by the `rspec --init` command. Conventionally, all
3 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
4 | # The generated `.rspec` file contains `--require spec_helper` which will cause
5 | # this file to always be loaded, without a need to explicitly require it in any
6 | # files.
7 | #
8 | # Given that it is always loaded, you are encouraged to keep this file as
9 | # light-weight as possible. Requiring heavyweight dependencies from this file
10 | # will add to the boot time of your test suite on EVERY test run, even for an
11 | # individual file that may not need all of that loaded. Instead, consider making
12 | # a separate helper file that requires the additional dependencies and performs
13 | # the additional setup, and require it from the spec files that actually need
14 | # it.
15 | #
16 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17 | RSpec.configure do |config|
18 | # rspec-expectations config goes here. You can use an alternate
19 | # assertion/expectation library such as wrong or the stdlib/minitest
20 | # assertions if you prefer.
21 | config.expect_with :rspec do |expectations|
22 | # This option will default to `true` in RSpec 4. It makes the `description`
23 | # and `failure_message` of custom matchers include text for helper methods
24 | # defined using `chain`, e.g.:
25 | # be_bigger_than(2).and_smaller_than(4).description
26 | # # => "be bigger than 2 and smaller than 4"
27 | # ...rather than:
28 | # # => "be bigger than 2"
29 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30 | end
31 |
32 | # rspec-mocks config goes here. You can use an alternate test double
33 | # library (such as bogus or mocha) by changing the `mock_with` option here.
34 | config.mock_with :rspec do |mocks|
35 | # Prevents you from mocking or stubbing a method that does not exist on
36 | # a real object. This is generally recommended, and will default to
37 | # `true` in RSpec 4.
38 | mocks.verify_partial_doubles = true
39 | end
40 |
41 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
42 | # have no way to turn it off -- the option exists only for backwards
43 | # compatibility in RSpec 3). It causes shared context metadata to be
44 | # inherited by the metadata hash of host groups and examples, rather than
45 | # triggering implicit auto-inclusion in groups with matching metadata.
46 | config.shared_context_metadata_behavior = :apply_to_host_groups
47 |
48 | # The settings below are suggested to provide a good initial experience
49 | # with RSpec, but feel free to customize to your heart's content.
50 | =begin
51 | # This allows you to limit a spec run to individual examples or groups
52 | # you care about by tagging them with `:focus` metadata. When nothing
53 | # is tagged with `:focus`, all examples get run. RSpec also provides
54 | # aliases for `it`, `describe`, and `context` that include `:focus`
55 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
56 | config.filter_run_when_matching :focus
57 |
58 | # Allows RSpec to persist some state between runs in order to support
59 | # the `--only-failures` and `--next-failure` CLI options. We recommend
60 | # you configure your source control system to ignore this file.
61 | config.example_status_persistence_file_path = "spec/examples.txt"
62 |
63 | # Limits the available syntax to the non-monkey patched syntax that is
64 | # recommended. For more details, see:
65 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
66 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
67 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
68 | config.disable_monkey_patching!
69 |
70 | # This setting enables warnings. It's recommended, but in some cases may
71 | # be too noisy due to issues in dependencies.
72 | config.warnings = true
73 |
74 | # Many RSpec users commonly either run the entire suite or an individual
75 | # file, and it's useful to allow more verbose output when running an
76 | # individual spec file.
77 | if config.files_to_run.one?
78 | # Use the documentation formatter for detailed output,
79 | # unless a formatter has already been configured
80 | # (e.g. via a command-line flag).
81 | config.default_formatter = "doc"
82 | end
83 |
84 | # Print the 10 slowest examples and example groups at the
85 | # end of the spec run, to help surface which specs are running
86 | # particularly slow.
87 | config.profile_examples = 10
88 |
89 | # Run specs in random order to surface order dependencies. If you find an
90 | # order dependency and want to debug it, you can fix the order by providing
91 | # the seed, which is printed after each run.
92 | # --seed 1234
93 | config.order = :random
94 |
95 | # Seed global randomization in this process using the `--seed` CLI option.
96 | # Setting this allows you to use `--seed` to deterministically reproduce
97 | # test failures related to randomization by passing the same `--seed` value
98 | # as the one that triggered the failure.
99 | Kernel.srand config.seed
100 | =end
101 | end
102 |
103 | # rubocop:enable Style/BlockComments
104 |
--------------------------------------------------------------------------------
/spec/test_files/eof.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 25px;
3 | background-color: rgb(240, 240, 240);
4 | font-family: arial, sans-serif;
5 | font-size: 14px;
6 | }
7 | h1 {
8 | font-size: 35px;
9 | font-weight: normal;
10 | margin-top: 5px;
11 | }
12 | .someclass {
13 | color: red;
14 | }
15 | #someid {
16 | color: green;
17 | }
--------------------------------------------------------------------------------
/spec/test_files/newline.css:
--------------------------------------------------------------------------------
1 | body { margin: 25px; background-color: rgb(240, 240, 240); font-family: arial, sans-serif; font-size: 14px;}h1 { font-size: 35px; font-weight: normal; margin-top: 5px;}.someclass { color: red;}#someid { color: green;}
2 |
--------------------------------------------------------------------------------
/spec/test_files/spacing.css:
--------------------------------------------------------------------------------
1 | body{
2 | margin:25px ;
3 | background-color: rgb (240,240, 240 );
4 | font-family: arial, sans-serif;
5 | font-size: 14px;
6 | }
7 | h1 {
8 | font-size: 35px;
9 | font-weight: normal;
10 | margin-top: 5px;
11 | }
12 | .someclass {
13 | color: red;
14 | }
15 | #someid {
16 | color: green;
17 | }
18 |
--------------------------------------------------------------------------------