famalam 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bd3085fa8fcb3bf78f3b7f9f431e799db0e1565d40a1c6502f1e5306f619917
4
- data.tar.gz: 2d2f4038b3192754fd2f7021c13aecc756d82d5d1861bf2ef1f8f124716adfa4
3
+ metadata.gz: 15842d54b4123e74f02c913e6075cc9d74bcbd71fdb449883e74cf7c00e3236b
4
+ data.tar.gz: 89521d9e9c1894ae915b8cd6ac7c071efadd20357a4b62de3c877ab484111738
5
5
  SHA512:
6
- metadata.gz: b4779f52a32333cff91752a526d74a451c642f83b66e0a78c744eadf39056cf4f035373174d6fd357e3d8960baa543f15b0abe92b1921604737281402310b8b3
7
- data.tar.gz: 6e0ea7f5efe9c30a7f9e965e4f1911fcfff9fc89dcecd7ed110b2dd454612e29d71c5ae22f05570723f496bc092eae72688811c31088ab77516850a07dde9ca8
6
+ metadata.gz: 0e4efdebbd3dd701108b0330a983077bdfac247e1a0946d9e66c91d9240c1064d13091b95856ae6b857cb6f16916a29e41cda954fb5a17ca148aea14512d4d0e
7
+ data.tar.gz: 932b9e080668bc7c146f2bfff9ed47992ed44ebdd39fa7a54e0a839952a6dee27f3f2683eeb432c46dc4766e3146e10990d260b3de908c7e4dbccf4b68a4f38f
data/README.md CHANGED
@@ -1,2 +1,5 @@
1
1
  # FAMalam
2
- FAMalam is a front end web interface for FAM
2
+ > FAMalam is a front end web interface for FAM
3
+
4
+ Still a work in progress, not very responsive.
5
+ ![](./screenshot.png)
@@ -1,10 +1,12 @@
1
+ require 'fam'
2
+ require 'haml'
3
+
1
4
  module FAMalam
2
- VERSIONS = { :major => 0, :minor => 0, :tiny => 1 }
5
+ VERSIONS = { :major => 0, :minor => 1, :tiny => 0 }
3
6
 
4
7
  def self.version *args
5
8
  VERSIONS.flatten.select.with_index { |val, i| i.odd? }.join '.'
6
9
  end
7
10
  end
8
11
 
9
- Dir["#{File.dirname __FILE__}/fam/*.rb"].each { |f| require f }
10
- Dir["#{File.dirname __FILE__}/fam/**/*.rb"].each { |f| require f }
12
+ Dir["#{File.dirname __FILE__}/famalam/*.rb"].each { |f| require f }
@@ -0,0 +1,33 @@
1
+ FAM::Machine::CPU.class_eval do
2
+ def cpu_output out, ascii
3
+ ascii == :PLAIN ? out.to_s : out.chr
4
+ end
5
+
6
+ def inject_input i
7
+ @inject = i
8
+ end
9
+
10
+ def cpu_input
11
+ @inject || -1
12
+ end
13
+
14
+ def step parsed
15
+ @parsed = parsed
16
+ return {:state => 'done'} if @tree_index >= parsed.tree.size || @halted
17
+ @tree_index += 1
18
+ node = parsed[@tree_index]
19
+ if parsed[@tree_index].base_name == 'InNode' && !@rerun
20
+ @tree_index -= 1
21
+ @rerun = true
22
+ return {:state => 'running', :input => true, :output => @outputting}
23
+ end
24
+
25
+ @rerun = false
26
+ status = execute node
27
+ if status == :STOP
28
+ @halted = true
29
+ return {:state => 'done'}
30
+ end
31
+ {:state => 'running', :input => false, :output => @outputting}
32
+ end
33
+ end
@@ -0,0 +1,112 @@
1
+ const pad = (n, width, char) => {
2
+ char = char || '0';
3
+ n = n + '';
4
+ return n.length >= width ? n : new Array(width - n.length + 1).join(char) + n;
5
+ }
6
+
7
+ const draw_step = (step) => {
8
+ if (step['ERROR']) {
9
+ $('#alloc').removeAttr('disabled');
10
+ return;
11
+ }
12
+ $('#alloc').prop('disabled', true);
13
+ for (let i = 0; i < step['ram'].length; i++) {
14
+ let elem = $('.address:nth-child(' + (i + 1) + ') > .value')
15
+ if (elem.html() != '' + step['ram'][i]) {
16
+ elem.empty();
17
+ elem.html(step['ram'][i])
18
+ elem.stop().css({ backgroundColor: '#ffff9c' });
19
+ elem.animate({ backgroundColor: '#ffffff' }, 3200 / get_clock());
20
+ }
21
+ }
22
+
23
+ let reg_elem = $('.cpu .registers');
24
+ reg_elem.empty();
25
+ for (let i = 0; i < Object.keys(step['registers']).length; i++) {
26
+ reg_elem.append(
27
+ `<div class="register">
28
+ <div class="name">`
29
+ + Object.keys(step['registers'])[i]
30
+ + `</div>
31
+ <div class="value">`
32
+ + Object.values(step['registers'])[i]
33
+ + `</div>
34
+ </div>`);
35
+ }
36
+
37
+ let out_elem = $('.cpu .output');
38
+ if (step['output'] != '') {
39
+ out_elem.append(`<div>` + step['output'] + `</div>`);
40
+ out_elem.animate({ scrollTop: out_elem.prop("scrollHeight")}, 100);
41
+ }
42
+ }
43
+
44
+ let get_alloc = () => {
45
+ return $('#alloc').val() - 0;
46
+ }
47
+
48
+ draw_ram = () => {
49
+ let alloc = get_alloc();
50
+ $('.ram').empty();
51
+ for (let i = 0; i < get_alloc(); i++) {
52
+ $('.ram').append(`
53
+ <div class="address">
54
+ <div class="number">` + pad(i, 3) + `</div>
55
+ <div class="value">NULL</div>
56
+ </div>
57
+ `);
58
+ $('.address:nth-child(' + (i + 1) + ') .value').css({
59
+ width: (500 / alloc) + 'em'
60
+ });
61
+ $('.address:nth-child(' + (i + 1) + ') .number').css({
62
+ width: (200 / alloc) + 'em'
63
+ });
64
+ }
65
+ }
66
+ $('#alloc').change(draw_ram);
67
+
68
+ $(document).ready(() => {
69
+ // disable inputs
70
+ $('input[name=input]').prop('disabled', true);
71
+ // Initiate RAM
72
+ draw_ram();
73
+ });
74
+
75
+ // Textbox tab handling
76
+ let tab_level = 0;
77
+ $(document).delegate('#code', 'keydown', function(e) {
78
+ let tab = " ";
79
+
80
+ let key_code = e.keyCode || e.which;
81
+ if (key_code == 9 && tab) { // Tab key
82
+ let start = this.selectionStart;
83
+ let end = this.selectionEnd;
84
+ $(this).val($(this).val().substring(0, start)
85
+ + tab
86
+ + $(this).val().substring(end));
87
+ this.selectionStart = this.selectionEnd = start + tab.length;
88
+ tab_level++;
89
+ return false
90
+ }
91
+ if (key_code == 13) { // Autotab on enter
92
+ let start = this.selectionStart;
93
+ let end = this.selectionEnd;
94
+ $(this).val($(this).val().substring(0, start)
95
+ + "\n"
96
+ + (new Array(tab.length * tab_level + 1).join(tab))
97
+ + $(this).val().substring(end));
98
+ this.selectionStart = this.selectionEnd = 1 + start + tab.length * tab_level;
99
+ return false;
100
+ }
101
+ if (key_code == 8) { // Backspace key
102
+ let start = this.selectionStart;
103
+ let end = this.selectionEnd;
104
+ if ($(this).val().substring(start, start - tab.length) == tab) {
105
+ $(this).val($(this).val().substring(0, start-tab.length)
106
+ + $(this).val().substring(end));
107
+ this.selectionStart = this.selectionEnd = start - tab.length;
108
+ if (tab_level > 0) { tab_level--; }
109
+ return false;
110
+ }
111
+ }
112
+ });
@@ -0,0 +1,99 @@
1
+ const get_clock = () => {
2
+ let c = Math.abs($('input[name=clock]').val() - 0) + 0.001;
3
+ if (c > 21) {
4
+ $('input[name=clock]').val("20");
5
+ get_clock();
6
+ }
7
+ return c;
8
+ }
9
+
10
+ const handle_input = interval => {
11
+ interval.disable();
12
+ let input_box = $('input[name=input]')
13
+ input_box.prop('disabled', false);
14
+ input_box.focus();
15
+
16
+ let input;
17
+ const wait = () => {
18
+ console.log('waiting...');
19
+ if (!input_box.val()) window.setTimeout(wait, 5);
20
+ else {
21
+ input = input_box.val();
22
+ interval.enable();
23
+ input_box
24
+ .val('')
25
+ .attr('placeholder', input)
26
+ .blur()
27
+ .prop('disabled', true);
28
+
29
+ $.ajax({
30
+ type: 'POST',
31
+ url: '/program/input',
32
+ async: false,
33
+ data: { input: input + " ...in" },
34
+ error: (s, e) => {
35
+ console.log(
36
+ 'An error occured in posting input!\nERROR: ',
37
+ s, '\n\n', e
38
+ );
39
+ }
40
+ });
41
+ return;
42
+ }
43
+ }
44
+ wait();
45
+ input_box.attr('placeholder', '');
46
+ return input;
47
+ }
48
+
49
+ class ClockInterval {
50
+ constructor(f, speed) {
51
+ this.lambda = f;
52
+ this.speed = speed;
53
+ this.interval = null;
54
+ }
55
+ enable(speed=this.speed) {
56
+ this.speed = speed;
57
+ this.interval = window.setInterval(this.lambda, (1.0 / speed) * 1000);
58
+ }
59
+ disable() {
60
+ window.clearInterval(this.interval);
61
+ }
62
+ }
63
+
64
+
65
+ const stepper = () => {
66
+ let clock = get_clock();
67
+ let step_interval;
68
+
69
+ const response = () => {
70
+ let step = {};
71
+ $.ajax({
72
+ url: "/program/step.json",
73
+ async: false,
74
+ dataType: 'json',
75
+ success: (data) => {
76
+ step = data;
77
+ }
78
+ });
79
+ if (step['ERROR']) step_interval.disable();
80
+ if (step['inputting']) handle_input(step_interval);
81
+ return step;
82
+ }
83
+
84
+ const update = () => {
85
+ let step = response();
86
+ console.log("Errors: ", step['ERROR']);
87
+ console.log("Step: ", step);
88
+ draw_step(step);
89
+ }
90
+
91
+
92
+ step_interval = new ClockInterval(update, clock)
93
+ step_interval.enable();
94
+ $('input[name=clock]').on("change paste keyup", () => {
95
+ clock = get_clock();
96
+ step_interval.disable();
97
+ step_interval.enable(clock);
98
+ });
99
+ }
@@ -0,0 +1,28 @@
1
+ $(document).ready(() => {
2
+ let clock = get_clock();
3
+
4
+ $('form').submit(() => {
5
+ $.ajax({
6
+ type: 'POST',
7
+ url: '/program',
8
+ async: false,
9
+ data: {
10
+ title: $('input[name=title]').val(),
11
+ code: $('textarea[name=code]').val(),
12
+ alloc: get_alloc(),
13
+ clock: clock,
14
+ },
15
+ error: () => {
16
+ console.log('Code could not be sent!');
17
+ },
18
+ success: (m) => {
19
+ stepper();
20
+ }
21
+ });
22
+ return false;
23
+ });
24
+ $('#submit').click(() => {
25
+ $('form').submit();
26
+ return false;
27
+ });
28
+ })
@@ -0,0 +1,68 @@
1
+ .cpu {
2
+ float: left;
3
+ width: 10em;
4
+ border: 1px dotted #ccc;
5
+ border-radius: 4px;
6
+ padding: 1em;
7
+ }
8
+
9
+ .cpu .input, .cpu .input input {
10
+ width: 100%;
11
+ padding: 0;
12
+ margin: 0;
13
+ }
14
+ .cpu .CU {
15
+ width: calc(100% - 8px);
16
+ padding: 4px;
17
+ margin: 5px 0;
18
+ border: 1px solid #e1e1e1;
19
+ border-radius: 4px;
20
+ min-height: 1em;
21
+ }
22
+
23
+ .cpu .registers, .cpu .output {
24
+ margin: 5px 0;
25
+ padding: 0 3px;
26
+ border: 1px solid #e1e1e1;
27
+ border-radius: 4px;
28
+ min-height: 1em;
29
+ }
30
+ .cpu .register {
31
+ margin: 4px 0;
32
+ border: 1px solid #e1e1e1;
33
+ border-radius: 3px;
34
+ padding: 0;
35
+ font-family: 'FantasqueSansMonoRegular', monospace;
36
+ }
37
+ .cpu .register .name {
38
+ display: inline-block;
39
+ text-align: right;
40
+ padding: 2px 4px 2px 0;
41
+ border-right: 1px solid #e1e1e1;
42
+ width: 2.4em;
43
+ }
44
+ .cpu .register .value {
45
+ display: inline-block;
46
+ }
47
+
48
+ .cpu .output {
49
+ overflow-y: scroll;
50
+ padding: 0;
51
+ min-height: 2em;
52
+ max-height: 4em;
53
+ }
54
+ .cpu .output div {
55
+ padding: 0 0.5em;
56
+ font-family: 'FantasqueSansMonoRegular', monospace;
57
+ }
58
+ .cpu .output div:nth-child(2n) {
59
+ background: #f4f4f4;
60
+ }
61
+
62
+ .cpu .input input {
63
+ width: calc(100% - 4px);
64
+ margin: 5px 0 0 0;
65
+ padding: 3px 2px;
66
+ border: 1px solid #e1e1e1;
67
+ border-radius: 4px;
68
+ }
@@ -0,0 +1,29 @@
1
+ @import url('https://fontlibrary.org/face/fantasque-sans-mono');
2
+ @import url('https://fonts.googleapis.com/css?family=Rubik:400,400i,700');
3
+
4
+ * { outline: none; }
5
+ body, html {
6
+ padding: 0;
7
+ margin: 0;
8
+ overflow-y: hidden;
9
+ font-size: 16px;
10
+ font-weight: 400;
11
+ font-family: 'Rubik', sans-serif;
12
+ color: #222;
13
+ }
14
+
15
+
16
+ #post, .computer {
17
+ height: calc(100vh - 5rem);
18
+ margin: 1em;
19
+ }
20
+
21
+ #post {
22
+ width: 14em;
23
+ float: left;
24
+ padding: 1.5em;
25
+ }
26
+ .computer {
27
+ width: 44em;
28
+ float: right;
29
+ }
@@ -0,0 +1,33 @@
1
+ .ram {
2
+ width: 31em;
3
+ background: none;
4
+ padding: 5px;
5
+ float: right;
6
+ overflow: auto;
7
+ text-align: right;
8
+ }
9
+ .address {
10
+ display: inline-block;
11
+ background-color: rgba(255, 255, 255, 0.8);
12
+ border: 1px solid #ccc;
13
+ border-radius: 3px;
14
+ margin: 3px 1px;
15
+ font-size: 0.6em;
16
+ font-family: 'FantasqueSansMonoRegular', monospace;
17
+ text-align: center;
18
+ }
19
+
20
+ .address .value, .address .number {
21
+ padding: 4px;
22
+ display: inline-block;
23
+ }
24
+
25
+ .address .number {
26
+ float: left;
27
+ border-right: 1px solid #ccc;
28
+ }
29
+
30
+ .address .value {
31
+ border-top-right-radius: 4px;
32
+ border-bottom-right-radius: 4px;
33
+ }
@@ -0,0 +1,53 @@
1
+ #post {
2
+ display: inline-block;
3
+ border: 1px solid #ccc;
4
+ border-radius: 5px;
5
+ }
6
+
7
+ #post input, #post #code, #post select {
8
+ display: block;
9
+ margin: 0 0 1em 0;
10
+ width: 100%;
11
+ font-family: 'Rubik', sans-serif;
12
+ }
13
+ #post input:last-child {
14
+ margin: 0;
15
+ }
16
+ #post input[name=title] {
17
+ border: none;
18
+ border-bottom: 1px dotted #ccc;
19
+ font-size: 1.5em;
20
+ }
21
+
22
+ #post #code {
23
+ padding: 0.5em;
24
+ width: calc(100% - 1em);
25
+ margin: 0 0 1em 0;
26
+ resize: none;
27
+ height: calc(100% - 13em);
28
+ font-family: 'FantasqueSansMonoRegular', monospace;
29
+ border: 1px solid #e1e1e1;
30
+ border-radius: 4px;
31
+ }
32
+
33
+ #post select {
34
+ border: 1px solid #e1e1e1;
35
+ border-radius: 4px;
36
+ background: none;
37
+ }
38
+
39
+ #post input[type=submit] {
40
+ background: #fff;
41
+ color: #333;
42
+ border-radius: 4px;
43
+ border: 1px solid #e1e1e1;
44
+ cursor: pointer;
45
+ transition: all .1s ease-in-out;
46
+ }
47
+ #post input[type=submit]:active {
48
+ background: #f3f3f3;
49
+ border: 1px solid #ccc;
50
+ }
51
+ #post input[type=submit]:hover {
52
+ background: #f8f8f8;
53
+ }
@@ -0,0 +1,8 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title FAMalam
5
+ %link{:rel => 'stylesheet', :type => 'text/css', :href => '/styles/master.css'}
6
+ %body
7
+ Create a new program:
8
+ %a{:href => '/program'} New
@@ -0,0 +1,79 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title FAMalam
5
+ %link{:rel => "stylesheet", :type => "text/css", :href => "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css"}
6
+ %link{:rel => "stylesheet", :type => "text/css", :href => "/styles/master.css"}
7
+ %link{:rel => "stylesheet", :type => "text/css", :href => "/styles/submit.css"}
8
+ %link{:rel => "stylesheet", :type => "text/css", :href => "/styles/cpu.css"}
9
+ %link{:rel => "stylesheet", :type => "text/css", :href => "/styles/ram.css"}
10
+ %script{:type=> "text/javascript", :src => "https://code.jquery.com/jquery-3.3.1.min.js"}
11
+ %script{:type=> "text/javascript", :src => "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"}
12
+ %body
13
+ %form{:id => "post", :action => "/program", :method => "POST"}
14
+ %input{:type => "text", :autofocus => "", :autocomplete => "off", :name => "title", :placeholder => "Project Title"}
15
+ %datalist{:id => "tickmarks"}
16
+ %option{:value => "2", :label => "2"}
17
+ %option{:value => "4"}
18
+ %option{:value => "6"}
19
+ %option{:value => "8"}
20
+ %option{:value => "10", :label => "10"}
21
+ %option{:value => "12"}
22
+ %option{:value => "14"}
23
+ %option{:value => "16"}
24
+ %option{:value => "18"}
25
+ %option{:value => "20", :label => "20"}
26
+ %input{:type => "range", :name => "clock",
27
+ :value => "1", :list => "tickmarks",
28
+ :max => "20", :min => "2", :step => "1"}
29
+
30
+
31
+ %textarea{:name => "code", :id => "code",
32
+ :autocomplete => "off",
33
+ :autocorrect => "off",
34
+ :autocapitalize => "off",
35
+ :spellcheck => "false"}
36
+ = preserve do
37
+ :escaped
38
+ STORE 55 : @0
39
+ DATA dyn : 0b01101
40
+ LOAD -0xf : &DAT
41
+ LOAD &DAT : &ACC
42
+
43
+ LOOP:
44
+ ADD 1 : &ACC
45
+ OUT &ACC
46
+ STORE &ACC : dyn
47
+
48
+ MORE &ACC : 29
49
+ | GOTO END
50
+ | GOTO LOOP
51
+
52
+ END: HALT 0
53
+
54
+
55
+
56
+ %select{:id => "alloc"}
57
+ %option{:value => "120", :selected => ""}120B
58
+ %option{:value => "94"}94B
59
+ %option{:value => "64"}64B
60
+ %option{:value => "32"}32B
61
+ %input{:type => "submit", :id => "submit", :value => "Run"}
62
+
63
+ .computer
64
+ .cpu
65
+ %span Control Unit
66
+ .CU
67
+ %span Registers
68
+ .registers
69
+ %span Output
70
+ .output
71
+ %span Input
72
+ .input
73
+ %input{:type => "text",
74
+ :autofocus => "", :autocomplete => "off",
75
+ :name => "input", :placeholder => "Enter value"}
76
+ .ram
77
+ %script{:type=> "text/javascript", :src => "/scripts/graphics.js"}
78
+ %script{:type=> "text/javascript", :src => "/scripts/step.js"}
79
+ %script{:type=> "text/javascript", :src => "/scripts/submitter.js"}
metadata CHANGED
@@ -1,26 +1,94 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: famalam
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Demonstrandum
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-13 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2018-04-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fam
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.0.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: haml
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 5.0.4
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '5.0'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 5.0.4
53
+ - !ruby/object:Gem::Dependency
54
+ name: sinatra
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '2.0'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 2.0.1
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '2.0'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.0.1
13
73
  description: FAMalam is a front end web interface for FAM
14
74
  email: knutsen@jetspace.co
15
- executables:
16
- - famalam
75
+ executables: []
17
76
  extensions: []
18
77
  extra_rdoc_files: []
19
78
  files:
20
79
  - LICENSE
21
80
  - README.md
22
- - bin/famalam
23
81
  - lib/famalam.rb
82
+ - lib/famalam/patches.rb
83
+ - public/scripts/graphics.js
84
+ - public/scripts/step.js
85
+ - public/scripts/submitter.js
86
+ - public/styles/cpu.css
87
+ - public/styles/master.css
88
+ - public/styles/ram.css
89
+ - public/styles/submit.css
90
+ - views/index.haml
91
+ - views/program.haml
24
92
  homepage: https://github.com/Demonstrandum/FAM
25
93
  licenses:
26
94
  - GPL-2.0
@@ -1 +0,0 @@
1
- #!/usr/bin/env ruby