├── .gitignore
├── .rubocop.yml
├── LICENSE
├── README.md
├── bar
├── bar.go
├── segment.go
├── segment_test.go
├── sink.go
└── sink_test.go
├── barista.go
├── barista_test.go
├── base
├── click
│ ├── buttons.go
│ ├── buttons.rb
│ ├── click.go
│ └── click_test.go
├── notifier
│ ├── notifier.go
│ └── notifier_test.go
├── value
│ ├── value.go
│ └── value_test.go
└── watchers
│ ├── dbus
│ ├── dbus.go
│ ├── dbus_test.go
│ ├── nameowner.go
│ ├── nameowner_test.go
│ ├── properties.go
│ ├── properties_test.go
│ ├── testbus.go
│ ├── testbus_test.go
│ ├── testobject.go
│ ├── testobject_test.go
│ ├── testservice.go
│ └── testservice_test.go
│ ├── file
│ ├── file.go
│ └── file_test.go
│ ├── localtz
│ ├── localtz.go
│ └── localtz_test.go
│ └── netlink
│ ├── netlink.go
│ ├── netlink_test.go
│ ├── nl.go
│ └── nl_test.go
├── build.sh
├── colors
├── colors.go
└── colors_test.go
├── core
├── module.go
├── module_test.go
├── moduleset.go
└── moduleset_test.go
├── format
├── bytes.go
├── bytes_test.go
├── siunit.go
├── siunit.rb
├── siunit_test.go
├── units.go
└── units_test.go
├── go.mod
├── go.sum
├── group
├── collapsing
│ ├── collapsing.go
│ └── collapsing_test.go
├── cycling
│ ├── cycling.go
│ └── cycling_test.go
├── following
│ ├── following.go
│ └── following_test.go
├── group.go
├── group_test.go
├── modal
│ ├── modal.go
│ └── modal_test.go
└── switching
│ ├── switching.go
│ └── switching_test.go
├── logging
├── id.go
├── id_test.go
├── logging.go
├── logging_test.go
├── nop.go
├── nop_test.go
└── race_test.go
├── modules
├── battery
│ ├── battery.go
│ └── battery_test.go
├── bluetooth
│ ├── adapter.go
│ ├── adapter_test.go
│ ├── device.go
│ └── device_test.go
├── clock
│ ├── clock.go
│ └── clock_test.go
├── counter
│ ├── counter.go
│ └── counter_test.go
├── cpuload
│ ├── cpuload.go
│ └── cpuload_test.go
├── cputemp
│ └── cputemp.go
├── diskio
│ ├── diskio.go
│ └── diskio_test.go
├── diskspace
│ ├── diskspace.go
│ └── diskspace_test.go
├── funcs
│ ├── funcs.go
│ └── funcs_test.go
├── github
│ ├── github.go
│ └── github_test.go
├── gsuite
│ ├── calendar
│ │ ├── calendar.go
│ │ └── calendar_test.go
│ └── gmail
│ │ ├── gmail.go
│ │ └── gmail_test.go
├── hwmon
│ └── hwmon.go
├── internal
│ └── temperature
│ │ ├── hwmon.go
│ │ ├── hwmon_test.go
│ │ ├── module.go
│ │ ├── thermalzone.go
│ │ └── thermalzone_test.go
├── media
│ ├── info.go
│ ├── media.go
│ └── media_test.go
├── meminfo
│ ├── meminfo.go
│ └── meminfo_test.go
├── meta
│ ├── multicast
│ │ ├── multicast.go
│ │ └── multicast_test.go
│ ├── reformat
│ │ ├── reformat.go
│ │ └── reformat_test.go
│ ├── slot
│ │ ├── slot.go
│ │ └── slot_test.go
│ └── split
│ │ ├── split.go
│ │ └── split_test.go
├── netinfo
│ ├── netinfo.go
│ └── netinfo_test.go
├── netspeed
│ ├── netspeed.go
│ └── netspeed_test.go
├── shell
│ ├── shell.go
│ ├── shell_test.go
│ ├── tail.go
│ └── tail_test.go
├── static
│ ├── static.go
│ └── static_test.go
├── sysinfo
│ ├── sysinfo.go
│ └── sysinfo_test.go
├── systemd
│ ├── systemd.go
│ └── systemd_test.go
├── volume
│ ├── alsa
│ │ ├── alsa.go
│ │ ├── alsa_capi.go
│ │ ├── alsa_capi_for_test.go
│ │ ├── alsa_test.go
│ │ └── capi.rb
│ ├── pulseaudio
│ │ └── pulse.go
│ ├── volume.go
│ └── volume_test.go
├── vpn
│ ├── vpn.go
│ └── vpn_test.go
├── weather
│ ├── direction.go
│ ├── direction_test.go
│ ├── metar
│ │ ├── metar.go
│ │ ├── metar_test.go
│ │ └── testdata
│ │ │ ├── bad.xml
│ │ │ ├── example.xml
│ │ │ └── no-entries.xml
│ ├── openweathermap
│ │ ├── openweathermap.go
│ │ ├── openweathermap_test.go
│ │ └── testdata
│ │ │ ├── bad.json
│ │ │ ├── empty.json
│ │ │ └── good.json.tpl
│ ├── weather.go
│ └── weather_test.go
└── wlan
│ ├── wlan.go
│ └── wlan_test.go
├── oauth
├── crypt.go
├── crypt_test.go
├── oauth.go
├── oauth_test.go
└── testdata
│ ├── empty.json
│ └── simple.json
├── outputs
├── group.go
├── group_test.go
├── outputs.go
├── outputs_test.go
├── timed.go
├── timed_test.go
├── timedelta.go
└── timedelta_test.go
├── pango
├── attrs.go
├── icon.go
├── icon_test.go
├── icons
│ ├── fontawesome
│ │ ├── fontawesome.go
│ │ └── fontawesome_test.go
│ ├── icons.go
│ ├── icons_test.go
│ ├── material
│ │ ├── material.go
│ │ └── material_test.go
│ ├── mdi
│ │ ├── mdi.go
│ │ └── mdi_test.go
│ └── typicons
│ │ ├── typicons.go
│ │ └── typicons_test.go
├── kwattrs.rb
├── kwattrs_size.go
├── kwattrs_stretch.go
├── kwattrs_strikethrough.go
├── kwattrs_style.go
├── kwattrs_underline.go
├── kwattrs_variant.go
├── kwattrs_weight.go
├── pango.go
└── pango_test.go
├── rb
├── gofile.rb
└── run_cmd.rb
├── samples
├── i3status
│ └── i3status.go
├── sample-bar
│ ├── cachehttp.go
│ └── sample-bar.go
├── simple
│ └── simple.go
├── stable-api
│ └── stable-api.go
└── yubikey
│ └── yubikey.go
├── sink
├── sink.go
└── sink_test.go
├── test.sh
├── testing
├── bar
│ ├── bar.go
│ └── bar_test.go
├── capi
│ ├── funcs.rb
│ ├── generators.rb
│ ├── main.rb
│ └── types.rb
├── cron
│ ├── cron.go
│ └── cron_test.go
├── fail
│ ├── fail.go
│ └── fail_test.go
├── flakes.sh
├── githubfs
│ ├── githubfs.go
│ └── githubfs_test.go
├── httpcache
│ ├── httpcache.go
│ ├── httpcache_test.go
│ └── testdata
│ │ └── debug.tpl
├── httpclient
│ ├── httpclient.go
│ └── httpclient_test.go
├── httpserver
│ ├── httpserver.go
│ └── httpserver_test.go
├── mockio
│ ├── mockio.go
│ └── mockio_test.go
├── module
│ ├── module.go
│ └── module_test.go
├── notifier
│ ├── notifier.go
│ └── notifier_test.go
├── output
│ ├── output.go
│ └── output_test.go
└── pango
│ ├── pango.go
│ └── pango_test.go
└── timing
├── internal
└── timerfd
│ ├── nativeendian.go
│ └── timerfd.go
├── misc.go
├── scheduler.go
├── scheduler_test.go
├── schedulerimpl_test.go
├── testmode.go
├── testmode_test.go
├── time_scheduler.go
├── time_scheduler_test.go
├── timerfd_scheduler.go
├── timerfd_scheduler_fallback.go
├── timerfd_scheduler_test.go
└── timing.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | coverage.txt
16 | /profiles
17 | c.out
18 | .idea
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | AllCops:
16 | TargetRubyVersion: 2.3
17 |
18 | Style/FrozenStringLiteralComment:
19 | EnforcedStyle: never
20 |
21 | Metrics:
22 | Enabled: false
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | # Barista
19 |
20 | Barista is an i3 status bar written in golang.
21 |
22 | **This is not an official Google product**
23 |
24 | ## Features
25 |
26 | - Based on push rather than fixed interval polling. This allows immediate updates
27 | for many modules, like volume, media, shell, etc.
28 |
29 | - Produces a single binary via go build. This makes it easy to set up the bar
30 | executable, since no import paths, environment variables, et al. need to be
31 | configured.
32 |
33 | - Good click handlers (especially media and volume), since we can wait for a
34 | command and update the bar immediately rather than waiting for the next 'tick'.
35 |
36 | - Configuration is code, providing oodles of customization options without
37 | needing myriad configuration options in a file somewhere. If/then/else, loops,
38 | functions, variables, and even other go packages can all be used seamlessly.
39 |
40 | ## Usage
41 |
42 | See samples/sample-bar.go for a sample bar.
43 |
44 | To build your own bar, simply create a `package main` go file,
45 | import and configure the modules you wish to use, and call `barista.Run()`.
46 |
47 | To show your bar in i3, set the `status_command` of a `bar { ... }` section
48 | to be the newly built bar binary, e.g.
49 |
50 | ```
51 | bar {
52 | position top
53 | status_command exec ~/bin/mybar
54 | font pango:DejaVu Sans Mono 10
55 | }
56 | ```
57 |
58 |
--------------------------------------------------------------------------------
/bar/sink.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package bar
16 |
17 | // Output updates the module's output on the bar.
18 | func (s Sink) Output(o Output) {
19 | s(o)
20 | }
21 |
22 | // Error is a convenience method that returns false and does nothing
23 | // when given a nil error, and outputs an error segment and returns
24 | // true when a non-nil error is given.
25 | func (s Sink) Error(e error) bool {
26 | if e != nil {
27 | s(ErrorSegment(e))
28 | return true
29 | }
30 | return false
31 | }
32 |
--------------------------------------------------------------------------------
/bar/sink_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package bar
16 |
17 | import (
18 | "io"
19 | "testing"
20 |
21 | "github.com/stretchr/testify/require"
22 | )
23 |
24 | func TestOutput(t *testing.T) {
25 | var sink Sink
26 | ch := make(chan Output, 1)
27 | sink = func(o Output) { ch <- o }
28 |
29 | sink.Output(TextSegment("foo"))
30 | select {
31 | case out := <-ch:
32 | txt, _ := out.Segments()[0].Content()
33 | require.Equal(t, "foo", txt)
34 | default:
35 | require.Fail(t, "expected output on Output(...)")
36 | }
37 |
38 | sink.Output(nil)
39 | require.Nil(t, <-ch)
40 | }
41 |
42 | func TestError(t *testing.T) {
43 | var sink Sink
44 | ch := make(chan Output, 1)
45 | sink = func(o Output) { ch <- o }
46 |
47 | require.False(t, sink.Error(nil), "nil error returns false")
48 | select {
49 | case <-ch:
50 | require.Fail(t, "Should not send any output on Error(nil)")
51 | default:
52 | // test passed.
53 | }
54 |
55 | require.True(t, sink.Error(io.EOF), "non-nil error returns true")
56 | select {
57 | case out := <-ch:
58 | require.Error(t, out.Segments()[0].GetError(),
59 | "output sent on Error(...) has error segment")
60 | default:
61 | require.Fail(t, "Expected an error output on Error(...)")
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/base/click/buttons.rb:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | require_relative '../../rb/gofile.rb'
16 |
17 | SCROLLS = %w[Left Right Up Down].freeze
18 | BUTTONS = %w[Left Right Middle Back Forward].freeze
19 |
20 | method_and_btn = BUTTONS.map { |n| [n, "Button#{n}"] } +
21 | SCROLLS.map { |n| ["Scroll#{n}", "Scroll#{n}"] }
22 |
23 | write_go_file('buttons.go') do |out|
24 | out.puts <<~HEADER
25 | package click
26 |
27 | import "github.com/soumya92/barista/bar"
28 | HEADER
29 |
30 | method_and_btn.each do |method, btn|
31 | out.write(<<~METHOD)
32 |
33 | // #{method} creates a click handler that invokes the given function
34 | // when a #{btn} event is received.
35 | func #{method}(do func()) func(bar.Event) {
36 | \treturn #{method}E(DiscardEvent(do))
37 | }
38 |
39 | // #{method}E wraps the click handler so that it is only triggered by a
40 | // #{btn} event.
41 | func #{method}E(handler func(bar.Event)) func(bar.Event) {
42 | \treturn ButtonE(handler, bar.#{btn})
43 | }
44 | METHOD
45 | end
46 | method_and_btn.each do |method, btn|
47 | out.write(<<~METHOD)
48 |
49 | // #{method} invokes the given function on #{btn} events.
50 | func (m Map) #{method}(do func()) Map {
51 | \treturn m.#{method}E(DiscardEvent(do))
52 | }
53 |
54 | // #{method}E sets the click handler for #{btn} events.
55 | func (m Map) #{method}E(handler func(bar.Event)) Map {
56 | \treturn m.Set(bar.#{btn}, handler)
57 | }
58 | METHOD
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/base/notifier/notifier.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package notifier provides a channel that can send update notifications.
17 | Specifically, a notifier automatically coalesces multiple notifications
18 | such that if a previous notification is already pending, a new notification
19 | will not be created. This is useful in scenarios like formatting changes,
20 | where if multiple updates come in before the first one is processed, it
21 | is preferable to apply just the final format, ignoring the intermediate ones.
22 | */
23 | package notifier
24 |
25 | import (
26 | "sync"
27 |
28 | l "github.com/soumya92/barista/logging"
29 | )
30 |
31 | // New constructs a new notifier. It returns a func that triggers a notification,
32 | // and a <-chan that consumes these notifications.
33 | func New() (func(), <-chan struct{}) {
34 | ch := make(chan struct{}, 1)
35 | return func() { notify(ch) }, ch
36 | }
37 |
38 | func notify(ch chan<- struct{}) {
39 | l.Fine("Notify %s", l.ID(ch))
40 | select {
41 | case ch <- struct{}{}:
42 | default:
43 | }
44 | }
45 |
46 | // Source can be used to notify multiple listeners of a signal. It provides both
47 | // one-shot listeners that close the channel on the next signal, and continuous
48 | // listeners that emit a struct{} on every signal (but need to be cleaned up).
49 | type Source struct {
50 | obs []chan struct{}
51 | subs map[<-chan struct{}]func()
52 | mu sync.Mutex
53 | }
54 |
55 | // Next returns a channel that will be closed on the next signal.
56 | func (s *Source) Next() <-chan struct{} {
57 | ch := make(chan struct{})
58 | s.mu.Lock()
59 | defer s.mu.Unlock()
60 | s.obs = append(s.obs, ch)
61 | return ch
62 | }
63 |
64 | // Subscribe returns a channel that will receive an empty struct{} on the next
65 | // signal, and a func to close the subscription.
66 | func (s *Source) Subscribe() (sub <-chan struct{}, done func()) {
67 | fn, sub := New()
68 | s.mu.Lock()
69 | defer s.mu.Unlock()
70 | if s.subs == nil {
71 | s.subs = map[<-chan struct{}]func(){}
72 | }
73 | s.subs[sub] = fn
74 | return sub, func() {
75 | s.mu.Lock()
76 | defer s.mu.Unlock()
77 | delete(s.subs, sub)
78 | }
79 | }
80 |
81 | // Notify notifies all interested listeners.
82 | func (s *Source) Notify() {
83 | s.mu.Lock()
84 | defer s.mu.Unlock()
85 | for _, o := range s.obs {
86 | close(o)
87 | }
88 | s.obs = nil
89 | for _, f := range s.subs {
90 | f()
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/base/watchers/dbus/dbus_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package dbus
16 |
17 | import (
18 | "errors"
19 | "os"
20 | "sync/atomic"
21 | "testing"
22 |
23 | "github.com/stretchr/testify/require"
24 | )
25 |
26 | func TestBusTypes(t *testing.T) {
27 | if os.Getenv("CI") != "true" {
28 | require.NotPanics(t, func() { Session() }, "session bus")
29 | require.NotPanics(t, func() { System() }, "system bus")
30 | }
31 |
32 | // To allow -count > 1 tests.
33 | testBusInstance = atomic.Value{}
34 |
35 | require.Panics(t, func() { Test() }, "test bus before setup")
36 | SetupTestBus()
37 | require.NotPanics(t, func() { Test() }, "test bus after setup")
38 |
39 | require.Panics(t, func() { connect(nil, errors.New("something")) })
40 | }
41 |
42 | func TestExpandAndShorten(t *testing.T) {
43 | require := require.New(t)
44 |
45 | require.Equal("com.example.service.Method",
46 | expand("com.example.service", "Method"))
47 | require.Equal("com.example.service.Method.SubMethod",
48 | expand("com.example.service", ".Method.SubMethod"))
49 | require.Equal("net.example.service.Method",
50 | expand("com.example.service", "net.example.service.Method"))
51 |
52 | require.Equal("Method",
53 | shorten("com.example.service", "com.example.service.Method"))
54 | require.Equal(".Method.SubMethod",
55 | shorten("com.example.service", "com.example.service.Method.SubMethod"))
56 | require.Equal("net.example.service.Method",
57 | shorten("com.example.service", "net.example.service.Method"))
58 | require.Equal("com.example.service2.Method",
59 | shorten("com.example.service", "com.example.service2.Method"))
60 | }
61 |
62 | func TestMakeDBusName(t *testing.T) {
63 | require := require.New(t)
64 |
65 | require.Equal(dbusName{"com.example.foo", "Service"},
66 | makeDbusName("com.example.foo.Service"))
67 | require.Equal(dbusName{"com.example", "foo"},
68 | makeDbusName("com.example.foo"))
69 | require.Equal(dbusName{"com", "example"},
70 | makeDbusName("com.example"))
71 | require.Equal(dbusName{"", "example"},
72 | makeDbusName("example"))
73 |
74 | for _, s := range []string{
75 | "com.example.foo.Service",
76 | "com.example.foo",
77 | "com.example",
78 | } {
79 | require.Equal(s, makeDbusName(s).String(),
80 | "%s -> dbus -> string != %s", s, s)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Copyright 2018 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -e
18 |
19 | # Save keys to ~/.config/barista/keys to avoid leaking them to git.
20 | # Or use your preferred way to set them as environment variables.
21 | CONFIG_DIR="${XDG_CONFIG_HOME:-"$HOME/.config"}"
22 | [ -e "$CONFIG_DIR/barista/keys" ] && . "$CONFIG_DIR/barista/keys"
23 |
24 | # Any KEYS will be inserted into the sample bar by replacing
25 | # '%%$NAME_OF_KEY%%' with the value of $NAME_OF_KEY from the environment.
26 | # e.g. '%%OWM_API_KEY%%' in the go file will be replaced with the value
27 | # of $OWM_API_KEY.
28 | KEYS=(
29 | 'GITHUB_CLIENT_ID' 'GITHUB_CLIENT_SECRET'
30 | 'GOOGLE_CLIENT_ID' 'GOOGLE_CLIENT_SECRET'
31 | 'OWM_API_KEY'
32 | )
33 |
34 | TARGET_FILE="./samples/sample-bar/sample-bar.go"
35 |
36 | # Save the current sample-bar, so we can revert it after building, to
37 | # prevent accidentally checking in the keys. We can't use git checkout
38 | # because the file could have other modifications that aren't committed.
39 | BACKUP_FILE="$(mktemp)"
40 | cp "$TARGET_FILE" "$BACKUP_FILE"
41 | function restore {
42 | cp "$BACKUP_FILE" "$TARGET_FILE"
43 | rm -f "$BACKUP_FILE"
44 | }
45 | trap restore EXIT
46 |
47 | for KEY in ${KEYS[@]}; do
48 | if [ -n "${!KEY}" ]; then
49 | sed -i "s/%%${KEY}%%/${!KEY}/g" "$TARGET_FILE"
50 | else
51 | echo "Skipping $KEY, value not set" >&2
52 | fi
53 | done
54 |
55 | # Build the sample bar with all the keys set. Pass all arguments to the
56 | # `go build` command, allowing e.g. `./build.sh -o ~/bin/mybar`, or even
57 | # `./build.sh -race -tags debuglog`.
58 | go build "$@"
59 |
60 |
--------------------------------------------------------------------------------
/core/moduleset.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package core
16 |
17 | import (
18 | "sync"
19 |
20 | "github.com/soumya92/barista/bar"
21 | l "github.com/soumya92/barista/logging"
22 | "github.com/soumya92/barista/sink"
23 | )
24 |
25 | // ModuleSet is a group of modules. It provides a channel for identifying module
26 | // updates, and methods to get the last output of the set or a specific module.
27 | type ModuleSet struct {
28 | modules []*Module
29 | updateCh chan int
30 | outputs []bar.Segments
31 | outputsMu sync.RWMutex
32 | }
33 |
34 | // NewModuleSet creates a ModuleSet with the given modules.
35 | func NewModuleSet(modules []bar.Module) *ModuleSet {
36 | set := &ModuleSet{
37 | modules: make([]*Module, len(modules)),
38 | outputs: make([]bar.Segments, len(modules)),
39 | updateCh: make(chan int),
40 | }
41 | for i, m := range modules {
42 | l.Fine("%s added as %s[%d]", l.ID(m), l.ID(set), i)
43 | set.modules[i] = NewModule(m)
44 | }
45 | return set
46 | }
47 |
48 | // Stream starts streaming all modules and returns a channel that receives the
49 | // index of the module any time one updates with new output.
50 | func (m *ModuleSet) Stream() <-chan int {
51 | for i, mod := range m.modules {
52 | go mod.Stream(m.sinkFn(i))
53 | }
54 | return m.updateCh
55 | }
56 |
57 | func (m *ModuleSet) sinkFn(idx int) bar.Sink {
58 | return sink.Func(func(out bar.Segments) {
59 | l.Fine("%s new output from %s",
60 | l.ID(m), l.ID(m.modules[idx].original))
61 | m.outputsMu.Lock()
62 | m.outputs[idx] = out
63 | m.outputsMu.Unlock()
64 | m.updateCh <- idx
65 | })
66 | }
67 |
68 | // Len returns the number of modules in this ModuleSet.
69 | func (m *ModuleSet) Len() int {
70 | return len(m.modules)
71 | }
72 |
73 | // LastOutput returns the last output from the module at a specific position.
74 | // If the module has not yet updated, an empty output will be used.
75 | func (m *ModuleSet) LastOutput(idx int) bar.Segments {
76 | m.outputsMu.RLock()
77 | defer m.outputsMu.RUnlock()
78 | return m.outputs[idx]
79 | }
80 |
81 | // LastOutputs returns the last output from all modules in order. The returned
82 | // slice will have exactly Len() elements, and if a module has not yet updated
83 | // an empty output will be placed in its position.
84 | func (m *ModuleSet) LastOutputs() []bar.Segments {
85 | m.outputsMu.RLock()
86 | defer m.outputsMu.RUnlock()
87 | cp := make([]bar.Segments, len(m.outputs))
88 | copy(cp, m.outputs)
89 | return cp
90 | }
91 |
--------------------------------------------------------------------------------
/core/moduleset_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package core
16 |
17 | import (
18 | "testing"
19 | "time"
20 |
21 | "github.com/soumya92/barista/bar"
22 | testModule "github.com/soumya92/barista/testing/module"
23 |
24 | "github.com/stretchr/testify/require"
25 | )
26 |
27 | func nextUpdate(t *testing.T, ch <-chan int, formatAndArgs ...interface{}) int {
28 | select {
29 | case idx := <-ch:
30 | return idx
31 | case <-time.After(time.Second):
32 | require.Fail(t, "No update from moduleset", formatAndArgs...)
33 | }
34 | return -1
35 | }
36 |
37 | func assertNoUpdate(t *testing.T, ch <-chan int, formatAndArgs ...interface{}) {
38 | select {
39 | case <-ch:
40 | require.Fail(t, "Unexpected update from moduleset", formatAndArgs...)
41 | case <-time.After(10 * time.Millisecond):
42 | // test passed.
43 | }
44 | }
45 |
46 | func TestModuleSet(t *testing.T) {
47 | tms := []*testModule.TestModule{
48 | testModule.New(t),
49 | testModule.New(t),
50 | testModule.New(t),
51 | }
52 | ms := NewModuleSet([]bar.Module{tms[0], tms[1], tms[2]})
53 | updateCh := ms.Stream()
54 | assertNoUpdate(t, updateCh, "on start")
55 | for _, tm := range tms {
56 | tm.AssertStarted("on moduleset stream")
57 | }
58 | require.Equal(t, 3, ms.Len())
59 |
60 | tms[1].OutputText("foo")
61 | require.Equal(t, 1, nextUpdate(t, updateCh, "on output"),
62 | "update notification on new output from module")
63 |
64 | tms[0].OutputText("baz")
65 | require.Equal(t, 0, nextUpdate(t, updateCh, "on update"),
66 | "update notification on new output from module")
67 |
68 | require.Empty(t, ms.LastOutput(2), "without any output")
69 | txt, _ := ms.LastOutput(0)[0].Content()
70 | require.Equal(t, "baz", txt)
71 |
72 | out := ms.LastOutputs()
73 | require.Equal(t, 1, len(out[0]))
74 | txt, _ = out[0][0].Content()
75 | require.Equal(t, "baz", txt)
76 | require.Equal(t, 1, len(out[1]))
77 | txt, _ = out[1][0].Content()
78 | require.Equal(t, "foo", txt)
79 | require.Empty(t, out[2])
80 | }
81 |
--------------------------------------------------------------------------------
/format/bytes.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package format
16 |
17 | import (
18 | "fmt"
19 |
20 | "github.com/dustin/go-humanize"
21 | "github.com/martinlindhe/unit"
22 | )
23 |
24 | // Bytesize formats a Datasize in SI units using go-humanize.
25 | // e.g. Bytesize(10 * unit.Megabyte) == "10 MB"
26 | func Bytesize(v unit.Datasize) string {
27 | intval := uint64(v.Bytes())
28 | return humanize.Bytes(intval)
29 | }
30 |
31 | // IBytesize formats a Datasize in IEC units using go-humanize.
32 | // e.g. IBytesize(10 * unit.Mebibyte) == "10 MiB"
33 | func IBytesize(v unit.Datasize) string {
34 | intval := uint64(v.Bytes())
35 | return humanize.IBytes(intval)
36 | }
37 |
38 | // Byterate formats a Datarate in SI units using go-humanize.
39 | // e.g. Byterate(10 * unit.MegabytePerSecond) == "10 MB/s"
40 | func Byterate(v unit.Datarate) string {
41 | intval := uint64(v.BytesPerSecond())
42 | return fmt.Sprintf("%s/s", humanize.Bytes(intval))
43 | }
44 |
45 | // IByterate formats a Datarate in IEC units using go-humanize.
46 | // e.g. Byterate(10 * unit.MebibytePerSecond) == "10 MiB/s"
47 | func IByterate(v unit.Datarate) string {
48 | intval := uint64(v.BytesPerSecond())
49 | return fmt.Sprintf("%s/s", humanize.IBytes(intval))
50 | }
51 |
--------------------------------------------------------------------------------
/format/bytes_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package format
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/martinlindhe/unit"
21 | "github.com/stretchr/testify/require"
22 | )
23 |
24 | func TestDataSize(t *testing.T) {
25 | require := require.New(t)
26 | require.Equal("10 KiB", IBytesize(10*unit.Kibibyte))
27 | require.Equal("10 kB", Bytesize(10*unit.Kilobyte))
28 | require.Equal("9.8 KiB", IBytesize(10*unit.Kilobyte))
29 | }
30 |
31 | func TestDataRate(t *testing.T) {
32 | require := require.New(t)
33 | require.Equal("10 KiB/s", IByterate(10*unit.KibibytePerSecond))
34 | require.Equal("10 kB/s", Byterate(10*1000*8*unit.BitPerSecond))
35 | require.Equal("9.8 KiB/s", IByterate(10*1000*8*unit.BitPerSecond))
36 | }
37 |
--------------------------------------------------------------------------------
/format/siunit.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by siunit.rb; DO NOT EDIT.
16 |
17 | package format
18 |
19 | import "github.com/martinlindhe/unit"
20 |
21 | // SIUnit formats a unit.Unit value to an appropriately scaled base unit.
22 | // For example, SIUnit(length) is equivalent to SI(length.Meters(), "m").
23 | // For non-base units (e.g. feet), use SI(length.Feet(), "ft").
24 | func SIUnit(val interface{}) (Value, bool) {
25 | switch v := val.(type) {
26 | case unit.Acceleration:
27 | return SI(v.MetersPerSecondSquared(), "m/s²"), true
28 | case unit.Angle:
29 | return SI(v.Radians(), "rad"), true
30 | case unit.Area:
31 | return SI(v.SquareMeters(), "m²"), true
32 | case unit.Datarate:
33 | return SI(v.BytesPerSecond(), "B/s"), true
34 | case unit.Datasize:
35 | return SI(v.Bytes(), "B"), true
36 | case unit.ElectricCurrent:
37 | return SI(v.Amperes(), "A"), true
38 | case unit.Energy:
39 | return SI(v.Joules(), "J"), true
40 | case unit.Force:
41 | return SI(v.Newtons(), "N"), true
42 | case unit.Frequency:
43 | return SI(v.Hertz(), "Hz"), true
44 | case unit.Length:
45 | return SI(v.Meters(), "m"), true
46 | case unit.Mass:
47 | return SI(v.Grams(), "g"), true
48 | case unit.Power:
49 | return SI(v.Watts(), "W"), true
50 | case unit.Pressure:
51 | return SI(v.Pascals(), "Pa"), true
52 | case unit.Speed:
53 | return SI(v.MetersPerSecond(), "m/s"), true
54 | case unit.Voltage:
55 | return SI(v.Volts(), "V"), true
56 | case unit.Volume:
57 | return SI(v.CubicMeters(), "m³"), true
58 | case unit.AmountOfSubstance:
59 | return SI(v.Moles(), "mol"), true
60 | case unit.ElectricalConductance:
61 | return SI(v.Siemens(), "S"), true
62 | case unit.ElectricalResistance:
63 | return SI(v.Ohms(), "Ω"), true
64 | case unit.Illuminance:
65 | return SI(v.Lux(), "lx"), true
66 | case unit.LuminousFlux:
67 | return SI(v.Lumen(), "lm"), true
68 | case unit.LuminousIntensity:
69 | return SI(v.Candela(), "cd"), true
70 | }
71 | return Value{}, false
72 | }
73 |
--------------------------------------------------------------------------------
/format/siunit.rb:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | require_relative '../rb/gofile.rb'
16 |
17 | Unit = Struct.new(:type, :base, :unit)
18 | UNITS = [
19 | Unit.new('Acceleration', 'MetersPerSecondSquared', 'm/s²'),
20 | Unit.new('Angle', 'Radians', 'rad'),
21 | Unit.new('Area', 'SquareMeters', 'm²'),
22 | Unit.new('Datarate', 'BytesPerSecond', 'B/s'),
23 | Unit.new('Datasize', 'Bytes', 'B'),
24 | Unit.new('ElectricCurrent', 'Amperes', 'A'),
25 | Unit.new('Energy', 'Joules', 'J'),
26 | Unit.new('Force', 'Newtons', 'N'),
27 | Unit.new('Frequency', 'Hertz', 'Hz'),
28 | Unit.new('Length', 'Meters', 'm'),
29 | Unit.new('Mass', 'Grams', 'g'),
30 | Unit.new('Power', 'Watts', 'W'),
31 | Unit.new('Pressure', 'Pascals', 'Pa'),
32 | Unit.new('Speed', 'MetersPerSecond', 'm/s'),
33 | Unit.new('Voltage', 'Volts', 'V'),
34 | Unit.new('Volume', 'CubicMeters', 'm³'),
35 | Unit.new('AmountOfSubstance', 'Moles', 'mol'),
36 | Unit.new('ElectricalConductance', 'Siemens', 'S'),
37 | Unit.new('ElectricalResistance', 'Ohms', 'Ω'),
38 | Unit.new('Illuminance', 'Lux', 'lx'),
39 | Unit.new('LuminousFlux', 'Lumen', 'lm'),
40 | Unit.new('LuminousIntensity', 'Candela', 'cd')
41 | ].freeze
42 |
43 | write_go_file('siunit.go') do |out|
44 | out.write <<~HEADER
45 | package format
46 |
47 | import "github.com/martinlindhe/unit"
48 |
49 | // SIUnit formats a unit.Unit value to an appropriately scaled base unit.
50 | // For example, SIUnit(length) is equivalent to SI(length.Meters(), "m").
51 | // For non-base units (e.g. feet), use SI(length.Feet(), "ft").
52 | func SIUnit(val interface{}) (Value, bool) {
53 | \tswitch v := val.(type) {
54 | HEADER
55 |
56 | UNITS.each do |u|
57 | out.write <<~CASE
58 | \tcase unit.#{u.type}:
59 | \t\treturn SI(v.#{u.base}(), "#{u.unit}"), true
60 | CASE
61 | end
62 |
63 | out.write <<~FOOTER
64 | \t}
65 | \treturn Value{}, false
66 | }
67 | FOOTER
68 | end
69 |
70 | write_go_file('siunit_test.go') do |out|
71 | out.write <<~HEADER
72 | package format
73 |
74 | import "github.com/martinlindhe/unit"
75 |
76 | // An example for each unit that can be handled by the SIUint function. This
77 | // is used to test that all declared units in the unit package are handled.
78 | var siUnitsHandled = map[string]interface{}{
79 | HEADER
80 | UNITS.each do |u|
81 | out.puts "\t\"#{u.type}\": unit.#{u.type}(1),"
82 | end
83 | out.puts '}'
84 | end
85 |
--------------------------------------------------------------------------------
/format/siunit_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by siunit.rb; DO NOT EDIT.
16 |
17 | package format
18 |
19 | import "github.com/martinlindhe/unit"
20 |
21 | // An example for each unit that can be handled by the SIUint function. This
22 | // is used to test that all declared units in the unit package are handled.
23 | var siUnitsHandled = map[string]interface{}{
24 | "Acceleration": unit.Acceleration(1),
25 | "Angle": unit.Angle(1),
26 | "Area": unit.Area(1),
27 | "Datarate": unit.Datarate(1),
28 | "Datasize": unit.Datasize(1),
29 | "ElectricCurrent": unit.ElectricCurrent(1),
30 | "Energy": unit.Energy(1),
31 | "Force": unit.Force(1),
32 | "Frequency": unit.Frequency(1),
33 | "Length": unit.Length(1),
34 | "Mass": unit.Mass(1),
35 | "Power": unit.Power(1),
36 | "Pressure": unit.Pressure(1),
37 | "Speed": unit.Speed(1),
38 | "Voltage": unit.Voltage(1),
39 | "Volume": unit.Volume(1),
40 | "AmountOfSubstance": unit.AmountOfSubstance(1),
41 | "ElectricalConductance": unit.ElectricalConductance(1),
42 | "ElectricalResistance": unit.ElectricalResistance(1),
43 | "Illuminance": unit.Illuminance(1),
44 | "LuminousFlux": unit.LuminousFlux(1),
45 | "LuminousIntensity": unit.LuminousIntensity(1),
46 | }
47 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/soumya92/barista
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/coreos/go-systemd/v22 v22.5.0
7 | github.com/dustin/go-humanize v1.0.1
8 | github.com/fsnotify/fsnotify v1.7.0
9 | github.com/godbus/dbus/v5 v5.1.0
10 | github.com/jfreymuth/pulse v0.1.1
11 | github.com/lucasb-eyer/go-colorful v1.2.0
12 | github.com/martinlindhe/unit v0.0.0-20220817221856-f7b595b5f97e
13 | github.com/maximbaz/yubikey-touch-detector v0.0.0-20220526224932-a2161c415e60
14 | github.com/spf13/afero v1.11.0
15 | github.com/stretchr/testify v1.9.0
16 | github.com/vishvananda/netlink v1.1.0
17 | github.com/zalando/go-keyring v0.2.5
18 | golang.org/x/crypto v0.24.0
19 | golang.org/x/net v0.26.0
20 | golang.org/x/oauth2 v0.19.0
21 | golang.org/x/sys v0.21.0
22 | golang.org/x/time v0.5.0
23 | google.golang.org/api v0.174.0
24 | gopkg.in/yaml.v2 v2.4.0
25 | )
26 |
27 | require (
28 | cloud.google.com/go/auth v0.2.0 // indirect
29 | cloud.google.com/go/auth/oauth2adapt v0.2.0 // indirect
30 | cloud.google.com/go/compute/metadata v0.3.0 // indirect
31 | github.com/alessio/shellescape v1.4.1 // indirect
32 | github.com/danieljoos/wincred v1.2.0 // indirect
33 | github.com/davecgh/go-spew v1.1.1 // indirect
34 | github.com/esiqveland/notify v0.11.1 // indirect
35 | github.com/felixge/httpsnoop v1.0.4 // indirect
36 | github.com/go-logr/logr v1.4.1 // indirect
37 | github.com/go-logr/stdr v1.2.2 // indirect
38 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
39 | github.com/golang/protobuf v1.5.4 // indirect
40 | github.com/google/s2a-go v0.1.7 // indirect
41 | github.com/google/uuid v1.6.0 // indirect
42 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
43 | github.com/googleapis/gax-go/v2 v2.12.3 // indirect
44 | github.com/pmezard/go-difflib v1.0.0 // indirect
45 | github.com/rjeczalik/notify v0.9.2 // indirect
46 | github.com/scylladb/go-set v1.0.2 // indirect
47 | github.com/sirupsen/logrus v1.9.0 // indirect
48 | github.com/vishvananda/netns v0.0.1 // indirect
49 | github.com/vtolstov/go-ioctl v0.0.0-20151206205506-6be9cced4810 // indirect
50 | go.opencensus.io v0.24.0 // indirect
51 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
52 | go.opentelemetry.io/otel v1.24.0 // indirect
53 | go.opentelemetry.io/otel/metric v1.24.0 // indirect
54 | go.opentelemetry.io/otel/trace v1.24.0 // indirect
55 | golang.org/x/text v0.16.0 // indirect
56 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
57 | google.golang.org/grpc v1.63.0 // indirect
58 | google.golang.org/protobuf v1.33.0 // indirect
59 | gopkg.in/yaml.v3 v3.0.1 // indirect
60 | )
61 |
--------------------------------------------------------------------------------
/group/collapsing/collapsing_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package collapsing
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/bar"
21 | "github.com/soumya92/barista/base/click"
22 | "github.com/soumya92/barista/outputs"
23 | testBar "github.com/soumya92/barista/testing/bar"
24 | testModule "github.com/soumya92/barista/testing/module"
25 |
26 | "github.com/stretchr/testify/require"
27 | )
28 |
29 | func TestCollapsing(t *testing.T) {
30 | testBar.New(t)
31 |
32 | tm0 := testModule.New(t)
33 | tm1 := testModule.New(t)
34 | tm2 := testModule.New(t)
35 |
36 | grp, ctrl := Group(tm0, tm1, tm2)
37 | tm0.AssertNotStarted("on group creation")
38 | tm1.AssertNotStarted()
39 | tm2.AssertNotStarted()
40 |
41 | testBar.Run(grp)
42 | tm0.AssertStarted("on stream")
43 | tm1.AssertStarted()
44 | tm2.AssertStarted()
45 |
46 | tm0.OutputText("a")
47 | out := testBar.NextOutput()
48 | out.AssertText([]string{"+"}, "starts collapsed")
49 |
50 | tm1.OutputText("b")
51 | testBar.AssertNoOutput("while collapsed")
52 | require.False(t, ctrl.Expanded())
53 |
54 | out.At(0).LeftClick()
55 | testBar.NextOutput().AssertText([]string{">", "a", "b", "<"},
56 | "Expands on click, uses previous output")
57 |
58 | require.True(t, ctrl.Expanded())
59 | tm2.OutputText("c")
60 | testBar.NextOutput().AssertText([]string{">", "a", "b", "c", "<"},
61 | "Updates immediately when expanded")
62 |
63 | ctrl.Collapse()
64 | testBar.NextOutput().AssertText([]string{"+"})
65 |
66 | ctrl.Collapse()
67 | testBar.AssertNoOutput("no change when already collapsed")
68 |
69 | ctrl.Expand()
70 | out = testBar.NextOutput()
71 | out.AssertText([]string{">", "a", "b", "c", "<"},
72 | "Uses last output when re-expanded")
73 |
74 | out.At(2).LeftClick()
75 | tm1.AssertClicked("after expansion")
76 |
77 | out.At(4).LeftClick()
78 | testBar.NextOutput().AssertText([]string{"+"})
79 | require.False(t, ctrl.Expanded())
80 |
81 | ctrl.ButtonFunc(func(c Controller) (start, end bar.Output) {
82 | if c.Expanded() {
83 | return outputs.Text("->").OnClick(click.Left(c.Collapse)),
84 | outputs.Text("<-").OnClick(click.Left(c.Collapse))
85 | }
86 | return outputs.Text("<->").OnClick(click.Left(c.Expand)), nil
87 | })
88 | testBar.NextOutput().AssertText([]string{"<->"},
89 | "On button func change")
90 |
91 | ctrl.Toggle()
92 | testBar.NextOutput().AssertText([]string{"->", "a", "b", "c", "<-"},
93 | "On expansion with custom button func")
94 | }
95 |
--------------------------------------------------------------------------------
/group/cycling/cycling.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package cycling provides a group that continuously cycles between
16 | // all modules at a fixed interval.
17 | package cycling
18 |
19 | import (
20 | "sync"
21 | "time"
22 |
23 | "github.com/soumya92/barista/bar"
24 | "github.com/soumya92/barista/base/notifier"
25 | "github.com/soumya92/barista/group"
26 | l "github.com/soumya92/barista/logging"
27 | "github.com/soumya92/barista/timing"
28 | )
29 |
30 | // Controller provides an interface to control a collapsing group.
31 | type Controller interface {
32 | SetInterval(time.Duration)
33 | }
34 |
35 | // grouper implements a cycling grouper.
36 | type grouper struct {
37 | current int
38 | count int
39 | scheduler *timing.Scheduler
40 |
41 | sync.Mutex
42 | notifyCh <-chan struct{}
43 | notifyFn func()
44 | }
45 |
46 | // Group returns a new cycling group with the given interval,
47 | // and a linked Controller.
48 | func Group(interval time.Duration, m ...bar.Module) (bar.Module, Controller) {
49 | g := &grouper{count: len(m), scheduler: timing.NewScheduler()}
50 | g.scheduler.Every(interval)
51 | g.notifyFn, g.notifyCh = notifier.New()
52 | go g.cycle()
53 | return group.New(g, m...), g
54 | }
55 |
56 | func (g *grouper) Visible(idx int) bool { return g.current == idx }
57 |
58 | func (g *grouper) Buttons() (start, end bar.Output) { return nil, nil }
59 |
60 | func (g *grouper) Signal() <-chan struct{} { return g.notifyCh }
61 |
62 | func (g *grouper) cycle() {
63 | for range g.scheduler.C {
64 | g.Lock()
65 | l.Fine("%s %d++", l.ID(g), g.current)
66 | g.current = (g.current + 1) % g.count
67 | g.Unlock()
68 | g.notifyFn()
69 | }
70 | }
71 |
72 | func (g *grouper) SetInterval(interval time.Duration) {
73 | g.scheduler.Every(interval)
74 | }
75 |
--------------------------------------------------------------------------------
/group/cycling/cycling_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cycling
16 |
17 | import (
18 | "testing"
19 | "time"
20 |
21 | testBar "github.com/soumya92/barista/testing/bar"
22 | testModule "github.com/soumya92/barista/testing/module"
23 | "github.com/soumya92/barista/timing"
24 |
25 | "github.com/stretchr/testify/require"
26 | )
27 |
28 | func TestCycling(t *testing.T) {
29 | testBar.New(t)
30 |
31 | tm0 := testModule.New(t)
32 | tm1 := testModule.New(t)
33 | tm2 := testModule.New(t)
34 |
35 | grp, ctrl := Group(time.Second, tm0, tm1, tm2)
36 | tm0.AssertNotStarted("on group creation")
37 | tm1.AssertNotStarted()
38 | tm2.AssertNotStarted()
39 |
40 | testBar.Run(grp)
41 | tm0.AssertStarted("on stream")
42 | tm1.AssertStarted()
43 | tm2.AssertStarted()
44 |
45 | start := timing.Now()
46 |
47 | testBar.NextOutput().AssertEmpty("With no module output")
48 | tm0.OutputText("a")
49 | testBar.NextOutput().AssertText([]string{"a"},
50 | "on active module update")
51 | require.Equal(t, start, timing.Now(), "First output is immediate")
52 |
53 | testBar.Tick()
54 | testBar.NextOutput().AssertEmpty(
55 | "switched to module with no output")
56 | require.Equal(t, start.Add(time.Second), timing.Now())
57 |
58 | tm1.OutputText("b")
59 | testBar.NextOutput().AssertText([]string{"b"},
60 | "on active module update")
61 | require.Equal(t, start.Add(time.Second), timing.Now())
62 |
63 | tm2.OutputText("c")
64 | testBar.AssertNoOutput("inactive module update")
65 | require.Equal(t, start.Add(time.Second), timing.Now())
66 |
67 | ctrl.SetInterval(time.Minute)
68 | testBar.Tick()
69 | testBar.NextOutput().AssertText([]string{"c"},
70 | "switched to module with an update")
71 | require.Equal(t, start.Add(61*time.Second), timing.Now())
72 | }
73 |
--------------------------------------------------------------------------------
/group/following/following.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package following provides a group that always shows the output from
16 | // the most recently updated module in the set.
17 | package following
18 |
19 | import (
20 | "sync/atomic"
21 |
22 | "github.com/soumya92/barista/bar"
23 | "github.com/soumya92/barista/group"
24 | )
25 |
26 | // grouper implements a following grouper.
27 | type grouper struct{ current int64 }
28 |
29 | // Group returns a new following group.
30 | func Group(m ...bar.Module) bar.Module {
31 | return group.New(&grouper{}, m...)
32 | }
33 |
34 | func (g *grouper) Visible(idx int) bool {
35 | return atomic.LoadInt64(&g.current) == int64(idx)
36 | }
37 |
38 | func (g *grouper) Updated(idx int) {
39 | atomic.StoreInt64(&g.current, int64(idx))
40 | }
41 |
42 | func (g *grouper) Buttons() (start, end bar.Output) { return nil, nil }
43 |
--------------------------------------------------------------------------------
/group/following/following_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package following
16 |
17 | import (
18 | "testing"
19 |
20 | testBar "github.com/soumya92/barista/testing/bar"
21 | testModule "github.com/soumya92/barista/testing/module"
22 | )
23 |
24 | func TestFollowing(t *testing.T) {
25 | testBar.New(t)
26 |
27 | tm0 := testModule.New(t)
28 | tm1 := testModule.New(t)
29 | tm2 := testModule.New(t)
30 |
31 | grp := Group(tm0, tm1, tm2)
32 | tm0.AssertNotStarted("on group creation")
33 | tm1.AssertNotStarted()
34 | tm2.AssertNotStarted()
35 |
36 | testBar.Run(grp)
37 | tm0.AssertStarted("on stream")
38 | tm1.AssertStarted()
39 | tm2.AssertStarted()
40 |
41 | testBar.NextOutput().AssertEmpty("With no module output")
42 |
43 | tm0.OutputText("a")
44 | testBar.NextOutput().AssertText([]string{"a"},
45 | "on module update")
46 |
47 | tm1.OutputText("b")
48 | testBar.NextOutput().AssertText([]string{"b"})
49 |
50 | tm2.OutputText("c")
51 | testBar.NextOutput().AssertText([]string{"c"})
52 | }
53 |
--------------------------------------------------------------------------------
/group/switching/switching_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package switching
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/bar"
21 | "github.com/soumya92/barista/base/click"
22 | "github.com/soumya92/barista/outputs"
23 | testBar "github.com/soumya92/barista/testing/bar"
24 | testModule "github.com/soumya92/barista/testing/module"
25 |
26 | "github.com/stretchr/testify/require"
27 | )
28 |
29 | func TestSwitching(t *testing.T) {
30 | testBar.New(t)
31 |
32 | tm0 := testModule.New(t)
33 | tm1 := testModule.New(t)
34 | tm2 := testModule.New(t)
35 |
36 | grp, ctrl := Group(tm0, tm1, tm2)
37 | tm0.AssertNotStarted("on group creation")
38 | tm1.AssertNotStarted()
39 | tm2.AssertNotStarted()
40 |
41 | testBar.Run(grp)
42 | tm0.AssertStarted("on stream")
43 | tm1.AssertStarted()
44 | tm2.AssertStarted()
45 |
46 | require.Equal(t, 3, ctrl.Count())
47 | require.Equal(t, 0, ctrl.Current())
48 | out := testBar.NextOutput()
49 | out.AssertText([]string{">"},
50 | "with no output from module")
51 |
52 | out.At(0).LeftClick()
53 | testBar.NextOutput().AssertText([]string{"<", ">"})
54 | require.Equal(t, 1, ctrl.Current())
55 |
56 | ctrl.Next()
57 | out = testBar.NextOutput()
58 | out.AssertText([]string{"<"})
59 |
60 | tm1.OutputText("a")
61 | testBar.AssertNoOutput("on hidden module update")
62 |
63 | out.At(0).LeftClick()
64 | testBar.NextOutput().AssertText([]string{"<", "a", ">"})
65 |
66 | ctrl.ButtonFunc(func(c Controller) (start, end bar.Output) {
67 | return outputs.Text("/*").OnClick(click.Left(c.Previous)),
68 | outputs.Text("*/").OnClick(click.Left(c.Next))
69 | })
70 | testBar.NextOutput().AssertText([]string{"/*", "a", "*/"})
71 |
72 | tm0.OutputText("0")
73 | testBar.AssertNoOutput("on hidden module update")
74 |
75 | ctrl.Show(0)
76 | out = testBar.NextOutput()
77 | out.AssertText([]string{"/*", "0", "*/"})
78 | out.At(0).LeftClick()
79 | testBar.NextOutput().AssertText([]string{"/*", "*/"})
80 | require.Equal(t, 2, ctrl.Current(), "wraparound on left")
81 |
82 | ctrl.Next()
83 | testBar.NextOutput().AssertText([]string{"/*", "0", "*/"})
84 | require.Equal(t, 0, ctrl.Current(), "wraparound on right")
85 | }
86 |
--------------------------------------------------------------------------------
/logging/nop_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // +build !baristadebuglog
16 |
17 | package logging
18 |
19 | import (
20 | "log"
21 | "testing"
22 |
23 | "github.com/soumya92/barista/testing/mockio"
24 |
25 | "github.com/stretchr/testify/require"
26 | )
27 |
28 | func TestNopMethods(t *testing.T) {
29 | SetOutput(mockio.Stdout())
30 | SetFlags(log.Lshortfile)
31 | Log("foo: %d", 42)
32 | Fine("bar: %g", 3.14159)
33 | require.Equal(t, "", ID(4))
34 | Label(&struct{}{}, "empty")
35 | Labelf(&struct{}{}, "empty: %b", true)
36 | Attach(t, 4, "->int")
37 | Attachf(t, 1.0, "->float:%g", 1.0)
38 | Register(t, "Fail", "FailNow")
39 | }
40 |
--------------------------------------------------------------------------------
/modules/bluetooth/adapter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package bluetooth provides modules for watching the status of Bluetooth adapters and devices.
16 | package bluetooth
17 |
18 | import (
19 | "github.com/soumya92/barista/bar"
20 | "github.com/soumya92/barista/base/value"
21 | "github.com/soumya92/barista/base/watchers/dbus"
22 | )
23 |
24 | // AdapterModule represents a Bluetooth bar module.
25 | type AdapterModule struct {
26 | adapter string
27 | outputFunc value.Value
28 | }
29 |
30 | // AdapterInfo represents a Bluetooth adapters information.
31 | type AdapterInfo struct {
32 | Name string
33 | Alias string
34 | Address string
35 | Discoverable bool
36 | Pairable bool
37 | Powered bool
38 | Discovering bool
39 | }
40 |
41 | // replaced in tests.
42 | var busType = dbus.System
43 |
44 | // DefaultAdapter constructs an instance of the bluetooth module using the first adapter ("hci0").
45 | func DefaultAdapter() *AdapterModule {
46 | return Adapter("hci0")
47 | }
48 |
49 | // Adapter constructs an instance of the bluetooth module with the provided device name (ex. "hci1").
50 | func Adapter(name string) *AdapterModule {
51 | return &AdapterModule{adapter: name}
52 | }
53 |
54 | // Output configures a module to display the output of a user-defined function.
55 | func (bt *AdapterModule) Output(outputFunc func(AdapterInfo) bar.Output) *AdapterModule {
56 | bt.outputFunc.Set(outputFunc)
57 | return bt
58 | }
59 |
60 | // Stream starts the module.
61 | func (bt *AdapterModule) Stream(sink bar.Sink) {
62 | w := dbus.WatchProperties(
63 | busType,
64 | "org.bluez",
65 | "/org/bluez/"+bt.adapter,
66 | "org.bluez.Adapter1",
67 | ).
68 | Add("Name", "Alias", "Address", "Discoverable", "Pairable", "Powered", "Discovering")
69 | defer w.Unsubscribe()
70 |
71 | outputFunc := bt.outputFunc.Get().(func(AdapterInfo) bar.Output)
72 | nextOutputFunc, done := bt.outputFunc.Subscribe()
73 | defer done()
74 |
75 | info := getAdapterInfo(w)
76 | for {
77 | sink.Output(outputFunc(info))
78 | select {
79 | case <-w.Updates:
80 | info = getAdapterInfo(w)
81 | case <-nextOutputFunc:
82 | outputFunc = bt.outputFunc.Get().(func(AdapterInfo) bar.Output)
83 | }
84 | }
85 | }
86 |
87 | func getAdapterInfo(w *dbus.PropertiesWatcher) AdapterInfo {
88 | i := AdapterInfo{}
89 | props := w.Get()
90 |
91 | if name, ok := props["Name"].(string); ok {
92 | i.Name = name
93 | }
94 | i.Alias, _ = props["Alias"].(string)
95 | i.Address, _ = props["Address"].(string)
96 | i.Discoverable, _ = props["Discoverable"].(bool)
97 | i.Pairable, _ = props["Pairable"].(bool)
98 | i.Powered, _ = props["Powered"].(bool)
99 | i.Discovering, _ = props["Discovering"].(bool)
100 |
101 | return i
102 | }
103 |
--------------------------------------------------------------------------------
/modules/bluetooth/adapter_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package bluetooth
16 |
17 | import (
18 | "testing"
19 |
20 | godbus "github.com/godbus/dbus/v5"
21 |
22 | "github.com/soumya92/barista/bar"
23 | "github.com/soumya92/barista/base/watchers/dbus"
24 | "github.com/soumya92/barista/outputs"
25 | testBar "github.com/soumya92/barista/testing/bar"
26 | )
27 |
28 | func init() {
29 | busType = dbus.Test
30 | }
31 |
32 | func TestAdapter(t *testing.T) {
33 | testBar.New(t)
34 |
35 | adapterName := "hci0"
36 | adapter := setupTestAdapter(adapterName)
37 | adapter.SetProperties(map[string]interface{}{
38 | "Name": "foo",
39 | "Alias": "foo alias",
40 | "Address": "28:C2:DD:8B:73:8C",
41 | "Discoverable": false,
42 | "Pairable": true,
43 | "Powered": true,
44 | "Discovering": false,
45 | }, dbus.SignalTypeNone)
46 |
47 | btModule := Adapter(adapterName)
48 | btModule.Output(func(i AdapterInfo) bar.Output {
49 | state := "OFF"
50 | if i.Powered {
51 | state = "ON"
52 | }
53 | return outputs.Textf("%s: %s", i.Name, state)
54 | })
55 | testBar.Run(btModule)
56 |
57 | testBar.LatestOutput().AssertText([]string{
58 | "foo: ON",
59 | })
60 | }
61 |
62 | func TestAdapterDisconnect(t *testing.T) {
63 | testBar.New(t)
64 |
65 | adapterName := "hci0"
66 | adapter := setupTestAdapter(adapterName)
67 | adapter.SetProperties(map[string]interface{}{
68 | "Name": "foo",
69 | "Alias": "foo alias",
70 | "Address": "28:C2:DD:8B:73:8C",
71 | "Discoverable": false,
72 | "Pairable": true,
73 | "Powered": true,
74 | "Discovering": false,
75 | }, dbus.SignalTypeNone)
76 |
77 | btModule := DefaultAdapter()
78 | btModule.Output(func(i AdapterInfo) bar.Output {
79 | state := "OFF"
80 | if i.Powered {
81 | state = "ON"
82 | }
83 | return outputs.Textf("%s", state)
84 | })
85 | testBar.Run(btModule)
86 |
87 | testBar.LatestOutput().AssertText([]string{
88 | "ON",
89 | })
90 |
91 | adapter.SetPropertyForTest("Powered", false, dbus.SignalTypeChanged)
92 |
93 | testBar.LatestOutput().AssertText([]string{
94 | "OFF",
95 | })
96 | }
97 |
98 | func setupTestAdapter(adapterName string) *dbus.TestBusObject {
99 | bus := dbus.SetupTestBus()
100 | bluez := bus.RegisterService("org.bluez")
101 |
102 | adapterObjPath := godbus.ObjectPath("/org/bluez/" + adapterName)
103 | adapter := bluez.Object(adapterObjPath, "org.bluez.Adapter1")
104 |
105 | return adapter
106 | }
107 |
--------------------------------------------------------------------------------
/modules/counter/counter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package counter demonstrates an extremely simple i3bar module that shows a counter
16 | // which can be chnaged by clicking on it. It showcases the asynchronous nature of
17 | // i3bar modules when written in go.
18 | package counter
19 |
20 | import (
21 | "github.com/soumya92/barista/bar"
22 | "github.com/soumya92/barista/base/value"
23 | l "github.com/soumya92/barista/logging"
24 | "github.com/soumya92/barista/outputs"
25 | )
26 |
27 | // Module represents a "counter" module that displays a count
28 | // in the given format, and adjusts the count on click/scroll.
29 | // This module exemplifies the event-based architecture of barista.
30 | type Module struct {
31 | count value.Value // of int
32 | format value.Value // of string
33 | }
34 |
35 | // New constructs a new counter module.
36 | func New(format string) *Module {
37 | m := &Module{}
38 | l.Register(m, "format", "count")
39 | m.count.Set(0)
40 | m.format.Set(format)
41 | return m
42 | }
43 |
44 | // Stream starts the module.
45 | func (m *Module) Stream(s bar.Sink) {
46 | count := m.count.Get().(int)
47 | countSub, done := m.count.Subscribe()
48 | defer done()
49 | format := m.format.Get().(string)
50 | formatSub, done := m.format.Subscribe()
51 | defer done()
52 | for {
53 | s.Output(outputs.Textf(format, count).OnClick(m.click))
54 | select {
55 | case <-countSub:
56 | count = m.count.Get().(int)
57 | case <-formatSub:
58 | format = m.format.Get().(string)
59 | }
60 | }
61 | }
62 |
63 | // Format sets the output format.
64 | // The given format string will receive the counter value
65 | // as the only argument.
66 | func (m *Module) Format(format string) *Module {
67 | m.format.Set(format)
68 | return m
69 | }
70 |
71 | // Click handles clicks on the module output.
72 | func (m *Module) click(e bar.Event) {
73 | current := m.count.Get().(int)
74 | switch e.Button {
75 | case bar.ButtonLeft, bar.ScrollDown, bar.ScrollLeft, bar.ButtonBack:
76 | current--
77 | case bar.ButtonRight, bar.ScrollUp, bar.ScrollRight, bar.ButtonForward:
78 | current++
79 | }
80 | m.count.Set(current)
81 | }
82 |
--------------------------------------------------------------------------------
/modules/counter/counter_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package counter
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/bar"
21 | testBar "github.com/soumya92/barista/testing/bar"
22 | )
23 |
24 | func TestCounter(t *testing.T) {
25 | ctr := New("C:%d")
26 | testBar.New(t)
27 | testBar.Run(ctr)
28 |
29 | out := testBar.NextOutput()
30 | out.AssertText([]string{"C:0"}, "on start")
31 |
32 | testBar.AssertNoOutput("without any interaction")
33 |
34 | out.At(0).Click(bar.Event{Button: bar.ScrollUp})
35 | out = testBar.NextOutput()
36 | out.AssertText([]string{"C:1"}, "on click")
37 |
38 | out.At(0).Click(bar.Event{Button: bar.ScrollDown})
39 | out = testBar.NextOutput()
40 | out.AssertText([]string{"C:0"}, "on click")
41 |
42 | out.At(0).Click(bar.Event{Button: bar.ButtonBack})
43 | out = testBar.NextOutput()
44 | out.AssertText([]string{"C:-1"}, "on click")
45 |
46 | ctr.Format("=%d=")
47 | out = testBar.NextOutput()
48 | out.AssertText([]string{"=-1="}, "on format change")
49 |
50 | out.At(0).Click(bar.Event{Button: bar.ScrollUp})
51 | testBar.NextOutput().AssertText(
52 | []string{"=0="}, "on click after format change")
53 | }
54 |
--------------------------------------------------------------------------------
/modules/cputemp/cputemp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package cputemp implements an i3bar module that shows the CPU temperature.
16 | package cputemp
17 |
18 | import (
19 | "github.com/soumya92/barista/modules/internal/temperature"
20 | )
21 |
22 | // Module represents a cputemp bar module. It supports setting the output
23 | // format, click handler, update frequency, and urgency/colour functions.
24 | type Module = temperature.Module
25 |
26 | // Zone constructs an instance of the cputemp module for the specified zone.
27 | // The file /sys/class/thermal//temp should return cpu temp in 1/1000 deg C.
28 | func Zone(thermalZone string) *Module {
29 | return temperature.ThermalZone(thermalZone)
30 | }
31 |
32 | // OfType constructs an instance of the cputemp module for the *first* available
33 | // sensor of the given type. "x86_pkg_temp" usually represents the temperature
34 | // of the actual CPU package, while others may be available depending on the
35 | // system, e.g. "iwlwifi" for wifi, or "acpitz" for the motherboard.
36 | func OfType(typ string) *Module {
37 | return temperature.ThermalOfType(typ)
38 | }
39 |
40 | // New constructs an instance of the cputemp module for zone type "x86_pkg_temp".
41 | // Returns nil of the x86_pkg_temp thermal zone is unavailable.
42 | func New() *Module {
43 | return temperature.NewDefaultThermal()
44 | }
45 |
--------------------------------------------------------------------------------
/modules/funcs/funcs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package funcs provides the ability to construct i3bar modules from simple Funcs.
16 | package funcs
17 |
18 | import (
19 | "github.com/soumya92/barista/base/notifier"
20 | "github.com/soumya92/barista/timing"
21 | "time"
22 |
23 | "github.com/soumya92/barista/bar"
24 | )
25 |
26 | // Func receives a bar.Sink and uses it for output.
27 | type Func func(bar.Sink)
28 |
29 | // Once constructs a bar module that runs the given function once.
30 | // Useful if the function loops internally.
31 | func Once(f Func) *OnceModule {
32 | return &OnceModule{Func: f}
33 | }
34 |
35 | // OnceModule represents a bar.Module that runs a function once.
36 | // If the function sets an error output, it will be restarted on
37 | // the next click.
38 | type OnceModule struct {
39 | Func
40 | }
41 |
42 | // Stream starts the module.
43 | func (o *OnceModule) Stream(s bar.Sink) {
44 | forever := make(chan struct{})
45 | o.Func(s)
46 | <-forever
47 | }
48 |
49 | // OnClick constructs a bar module that runs the given function
50 | // when clicked. The function is given a Channel to allow
51 | // multiple outputs (e.g. Loading... Done), and when the function
52 | // returns, the next click will call it again.
53 | func OnClick(f Func) *OnclickModule {
54 | return &OnclickModule{f}
55 | }
56 |
57 | // OnclickModule represents a bar.Module that runs a function and
58 | // marks the module as finished, causing the next click to start the
59 | // module again.
60 | type OnclickModule struct {
61 | Func
62 | }
63 |
64 | // Stream starts the module.
65 | func (o OnclickModule) Stream(s bar.Sink) {
66 | o.Func(s)
67 | }
68 |
69 | // Every constructs a bar module that repeatedly runs the given function.
70 | // Useful if the function needs to poll a resource for output.
71 | func Every(d time.Duration, f Func) *RepeatingModule {
72 | r := &RepeatingModule{fn: f, d: d}
73 | r.scheduler = timing.NewScheduler().Every(d)
74 | r.notifyFn, r.notifyCh = notifier.New()
75 |
76 | return r
77 | }
78 |
79 | // RepeatingModule represents a bar.Module that runs a function at a fixed
80 | // interval (while accounting for bar paused/resumed state).
81 | type RepeatingModule struct {
82 | notifyCh <-chan struct{}
83 | notifyFn func()
84 | fn Func
85 | d time.Duration
86 | scheduler *timing.Scheduler
87 | }
88 |
89 | // Stream starts the module.
90 | func (r *RepeatingModule) Stream(s bar.Sink) {
91 | r.fn(s)
92 | for {
93 | select {
94 | case <-r.notifyCh:
95 | r.scheduler.Every(r.d)
96 | r.fn(s)
97 | case <-r.scheduler.C:
98 | r.fn(s)
99 | }
100 | }
101 | }
102 |
103 | // Refresh forces a refresh of the data being displayed.
104 | func (r *RepeatingModule) Refresh() {
105 | r.notifyFn()
106 | }
107 |
--------------------------------------------------------------------------------
/modules/hwmon/hwmon.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package hwmon implements an i3bar module that shows the temperature from /sys/class/hwmon
16 | package hwmon
17 |
18 | import (
19 | "github.com/soumya92/barista/modules/internal/temperature"
20 | )
21 |
22 | // Module represents a hwmon bar module. It supports setting the output
23 | // format, click handler, update frequency, and urgency/colour functions.
24 | type Module = temperature.Module
25 |
26 | // OfNameAndLabel finds a sensor given hwmon name and label.
27 | //
28 | // For example, if you have /sys/class/hwmon/hwmon4/name containing
29 | // "k10temp", and /sys/class/hwmon/hwmon4/temp1_label containing
30 | // "Tctl", the former would be name, and the latter would be label.
31 | func OfNameAndLabel(name string, label string) *Module {
32 | return temperature.HwmonOfNameAndLabel(name, label)
33 | }
34 |
--------------------------------------------------------------------------------
/modules/internal/temperature/hwmon.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package temperature
16 |
17 | import (
18 | "path/filepath"
19 | "strings"
20 |
21 | "github.com/spf13/afero"
22 | )
23 |
24 | // HwmonOfNameAndLabel finds a sensor given hwmon name and label.
25 | //
26 | // For example, if you have /sys/class/hwmon/hwmon4/name containing
27 | // "k10temp", and /sys/class/hwmon/hwmon4/temp1_label containing
28 | // "Tctl", the former would be name, and the latter would be label.
29 | func HwmonOfNameAndLabel(name string, label string) *Module {
30 | baseDir := "/sys/class/hwmon"
31 | files, _ := afero.ReadDir(fs, baseDir)
32 | for _, file := range files {
33 | n, _ := afero.ReadFile(fs, filepath.Join(baseDir, file.Name(), "name"))
34 | if strings.TrimSpace(string(n)) == name {
35 | baseDir := filepath.Join(baseDir, file.Name())
36 | files, _ := afero.ReadDir(fs, filepath.Join(baseDir))
37 | for _, file := range files {
38 | if strings.HasSuffix(file.Name(), "_label") {
39 | l, _ := afero.ReadFile(fs, filepath.Join(baseDir, file.Name()))
40 | if strings.TrimSpace(string(l)) == label {
41 | filename := file.Name()
42 | filename = strings.TrimSuffix(filename, "_label") + "_input"
43 | return newModule(filepath.Join(baseDir, filename))
44 | }
45 | }
46 | }
47 | }
48 | }
49 | return newModule("")
50 | }
51 |
--------------------------------------------------------------------------------
/modules/internal/temperature/hwmon_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package temperature
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/spf13/afero"
21 | "github.com/stretchr/testify/assert"
22 | "github.com/stretchr/testify/require"
23 | )
24 |
25 | func TestHwmonDetection(t *testing.T) {
26 | fs = afero.NewMemMapFs()
27 | contents := []struct {
28 | path string
29 | data string
30 | }{
31 | {"/sys/class/hwmon/hwmon0/name", "k10temp\n"},
32 | {"/sys/class/hwmon/hwmon0/temp1_label", "Tctl\n"},
33 | {"/sys/class/hwmon/hwmon0/temp1_input", "63500\n"},
34 | {"/sys/class/hwmon/hwmon1/name", "amdgpu\n"},
35 | {"/sys/class/hwmon/hwmon1/temp1_label", "edge\n"},
36 | {"/sys/class/hwmon/hwmon1/temp2_label", "junction\n"},
37 | {"/sys/class/hwmon/hwmon1/temp3_label", "mem\n"},
38 | {"/sys/class/hwmon/hwmon1/temp1_input", "56000\n"},
39 | {"/sys/class/hwmon/hwmon1/temp2_input", "56000\n"},
40 | {"/sys/class/hwmon/hwmon1/temp3_input", "56000\n"},
41 | }
42 | for _, content := range contents {
43 | err := afero.WriteFile(fs, content.path, []byte(content.data), 0644)
44 | require.NoError(t, err)
45 | }
46 | // This only tests detection code.
47 | // functional tests are in thermalzone_test.go
48 | var m *Module
49 | m = HwmonOfNameAndLabel("k10temp", "Tctl")
50 | assert.Equal(t, "/sys/class/hwmon/hwmon0/temp1_input", m.thermalFile)
51 | m = HwmonOfNameAndLabel("amdgpu", "junction")
52 | assert.Equal(t, "/sys/class/hwmon/hwmon1/temp2_input", m.thermalFile)
53 | }
54 |
--------------------------------------------------------------------------------
/modules/internal/temperature/module.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, 2022 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package temperature
16 |
17 | import (
18 | "strconv"
19 | "strings"
20 | "time"
21 |
22 | "github.com/soumya92/barista/bar"
23 | "github.com/soumya92/barista/base/value"
24 | l "github.com/soumya92/barista/logging"
25 | "github.com/soumya92/barista/outputs"
26 | "github.com/soumya92/barista/timing"
27 |
28 | "github.com/martinlindhe/unit"
29 | "github.com/spf13/afero"
30 | )
31 |
32 | // Module represents a cputemp bar module. It supports setting the output
33 | // format, click handler, update frequency, and urgency/colour functions.
34 | type Module struct {
35 | thermalFile string
36 | scheduler *timing.Scheduler
37 | outputFunc value.Value // of func(unit.Temperature) bar.Output
38 | }
39 |
40 | func newModule(thermalFile string) *Module {
41 | m := &Module{
42 | thermalFile: thermalFile,
43 | scheduler: timing.NewScheduler(),
44 | }
45 | l.Label(m, thermalFile)
46 | l.Register(m, "scheduler", "format")
47 | m.RefreshInterval(3 * time.Second)
48 | // Default output, if no function is specified later.
49 | m.Output(func(t unit.Temperature) bar.Output {
50 | return outputs.Textf("%.1f℃", t.Celsius())
51 | })
52 | return m
53 | }
54 |
55 | // Output configures a module to display the output of a user-defined function.
56 | func (m *Module) Output(outputFunc func(unit.Temperature) bar.Output) *Module {
57 | m.outputFunc.Set(outputFunc)
58 | return m
59 | }
60 |
61 | // RefreshInterval configures the polling frequency for cpu temperatures.
62 | // Note: updates might still be less frequent if the temperature does not change.
63 | func (m *Module) RefreshInterval(interval time.Duration) *Module {
64 | m.scheduler.Every(interval)
65 | return m
66 | }
67 |
68 | var fs = afero.NewOsFs()
69 |
70 | // Stream starts the module.
71 | func (m *Module) Stream(s bar.Sink) {
72 | temp, err := getTemperature(m.thermalFile)
73 | outputFunc := m.outputFunc.Get().(func(unit.Temperature) bar.Output)
74 | nextOutputFunc, done := m.outputFunc.Subscribe()
75 | defer done()
76 | for {
77 | if s.Error(err) {
78 | return
79 | }
80 | s.Output(outputFunc(temp))
81 | select {
82 | case <-m.scheduler.C:
83 | temp, err = getTemperature(m.thermalFile)
84 | case <-nextOutputFunc:
85 | outputFunc = m.outputFunc.Get().(func(unit.Temperature) bar.Output)
86 | }
87 | }
88 | }
89 |
90 | func getTemperature(thermalFile string) (unit.Temperature, error) {
91 | bytes, err := afero.ReadFile(fs, thermalFile)
92 | if err != nil {
93 | return 0, err
94 | }
95 | value := strings.TrimSpace(string(bytes))
96 | milliC, err := strconv.Atoi(value)
97 | if err != nil {
98 | return 0, err
99 | }
100 | return unit.FromCelsius(float64(milliC) / 1000.0), nil
101 | }
102 |
--------------------------------------------------------------------------------
/modules/internal/temperature/thermalzone.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package temperature
16 |
17 | import (
18 | "fmt"
19 | "strings"
20 |
21 | "github.com/spf13/afero"
22 | )
23 |
24 | // ThermalZone constructs an instance of the temperature module for the specified zone.
25 | // The file /sys/class/thermal//temp should return cpu temp in 1/1000 deg C.
26 | func ThermalZone(thermalZone string) *Module {
27 | return newModule(fmt.Sprintf("/sys/class/thermal/%s/temp", thermalZone))
28 | }
29 |
30 | // ThermalOfType constructs an instance of the cputemp module for the *first*
31 | // available sensor of the given type. "x86_pkg_temp" usually represents the temperature
32 | // of the actual CPU package, while others may be available depending on the
33 | // system, e.g. "iwlwifi" for wifi, or "acpitz" for the motherboard.
34 | func ThermalOfType(typ string) *Module {
35 | files, _ := afero.ReadDir(fs, "/sys/class/thermal")
36 | for _, file := range files {
37 | name := file.Name()
38 | typFile := fmt.Sprintf("/sys/class/thermal/%s/type", name)
39 | typBytes, _ := afero.ReadFile(fs, typFile)
40 | if strings.TrimSpace(string(typBytes)) == typ {
41 | return ThermalZone(name)
42 | }
43 | }
44 | return ThermalZone("")
45 | }
46 |
47 | // NewDefaultThermal constructs an instance of the cputemp module for zone type
48 | // "x86_pkg_temp". Returns nil of the x86_pkg_temp thermal zone is unavailable.
49 | func NewDefaultThermal() *Module {
50 | return ThermalOfType("x86_pkg_temp")
51 | }
52 |
--------------------------------------------------------------------------------
/modules/meta/multicast/multicast.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package multicast provides a method to convert any bar.Module into one that
16 | // can be added to the bar multiple times. When combined with group.Simple, this
17 | // allows multiple combinations of modules without creating extra instances.
18 | package multicast
19 |
20 | import (
21 | "sync"
22 |
23 | "github.com/soumya92/barista/bar"
24 | "github.com/soumya92/barista/base/value"
25 | "github.com/soumya92/barista/core"
26 | "github.com/soumya92/barista/sink"
27 | )
28 |
29 | type module struct {
30 | *value.Value
31 | start func() // called on Stream(), to ensure backing module is started
32 | }
33 |
34 | // Stream starts the module, and tries to stream the original module as well.
35 | func (m module) Stream(sink bar.Sink) {
36 | go m.start()
37 | for {
38 | next := m.Next()
39 | s, _ := m.Get().(bar.Segments)
40 | sink.Output(s)
41 | <-next
42 | }
43 | }
44 |
45 | // New creates a multicast module that can be added to the bar any number of
46 | // times, and mirrors the output of the original module at each location.
47 | // IMPORTANT: The original module must not be added to the bar.
48 | func New(original bar.Module) bar.Module {
49 | output, sink := sink.Value()
50 | coreModule := core.NewModule(original)
51 | var once sync.Once
52 | start := func() {
53 | once.Do(func() {
54 | coreModule.Stream(sink)
55 | })
56 | }
57 | return module{output, start}
58 | }
59 |
--------------------------------------------------------------------------------
/modules/meta/multicast/multicast_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package multicast
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/outputs"
21 | testBar "github.com/soumya92/barista/testing/bar"
22 | testModule "github.com/soumya92/barista/testing/module"
23 | )
24 |
25 | func TestMulticast(t *testing.T) {
26 | testBar.New(t)
27 |
28 | original := testModule.New(t)
29 | mcast := New(original)
30 |
31 | original.AssertNotStarted("when wrapped")
32 | testBar.Run(mcast, mcast, mcast)
33 | original.AssertStarted("on stream of multicast modules")
34 |
35 | testBar.LatestOutput().AssertEmpty("On start with no output")
36 |
37 | original.OutputText("foo")
38 | out := testBar.LatestOutput()
39 | out.AssertText([]string{"foo", "foo", "foo"},
40 | "All copies update with new output")
41 |
42 | out.At(1).LeftClick()
43 | original.AssertClicked("clicked on multicasted output")
44 |
45 | original.Output(outputs.Group(
46 | outputs.Text("test"), outputs.Text("baz"),
47 | ))
48 | testBar.LatestOutput().AssertText(
49 | []string{"test", "baz", "test", "baz", "test", "baz"},
50 | "multiple segments from original module")
51 |
52 | original.Output(nil)
53 | testBar.LatestOutput().AssertEmpty("empty output from original")
54 |
55 | original.Output(outputs.Errorf("something went wrong"))
56 | testBar.LatestOutput().AssertError("On error in original")
57 |
58 | original.Close()
59 | out = testBar.LatestOutput()
60 | original.AssertNotStarted()
61 |
62 | out.At(0).LeftClick()
63 | original.AssertStarted("On click of multicast output")
64 | }
65 |
--------------------------------------------------------------------------------
/modules/meta/slot/slot.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package slot provides multiple slots for a single module, allowing it to be
16 | // moved between various positions on the bar. When used carefully, this can be
17 | // useful for conveying limited information by re-ordering modules, but it has
18 | // the potential to become too distracting if overused.
19 | package slot
20 |
21 | import (
22 | "sync"
23 |
24 | "github.com/soumya92/barista/bar"
25 | "github.com/soumya92/barista/base/value"
26 | "github.com/soumya92/barista/sink"
27 | )
28 |
29 | // Slotter provides the ability to display the output of a module into named
30 | // slots, and allows changing the active slot at runtime to effectively move
31 | // the module on the bar.
32 | type Slotter struct {
33 | module bar.Module
34 | stream sync.Once
35 | activeSlot value.Value // of string
36 |
37 | sink bar.Sink
38 | lastOutput *value.Value // of bar.Segments
39 | }
40 |
41 | // New creates a slotter for the given module. The module is 'consumed' by
42 | // this operation and should not be used except through slots created from the
43 | // returned Slotter.
44 | func New(m bar.Module) *Slotter {
45 | s := &Slotter{module: m}
46 | s.lastOutput, s.sink = sink.Value()
47 | s.activeSlot.Set("")
48 | return s
49 | }
50 |
51 | // Slot creates a named slot for the module output.
52 | func (s *Slotter) Slot(name string) bar.Module {
53 | return &slotModule{s, name}
54 | }
55 |
56 | // Activate moves the module output to the named slot.
57 | func (s *Slotter) Activate(slotName string) {
58 | s.activeSlot.Set(slotName)
59 | }
60 |
61 | type slotModule struct {
62 | *Slotter
63 | slotName string
64 | }
65 |
66 | func (s *slotModule) Stream(sink bar.Sink) {
67 | go s.stream.Do(func() { s.module.Stream(s.sink) })
68 |
69 | activeSub, done := s.activeSlot.Subscribe()
70 | defer done()
71 | active := s.activeSlot.Get().(string)
72 |
73 | outputSub, done := s.lastOutput.Subscribe()
74 | defer done()
75 | out := s.lastOutput.Get().(bar.Segments)
76 |
77 | hasOutput := false
78 | outputChanged := true
79 |
80 | for {
81 | if active == s.slotName {
82 | if !hasOutput || outputChanged {
83 | sink(out)
84 | hasOutput = true
85 | }
86 | } else {
87 | if hasOutput {
88 | sink(nil)
89 | hasOutput = false
90 | }
91 | }
92 | outputChanged = false
93 | select {
94 | case <-activeSub:
95 | active = s.activeSlot.Get().(string)
96 | case <-outputSub:
97 | out = s.lastOutput.Get().(bar.Segments)
98 | outputChanged = true
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/modules/meta/slot/slot_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package slot
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/modules/static"
21 | "github.com/soumya92/barista/outputs"
22 | testBar "github.com/soumya92/barista/testing/bar"
23 | testModule "github.com/soumya92/barista/testing/module"
24 | )
25 |
26 | func TestReformat(t *testing.T) {
27 | testBar.New(t)
28 | original := testModule.New(t)
29 | slotter := New(original)
30 | original.AssertNotStarted("on construction of slotter module")
31 |
32 | slotA := slotter.Slot("A")
33 | slotB := slotter.Slot("B")
34 | slotC := slotter.Slot("C")
35 | testBar.Run(
36 | static.New(outputs.Text("<")),
37 | slotA,
38 | static.New(outputs.Text("-")),
39 | slotB,
40 | static.New(outputs.Text("-")),
41 | slotC,
42 | static.New(outputs.Text(">")),
43 | )
44 |
45 | testBar.LatestOutput(0, 2, 4, 6).AssertText([]string{"<", "-", "-", ">"})
46 | original.AssertStarted("on stream of slots")
47 |
48 | original.OutputText("foo")
49 | testBar.AssertNoOutput("not showing in any slot")
50 |
51 | slotter.Activate("A")
52 | testBar.NextOutput("On slot change").AssertText(
53 | []string{"<", "foo", "-", "-", ">"})
54 |
55 | slotter.Activate("B")
56 | testBar.LatestOutput(1, 3).AssertText(
57 | []string{"<", "-", "foo", "-", ">"})
58 |
59 | slotter.Activate("C")
60 | out := testBar.LatestOutput(3, 5)
61 | out.AssertText([]string{"<", "-", "-", "foo", ">"})
62 |
63 | out.At(3).LeftClick()
64 | original.AssertClicked("on click of active slot")
65 |
66 | original.OutputText("new text")
67 | testBar.NextOutput().AssertText([]string{"<", "-", "-", "new text", ">"})
68 |
69 | original.Output(nil)
70 | testBar.NextOutput().AssertText([]string{"<", "-", "-", ">"})
71 |
72 | slotter.Activate("A")
73 | testBar.LatestOutput(1, 5).AssertText([]string{"<", "-", "-", ">"})
74 |
75 | original.OutputText("baz")
76 | testBar.NextOutput().AssertText([]string{"<", "baz", "-", "-", ">"})
77 | }
78 |
--------------------------------------------------------------------------------
/modules/meta/split/split_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package split
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/bar"
21 | "github.com/soumya92/barista/outputs"
22 | testBar "github.com/soumya92/barista/testing/bar"
23 | testModule "github.com/soumya92/barista/testing/module"
24 | "github.com/stretchr/testify/require"
25 | )
26 |
27 | func TestSplit(t *testing.T) {
28 | testBar.New(t)
29 |
30 | original := testModule.New(t)
31 | first, rest := New(original, 2)
32 |
33 | // To delineate the portions of the output coming from first and rest.
34 | start := testModule.New(t)
35 | separator := testModule.New(t)
36 | end := testModule.New(t)
37 |
38 | original.AssertNotStarted("on construction of split modules")
39 | testBar.Run(start, rest, separator, first, end)
40 | original.AssertStarted("on stream of split modules")
41 |
42 | start.AssertStarted()
43 | start.Output(outputs.Text("*"))
44 | separator.AssertStarted()
45 | separator.Output(outputs.Text("*"))
46 | end.AssertStarted()
47 | end.Output(outputs.Text("*"))
48 | testBar.LatestOutput(0, 2, 4).Expect("setup")
49 |
50 | original.Output(outputs.Group(
51 | outputs.Text("a"),
52 | outputs.Text("b"),
53 | outputs.Text("c"),
54 | outputs.Text("d"),
55 | outputs.Text("e"),
56 | ))
57 | out := testBar.LatestOutput(1, 3)
58 | out.AssertText([]string{"*", "c", "d", "e", "*", "a", "b", "*"},
59 | "Segments split up between the two modules")
60 |
61 | out.At(3).LeftClick()
62 | original.AssertClicked("clicked on rest")
63 |
64 | out.At(5).LeftClick()
65 | original.AssertClicked("clicked on first")
66 |
67 | out.At(4).LeftClick()
68 | original.AssertNotClicked("clicked on other")
69 |
70 | original.Output(outputs.Textf("test"))
71 | testBar.LatestOutput(1, 3).AssertText(
72 | []string{"*", "*", "test", "*"},
73 | "not enough segments for rest")
74 |
75 | original.Output(nil)
76 | testBar.LatestOutput(1, 3).AssertText(
77 | []string{"*", "*", "*"},
78 | "no segments in original output")
79 |
80 | clickChan := make(chan string, 1)
81 | original.Output(outputs.Group(
82 | outputs.Text("a").OnClick(func(bar.Event) { clickChan <- "a" }),
83 | outputs.Text("b").OnClick(func(bar.Event) { clickChan <- "b" }),
84 | outputs.Text("c").OnClick(func(bar.Event) { clickChan <- "c" }),
85 | outputs.Text("d").OnClick(func(bar.Event) { clickChan <- "d" }),
86 | outputs.Text("e").OnClick(func(bar.Event) { clickChan <- "e" }),
87 | ))
88 |
89 | out = testBar.LatestOutput(1, 3)
90 | out.At(1).LeftClick()
91 | require.Equal(t, "c", <-clickChan)
92 |
93 | out.At(6).LeftClick()
94 | require.Equal(t, "b", <-clickChan)
95 | }
96 |
--------------------------------------------------------------------------------
/modules/netinfo/netinfo_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package netinfo
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/bar"
21 | "github.com/soumya92/barista/base/watchers/netlink"
22 | "github.com/soumya92/barista/outputs"
23 | testBar "github.com/soumya92/barista/testing/bar"
24 | )
25 |
26 | func TestNetinfo(t *testing.T) {
27 | nlt := netlink.TestMode()
28 | link0 := nlt.AddLink(netlink.Link{Name: "lo0", State: netlink.Up})
29 | link1 := nlt.AddLink(netlink.Link{Name: "eth0", State: netlink.Down})
30 | link2 := nlt.AddLink(netlink.Link{Name: "eth1", State: netlink.Down})
31 | link3 := nlt.AddLink(netlink.Link{Name: "wlan0", State: netlink.NotPresent})
32 |
33 | testBar.New(t)
34 | n1 := New().Output(func(s State) bar.Output {
35 | if !s.Connected() {
36 | return outputs.Text("No net")
37 | }
38 | return outputs.Text(s.Name)
39 | })
40 | n2 := Interface("wlan0").Output(func(s State) bar.Output {
41 | if !s.Enabled() {
42 | return nil
43 | }
44 | if s.Connected() {
45 | return outputs.Textf("W:%s", s.Name)
46 | } else if s.Connecting() {
47 | return outputs.Text("W:...")
48 | } else {
49 | return outputs.Text("W:down")
50 | }
51 | })
52 | n3 := Prefix("eth").Output(func(s State) bar.Output {
53 | if !s.Connected() {
54 | return nil
55 | }
56 | return outputs.Textf("E:%s", s.Name)
57 | })
58 | n4 := New()
59 | testBar.Run(n1, n2, n3, n4)
60 |
61 | testBar.LatestOutput().AssertText([]string{"lo0", "lo0"})
62 |
63 | nlt.UpdateLink(link0, netlink.Link{State: netlink.Down})
64 | testBar.LatestOutput(0, 3).Expect("on link update")
65 | nlt.UpdateLink(link3, netlink.Link{State: netlink.Down})
66 | testBar.LatestOutput(0, 1, 3).AssertText([]string{"No net", "W:down"})
67 |
68 | nlt.UpdateLink(link1, netlink.Link{State: netlink.Dormant})
69 | testBar.LatestOutput(0, 2, 3).Expect("on link update")
70 | nlt.UpdateLink(link2, netlink.Link{State: netlink.Up})
71 | testBar.LatestOutput(0, 2, 3).Expect("on link update")
72 |
73 | n1.Output(func(s State) bar.Output {
74 | return outputs.Textf("%v", s.State)
75 | })
76 | testBar.NextOutput().AssertText([]string{"6", "W:down", "E:eth1", "eth1"})
77 | }
78 |
--------------------------------------------------------------------------------
/modules/shell/shell.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package shell provides modules to display the output of shell commands.
17 | It supports both long-running commands, where the output is the last line,
18 | e.g. dmesg or tail -f /var/log/some.log, and repeatedly running commands,
19 | e.g. whoami, date +%s.
20 | */
21 | package shell
22 |
23 | import (
24 | "os/exec"
25 | "strings"
26 | "time"
27 |
28 | "github.com/soumya92/barista/bar"
29 | "github.com/soumya92/barista/base/notifier"
30 | "github.com/soumya92/barista/base/value"
31 | "github.com/soumya92/barista/outputs"
32 | "github.com/soumya92/barista/timing"
33 | )
34 |
35 | // Module represents a shell module that updates on a timer or on demand.
36 | type Module struct {
37 | cmd string
38 | args []string
39 | outf value.Value // of func(string) bar.Output
40 | notifyCh <-chan struct{}
41 | notifyFn func()
42 | scheduler *timing.Scheduler
43 | }
44 |
45 | // New constructs a new shell module.
46 | func New(cmd string, args ...string) *Module {
47 | m := &Module{cmd: cmd, args: args}
48 | m.notifyFn, m.notifyCh = notifier.New()
49 | m.scheduler = timing.NewScheduler()
50 | m.outf.Set(func(text string) bar.Output {
51 | return outputs.Text(text)
52 | })
53 | return m
54 | }
55 |
56 | // Stream starts the module.
57 | func (m *Module) Stream(s bar.Sink) {
58 | out, err := exec.Command(m.cmd, m.args...).Output()
59 | outf := m.outf.Get().(func(string) bar.Output)
60 | for {
61 | if s.Error(err) {
62 | return
63 | }
64 | s.Output(outf(strings.TrimSpace(string(out))))
65 | select {
66 | case <-m.outf.Next():
67 | outf = m.outf.Get().(func(string) bar.Output)
68 | case <-m.notifyCh:
69 | out, err = exec.Command(m.cmd, m.args...).Output()
70 | case <-m.scheduler.C:
71 | out, err = exec.Command(m.cmd, m.args...).Output()
72 | }
73 | }
74 | }
75 |
76 | // Output sets the output format. The format func will be passed the entire
77 | // trimmed output from the command once it's done executing. To process output
78 | // by lines, see Tail().
79 | func (m *Module) Output(format func(string) bar.Output) *Module {
80 | m.outf.Set(format)
81 | return m
82 | }
83 |
84 | // Every sets the refresh interval for the module. The command will be executed
85 | // repeatedly at the given interval, and the output updated. A zero interval
86 | // stops automatic repeats (but Refresh will still work).
87 | func (m *Module) Every(interval time.Duration) *Module {
88 | if interval == 0 {
89 | m.scheduler.Stop()
90 | } else {
91 | m.scheduler.Every(interval)
92 | }
93 | return m
94 | }
95 |
96 | // Refresh executes the command and updates the output.
97 | func (m *Module) Refresh() {
98 | m.notifyFn()
99 | }
100 |
--------------------------------------------------------------------------------
/modules/shell/shell_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package shell
16 |
17 | import (
18 | "testing"
19 | "time"
20 |
21 | "github.com/soumya92/barista/bar"
22 | "github.com/soumya92/barista/outputs"
23 | testBar "github.com/soumya92/barista/testing/bar"
24 | "github.com/soumya92/barista/timing"
25 |
26 | "github.com/stretchr/testify/require"
27 | )
28 |
29 | func TestRepeating(t *testing.T) {
30 | testBar.New(t)
31 |
32 | rep := New("echo", "foo").Every(time.Second)
33 | testBar.Run(rep)
34 |
35 | testBar.NextOutput().AssertText([]string{"foo"}, "on start")
36 |
37 | then := timing.Now()
38 | now := timing.NextTick()
39 | require.InDelta(t, float64(time.Second), float64(now.Sub(then)),
40 | float64(time.Millisecond))
41 |
42 | testBar.NextOutput().Expect("on tick")
43 | testBar.AssertNoOutput("until tick")
44 |
45 | for i := 0; i < 10; i++ {
46 | testBar.Tick()
47 | testBar.NextOutput().Expect("on tick")
48 | }
49 |
50 | rep.Every(0)
51 | testBar.Tick()
52 | testBar.AssertNoOutput("on zero interval")
53 |
54 | testBar.New(t)
55 | rep = New("this-is-not-a-valid-command", "foo").Every(time.Second)
56 | testBar.Run(rep)
57 | testBar.NextOutput().AssertError("when starting an invalid command")
58 | out := testBar.NextOutput("sets restart handler")
59 | testBar.Tick()
60 | testBar.AssertNoOutput("on next tick with error")
61 | out.At(0).LeftClick()
62 | testBar.NextOutput("clears error segment")
63 | testBar.NextOutput().AssertError("when starting an invalid command")
64 | }
65 |
66 | func TestUpdate(t *testing.T) {
67 | testBar.New(t)
68 | m := New("echo", "bar").Output(func(in string) bar.Output {
69 | return outputs.Textf(">%s<", in)
70 | })
71 | testBar.Run(m)
72 | testBar.NextOutput().AssertText([]string{">bar<"}, "on start")
73 | testBar.AssertNoOutput("after the first output")
74 |
75 | m.Output(func(in string) bar.Output {
76 | return outputs.Textf("*%s*", in)
77 | })
78 | testBar.NextOutput("on format change").AssertText([]string{"*bar*"})
79 |
80 | m.Refresh()
81 | testBar.NextOutput("on refresh").AssertText([]string{"*bar*"})
82 | }
83 |
--------------------------------------------------------------------------------
/modules/shell/tail.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package shell
16 |
17 | import (
18 | "bufio"
19 | "os/exec"
20 | "syscall"
21 |
22 | "github.com/soumya92/barista/bar"
23 | "github.com/soumya92/barista/base/value"
24 | "github.com/soumya92/barista/outputs"
25 | )
26 |
27 | // TailModule represents a bar.Module that displays the last line of output from
28 | // a shell command in the bar.
29 | type TailModule struct {
30 | cmd string
31 | args []string
32 | outf value.Value // of func(string) bar.Output
33 | }
34 |
35 | // Tail constructs a module that displays the last line of output from a long
36 | // running command.
37 | func Tail(cmd string, args ...string) *TailModule {
38 | t := &TailModule{cmd: cmd, args: args}
39 | t.outf.Set(func(text string) bar.Output {
40 | return outputs.Text(text)
41 | })
42 | return t
43 | }
44 |
45 | // Stream starts the module.
46 | func (m *TailModule) Stream(s bar.Sink) {
47 | cmd := exec.Command(m.cmd, m.args...)
48 | // Prevent SIGUSR for bar pause/resume from propagating to the
49 | // child process. Some commands don't play nice with signals.
50 | cmd.SysProcAttr = &syscall.SysProcAttr{
51 | Setpgid: true,
52 | Pgid: 0,
53 | }
54 | stdout, err := cmd.StdoutPipe()
55 | if s.Error(err) {
56 | return
57 | }
58 | if s.Error(cmd.Start()) {
59 | return
60 | }
61 | var out *string
62 | outf := m.outf.Get().(func(string) bar.Output)
63 | errChan := make(chan error)
64 | outChan := make(chan string)
65 | go func() {
66 | scanner := bufio.NewScanner(stdout)
67 | for scanner.Scan() {
68 | outChan <- scanner.Text()
69 | }
70 | errChan <- cmd.Wait()
71 | }()
72 | for {
73 | select {
74 | case e := <-errChan:
75 | s.Error(e)
76 | return
77 | case <-m.outf.Next():
78 | outf = m.outf.Get().(func(string) bar.Output)
79 | case txt := <-outChan:
80 | out = &txt
81 | }
82 | if out != nil {
83 | s.Output(outf(*out))
84 | }
85 | }
86 | }
87 |
88 | // Output sets the output format for each line of output.
89 | func (m *TailModule) Output(format func(string) bar.Output) *TailModule {
90 | m.outf.Set(format)
91 | return m
92 | }
93 |
--------------------------------------------------------------------------------
/modules/shell/tail_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package shell
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/bar"
21 | "github.com/soumya92/barista/outputs"
22 | testBar "github.com/soumya92/barista/testing/bar"
23 | )
24 |
25 | func TestTail(t *testing.T) {
26 | testBar.New(t)
27 | tail := Tail("bash", "-c", "for i in `seq 1 5`; do echo $i; sleep 0.075; done")
28 | testBar.Run(tail)
29 |
30 | for _, i := range []string{"1", "2", "3", "4", "5"} {
31 | testBar.NextOutput().AssertText([]string{i}, i)
32 | }
33 |
34 | testBar.AssertNoOutput("when command terminates normally")
35 |
36 | tail.Output(func(in string) bar.Output {
37 | return outputs.Textf("++%s++", in)
38 | })
39 | testBar.NextOutput("on format func change").AssertText(
40 | []string{"++5++"}, "applies format func to last output line")
41 | }
42 |
43 | func TestTailExitCode(t *testing.T) {
44 | testBar.New(t)
45 | tail := Tail("bash", "-c", "for i in `seq 1 3`; do echo $i; sleep 0.075; done; exit 1")
46 | testBar.Run(tail)
47 | for _, i := range []string{"1", "2", "3"} {
48 | testBar.NextOutput().AssertText([]string{i}, i)
49 | }
50 |
51 | testBar.NextOutput().AssertError(
52 | "when command terminates with an error")
53 | }
54 |
55 | func TestTailInvalidCommand(t *testing.T) {
56 | testBar.New(t)
57 | tail := Tail("this-is-not-a-valid-command", "--but", "'have'", "-some", "args")
58 | testBar.Run(tail)
59 | testBar.NextOutput().AssertError(
60 | "when starting an invalid command")
61 | }
62 |
--------------------------------------------------------------------------------
/modules/static/static.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package static provides a simple module that shows static content on the bar,
16 | // with methods to set the content. In a pinch, this can be used to create
17 | // buttons, or show additional information by setting the output from within
18 | // a format function.
19 | package static
20 |
21 | import (
22 | "github.com/soumya92/barista/bar"
23 | "github.com/soumya92/barista/base/value"
24 | )
25 |
26 | // Module represents a module that displays static content on the bar.
27 | type Module struct {
28 | output value.Value
29 | }
30 |
31 | // Stream starts the module.
32 | func (m *Module) Stream(sink bar.Sink) {
33 | for {
34 | next := m.output.Next()
35 | out, _ := m.output.Get().(bar.Output)
36 | sink.Output(out)
37 | <-next
38 | }
39 | }
40 |
41 | // Set sets the output to display.
42 | func (m *Module) Set(out bar.Output) {
43 | m.output.Set(out)
44 | }
45 |
46 | // Clear sets an empty output.
47 | func (m *Module) Clear() {
48 | m.output.Set(nil)
49 | }
50 |
51 | // New constructs a static module that displays the given output.
52 | func New(initial bar.Output) *Module {
53 | m := new(Module)
54 | m.Set(initial)
55 | return m
56 | }
57 |
--------------------------------------------------------------------------------
/modules/static/static_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package static
16 |
17 | import (
18 | "testing"
19 | "time"
20 |
21 | "github.com/soumya92/barista/bar"
22 | "github.com/soumya92/barista/outputs"
23 | testBar "github.com/soumya92/barista/testing/bar"
24 | "github.com/stretchr/testify/require"
25 | )
26 |
27 | func TestStatic(t *testing.T) {
28 | testBar.New(t)
29 |
30 | s0 := New(nil)
31 | s1 := New(outputs.Text("foo"))
32 | s2 := new(Module)
33 | testBar.Run(s0, s1, s2)
34 |
35 | testBar.LatestOutput().AssertText([]string{"foo"}, "on start")
36 | testBar.AssertNoOutput("no change to static modules")
37 |
38 | s0.Set(outputs.Text("baz"))
39 | testBar.LatestOutput(0).AssertText([]string{"baz", "foo"})
40 |
41 | s1.Clear()
42 | testBar.LatestOutput(1).AssertText([]string{"baz"})
43 |
44 | s2.Clear()
45 | testBar.LatestOutput(2).AssertText([]string{"baz"})
46 |
47 | clickCh := make(chan bool, 1)
48 | s2.Set(outputs.Text("foo").OnClick(func(bar.Event) { clickCh <- true }))
49 |
50 | out := testBar.LatestOutput(2)
51 | out.AssertText([]string{"baz", "foo"})
52 |
53 | select {
54 | case <-clickCh:
55 | require.Fail(t, "spurious click event")
56 | case <-time.After(10 * time.Millisecond):
57 | // test passed
58 | }
59 |
60 | out.At(0).LeftClick()
61 | select {
62 | case <-clickCh:
63 | require.Fail(t, "spurious click event")
64 | case <-time.After(10 * time.Millisecond):
65 | // test passed
66 | }
67 |
68 | out.At(1).LeftClick()
69 | select {
70 | case v := <-clickCh:
71 | require.True(t, v, "click handler triggered")
72 | case <-time.After(time.Second):
73 | require.Fail(t, "click event not received")
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/modules/volume/alsa/capi.rb:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | require_relative '../../../testing/capi/main.rb'
16 |
17 | make_capi 'alsa/asoundlib.h', library: 'alsa', pkg_config: 'alsa', go_src_files: ['alsa.go']
18 |
--------------------------------------------------------------------------------
/modules/vpn/vpn.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package vpn provides an i3bar module for openvpn information.
16 | package vpn
17 |
18 | import (
19 | "github.com/soumya92/barista/bar"
20 | "github.com/soumya92/barista/base/value"
21 | "github.com/soumya92/barista/base/watchers/netlink"
22 | l "github.com/soumya92/barista/logging"
23 | "github.com/soumya92/barista/outputs"
24 | )
25 |
26 | // State represents the vpn state.
27 | type State int
28 |
29 | // Connected returns true if the VPN is connected.
30 | func (s State) Connected() bool {
31 | return s == Connected
32 | }
33 |
34 | // Disconnected returns true if the VPN is off.
35 | func (s State) Disconnected() bool {
36 | return s == Disconnected
37 | }
38 |
39 | // Valid states for the vpn
40 | const (
41 | Disconnected State = iota
42 | Waiting
43 | Connected
44 | )
45 |
46 | // Module represents a VPN bar module.
47 | type Module struct {
48 | intf string
49 | outputFunc value.Value // of func(State) bar.Output
50 | }
51 |
52 | // New constructs an instance of the VPN module for the specified interface.
53 | func New(iface string) *Module {
54 | m := &Module{intf: iface}
55 | l.Label(m, iface)
56 | l.Register(m, "outputFunc")
57 | // Default output is just 'VPN' when connected.
58 | m.Output(func(s State) bar.Output {
59 | if s.Connected() {
60 | return outputs.Text("VPN")
61 | }
62 | return nil
63 | })
64 | return m
65 | }
66 |
67 | // DefaultInterface constructs an instance of the VPN module for "tun0",
68 | // the usual interface for VPNs.
69 | func DefaultInterface() *Module {
70 | return New("tun0")
71 | }
72 |
73 | // Output configures a module to display the output of a user-defined function.
74 | func (m *Module) Output(outputFunc func(State) bar.Output) *Module {
75 | m.outputFunc.Set(outputFunc)
76 | return m
77 | }
78 |
79 | // Stream starts the module.
80 | func (m *Module) Stream(s bar.Sink) {
81 | outputFunc := m.outputFunc.Get().(func(State) bar.Output)
82 | nextOutputFunc, done := m.outputFunc.Subscribe()
83 | defer done()
84 |
85 | linkSub := netlink.ByName(m.intf)
86 | defer linkSub.Unsubscribe()
87 |
88 | state := getState(linkSub.Get().State)
89 | for {
90 | s.Output(outputFunc(state))
91 | select {
92 | case <-linkSub.C:
93 | state = getState(linkSub.Get().State)
94 | case <-nextOutputFunc:
95 | outputFunc = m.outputFunc.Get().(func(State) bar.Output)
96 | }
97 | }
98 | }
99 |
100 | func getState(state netlink.OperState) State {
101 | switch state {
102 | case netlink.Up:
103 | return Connected
104 | case netlink.Dormant:
105 | return Waiting
106 | default:
107 | return Disconnected
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/modules/vpn/vpn_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package vpn
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/bar"
21 | "github.com/soumya92/barista/base/watchers/netlink"
22 | "github.com/soumya92/barista/outputs"
23 | testBar "github.com/soumya92/barista/testing/bar"
24 | )
25 |
26 | func TestVpn(t *testing.T) {
27 | nlt := netlink.TestMode()
28 | link := nlt.AddLink(netlink.Link{Name: "tun0", State: netlink.Down})
29 |
30 | testBar.New(t)
31 | v := DefaultInterface()
32 |
33 | testBar.Run(v)
34 | testBar.NextOutput().AssertText([]string{})
35 |
36 | nlt.UpdateLink(link, netlink.Link{Name: "tun0", State: netlink.Up})
37 | testBar.NextOutput().AssertText([]string{"VPN"})
38 |
39 | v.Output(func(s State) bar.Output {
40 | switch {
41 | case s.Connected():
42 | return outputs.Text("VPN!")
43 | case s.Disconnected():
44 | return nil
45 | default:
46 | return outputs.Text("...")
47 | }
48 | })
49 | testBar.NextOutput().AssertText([]string{"VPN!"})
50 |
51 | nlt.UpdateLink(link, netlink.Link{Name: "tun0", State: netlink.Dormant})
52 | testBar.NextOutput().AssertText([]string{"..."})
53 |
54 | nlt.UpdateLink(link, netlink.Link{Name: "tun0", State: netlink.Up})
55 | testBar.NextOutput().AssertText([]string{"VPN!"})
56 |
57 | v.Output(func(s State) bar.Output {
58 | if s.Disconnected() {
59 | return outputs.Text("NO VPN")
60 | }
61 | return nil
62 | })
63 | testBar.NextOutput().AssertText([]string{})
64 |
65 | nlt.RemoveLink(link)
66 | testBar.NextOutput().AssertText([]string{"NO VPN"})
67 | }
68 |
--------------------------------------------------------------------------------
/modules/weather/direction.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package weather
16 |
17 | // Deg returns the direction in meteorological degrees.
18 | func (d Direction) Deg() int {
19 | return int(d)
20 | }
21 |
22 | // Cardinal returns the cardinal direction.
23 | func (d Direction) Cardinal() string {
24 | cardinal := ""
25 | deg := d.Deg()
26 | m := 34 // rounded from (90/4 + 90/8)
27 | // primary cardinal direction first. N, E, S, W.
28 | switch {
29 | case deg < m || deg > 360-m:
30 | cardinal = "N"
31 | case 90-m < deg && deg < 90+m:
32 | cardinal = "E"
33 | case 180-m < deg && deg < 180+m:
34 | cardinal = "S"
35 | case 270-m < deg && deg < 270+m:
36 | cardinal = "W"
37 | }
38 | // Now append the midway points. NE, NW, SE, SW.
39 | switch {
40 | case 45-m < deg && deg < 45+m:
41 | cardinal += "NE"
42 | case 135-m < deg && deg < 135+m:
43 | cardinal += "SE"
44 | case 225-m < deg && deg < 225+m:
45 | cardinal += "SW"
46 | case 315-m < deg && deg < 315+m:
47 | cardinal += "NW"
48 | }
49 | return cardinal
50 | }
51 |
--------------------------------------------------------------------------------
/modules/weather/direction_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package weather
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/stretchr/testify/require"
21 | )
22 |
23 | func TestDirection(t *testing.T) {
24 | for _, c := range []struct {
25 | deg int
26 | card string
27 | }{
28 | // Basic sanity checks.
29 | {0, "N"}, {90, "E"}, {180, "S"}, {270, "W"},
30 | {45, "NE"}, {135, "SE"}, {225, "SW"}, {315, "NW"},
31 |
32 | // All boundary conditions. Should be safe to assume that
33 | // points within are correct.
34 | {349, "N"}, {11, "N"},
35 | {12, "NNE"}, {33, "NNE"},
36 | {34, "NE"}, {56, "NE"},
37 | {57, "ENE"}, {78, "ENE"},
38 | {79, "E"}, {101, "E"},
39 | {102, "ESE"}, {123, "ESE"},
40 | {124, "SE"}, {146, "SE"},
41 | {147, "SSE"}, {168, "SSE"},
42 | {169, "S"}, {191, "S"},
43 | {192, "SSW"}, {213, "SSW"},
44 | {214, "SW"}, {236, "SW"},
45 | {237, "WSW"}, {258, "WSW"},
46 | {259, "W"}, {281, "W"},
47 | {282, "WNW"}, {303, "WNW"},
48 | {304, "NW"}, {326, "NW"},
49 | {327, "NNW"}, {348, "NNW"},
50 | } {
51 | dir := Direction(c.deg)
52 | require.Equal(t, c.card, dir.Cardinal())
53 | require.Equal(t, c.deg, dir.Deg())
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/modules/weather/metar/metar_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package metar
16 |
17 | import (
18 | "testing"
19 | "time"
20 |
21 | "github.com/soumya92/barista/modules/weather"
22 | testServer "github.com/soumya92/barista/testing/httpserver"
23 |
24 | "github.com/martinlindhe/unit"
25 | "github.com/stretchr/testify/require"
26 | )
27 |
28 | func TestExample(t *testing.T) {
29 | ts := testServer.New()
30 | defer ts.Close()
31 |
32 | provider := Station("KBFI").StripRemarks().IncludeFlightCat().Build().(*provider)
33 | provider.url = ts.URL + "/static/example.xml"
34 |
35 | wthr, err := provider.GetWeather()
36 | require.NoError(t, err)
37 | require.NotNil(t, wthr)
38 | require.Equal(t, weather.Weather{
39 | Location: "KBFI",
40 | Condition: weather.Overcast,
41 | Description: "[VFR] KBFI 252053Z 00000KT 10SM BKN090 OVC110 09.4/M03.3 A3013",
42 | Humidity: 0.4070166689067204,
43 | Pressure: 1020.4 * unit.Millibar,
44 | Temperature: unit.FromCelsius(9.4),
45 | CloudCover: 1.0,
46 | Updated: time.Unix(1543179180, 0).In(time.UTC),
47 | Attribution: "NWS",
48 | }, wthr)
49 |
50 | provider.url = ts.URL + "/code/503"
51 | wthr, err = provider.GetWeather()
52 | require.NoError(t, err)
53 | require.NotNil(t, wthr)
54 | require.Equal(t, weather.Weather{
55 | Location: "KBFI",
56 | Condition: weather.Overcast,
57 | Description: "[VFR] KBFI 252053Z 00000KT 10SM BKN090 OVC110 09.4/M03.3 A3013",
58 | Humidity: 0.4070166689067204,
59 | Pressure: 1020.4 * unit.Millibar,
60 | Temperature: unit.FromCelsius(9.4),
61 | CloudCover: 1.0,
62 | Updated: time.Unix(1543179180, 0).In(time.UTC),
63 | Attribution: "NWS",
64 | }, wthr)
65 |
66 | provider.url = ts.URL + "/code/401"
67 | wthr, err = provider.GetWeather()
68 | require.Error(t, err)
69 |
70 | provider.url = ts.URL + "/static/bad.xml"
71 | wthr, err = provider.GetWeather()
72 | require.Error(t, err)
73 |
74 | provider.url = ts.URL + "/static/no-entries.xml"
75 | wthr, err = provider.GetWeather()
76 | require.Error(t, err)
77 | }
78 |
--------------------------------------------------------------------------------
/modules/weather/metar/testdata/bad.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 17357439
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/modules/weather/metar/testdata/example.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 17357439
4 |
5 |
6 |
7 |
8 | 4
9 |
10 |
11 | KBFI 252053Z 00000KT 10SM BKN090 OVC110 09/03 A3013 RMK AO2 RAB37E51 SLP204 P0000 60000 T00940033 56009
12 | KBFI
13 | 2018-11-25T20:53:00Z
14 | 47.55
15 | -122.32
16 | 9.4
17 | -3.3
18 | 0
19 | 0
20 | 10.0
21 | 30.129921
22 | 1020.4
23 |
24 | TRUE
25 |
26 |
27 |
28 | VFR
29 | -0.9
30 | 0.005
31 | 0.005
32 | METAR
33 | 4.0
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/modules/weather/metar/testdata/no-entries.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 17357439
4 |
5 |
6 |
7 |
8 | 4
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/modules/weather/openweathermap/testdata/bad.json:
--------------------------------------------------------------------------------
1 | {"coord":
2 | {"lon":145.77,"lat":-16.92},
3 | "weather":[{"id":901,"main":"condition","description":"description","icon":"04n"}],
4 | "base":,
5 | "main":---
6 |
--------------------------------------------------------------------------------
/modules/weather/openweathermap/testdata/empty.json:
--------------------------------------------------------------------------------
1 | {"weather":[]}
2 |
--------------------------------------------------------------------------------
/modules/weather/openweathermap/testdata/good.json.tpl:
--------------------------------------------------------------------------------
1 | {"coord":
2 | {"lon":145.77,"lat":-16.92},
3 | "weather":[{"id":{{.id}},"main":"{{.cond}}","description":"{{.desc}}","icon":"04n"}],
4 | "base":"cmc stations",
5 | "main":{"temp":293.25,"pressure":1019,"humidity":83,"temp_min":289.82,"temp_max":295.37},
6 | "wind":{"speed":5.1,"deg":150},
7 | "clouds":{"all":75},
8 | "rain":{"3h":3},
9 | "dt":1435658272,
10 | "sys":{"type":1,"id":8166,"message":0.0166,"country":"AU","sunrise":1435610796,"sunset":1435650870},
11 | "id":2172797,
12 | "name":"Cairns",
13 | "cod":200}
14 |
--------------------------------------------------------------------------------
/modules/weather/weather_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package weather
16 |
17 | import (
18 | "errors"
19 | "sync"
20 | "testing"
21 |
22 | "github.com/soumya92/barista/bar"
23 | "github.com/soumya92/barista/outputs"
24 | testBar "github.com/soumya92/barista/testing/bar"
25 |
26 | "github.com/martinlindhe/unit"
27 | )
28 |
29 | type testProvider struct {
30 | sync.RWMutex
31 | Weather
32 | error
33 | }
34 |
35 | func (t *testProvider) GetWeather() (Weather, error) {
36 | t.RLock()
37 | defer t.RUnlock()
38 | return t.Weather, t.error
39 | }
40 |
41 | func TestWeather(t *testing.T) {
42 | testBar.New(t)
43 | p := &testProvider{Weather: Weather{
44 | Location: "Swallow Falls",
45 | Condition: Cloudy,
46 | Description: "chance of meatballs",
47 | Temperature: unit.FromFahrenheit(72),
48 | Humidity: 0.7,
49 | Attribution: "FLDSMDFR",
50 | }}
51 | w := New(p)
52 | testBar.Run(w)
53 |
54 | testBar.NextOutput().AssertText(
55 | []string{"22.2℃ chance of meatballs (FLDSMDFR)"}, "on start")
56 |
57 | testBar.Tick()
58 | testBar.NextOutput().Expect("on tick")
59 |
60 | w.Output(func(w Weather) bar.Output {
61 | return outputs.Textf("%.0f, by %s", w.Temperature.Fahrenheit(), w.Attribution)
62 | })
63 | testBar.NextOutput().AssertText([]string{
64 | "72, by FLDSMDFR"}, "on template change")
65 |
66 | p.Lock()
67 | p.error = errors.New("foo")
68 | p.Unlock()
69 |
70 | testBar.Tick()
71 | testBar.NextOutput().AssertError("on tick with error")
72 |
73 | testBar.Tick()
74 | out := testBar.NextOutput("on tick with error")
75 |
76 | p.Lock()
77 | p.error = nil
78 | out.At(0).LeftClick()
79 | testBar.NextOutput().AssertEmpty("clears error on refresh")
80 |
81 | p.Unlock()
82 | testBar.NextOutput().AssertText([]string{"72, by FLDSMDFR"})
83 | }
84 |
--------------------------------------------------------------------------------
/oauth/testdata/empty.json:
--------------------------------------------------------------------------------
1 | {"Salt":"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==","IV":"AAECAwQFBgcICQoLDA0ODw==","Token":"1H/HbuTbnVAnvEJCu1+hJ7vas0gATXDa93M33RVrsYoWpLyX03hQxuzR6RjcidHoAMhD"}
2 |
--------------------------------------------------------------------------------
/oauth/testdata/simple.json:
--------------------------------------------------------------------------------
1 | {"Salt":"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==","IV":"AAECAwQFBgcICQoLDA0ODw==","Token":"1H/HbuTbnVAnvEJCu1+hJ7ue8AUHVHKRqShnglEp5Mk7aW2zB9LGs5SeAOEzA0vtJiDRTwOq9uSRq+DYqxry4jZ/U0dHq/jCBFjQOvC+Dx+1oc+Qs3Gw"}
2 |
--------------------------------------------------------------------------------
/outputs/outputs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package outputs provides helper functions to construct bar.Outputs.
16 | package outputs
17 |
18 | import (
19 | "fmt"
20 |
21 | "github.com/soumya92/barista/bar"
22 | "github.com/soumya92/barista/format"
23 | "github.com/soumya92/barista/pango"
24 | )
25 |
26 | // Errorf constructs a bar output that indicates an error,
27 | // using the given format string and arguments.
28 | func Errorf(format string, args ...interface{}) *bar.Segment {
29 | return Error(fmt.Errorf(format, args...))
30 | }
31 |
32 | // Error constructs a bar output that indicates an error.
33 | func Error(e error) *bar.Segment {
34 | return bar.ErrorSegment(e)
35 | }
36 |
37 | // Textf constructs simple text output from a format string and arguments.
38 | func Textf(format string, args ...interface{}) *bar.Segment {
39 | return Text(fmt.Sprintf(format, args...))
40 | }
41 |
42 | //Text constructs a simple text output from the given string.
43 | func Text(text string) *bar.Segment {
44 | return bar.TextSegment(text)
45 | }
46 |
47 | // Pango constructs a pango bar segment from a list of pango Nodes and strings.
48 | func Pango(things ...interface{}) *bar.Segment {
49 | nodes := []*pango.Node{}
50 | for _, thing := range things {
51 | switch t := thing.(type) {
52 | case *pango.Node:
53 | nodes = append(nodes, t)
54 | case string:
55 | nodes = append(nodes, pango.Text(t))
56 | case format.Value:
57 | nodes = append(nodes, pango.Unit(t))
58 | case format.Values:
59 | nodes = append(nodes, pango.Unit(t...))
60 | default:
61 | if val, ok := format.Unit(t); ok {
62 | nodes = append(nodes, pango.Unit(val...))
63 | } else {
64 | nodes = append(nodes, pango.Textf("%v", t))
65 | }
66 | }
67 | }
68 | return bar.PangoSegment(pango.New(nodes...).String())
69 | }
70 |
71 | // Group concatenates several outputs into a single SegmentGroup,
72 | // to facilitate easier manipulation of output properties.
73 | // For example, setting a colour or urgency for all segments together.
74 | func Group(outputs ...bar.Output) *SegmentGroup {
75 | group := new(SegmentGroup)
76 | for _, o := range outputs {
77 | group.Append(o)
78 | }
79 | return group
80 | }
81 |
--------------------------------------------------------------------------------
/pango/icon.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package pango
16 |
17 | import (
18 | "strings"
19 |
20 | l "github.com/soumya92/barista/logging"
21 | )
22 |
23 | // IconProvider is an interface for providing pango Icons.
24 | // The function should return a pango node for the given
25 | // icon name, or nil if an icon could not be found.
26 | type IconProvider func(string) *Node
27 |
28 | var iconProviders = map[string]IconProvider{}
29 |
30 | // Icon returns a pango node that displays the given icon.
31 | // The identifier must be of the form $provider-$name, and the returned
32 | // node will render the $name icon using $provider. e.g. "fa-add" will
33 | // render the "add" icon using font awesome.
34 | func Icon(ident string) *Node {
35 | providerAndName := strings.SplitN(ident, "-", 2)
36 | if len(providerAndName) != 2 {
37 | l.Log("Could not identify icon provider in '%s'", ident)
38 | return &Node{}
39 | }
40 | provider := providerAndName[0]
41 | name := providerAndName[1]
42 | if p, ok := iconProviders[provider]; ok {
43 | node := p(name)
44 | if node != nil {
45 | node.attributes["fallback"] = "false"
46 | return New(node)
47 | }
48 | }
49 | return &Node{}
50 | }
51 |
52 | // AddIconProvider adds an icon provider for a given prefix.
53 | // This is intended for use only by the pango/icons package.
54 | // See "pango/icons".LoadFromFile for more details.
55 | func AddIconProvider(name string, provider IconProvider) {
56 | iconProviders[name] = provider
57 | }
58 |
--------------------------------------------------------------------------------
/pango/icon_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package pango
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/testing/pango"
21 |
22 | "github.com/stretchr/testify/require"
23 | )
24 |
25 | func TestNoProviders(t *testing.T) {
26 | iconProviders = map[string]IconProvider{}
27 | require.Empty(t,
28 | Icon("anything-iconname").String(),
29 | "when no providers are added")
30 |
31 | require.Empty(t,
32 | Icon("alert").String(),
33 | "when no providers are added")
34 | }
35 |
36 | func singleIconProvider(name string) IconProvider {
37 | return func(s string) *Node {
38 | if string(s) == name {
39 | return Textf("s:%s", s).Small()
40 | }
41 | return nil
42 | }
43 | }
44 |
45 | func TestProviders(t *testing.T) {
46 | iconProviders = map[string]IconProvider{}
47 | AddIconProvider("t1", singleIconProvider("foo"))
48 | AddIconProvider("t2", singleIconProvider("bar"))
49 |
50 | require.Empty(t, Icon("t0-bar").String(), "non-existent provider")
51 | require.Empty(t, Icon("t1-bar").String(), "non-existent icon")
52 | pango.AssertText(t, "s:bar", Icon("t2-bar").String(),
53 | "provider name is not passed to Icon(...)")
54 | }
55 |
--------------------------------------------------------------------------------
/pango/icons/fontawesome/fontawesome.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package fontawesome provides support for FontAwesome Icons
17 | from https://github.com/FortAwesome/Font-Awesome
18 |
19 | It uses metadata/icons.yml to get the list of icons,
20 | and requires fonts/fontawesome-webfont.ttf to be installed.
21 | */
22 | package fontawesome
23 |
24 | import (
25 | "fmt"
26 | "path/filepath"
27 |
28 | "github.com/soumya92/barista/pango"
29 | "github.com/soumya92/barista/pango/icons"
30 |
31 | "github.com/spf13/afero"
32 | "gopkg.in/yaml.v2"
33 | )
34 |
35 | type faMetadata struct {
36 | Code string `yaml:"unicode"`
37 | Styles []string `yaml:"styles"`
38 | }
39 |
40 | var fs = afero.NewOsFs()
41 |
42 | // Load initialises the fontawesome icon provider from the given repo.
43 | func Load(repoPath string) error {
44 | f, err := fs.Open(filepath.Join(repoPath, "metadata/icons.yml"))
45 | if err != nil {
46 | return err
47 | }
48 | defer f.Close()
49 |
50 | // Defaults to solid since that style has the most icons available.
51 | faSolid := icons.NewProvider("fa")
52 | faSolid.Font("Font Awesome 5 Free")
53 | faSolid.AddStyle(func(n *pango.Node) { n.Weight(900) })
54 |
55 | faBrands := icons.NewProvider("fab")
56 | faBrands.Font("Font Awesome 5 Brands")
57 |
58 | faRegular := icons.NewProvider("far")
59 | faRegular.Font("Font Awesome 5 Free")
60 |
61 | styles := map[string]*icons.Provider{
62 | "solid": faSolid,
63 | "regular": faRegular,
64 | "brands": faBrands,
65 | }
66 |
67 | var glyphs map[string]faMetadata
68 | err = yaml.NewDecoder(f).Decode(&glyphs)
69 | if err != nil {
70 | return err
71 | }
72 | for name, meta := range glyphs {
73 | for _, style := range meta.Styles {
74 | p, ok := styles[style]
75 | if !ok {
76 | return fmt.Errorf("Unknown FontAwesome style: '%s'", style)
77 | }
78 | err = p.Hex(name, meta.Code)
79 | if err != nil {
80 | return err
81 | }
82 | }
83 | }
84 | return nil
85 | }
86 |
--------------------------------------------------------------------------------
/pango/icons/icons.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package icons provides an interface for using icon fonts in a bar.
17 | To use an icon font:
18 | - Clone a supported repository
19 | - Link the ttf into ~/.fonts
20 | - Load the icon by passing it the path to the repo
21 | - Use icons as pango constructs in your bar
22 |
23 | Compatible icon fonts:
24 | - Material Design Icons (+community fork)
25 | - FontAwesome
26 | - Typicons
27 |
28 | Example usage:
29 | material.Load("/Users/me/Github/google/material-design-icons")
30 | ...
31 | return pango.Icon("material-today").Color(colors.Hex("#ddd")).
32 | Append(pango.Text(now.Sprintf("%H:%M")))
33 | */
34 | package icons
35 |
36 | import (
37 | "strconv"
38 |
39 | "github.com/soumya92/barista/pango"
40 | )
41 |
42 | // Provider provides pango nodes for icons
43 | type Provider struct {
44 | symbols map[string]string
45 | styles []func(*pango.Node)
46 | }
47 |
48 | // NewProvider creates a new icon provider with the given name,
49 | // registers it with pango.Icon, and returns it so that an appropriate
50 | // Load method can be used.
51 | func NewProvider(name string) *Provider {
52 | p := &Provider{symbols: map[string]string{}}
53 | pango.AddIconProvider(name, p.icon)
54 | return p
55 | }
56 |
57 | // icon creates a pango node that renders the named icon.
58 | func (p *Provider) icon(name string) *pango.Node {
59 | symbol, ok := p.symbols[name]
60 | if !ok {
61 | return nil
62 | }
63 | n := pango.Text(symbol)
64 | for _, s := range p.styles {
65 | s(n)
66 | }
67 | return n
68 | }
69 |
70 | // Hex adds a symbol to the provider where the value is given
71 | // in hex-encoded form.
72 | func (p *Provider) Hex(name, value string) error {
73 | sym, err := SymbolFromHex(value)
74 | if err != nil {
75 | return err
76 | }
77 | p.Symbol(name, sym)
78 | return nil
79 | }
80 |
81 | // Symbol adds a symbol to the provider where the value is the
82 | // symbol/string to use for the icon.
83 | func (p *Provider) Symbol(name, value string) {
84 | p.symbols[name] = value
85 | }
86 |
87 | // Font sets the font set on the returned pango nodes.
88 | func (p *Provider) Font(font string) {
89 | p.AddStyle(func(n *pango.Node) { n.Font(font) })
90 | }
91 |
92 | // AddStyle sets additional styles on all returned pango nodes.
93 | func (p *Provider) AddStyle(style func(*pango.Node)) {
94 | p.styles = append(p.styles, style)
95 | }
96 |
97 | // SymbolFromHex parses a hex string (e.g. "1F44D") and converts
98 | // it to a string (e.g. "👍").
99 | func SymbolFromHex(hex string) (string, error) {
100 | intVal, err := strconv.ParseUint(hex, 16, 32)
101 | if err != nil {
102 | return "", err
103 | }
104 | return string(rune(intVal)), nil
105 | }
106 |
--------------------------------------------------------------------------------
/pango/icons/icons_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package icons
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/colors"
21 | "github.com/soumya92/barista/pango"
22 | pangoTesting "github.com/soumya92/barista/testing/pango"
23 |
24 | "github.com/stretchr/testify/require"
25 | )
26 |
27 | func TestSymbolFromHex(t *testing.T) {
28 | tests := []struct{ input, expected string }{
29 | {"1F44D", "👍"},
30 | {"0024", "$"},
31 | {"20AC", "€"},
32 | {"10437", "𐐷"},
33 | {"24B62", "𤭢"},
34 | }
35 | for _, tc := range tests {
36 | value, err := SymbolFromHex(tc.input)
37 | require.NoError(t, err, "Decoding a valid symbol from hex")
38 | require.Equal(t, tc.expected, value, "Symbol is decoded correctly")
39 | }
40 |
41 | _, err := SymbolFromHex("04xc")
42 | require.Error(t, err, "Error with invalid hex string")
43 | _, err = SymbolFromHex("ffffffffffff")
44 | require.Error(t, err, "Error with out of bounds hex")
45 | }
46 |
47 | func TestIconProvider(t *testing.T) {
48 | p := NewProvider("test")
49 | require.NoError(t, p.Hex("lgtm", "1F44D"))
50 | require.Error(t, p.Hex("not-real", "xx"))
51 | p.Symbol("test", "a")
52 | p.Symbol("ligature-font", "home")
53 | p.Font("testfont")
54 | p.AddStyle(func(n *pango.Node) { n.Weight(200) })
55 |
56 | tests := []struct{ desc, icon, expected string }{
57 | {"no output for unknown icon", "unknown", ""},
58 | {"simple icon", "test", "a"},
59 | {"emoji", "lgtm", "👍"},
60 | {"ligature", "ligature-font", "home"},
61 | }
62 | for _, tc := range tests {
63 | pangoTesting.AssertEqual(t, tc.expected, pango.Icon("test-"+tc.icon).String(), tc.desc)
64 | }
65 |
66 | pangoTesting.AssertEqual(t,
67 | "a",
68 | pango.Icon("test-test").Color(colors.Hex("#f00")).String(),
69 | "Attributes are added to a wrapping ",
70 | )
71 |
72 | pangoTesting.AssertEqual(t,
73 | `homefoobar`,
75 | pango.Icon("test-ligature-font").Italic().Append(pango.Text("foobar").Bold()).String(),
76 | "Append adds new elements without icon font styling",
77 | )
78 | }
79 |
--------------------------------------------------------------------------------
/pango/icons/material/material.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package material provides support for Google's Material Design Icons
17 | from https://github.com/google/material-design-icons
18 |
19 | It uses font/MaterialIcons-Regular.codepoints to get the list of icons,
20 | and requires font/MaterialIcons-Regular.ttf to be installed.
21 | */
22 | package material
23 |
24 | import (
25 | "bufio"
26 | "fmt"
27 | "path/filepath"
28 | "strings"
29 |
30 | "github.com/soumya92/barista/pango"
31 | "github.com/soumya92/barista/pango/icons"
32 |
33 | "github.com/spf13/afero"
34 | )
35 |
36 | var fs = afero.NewOsFs()
37 |
38 | // Load initialises the material design icon provider from the given repo.
39 | func Load(repoPath string) error {
40 | f, err := fs.Open(filepath.Join(repoPath, "font/MaterialIcons-Regular.codepoints"))
41 | if err != nil {
42 | return err
43 | }
44 | defer f.Close()
45 | material := icons.NewProvider("material")
46 | material.Font("Material Icons")
47 | material.AddStyle(func(n *pango.Node) { n.UltraLight().Rise(-4000) })
48 | s := bufio.NewScanner(f)
49 | s.Split(bufio.ScanLines)
50 | for s.Scan() {
51 | line := strings.TrimSpace(s.Text())
52 | components := strings.Split(line, " ")
53 | if len(components) != 2 {
54 | return fmt.Errorf("Unexpected line '%s' in 'font/MaterialIcons-Regular.codepoints'", line)
55 | }
56 | // Material Design Icons uses '_', but all other fonts use '-',
57 | // so we'll normalise it here.
58 | name := strings.Replace(components[0], "_", "-", -1)
59 | value := components[1]
60 | err = material.Hex(name, value)
61 | if err != nil {
62 | return err
63 | }
64 | }
65 | return nil
66 | }
67 |
--------------------------------------------------------------------------------
/pango/icons/material/material_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package material
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/pango"
21 | "github.com/soumya92/barista/testing/cron"
22 | "github.com/soumya92/barista/testing/githubfs"
23 | pangoTesting "github.com/soumya92/barista/testing/pango"
24 |
25 | "github.com/spf13/afero"
26 | "github.com/stretchr/testify/require"
27 | )
28 |
29 | func TestInvalid(t *testing.T) {
30 | fs = afero.NewMemMapFs()
31 | require.Error(t, Load("/src/no-such-directory"))
32 |
33 | afero.WriteFile(fs, "/src/material-error-1/font/MaterialIcons-Regular.codepoints", []byte(
34 | `-- Lines in weird formats --
35 | Empty:
36 |
37 | Valid line:
38 | someIcon 61
39 | otherIcon 62
40 | Invalid codepoint:
41 | badIcon xy`,
42 | ), 0644)
43 | require.Error(t, Load("/src/material-error-1"))
44 |
45 | afero.WriteFile(fs, "/src/material-error-2/iconfont/codepoint", nil, 0644)
46 | require.Error(t, Load("/src/material-error-2"))
47 |
48 | afero.WriteFile(fs, "/src/material-error-3/font/MaterialIcons-Regular.codepoints", []byte(
49 | `someIcon 61
50 | otherIcon 62
51 | badIcon xy`,
52 | ), 0644)
53 | require.Error(t, Load("/src/material-error-3"))
54 | }
55 |
56 | func TestValid(t *testing.T) {
57 | fs = afero.NewMemMapFs()
58 | afero.WriteFile(fs, "/src/material/font/MaterialIcons-Regular.codepoints", []byte(
59 | `someIcon 61
60 | otherIcon 62
61 | thirdIcon 63`,
62 | ), 0644)
63 | require.NoError(t, Load("/src/material"))
64 | pangoTesting.AssertText(t, "a", pango.Icon("material-someIcon").String())
65 | }
66 |
67 | // TestLive tests that current master branch of the icon font works with
68 | // this package. This test only runs when CI runs tests in 'cron' mode,
69 | // which provides timely notifications of incompatible changes while
70 | // keeping default tests hermetic.
71 | func TestLive(t *testing.T) {
72 | fs = githubfs.New()
73 | cron.Test(t, func() error {
74 | if err := Load("/google/material-design-icons/master"); err != nil {
75 | return err
76 | }
77 | // At least one of these icons should be loaded.
78 | testIcons := pango.New(
79 | pango.Icon("material-face"),
80 | pango.Icon("material-room"),
81 | pango.Icon("material-view-agenda"),
82 | pango.Icon("material-link-off"),
83 | )
84 | require.NotEmpty(t, testIcons.String(), "No expected icons were loaded")
85 | return nil
86 | })
87 | }
88 |
--------------------------------------------------------------------------------
/pango/icons/mdi/mdi.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package mdi provides support for "Material Design Icons" from
17 | https://materialdesignicons.com/, a fork and extension of Material.
18 |
19 | It requires cloning the webfont repo
20 | https://github.com/Templarian/MaterialDesign-Webfont,
21 | uses scss/_variables.scss to get the list of icons,
22 | and requires fonts/materialdesignicons-webfont.ttf to be installed.
23 | */
24 | package mdi
25 |
26 | import (
27 | "bufio"
28 | "errors"
29 | "fmt"
30 | "path/filepath"
31 | "strings"
32 | "unicode"
33 |
34 | "github.com/soumya92/barista/pango"
35 | "github.com/soumya92/barista/pango/icons"
36 |
37 | "github.com/spf13/afero"
38 | )
39 |
40 | var fs = afero.NewOsFs()
41 |
42 | // Load initialises the material design (community) icon provider
43 | // from the given repo.
44 | func Load(repoPath string) error {
45 | f, err := fs.Open(filepath.Join(repoPath, "scss/_variables.scss"))
46 | if err != nil {
47 | return err
48 | }
49 | defer f.Close()
50 | mdi := icons.NewProvider("mdi")
51 | mdi.Font("Material Design Icons")
52 | mdi.AddStyle(func(n *pango.Node) { n.UltraLight() })
53 | started := false
54 | s := bufio.NewScanner(f)
55 | s.Split(bufio.ScanLines)
56 | for s.Scan() {
57 | line := strings.TrimSpace(s.Text())
58 | if !started {
59 | if strings.Contains(line, "$mdi-icons:") {
60 | started = true
61 | }
62 | continue
63 | }
64 | if line == ");" {
65 | return nil
66 | }
67 | colon := strings.Index(line, ":")
68 | if colon < 0 {
69 | return fmt.Errorf("Unexpected line '%s'", line)
70 | }
71 | name := strings.TrimFunc(line[:colon], func(r rune) bool {
72 | return unicode.IsSpace(r) || r == '"'
73 | })
74 | value := strings.TrimFunc(line[colon+1:], func(r rune) bool {
75 | return unicode.IsSpace(r) || r == ','
76 | })
77 | err = mdi.Hex(name, value)
78 | if err != nil {
79 | return err
80 | }
81 | }
82 | if !started {
83 | return errors.New("Could not find any icons in _variables.scss")
84 | }
85 | return errors.New("Expected ); to end $mdi-icons, got end of file")
86 | }
87 |
--------------------------------------------------------------------------------
/pango/icons/typicons/typicons.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package typicons provides support for Typicons
17 | from https://github.com/stephenhutchings/typicons.font
18 |
19 | It uses config.yml to get the list of icons,
20 | and requires src/font/typicons.ttf to be installed.
21 | */
22 | package typicons
23 |
24 | import (
25 | "encoding/json"
26 | "path/filepath"
27 |
28 | "github.com/soumya92/barista/pango/icons"
29 |
30 | "github.com/spf13/afero"
31 | )
32 |
33 | var fs = afero.NewOsFs()
34 |
35 | // Load initialises the typicons icon provider from the given repo.
36 | func Load(repoPath string) error {
37 | f, err := fs.Open(filepath.Join(repoPath, "src/font/typicons.json"))
38 | if err != nil {
39 | return err
40 | }
41 | defer f.Close()
42 | t := icons.NewProvider("typecn")
43 | t.Font("Typicons")
44 | var conf map[string]int
45 | err = json.NewDecoder(f).Decode(&conf)
46 | if err != nil {
47 | return err
48 | }
49 | for name, glyphCode := range conf {
50 | t.Symbol(name, string(rune(glyphCode)))
51 | }
52 | return nil
53 | }
54 |
--------------------------------------------------------------------------------
/pango/icons/typicons/typicons_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package typicons
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/soumya92/barista/pango"
21 | "github.com/soumya92/barista/testing/cron"
22 | "github.com/soumya92/barista/testing/githubfs"
23 | pangoTesting "github.com/soumya92/barista/testing/pango"
24 |
25 | "github.com/spf13/afero"
26 | "github.com/stretchr/testify/require"
27 | )
28 |
29 | func TestInvalid(t *testing.T) {
30 | fs = afero.NewMemMapFs()
31 | require.Error(t, Load("/src/no-such-directory"))
32 |
33 | afero.WriteFile(fs, "/src/typicons-error-1/src/font/typicons.json", []byte(
34 | `-- Invalid JSON --`,
35 | ), 0644)
36 | require.Error(t, Load("/src/typicons-error-1"))
37 |
38 | afero.WriteFile(fs, "/src/typicons-error-2/src/font/typicons.json", nil, 0644)
39 | require.Error(t, Load("/src/typicons-error-2"))
40 |
41 | afero.WriteFile(fs, "/src/typicons-error-3/src/font/typicons.json", []byte(
42 | `{
43 | "someIcon": 97,
44 | "otherIcon": 98,
45 | "thirdIcon": "ghij"
46 | }
47 | `,
48 | ), 0644)
49 | require.Error(t, Load("/src/typicons-error-3"))
50 | }
51 |
52 | func TestValid(t *testing.T) {
53 | fs = afero.NewMemMapFs()
54 | afero.WriteFile(fs, "/src/typicons/src/font/typicons.json", []byte(
55 | `{
56 | "someIcon": 97,
57 | "otherIcon": 98,
58 | "thirdIcon": 99
59 | }
60 | `,
61 | ), 0644)
62 | require.NoError(t, Load("/src/typicons"))
63 | pangoTesting.AssertText(t, "a", pango.Icon("typecn-someIcon").String())
64 | pangoTesting.AssertText(t, "b", pango.Icon("typecn-otherIcon").String())
65 | }
66 |
67 | // TestLive tests that current master branch of the icon font works with
68 | // this package. This test only runs when CI runs tests in 'cron' mode,
69 | // which provides timely notifications of incompatible changes while
70 | // keeping default tests hermetic.
71 | func TestLive(t *testing.T) {
72 | fs = githubfs.New()
73 | cron.Test(t, func() error {
74 | if err := Load("/stephenhutchings/typicons.font/master"); err != nil {
75 | return err
76 | }
77 | // At least one of these icons should be loaded.
78 | testIcons := pango.New(
79 | pango.Icon("typecn-pen"),
80 | pango.Icon("typecn-flag-outline"),
81 | pango.Icon("typecn-plus"),
82 | pango.Icon("typecn-beaker"),
83 | )
84 | require.NotEmpty(t, testIcons.String(), "No expected icons were loaded")
85 | return nil
86 | })
87 | }
88 |
--------------------------------------------------------------------------------
/pango/kwattrs.rb:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | require 'optparse'
16 | require_relative '../rb/gofile.rb'
17 |
18 | attr_name = nil
19 | OptionParser.new do |opts|
20 | opts.on('-n', '--name NAME', 'Attribute name') { |v| attr_name = v }
21 | end.parse!
22 | abort if attr_name.nil?
23 |
24 | write_go_file("kwattrs_#{attr_name}.go") do |out|
25 | out.puts 'package pango'
26 | ARGV.map { |it| it.split ':' }.each do |method_name, value|
27 | value = method_name.downcase if value.nil?
28 | out.write(<<~METHOD)
29 |
30 | // #{method_name} sets the pango #{attr_name} to "#{value}".
31 | func (n *Node) #{method_name}() *Node {
32 | \treturn n.setAttr("#{attr_name}", "#{value}")
33 | }
34 | METHOD
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/pango/kwattrs_size.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by kwattrs.rb; DO NOT EDIT.
16 |
17 | package pango
18 |
19 | // XXSmall sets the pango size to "xx-small".
20 | func (n *Node) XXSmall() *Node {
21 | return n.setAttr("size", "xx-small")
22 | }
23 |
24 | // XSmall sets the pango size to "x-small".
25 | func (n *Node) XSmall() *Node {
26 | return n.setAttr("size", "x-small")
27 | }
28 |
29 | // Small sets the pango size to "small".
30 | func (n *Node) Small() *Node {
31 | return n.setAttr("size", "small")
32 | }
33 |
34 | // Medium sets the pango size to "medium".
35 | func (n *Node) Medium() *Node {
36 | return n.setAttr("size", "medium")
37 | }
38 |
39 | // Large sets the pango size to "large".
40 | func (n *Node) Large() *Node {
41 | return n.setAttr("size", "large")
42 | }
43 |
44 | // XLarge sets the pango size to "x-large".
45 | func (n *Node) XLarge() *Node {
46 | return n.setAttr("size", "x-large")
47 | }
48 |
49 | // XXLarge sets the pango size to "xx-large".
50 | func (n *Node) XXLarge() *Node {
51 | return n.setAttr("size", "xx-large")
52 | }
53 |
--------------------------------------------------------------------------------
/pango/kwattrs_stretch.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by kwattrs.rb; DO NOT EDIT.
16 |
17 | package pango
18 |
19 | // UltraCondensed sets the pango stretch to "ultracondensed".
20 | func (n *Node) UltraCondensed() *Node {
21 | return n.setAttr("stretch", "ultracondensed")
22 | }
23 |
24 | // ExtraCondensed sets the pango stretch to "extracondensed".
25 | func (n *Node) ExtraCondensed() *Node {
26 | return n.setAttr("stretch", "extracondensed")
27 | }
28 |
29 | // Condensed sets the pango stretch to "condensed".
30 | func (n *Node) Condensed() *Node {
31 | return n.setAttr("stretch", "condensed")
32 | }
33 |
34 | // SemiCondensed sets the pango stretch to "semicondensed".
35 | func (n *Node) SemiCondensed() *Node {
36 | return n.setAttr("stretch", "semicondensed")
37 | }
38 |
39 | // StretchNormal sets the pango stretch to "normal".
40 | func (n *Node) StretchNormal() *Node {
41 | return n.setAttr("stretch", "normal")
42 | }
43 |
44 | // SemiExpanded sets the pango stretch to "semiexpanded".
45 | func (n *Node) SemiExpanded() *Node {
46 | return n.setAttr("stretch", "semiexpanded")
47 | }
48 |
49 | // Expanded sets the pango stretch to "expanded".
50 | func (n *Node) Expanded() *Node {
51 | return n.setAttr("stretch", "expanded")
52 | }
53 |
54 | // ExtraExpanded sets the pango stretch to "extraexpanded".
55 | func (n *Node) ExtraExpanded() *Node {
56 | return n.setAttr("stretch", "extraexpanded")
57 | }
58 |
59 | // UltraExpanded sets the pango stretch to "ultraexpanded".
60 | func (n *Node) UltraExpanded() *Node {
61 | return n.setAttr("stretch", "ultraexpanded")
62 | }
63 |
--------------------------------------------------------------------------------
/pango/kwattrs_strikethrough.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by kwattrs.rb; DO NOT EDIT.
16 |
17 | package pango
18 |
19 | // Strikethrough sets the pango strikethrough to "true".
20 | func (n *Node) Strikethrough() *Node {
21 | return n.setAttr("strikethrough", "true")
22 | }
23 |
24 | // NoStrikethrough sets the pango strikethrough to "false".
25 | func (n *Node) NoStrikethrough() *Node {
26 | return n.setAttr("strikethrough", "false")
27 | }
28 |
--------------------------------------------------------------------------------
/pango/kwattrs_style.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by kwattrs.rb; DO NOT EDIT.
16 |
17 | package pango
18 |
19 | // StyleNormal sets the pango style to "normal".
20 | func (n *Node) StyleNormal() *Node {
21 | return n.setAttr("style", "normal")
22 | }
23 |
24 | // Oblique sets the pango style to "oblique".
25 | func (n *Node) Oblique() *Node {
26 | return n.setAttr("style", "oblique")
27 | }
28 |
29 | // Italic sets the pango style to "italic".
30 | func (n *Node) Italic() *Node {
31 | return n.setAttr("style", "italic")
32 | }
33 |
--------------------------------------------------------------------------------
/pango/kwattrs_underline.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by kwattrs.rb; DO NOT EDIT.
16 |
17 | package pango
18 |
19 | // UnderlineNone sets the pango underline to "none".
20 | func (n *Node) UnderlineNone() *Node {
21 | return n.setAttr("underline", "none")
22 | }
23 |
24 | // UnderlineSingle sets the pango underline to "single".
25 | func (n *Node) UnderlineSingle() *Node {
26 | return n.setAttr("underline", "single")
27 | }
28 |
29 | // UnderlineDouble sets the pango underline to "double".
30 | func (n *Node) UnderlineDouble() *Node {
31 | return n.setAttr("underline", "double")
32 | }
33 |
34 | // UnderlineLow sets the pango underline to "low".
35 | func (n *Node) UnderlineLow() *Node {
36 | return n.setAttr("underline", "low")
37 | }
38 |
39 | // UnderlineError sets the pango underline to "error".
40 | func (n *Node) UnderlineError() *Node {
41 | return n.setAttr("underline", "error")
42 | }
43 |
--------------------------------------------------------------------------------
/pango/kwattrs_variant.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by kwattrs.rb; DO NOT EDIT.
16 |
17 | package pango
18 |
19 | // VariantNormal sets the pango variant to "normal".
20 | func (n *Node) VariantNormal() *Node {
21 | return n.setAttr("variant", "normal")
22 | }
23 |
24 | // SmallCaps sets the pango variant to "smallcaps".
25 | func (n *Node) SmallCaps() *Node {
26 | return n.setAttr("variant", "smallcaps")
27 | }
28 |
--------------------------------------------------------------------------------
/pango/kwattrs_weight.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Code generated by kwattrs.rb; DO NOT EDIT.
16 |
17 | package pango
18 |
19 | // UltraLight sets the pango weight to "ultralight".
20 | func (n *Node) UltraLight() *Node {
21 | return n.setAttr("weight", "ultralight")
22 | }
23 |
24 | // Light sets the pango weight to "light".
25 | func (n *Node) Light() *Node {
26 | return n.setAttr("weight", "light")
27 | }
28 |
29 | // WeightNormal sets the pango weight to "normal".
30 | func (n *Node) WeightNormal() *Node {
31 | return n.setAttr("weight", "normal")
32 | }
33 |
34 | // Bold sets the pango weight to "bold".
35 | func (n *Node) Bold() *Node {
36 | return n.setAttr("weight", "bold")
37 | }
38 |
39 | // UltraBold sets the pango weight to "ultrabold".
40 | func (n *Node) UltraBold() *Node {
41 | return n.setAttr("weight", "ultrabold")
42 | }
43 |
44 | // Heavy sets the pango weight to "heavy".
45 | func (n *Node) Heavy() *Node {
46 | return n.setAttr("weight", "heavy")
47 | }
48 |
--------------------------------------------------------------------------------
/rb/gofile.rb:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | require_relative 'run_cmd.rb'
16 |
17 | COPYRIGHT = File.readlines(__FILE__)
18 | .take_while { |line| line.start_with? '#' }
19 | .map { |comment| comment.sub('#', '//') }
20 | .join
21 |
22 | # Writes the standard copyright and generated file header, then provides a file
23 | # handle for callers to append the actual go code, and finally runs the file
24 | # through goimports to clean up any unused imports and format the code.
25 | def write_go_file(name)
26 | out = File.open(name, 'w')
27 | out.puts COPYRIGHT
28 | out.puts
29 | out.puts "// Code generated by #{File.basename($PROGRAM_NAME)}; DO NOT EDIT."
30 | out.puts
31 | yield out
32 | out.close
33 | run_cmd('goimports', '-w', name)
34 | end
35 |
--------------------------------------------------------------------------------
/rb/run_cmd.rb:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | require 'open3'
16 |
17 | def run_cmd(*args)
18 | output, status = Open3.capture2e(*args)
19 | raise "Error executing #{args}: #{status}" unless status.success?
20 |
21 | output
22 | end
23 |
--------------------------------------------------------------------------------
/samples/sample-bar/cachehttp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // +build !prod
16 |
17 | // When included in the main build process, this file PERMANENTLY caches all
18 | // HTTP requests. This is useful for quickly prototyping customisations to the
19 | // bar without incurring HTTP costs or consuming quota on remote services.
20 | // The cache is stored in ~/.cache/barista/http (using XDG_CACHE_HOME if set),
21 | // and individual responses can be deleted if a fresher copy is needed.
22 | // Once you are satisfied with the bar, simply omit this file (or build with
23 | // the "prod" tag) to build a production version of the bar with no caching.
24 |
25 | package main
26 |
27 | import "net/http"
28 | import "github.com/soumya92/barista/testing/httpcache"
29 |
30 | func init() {
31 | http.DefaultTransport = httpcache.Wrap(http.DefaultTransport)
32 | }
33 |
--------------------------------------------------------------------------------
/samples/stable-api/stable-api.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // stable-api demonstrates a bar that exercises barista's stable API.
16 | package main
17 |
18 | import (
19 | "syscall"
20 | "time"
21 |
22 | "github.com/soumya92/barista"
23 | "github.com/soumya92/barista/bar"
24 | "github.com/soumya92/barista/timing"
25 |
26 | "github.com/dustin/go-humanize"
27 | )
28 |
29 | type simpleClockModule struct {
30 | format string
31 | interval time.Duration
32 | }
33 |
34 | func (s simpleClockModule) Stream(sink bar.Sink) {
35 | sch := timing.NewScheduler()
36 | for {
37 | now := timing.Now()
38 | sink.Output(bar.TextSegment(now.Format(s.format)))
39 | next := now.Add(s.interval).Truncate(s.interval)
40 | sch.At(next).Tick()
41 | }
42 | }
43 |
44 | type diskSpaceModule string
45 |
46 | func (d diskSpaceModule) Stream(sink bar.Sink) {
47 | sch := timing.NewScheduler().Every(5 * time.Second)
48 | for {
49 | var stat syscall.Statfs_t
50 | err := syscall.Statfs(string(d), &stat)
51 | if sink.Error(err) {
52 | return
53 | }
54 | sink.Output(bar.TextSegment(
55 | humanize.IBytes(stat.Bavail * uint64(stat.Bsize)),
56 | ))
57 | sch.Tick()
58 | }
59 | }
60 |
61 | func main() {
62 | panic(barista.Run(
63 | diskSpaceModule("/home"),
64 | simpleClockModule{"Mon Jan 02", time.Hour},
65 | simpleClockModule{"15:04:05", time.Second},
66 | ))
67 | }
68 |
--------------------------------------------------------------------------------
/sink/sink.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package sink provides functions to create sinks.
16 | package sink
17 |
18 | import (
19 | "github.com/soumya92/barista/bar"
20 | "github.com/soumya92/barista/base/value"
21 | )
22 |
23 | // Func creates a bar.Sink that sends sends output in the form of Segments.
24 | func Func(s func(bar.Segments)) bar.Sink {
25 | return func(o bar.Output) {
26 | if o == nil {
27 | s(nil)
28 | return
29 | }
30 | s(o.Segments())
31 | }
32 | }
33 |
34 | // New creates a new sink and returns a channel that
35 | // will emit any outputs sent to the sink.
36 | func New() (<-chan bar.Segments, bar.Sink) {
37 | return Buffered(0)
38 | }
39 |
40 | // Buffered creates a new buffered sink.
41 | func Buffered(bufCount int) (<-chan bar.Segments, bar.Sink) {
42 | ch := make(chan bar.Segments, bufCount)
43 | return ch, Func(func(o bar.Segments) { ch <- o })
44 | }
45 |
46 | // Null returns a sink that swallows any output sent to it.
47 | func Null() bar.Sink {
48 | ch, sink := New()
49 | go func() {
50 | for range ch {
51 | }
52 | }()
53 | return sink
54 | }
55 |
56 | // Value returns a sink that sends output to a base.Value.
57 | func Value() (*value.Value, bar.Sink) {
58 | ch, sink := New()
59 | val := new(value.Value)
60 | go func(ch <-chan bar.Segments, val *value.Value) {
61 | for o := range ch {
62 | val.Set(o)
63 | }
64 | }(ch, val)
65 | val.Set(bar.Segments(nil))
66 | return val, sink
67 | }
68 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Copyright 2018 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -e
18 | mkdir -p profiles/
19 |
20 | # Since quite a few tests have sleeps, running nproc + 2 tests should result in most
21 | # effective parallelisation.
22 | NPAR="$(($(nproc) + 2))"
23 |
24 | # Skip running go vet in cron, since the code hasn't changed.
25 | if [ "$CRON" != "true" ]; then
26 | echo "Vet: go vet"
27 | go vet
28 | fi
29 |
30 | echo "Test: Running $NPAR in parallel"
31 | # Run tests with coverage for all barista packages
32 | go list ./... \
33 | | grep -v /samples/ \
34 | | sed "s|_$PWD|.|" \
35 | | tac \
36 | | xargs -n1 -P$NPAR -IPKG sh -c \
37 | 'for try in `seq 1 3`; do
38 | go test \
39 | -timeout 90s \
40 | -coverprofile=profiles/$(echo "PKG" | sed -e "s|./||" -e "s|/|_|g").out \
41 | -covermode=atomic \
42 | "PKG" \
43 | && exit 0
44 | done
45 | exit 1'
46 |
47 | echo "Test: Logging with -tags baristadebuglog"
48 | # Debug log tests need the build tag, otherwise the nop versions will be used.
49 | go test -tags baristadebuglog -coverprofile=profiles/logging_real.out -covermode=atomic github.com/soumya92/barista/logging
50 |
51 | # Remove all _capi.go coverage since those will intentionally not be tested.
52 | for profile in profiles/*.out; do
53 | perl -i -ne 'print unless /_capi\.go:/' "$profile"
54 | done
55 |
56 | # Merge all code coverage reports. Doing this here means that after running
57 | # ./test.sh,
58 | # go tool cover -html=c.out
59 | # will show a coverage report instead of complaining about a bad format.
60 | grep -E '^mode: \w+$' "$(find profiles/ -name '*.out' -print -quit)" > c.out
61 | grep -hEv '^(mode: \w+)?$' profiles/*.out >> c.out
62 | rm -rf profiles/
63 |
64 | echo "Test: Samples"
65 | # Run tests only for samples.
66 | # This is just to make sure that all samples compile.
67 | go test ./samples/...
68 |
--------------------------------------------------------------------------------
/testing/capi/funcs.rb:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | require_relative 'types.rb'
16 |
17 | Argument = Struct.new(:name, :type) do
18 | def initialize(sig, idx)
19 | type, _, name = sig.rpartition ' '
20 | if type.empty? || (name == '*')
21 | type = "#{type} #{name}".strip
22 | name = "unnamed_#{idx}"
23 | else
24 | name = "arg_#{name}"
25 | end
26 | if name[-1] == ']'
27 | name, _, count = name[0...-1].rpartition '['
28 | super(name, CTypePrimAry.new(from_c_type(type), count))
29 | return
30 | end
31 | super(name, from_c_type(type))
32 | end
33 | end
34 |
35 | def parse_args(signature)
36 | in_fn_ptr = 0
37 | curr = ''
38 | args = []
39 | signature.each_char do |c|
40 | if c == '('
41 | in_fn_ptr += 1
42 | next
43 | end
44 | if in_fn_ptr.positive?
45 | in_fn_ptr -= 1 if c == ')'
46 | curr = "void * funcptr_#{args.size}" if in_fn_ptr.zero?
47 | next
48 | end
49 | if c == ','
50 | args << curr unless curr.empty?
51 | curr = ''
52 | next
53 | end
54 | curr += c
55 | end
56 | args << curr
57 | args.reject { |arg| arg.strip == 'void' || arg.strip == '...' }
58 | .map.with_index { |arg, idx| Argument.new(arg, idx) }
59 | end
60 |
61 | Prototype = Struct.new(:name, :type, :args) do
62 | def initialize(name, typeref, signature)
63 | name = "_#{name}" if GO_KEYWORDS.include? name
64 | super(name,
65 | from_c_type(typeref.sub('typename:', '').sub('struct:', 'struct ')),
66 | parse_args(signature[1...-1]))
67 | end
68 |
69 | def go_func
70 | arglist = args.map { |a| "#{a.name} #{a.type.gotype}" }
71 | "#{name}(#{arglist.join(', ')}) #{type.gotype}"
72 | end
73 |
74 | def go_type
75 | argtypes = args.map { |a| a.type.gotype.to_s }
76 | "func(#{argtypes.join(', ')}) #{type.gotype}"
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/testing/cron/cron.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package cron provides a function to run a test only in Travis CI cron
17 | runs, and retry the test with increasing delays a few times before
18 | failing the build.
19 |
20 | The primary purpose of this method is to allow cron test that are
21 | non-hermetic and run against live (usually http) endpoints. Since the
22 | live endpoints could occasionally throw errors, there is built-in retry
23 | with delays between attempts.
24 | */
25 | package cron
26 |
27 | import (
28 | "os"
29 | "testing"
30 | "time"
31 |
32 | "github.com/stretchr/testify/require"
33 | )
34 |
35 | var getenv = os.Getenv
36 | var waits = []int{1, 3, 7, 15}
37 |
38 | // Test runs a test if running in the CI's cron mode. It handles retries if the
39 | // test returns an error, but passes through failures to the test suite. This
40 | // allows the test function to retry by returning transient errors, while not
41 | // wasting attempts on non-retryable failures.
42 | func Test(t *testing.T, testFunc func() error) {
43 | if getenv("CRON") != "true" {
44 | t.Skipf("Skipping LiveVersion test in non-cron mode")
45 | }
46 | for _, wait := range waits {
47 | err := testFunc()
48 | if err == nil {
49 | return
50 | }
51 | t.Logf("Waiting %ds due to %v", wait, err)
52 | time.Sleep(time.Duration(wait) * time.Second)
53 | }
54 | require.NoError(t, testFunc(), "On last cron attempt")
55 | }
56 |
--------------------------------------------------------------------------------
/testing/cron/cron_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cron
16 |
17 | import (
18 | "fmt"
19 | "os"
20 | "testing"
21 | "time"
22 |
23 | "github.com/soumya92/barista/testing/fail"
24 |
25 | "github.com/stretchr/testify/require"
26 | )
27 |
28 | func init() {
29 | // Not waiting 31 seconds for this test.
30 | waits = []int{0, 1, 2, 3}
31 | }
32 |
33 | func failNTimes(n int) func() error {
34 | count := 0
35 | return func() error {
36 | count++
37 | if count > n {
38 | return nil
39 | }
40 | return fmt.Errorf("Failing N Times: %d out of %d", count, n)
41 | }
42 | }
43 |
44 | func mockGetenv(cronEnvVar string) func(string) string {
45 | return func(key string) string {
46 | if key == "CRON" {
47 | return cronEnvVar
48 | }
49 | return os.Getenv(key)
50 | }
51 | }
52 |
53 | func TestNotCron(t *testing.T) {
54 | getenv = mockGetenv("false")
55 | Test(t, func() error {
56 | require.Fail(t, "test func called but not a cron build")
57 | return nil
58 | })
59 | }
60 |
61 | func TestCron(t *testing.T) {
62 | getenv = mockGetenv("true")
63 |
64 | start := time.Now()
65 | fail.AssertFails(t, func(testT *testing.T) {
66 | Test(testT, failNTimes(100))
67 | }, "More than 4 failures from test function")
68 | end := time.Now()
69 | require.WithinDuration(t, start.Add(6*time.Second), end, time.Second)
70 |
71 | start = time.Now()
72 | Test(t, failNTimes(2))
73 | end = time.Now()
74 | require.WithinDuration(t, start.Add(1*time.Second), end, time.Second,
75 | "Test should only wait 0+1 seconds (for the 2 failures)")
76 |
77 | called := false
78 | Test(t, func() error {
79 | require.False(t, called, "test func called again after no error")
80 | called = true
81 | return nil
82 | })
83 | }
84 |
--------------------------------------------------------------------------------
/testing/fail/fail.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package fail provides methods to test and verify failing assertions.
16 | package fail
17 |
18 | import (
19 | "testing"
20 |
21 | "github.com/stretchr/testify/require"
22 | )
23 |
24 | // failed returns true if the given function failed the test,
25 | // using the provided fake testing.T instead of a new one, allowing
26 | // re-use between calls.
27 | func failed(fakeT *testing.T, fn func(*testing.T)) bool {
28 | doneCh := make(chan bool)
29 | // Need a separate goroutine in case the test function calls FailNow.
30 | go func() {
31 | defer func() { doneCh <- true }()
32 | fn(fakeT)
33 | }()
34 | <-doneCh
35 | return fakeT.Failed()
36 | }
37 |
38 | // Failed returns true if the given function failed the test.
39 | func Failed(fn func(*testing.T)) bool {
40 | return failed(&testing.T{}, fn)
41 | }
42 |
43 | // AssertFails asserts that the given test function fails the test.
44 | func AssertFails(t *testing.T, fn func(*testing.T), formatAndArgs ...interface{}) {
45 | if !Failed(fn) {
46 | require.Fail(t, "Expected test to fail", formatAndArgs...)
47 | }
48 | }
49 |
50 | // TestSetup represents an already set up test environment, which provides
51 | // a variant on AssertFails that fails the test as normal, but also fails
52 | // the test if the setup function causes test failures.
53 | type TestSetup struct {
54 | setupFailed bool
55 | fakeT *testing.T
56 | }
57 |
58 | // Setup shares the fake testing.T instance between a setup method
59 | // and a test method, providing an AssertFails method that fails the test
60 | // if the setup fails, or if the test method does not.
61 | func Setup(setupFn func(*testing.T)) *TestSetup {
62 | t := &TestSetup{fakeT: &testing.T{}}
63 | t.setupFailed = failed(t.fakeT, setupFn)
64 | return t
65 | }
66 |
67 | // AssertFails asserts that the given test function fails the test, and
68 | // that the setup function used did not cause any test failures.
69 | func (s *TestSetup) AssertFails(t *testing.T, fn func(*testing.T), formatAndArgs ...interface{}) {
70 | if s.setupFailed {
71 | require.Fail(t, "Test failed in setup", formatAndArgs...)
72 | }
73 | if !failed(s.fakeT, fn) {
74 | require.Fail(t, "Expected test to fail", formatAndArgs...)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/testing/fail/fail_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package fail
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/stretchr/testify/assert"
21 | "github.com/stretchr/testify/require"
22 | )
23 |
24 | var failures = []struct {
25 | fn func(*testing.T)
26 | desc string
27 | }{
28 | {func(t *testing.T) { t.Fail() }, "Fail"},
29 | {func(t *testing.T) { t.FailNow() }, "FailNow"},
30 | {func(t *testing.T) { t.Fatal("fatal") }, "Fatal"},
31 | {func(t *testing.T) { assert.Fail(t, "something") }, "assert.Fail"},
32 | {func(t *testing.T) { assert.FailNow(t, "error") }, "assert.FailNow"},
33 | {func(t *testing.T) { require.Fail(t, "required") }, "require.Fail"},
34 | {func(t *testing.T) { assert.True(t, false) }, "assert.True"},
35 | {func(t *testing.T) { require.Equal(t, "a", "b") }, "require.Equal"},
36 | }
37 |
38 | var noFailures = []struct {
39 | fn func(*testing.T)
40 | desc string
41 | }{
42 | {func(t *testing.T) {}, "Nop"},
43 | {func(t *testing.T) { t.Log("everything is awesome") }, "Log"},
44 | {func(t *testing.T) { require.True(t, true) }, "require.True"},
45 | {func(t *testing.T) { assert.Equal(t, 4, 2+2) }, "assert.Equal"},
46 | }
47 |
48 | func TestFailures(t *testing.T) {
49 | for _, f := range failures {
50 | AssertFails(t, f.fn, f.desc)
51 | }
52 | }
53 |
54 | func TestNoFailure(t *testing.T) {
55 | for _, f := range noFailures {
56 | assert.False(t, Failed(f.fn), f.desc)
57 | }
58 | }
59 |
60 | func TestAssertionWithNoFailure(t *testing.T) {
61 | for _, f := range noFailures {
62 | AssertFails(t, func(t *testing.T) {
63 | AssertFails(t, f.fn)
64 | }, f.desc)
65 | }
66 | }
67 |
68 | func TestSetupFailures(t *testing.T) {
69 | for _, f := range failures {
70 | for _, ff := range failures {
71 | AssertFails(t, func(t *testing.T) {
72 | Setup(f.fn).AssertFails(t, ff.fn,
73 | "Setup(%s).AssertFails(%s)", f.desc, ff.desc)
74 | })
75 | }
76 | for _, nf := range noFailures {
77 | AssertFails(t, func(t *testing.T) {
78 | Setup(f.fn).AssertFails(t, nf.fn,
79 | "Setup(%s).AssertFails(%s)", f.desc, nf.desc)
80 | })
81 | }
82 | }
83 | }
84 |
85 | func TestNoFailureWithSetup(t *testing.T) {
86 | for _, s := range noFailures {
87 | for _, nf := range noFailures {
88 | AssertFails(t, func(t *testing.T) {
89 | Setup(s.fn).AssertFails(t, nf.fn,
90 | "Setup(%s).AssertFails(%s)", s.desc, nf.desc)
91 | })
92 | }
93 | }
94 | }
95 |
96 | func TestFailureWithSetup(t *testing.T) {
97 | for _, s := range noFailures {
98 | for _, f := range failures {
99 | Setup(s.fn).AssertFails(t, f.fn,
100 | "Setup(%s).AssertFails(%s)", s.desc, f.desc)
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/testing/flakes.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Copyright 2018 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | # This script runs the given test many times, in parallel, with all logging
17 | # enabled, and prints the names of the files containing output from failures.
18 | #
19 | # This is useful for tracking down flaky tests, hence the name.
20 |
21 | # To avoid depending on a second script, use a 'magic' first argument.
22 | if [ "$1" = "BARISTA_FLAKE_TEST" ]; then
23 | # When invoked by xargs using the magic first arg, it simply executes the
24 | # given command, piping the stdout and stderr to a temporary file.
25 | # If the command exits successfully (or ^C), nothing happens, otherwise
26 | # the name of the file is printed.
27 | shift
28 | tmpdir="$1"
29 | shift
30 | outfile="$(mktemp --tmpdir="$tmpdir")"
31 | "$@" >"$outfile" 2>&1
32 | status="$?"
33 | if [ "$status" -eq 0 ] || [ "$status" -eq 127 ]; then
34 | rm -f "$outfile"
35 | else
36 | echo "$outfile"
37 | fi
38 | exit $status
39 | fi
40 |
41 | # When invoked normally, sets up a parallel pipeline using seq | xargs to
42 | # run the given test many times and catching any failures.
43 |
44 | # TODO: Use argument parsing here.
45 | parallel=16
46 | total=96
47 | tmpdir="$(mktemp -d)"
48 |
49 | echo "Saving results to $tmpdir"
50 | seq 1 $total | xargs -n 1 -P $parallel $0 BARISTA_FLAKE_TEST "$tmpdir" go test -v -race -tags debuglog "$@" -- -finelog=
51 |
--------------------------------------------------------------------------------
/testing/githubfs/githubfs_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package githubfs
16 |
17 | import (
18 | "io/ioutil"
19 | "os"
20 | "testing"
21 | "time"
22 |
23 | testServer "github.com/soumya92/barista/testing/httpserver"
24 |
25 | "github.com/stretchr/testify/require"
26 | )
27 |
28 | func TestName(t *testing.T) {
29 | require.Contains(t, New().Name(), "GitHubFS")
30 | }
31 |
32 | func TestFs(t *testing.T) {
33 | ts := testServer.New()
34 | defer ts.Close()
35 | root = ts.URL
36 |
37 | fs := New()
38 |
39 | _, err := fs.Open("/code/500")
40 | require.Error(t, err)
41 | _, err = fs.OpenFile("/redir", 0, 0444)
42 | require.Error(t, err)
43 | _, err = fs.Stat("/code/403")
44 | require.Error(t, err)
45 |
46 | info, err := fs.Stat("/modtime/1382140800")
47 | require.NoError(t, err)
48 | modTime := time.Date(2013, time.October, 19, 0, 0, 0, 0, time.UTC)
49 | require.WithinDuration(t, modTime, info.ModTime(), time.Minute)
50 |
51 | f, err := fs.Open("/basic/empty")
52 | require.NoError(t, err)
53 | contents, err := ioutil.ReadAll(f)
54 | require.NoError(t, err)
55 | require.Equal(t, []byte{}, contents)
56 |
57 | f, err = fs.OpenFile("/basic/foo", os.O_RDONLY, 0600)
58 | require.NoError(t, err)
59 | contents, err = ioutil.ReadAll(f)
60 | require.NoError(t, err)
61 | require.Equal(t, "bar", string(contents))
62 | }
63 |
--------------------------------------------------------------------------------
/testing/httpcache/httpcache_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package httpcache
16 |
17 | import (
18 | "fmt"
19 | "io/ioutil"
20 | "net/http"
21 | "net/url"
22 | "os"
23 | "testing"
24 |
25 | testServer "github.com/soumya92/barista/testing/httpserver"
26 | "github.com/stretchr/testify/require"
27 | )
28 |
29 | func TestCache(t *testing.T) {
30 | server := testServer.New()
31 | defer server.Close()
32 | u, _ := url.Parse(server.URL)
33 | base := sanitizeRe.ReplaceAllString(u.Host, "-")
34 |
35 | dir, err := ioutil.TempDir("", "httpcache")
36 | if err != nil {
37 | t.Fatalf("failed to create test directory: %s", err)
38 | }
39 | defer os.RemoveAll(dir)
40 |
41 | os.Setenv("XDG_CACHE_HOME", dir)
42 | client := &http.Client{Transport: Wrap(http.DefaultTransport)}
43 |
44 | client.Get(server.URL + "/basic/foo")
45 | _, err = os.Stat(fmt.Sprintf("%s/barista/http/%s_basic-foo", dir, base))
46 | require.NoError(t, err, "simple response cached to correct file")
47 |
48 | client.Get(server.URL + "/code/404")
49 | _, err = os.Stat(fmt.Sprintf("%s/barista/http/%s_code-404", dir, base))
50 | require.NoError(t, err, "http non-200 responses also cached")
51 |
52 | _, redirectError := client.Get(server.URL + "/redir")
53 |
54 | r, err := client.Get(server.URL + "/tpl/debug?param=foo&bar=baz")
55 | require.NoError(t, err)
56 | body, _ := ioutil.ReadAll(r.Body)
57 | require.Equal(t, "\nbar = baz\nparam = foo\n", string(body))
58 |
59 | server.Close()
60 |
61 | r, err = client.Get(server.URL + "/basic/foo")
62 | require.NoError(t, err, "simple response cached")
63 | body, _ = ioutil.ReadAll(r.Body)
64 | require.Equal(t, "bar", string(body), "full body cached")
65 |
66 | r, err = client.Get(server.URL + "/tpl/debug?param=other-value")
67 | require.NoError(t, err, "response cached, despite different query params")
68 | body, _ = ioutil.ReadAll(r.Body)
69 | require.Equal(t, "\nbar = baz\nparam = foo\n", string(body),
70 | "body cached, not affected by query parameters")
71 |
72 | _, err = client.Get(server.URL + "/redir")
73 | require.Equal(t, redirectError, err, "redirects also cached")
74 |
75 | r, err = client.Get(server.URL + "/code/404")
76 | require.NoError(t, err, "server stopped, response from cache")
77 | require.Equal(t, r.StatusCode, 404, "status code also cached")
78 |
79 | _, err = client.Get(server.URL + "/code/204")
80 | require.Error(t, err, "no successful cached response, server stopped")
81 |
82 | _, err = os.Stat(fmt.Sprintf("%s/barista/http/%s_code-204", dir, base))
83 | require.True(t, os.IsNotExist(err), "transport error not cached")
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/testing/httpcache/testdata/debug.tpl:
--------------------------------------------------------------------------------
1 | {{range $key, $value := .}}
2 | {{$key}} = {{$value}}{{end}}
3 |
--------------------------------------------------------------------------------
/testing/httpclient/httpclient.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package httpclient provides a testable wrapper around an existing *http.Client.
16 | package httpclient
17 |
18 | import (
19 | "net/http"
20 | "net/url"
21 |
22 | l "github.com/soumya92/barista/logging"
23 |
24 | "golang.org/x/oauth2"
25 | )
26 |
27 | type rewritingTransport struct {
28 | newURL *url.URL
29 | transport http.RoundTripper
30 | }
31 |
32 | func (r rewritingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
33 | newReq := &http.Request{}
34 | *newReq = *req
35 | newReq.URL = &url.URL{}
36 | *newReq.URL = *req.URL
37 | newReq.URL.Scheme = r.newURL.Scheme
38 | newReq.URL.Host = r.newURL.Host
39 | return r.transport.RoundTrip(newReq)
40 | }
41 |
42 | // Wrap redirects all calls from the original *http.Client to the given host.
43 | // Typical usage would be httpclient.Wrap(client, server.URL), where server
44 | // is a httptest.Server or equivalent.
45 | func Wrap(client *http.Client, newURL string) {
46 | u, _ := url.Parse(newURL)
47 | client.Transport = rewritingTransport{
48 | newURL: u,
49 | transport: client.Transport,
50 | }
51 | }
52 |
53 | // FreezeOauthToken sets the client's token source to a static token source that
54 | // always provides the given access token.
55 | func FreezeOauthToken(client *http.Client, accessToken string) {
56 | if t, ok := client.Transport.(*oauth2.Transport); ok {
57 | t.Source = oauth2.StaticTokenSource(
58 | &oauth2.Token{AccessToken: accessToken})
59 | } else {
60 | l.Log("Client %v does not use an oauth transport (%T)",
61 | client, client.Transport)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/testing/notifier/notifier.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Package notifier provides assertions that notifier channels (<-chan struct{})
16 | // received or did not receive a signal.
17 | package notifier
18 |
19 | import (
20 | "testing"
21 | "time"
22 |
23 | "github.com/stretchr/testify/require"
24 | )
25 |
26 | var positiveTimeout = 10 * time.Millisecond
27 | var negativeTimeout = 10 * time.Second
28 |
29 | // AssertNotified asserts that the given channel received a notification.
30 | func AssertNotified(t *testing.T, ch <-chan struct{}, formatAndArgs ...interface{}) {
31 | select {
32 | case _, ok := <-ch:
33 | if !ok {
34 | require.Fail(t, "Expected notification but channel was closed", formatAndArgs...)
35 | }
36 | case <-time.After(negativeTimeout):
37 | require.Fail(t, "Expected notification not received", formatAndArgs...)
38 | }
39 | }
40 |
41 | // AssertClosed asserts that the given channel was closed.
42 | func AssertClosed(t *testing.T, ch <-chan struct{}, formatAndArgs ...interface{}) {
43 | select {
44 | case _, ok := <-ch:
45 | if ok {
46 | require.Fail(t, "Expected channel close, received notification", formatAndArgs...)
47 | }
48 | case <-time.After(negativeTimeout):
49 | require.Fail(t, "Channel not closed when expected", formatAndArgs...)
50 | }
51 | }
52 |
53 | // AssertNoUpdate asserts that the given channel was not notified or closed.
54 | func AssertNoUpdate(t *testing.T, ch <-chan struct{}, formatAndArgs ...interface{}) {
55 | select {
56 | case <-ch:
57 | require.Fail(t, "Unexpected notification", formatAndArgs...)
58 | case <-time.After(positiveTimeout):
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/testing/notifier/notifier_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package notifier
16 |
17 | import (
18 | "testing"
19 | "time"
20 |
21 | "github.com/soumya92/barista/base/notifier"
22 | "github.com/soumya92/barista/testing/fail"
23 | )
24 |
25 | func TestAssertions(t *testing.T) {
26 | fn, ch := notifier.New()
27 |
28 | AssertNoUpdate(t, ch, "no updates sent on channel")
29 | fn()
30 | AssertNotified(t, ch, "after update sent on channel")
31 |
32 | sendCh := make(chan struct{})
33 | ch = sendCh
34 | AssertNoUpdate(t, ch, "on open channel")
35 | close(sendCh)
36 | AssertClosed(t, ch, "after close")
37 | }
38 |
39 | func TestAssertionFailures(t *testing.T) {
40 | defer func(prev time.Duration) { negativeTimeout = prev }(negativeTimeout)
41 | negativeTimeout = 10 * time.Millisecond
42 |
43 | fn, ch := notifier.New()
44 | fail.AssertFails(t, func(t *testing.T) {
45 | AssertNotified(t, ch)
46 | }, "AssertNotified with no updates")
47 |
48 | fail.AssertFails(t, func(t *testing.T) {
49 | AssertClosed(t, ch)
50 | }, "AssertClosed with no updates")
51 |
52 | fn()
53 | fail.AssertFails(t, func(t *testing.T) {
54 | AssertClosed(t, ch)
55 | }, "AssertClosed with non-closing update")
56 |
57 | fn, ch = notifier.New()
58 | fn()
59 |
60 | fail.AssertFails(t, func(t *testing.T) {
61 | AssertNoUpdate(t, ch)
62 | }, "AssertNoUpdate with update")
63 |
64 | sendCh := make(chan struct{})
65 | ch = sendCh
66 | close(sendCh)
67 |
68 | fail.AssertFails(t, func(t *testing.T) {
69 | AssertNotified(t, ch)
70 | }, "AssertNotified after close")
71 |
72 | fail.AssertFails(t, func(t *testing.T) {
73 | AssertNoUpdate(t, ch)
74 | }, "AssertNoUpdate after close")
75 | }
76 |
--------------------------------------------------------------------------------
/testing/pango/pango.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package pango provides provides a method to test markup equality.
17 | It compares to strings that represent pango markup while ignoring
18 | differences in attribute order, escaping, etc.
19 | */
20 | package pango
21 |
22 | import (
23 | "fmt"
24 | "strings"
25 | "testing"
26 |
27 | "github.com/stretchr/testify/require"
28 | "golang.org/x/net/html"
29 | )
30 |
31 | // AssertEqual asserts that the given strings represent equivalent pango markup,
32 | // i.e. the result of their rendering will be the same.
33 | func AssertEqual(t *testing.T, expected, actual string, args ...interface{}) {
34 | expectedR, err := html.Parse(strings.NewReader(expected))
35 | require.NoError(t, err, args...)
36 | actualR, err := html.Parse(strings.NewReader(actual))
37 | require.NoError(t, err, args...)
38 | if !equalMarkup(expectedR, actualR) {
39 | require.Fail(t, fmt.Sprintf("%s !~= %s", expected, actual), args...)
40 | }
41 | }
42 |
43 | // AssertText asserts that the markup string has the expected text content.
44 | // Text content ignores any tags and attributes, using only rendered text.
45 | func AssertText(t *testing.T, expected string, markup string, args ...interface{}) {
46 | markupR, err := html.Parse(strings.NewReader(markup))
47 | require.NoError(t, err, args...)
48 | require.Equal(t, expected, textOf(markupR), args...)
49 | }
50 |
51 | func equalMarkup(a, b *html.Node) bool {
52 | if a == nil && b == nil {
53 | return true
54 | }
55 | if a == nil || b == nil {
56 | return false
57 | }
58 | if a.Data != b.Data {
59 | return false
60 | }
61 | if len(a.Attr) != len(b.Attr) {
62 | return false
63 | }
64 | aAttrMap := map[string]string{}
65 | for _, aAttr := range a.Attr {
66 | aAttrMap[aAttr.Key] = aAttr.Val
67 | }
68 | for _, bAttr := range b.Attr {
69 | if aAttrMap[bAttr.Key] != bAttr.Val {
70 | return false
71 | }
72 | }
73 | if !equalMarkup(a.NextSibling, b.NextSibling) {
74 | return false
75 | }
76 | if !equalMarkup(a.FirstChild, b.FirstChild) {
77 | return false
78 | }
79 | return true
80 | }
81 |
82 | func textOf(n *html.Node) (text string) {
83 | if n == nil {
84 | return text
85 | }
86 | if n.Type == html.TextNode {
87 | text += n.Data
88 | }
89 | text += textOf(n.FirstChild)
90 | text += textOf(n.NextSibling)
91 | return text
92 | }
93 |
--------------------------------------------------------------------------------
/timing/internal/timerfd/nativeendian.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package timerfd
16 |
17 | import (
18 | "encoding/binary"
19 | "unsafe"
20 | )
21 |
22 | var nativeEndian binary.ByteOrder
23 |
24 | func init() {
25 | i := uint32(1)
26 | b := (*[4]byte)(unsafe.Pointer(&i))
27 | if b[0] == 1 {
28 | nativeEndian = binary.LittleEndian
29 | } else {
30 | nativeEndian = binary.BigEndian
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/timing/internal/timerfd/timerfd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // +build linux
16 |
17 | package timerfd
18 |
19 | import (
20 | "errors"
21 | "fmt"
22 | "os"
23 |
24 | "golang.org/x/sys/unix"
25 | )
26 |
27 | // ErrTimerfdCancelled is the error returned when timer is cancelled due to
28 | // discontinuous clock change. See man timerfd_settime for details.
29 | var ErrTimerfdCancelled = errors.New("Timerfd was cancelled due to discontinuous change")
30 |
31 | // Timerfd provides higher-level abstraction for Linux-specific timerfd timers.
32 | type Timerfd struct {
33 | fd *os.File
34 | clockid int
35 | }
36 |
37 | func newTimerfd(clockid int) (*Timerfd, error) {
38 | fd, err := unix.TimerfdCreate(clockid, unix.TFD_NONBLOCK|unix.TFD_CLOEXEC)
39 | if err != nil {
40 | return nil, err
41 | }
42 | f := os.NewFile(uintptr(fd), "Timerfd")
43 | return &Timerfd{fd: f, clockid: clockid}, nil
44 | }
45 |
46 | // Settime arms or disarms the timer. See man timerfd_settime for details.
47 | func (t *Timerfd) Settime(newValue *unix.ItimerSpec, oldValue *unix.ItimerSpec, absolute bool, cancelOnSet bool) error {
48 | rawConn, err := t.fd.SyscallConn()
49 | if err != nil {
50 | return err
51 | }
52 | var err2 error
53 | err = rawConn.Control(func(fd uintptr) {
54 | var flags int
55 | if absolute {
56 | flags |= unix.TFD_TIMER_ABSTIME
57 | }
58 | if cancelOnSet {
59 | flags |= unix.TFD_TIMER_CANCEL_ON_SET
60 | }
61 | err2 = unix.TimerfdSettime(int(fd), flags, newValue, oldValue)
62 | })
63 | if err != nil {
64 | return err
65 | }
66 | return err2
67 | }
68 |
69 | // Wait waits until timer expiration. See man timerfd_create for details.
70 | func (t *Timerfd) Wait() (expirations uint64, err error) {
71 | var buf [8]byte
72 |
73 | n, err := t.fd.Read(buf[:])
74 | if err != nil {
75 | if pe, ok := err.(*os.PathError); ok {
76 | err = pe.Err
77 | }
78 | if err == unix.ECANCELED {
79 | err = ErrTimerfdCancelled
80 | }
81 | return 0, err
82 | }
83 | if n != 8 {
84 | panic(fmt.Sprintf("Timerfd returned %d bytes (expected 8)", n))
85 | }
86 | return nativeEndian.Uint64(buf[:]), nil
87 | }
88 |
89 | // GetClockid returns the clock id that this timerfd uses.
90 | func (t *Timerfd) GetClockid() int {
91 | return t.clockid
92 | }
93 |
94 | // Close closes the underlying timerfd descriptor.
95 | func (t *Timerfd) Close() error {
96 | return t.fd.Close()
97 | }
98 |
99 | // NewRealtimeTimerfd returns a Timerfd backed by CLOCK_REALTIME.
100 | func NewRealtimeTimerfd() (*Timerfd, error) {
101 | return newTimerfd(unix.CLOCK_REALTIME)
102 | }
103 |
--------------------------------------------------------------------------------
/timing/misc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package timing
16 |
17 | import (
18 | "time"
19 | )
20 |
21 | // nextAlignedExpiration calculates the next expiration time.
22 | // The expiration is first rounded to interval granularity, and then offset is added.
23 | //
24 | // For example, given interval 1h, and offset 15m, the expirations will happen at
25 | // :15 of every hour regardless of the initial time.
26 | func nextAlignedExpiration(initial time.Time, interval time.Duration, offset time.Duration) time.Time {
27 | next := initial.Truncate(interval).Add(offset)
28 | if !next.After(initial) {
29 | next = next.Add(interval)
30 | }
31 | if !next.After(initial) {
32 | panic("nextAlignedExpiration: bug: !next.After(initial)")
33 | }
34 | return next
35 | }
36 |
--------------------------------------------------------------------------------
/timing/time_scheduler.go:
--------------------------------------------------------------------------------
1 | package timing
2 |
3 | import (
4 | "sync"
5 | "time"
6 | )
7 |
8 | var _ schedulerImpl = &timeScheduler{}
9 |
10 | // timeScheduler is a scheduler backed by "time" package.
11 | type timeScheduler struct {
12 | mu sync.Mutex
13 | timer *time.Timer
14 | ticker *time.Ticker
15 | quitter chan struct{}
16 | }
17 |
18 | // NewScheduler creates a new scheduler.
19 | //
20 | // The scheduler is backed by "time" package. Its "At" implementation
21 | // is unreliable, as it's unable to take system suspend and time adjustments
22 | // into account.
23 | func NewScheduler() *Scheduler {
24 | if testModeScheduler := maybeNewTestModeScheduler(); testModeScheduler != nil {
25 | return newScheduler(testModeScheduler)
26 | }
27 | return newScheduler(&timeScheduler{})
28 | }
29 |
30 | // At implements the schedulerImpl interface.
31 | func (s *timeScheduler) At(when time.Time, f func()) {
32 | s.mu.Lock()
33 | defer s.mu.Unlock()
34 | s.stop()
35 | s.timer = time.AfterFunc(when.Sub(Now()), f)
36 | }
37 |
38 | // After implements the schedulerImpl interface.
39 | func (s *timeScheduler) After(delay time.Duration, f func()) {
40 | s.mu.Lock()
41 | defer s.mu.Unlock()
42 | s.stop()
43 | s.timer = time.AfterFunc(delay, f)
44 | }
45 |
46 | // Every implements the schedulerImpl interface.
47 | func (s *timeScheduler) Every(interval time.Duration, f func()) {
48 | s.mu.Lock()
49 | defer s.mu.Unlock()
50 | s.stop()
51 | s.quitter = make(chan struct{})
52 | s.ticker = time.NewTicker(interval)
53 | go func() {
54 | s.mu.Lock()
55 | ticker := s.ticker
56 | quitter := s.quitter
57 | s.mu.Unlock()
58 | if ticker == nil || quitter == nil {
59 | // Scheduler stopped before goroutine was started.
60 | return
61 | }
62 | for {
63 | select {
64 | case <-ticker.C:
65 | f()
66 | case <-quitter:
67 | return
68 | }
69 | }
70 | }()
71 | }
72 |
73 | // EveryAlign implements the schedulerImpl interface.
74 | func (s *timeScheduler) EveryAlign(interval time.Duration, offset time.Duration, f func()) {
75 | s.mu.Lock()
76 | defer s.mu.Unlock()
77 | s.stop()
78 | quitter := make(chan struct{})
79 | s.quitter = quitter
80 | go func() {
81 | var timer *time.Timer
82 | for {
83 | now := time.Now()
84 | next := nextAlignedExpiration(now, interval, offset)
85 | delay := next.Sub(now)
86 | if timer == nil {
87 | timer = time.NewTimer(delay)
88 | defer timer.Stop()
89 | } else {
90 | timer.Reset(delay)
91 | }
92 | select {
93 | case <-timer.C:
94 | f()
95 | case <-quitter:
96 | return
97 | }
98 | }
99 | }()
100 | }
101 |
102 | // Stop implements the schedulerImpl interface.
103 | func (s *timeScheduler) Stop() {
104 | s.mu.Lock()
105 | defer s.mu.Unlock()
106 | s.stop()
107 | }
108 |
109 | // Close implements the schedulerImpl interface.
110 | func (s *timeScheduler) Close() {
111 | s.Stop()
112 | }
113 |
114 | func (s *timeScheduler) stop() {
115 | if s.timer != nil {
116 | s.timer.Stop()
117 | s.timer = nil
118 | }
119 | if s.ticker != nil {
120 | s.ticker.Stop()
121 | s.ticker = nil
122 | }
123 | if s.quitter != nil {
124 | close(s.quitter)
125 | s.quitter = nil
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/timing/time_scheduler_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package timing
16 |
17 | import (
18 | "testing"
19 | )
20 |
21 | func TestTimeSchedulerImpl(t *testing.T) {
22 | testSchedulerImplementation(t, NewScheduler)
23 | }
24 |
--------------------------------------------------------------------------------
/timing/timerfd_scheduler_fallback.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // +build !linux
16 |
17 | package timing
18 |
19 | // NewRealtimeScheduler creates a scheduler backed by system real-time clock.
20 | //
21 | // It properly handles system suspend (sleep mode) and time adjustments. For periodic timers,
22 | // it triggers immediately whenever time changes discontinuously. For one-shot timers
23 | // (At and After), it will fire immediately if the time is skipped over
24 | // the set trigger time, and will properly wait for it otherwise.
25 | //
26 | // This scheduler is only properly supported on Linux. On other systems,
27 | // plain scheduler based on "time" package is returned.
28 | //
29 | // In order to clean up resources associated with it,
30 | // remember to call Stop().
31 | func NewRealtimeScheduler() (*Scheduler, error) {
32 | return NewScheduler(), nil
33 | }
34 |
--------------------------------------------------------------------------------
/timing/timerfd_scheduler_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // +build linux
16 |
17 | package timing
18 |
19 | import (
20 | "testing"
21 | )
22 |
23 | func TestRealtimeTimerfdSchedulerImpl(t *testing.T) {
24 | create := func() *Scheduler {
25 | s, err := NewRealtimeScheduler()
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | return s
30 | }
31 | testSchedulerImplementation(t, create)
32 | }
33 |
--------------------------------------------------------------------------------
/timing/timing.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /*
16 | Package timing provides a testable interface for timing and scheduling.
17 |
18 | This makes it simple to update a module at a fixed interval or
19 | at a fixed point in time.
20 |
21 | Typically, modules will make a scheduler:
22 | mod.sch = timing.NewScheduler()
23 | and use the scheduling calls to control the update timing:
24 | mod.sch.Every(time.Second)
25 |
26 | The Stream() goroutine will then loop over the ticker, and update
27 | the module with fresh information:
28 | for range mod.sch.C {
29 | // update code.
30 | }
31 |
32 | This will automatically suspend processing when the bar is hidden.
33 |
34 | Modules should also use timing.Now() instead of time.Now() to control time
35 | during tests, as well as correctly track the machine's time zone.
36 | */
37 | package timing
38 |
39 | import (
40 | "time"
41 |
42 | "github.com/soumya92/barista/base/watchers/localtz"
43 | )
44 |
45 | // Now returns the current time, in the machine's local time zone.
46 | //
47 | // IMPORTANT: This function differs from time.Now() in that the time zone can
48 | // change between invocations. Use timing.Now().Local() to get a consistent
49 | // zone, however note that the zone will be the machine's zone at the start
50 | // of the bar and may be out of date.
51 | func Now() time.Time {
52 | mu.Lock()
53 | defer mu.Unlock()
54 | var now time.Time
55 | if testMode {
56 | now = testNow()
57 | } else {
58 | now = time.Now()
59 | }
60 | return now.In(localtz.Get())
61 | }
62 |
--------------------------------------------------------------------------------