mirror of https://github.com/markbates/pkger.git
laugh with you
This commit is contained in:
parent
1d23c6b87b
commit
d4e044edbd
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
This package is WIP. Please use/test/try and report issues, but be careful with production. OK?
|
||||||
|
|
||||||
|
Pkger is powered by the dark magic of Go Modules, so they're like, totally required.
|
||||||
|
|
||||||
|
With Go Modules pkger can resolve packages with accuracy. No more guessing and trying to
|
||||||
|
figure out build paths, GOPATHS, etc... for this tired old lad.
|
||||||
|
|
||||||
|
With the module's path correctly resolved, it can serve as the "root" directory for that
|
||||||
|
module, and all files in that module's directory are available.
|
||||||
|
|
||||||
|
Paths:
|
||||||
|
* Paths should use UNIX style paths:
|
||||||
|
/cmd/pkger/main.go
|
||||||
|
* If unspecified the path's package is assumed to be the current module.
|
||||||
|
* Packages can specified in at the beginning of a path with a `:` seperator.
|
||||||
|
github.com/markbates/pkger:/cmd/pkger/main.go
|
||||||
|
|
||||||
|
"github.com/gobuffalo/buffalo:/go.mod" => $GOPATH/pkg/mod/github.com/gobuffalo/buffalo@v0.14.7/go.mod
|
||||||
|
*/
|
||||||
|
package pkger
|
96
file.go
96
file.go
|
@ -21,9 +21,74 @@ type File struct {
|
||||||
path Path
|
path Path
|
||||||
data []byte
|
data []byte
|
||||||
index *index
|
index *index
|
||||||
|
writer io.ReadWriter
|
||||||
Source io.ReadCloser
|
Source io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *File) Close() error {
|
||||||
|
defer func() {
|
||||||
|
f.Source = nil
|
||||||
|
f.writer = nil
|
||||||
|
}()
|
||||||
|
if f.Source != nil {
|
||||||
|
if c, ok := f.Source.(io.Closer); ok {
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.writer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(f.writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.data = b
|
||||||
|
|
||||||
|
fi := f.info
|
||||||
|
fi.size = int64(len(f.data))
|
||||||
|
fi.modTime = time.Now()
|
||||||
|
f.info = fi
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Read(p []byte) (int, error) {
|
||||||
|
if len(f.data) > 0 && len(f.data) <= len(p) {
|
||||||
|
return copy(p, f.data), io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.data) > 0 {
|
||||||
|
f.Source = ioutil.NopCloser(bytes.NewReader(f.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Source != nil {
|
||||||
|
return f.Source.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
of, err := f.her.Open(f.Path())
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
f.Source = of
|
||||||
|
return f.Source.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Write(b []byte) (int, error) {
|
||||||
|
if f.writer == nil {
|
||||||
|
f.writer = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
i, err := f.writer.Write(b)
|
||||||
|
fmt.Println(f.Name(), i, err)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) HereInfo() here.Info {
|
||||||
|
return f.her
|
||||||
|
}
|
||||||
|
|
||||||
func (f File) MarshalJSON() ([]byte, error) {
|
func (f File) MarshalJSON() ([]byte, error) {
|
||||||
m := map[string]interface{}{}
|
m := map[string]interface{}{}
|
||||||
m["info"] = f.info
|
m["info"] = f.info
|
||||||
|
@ -76,9 +141,7 @@ func (f *File) UnmarshalJSON(b []byte) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("missing index")
|
return fmt.Errorf("missing index")
|
||||||
}
|
}
|
||||||
f.index = &index{
|
f.index = newIndex()
|
||||||
Files: map[Path]*File{},
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(ind, f.index); err != nil {
|
if err := json.Unmarshal(ind, f.index); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -87,9 +150,7 @@ func (f *File) UnmarshalJSON(b []byte) error {
|
||||||
|
|
||||||
func (f *File) Open(name string) (http.File, error) {
|
func (f *File) Open(name string) (http.File, error) {
|
||||||
if f.index == nil {
|
if f.index == nil {
|
||||||
f.index = &index{
|
f.index = newIndex()
|
||||||
Files: map[Path]*File{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pt, err := Parse(name)
|
pt, err := Parse(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -148,16 +209,6 @@ func (f File) Stat() (os.FileInfo, error) {
|
||||||
return f.info, nil
|
return f.info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Close() error {
|
|
||||||
if f.Source == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if c, ok := f.Source.(io.Closer); ok {
|
|
||||||
return c.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) Name() string {
|
func (f File) Name() string {
|
||||||
return f.info.Name()
|
return f.info.Name()
|
||||||
}
|
}
|
||||||
|
@ -174,19 +225,6 @@ func (f File) String() string {
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Read(p []byte) (int, error) {
|
|
||||||
if f.Source != nil {
|
|
||||||
return f.Source.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
of, err := f.her.Open(f.Path())
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
f.Source = of
|
|
||||||
return f.Source.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readdir reads the contents of the directory associated with file and returns a slice of up to n FileInfo values, as would be returned by Lstat, in directory order. Subsequent calls on the same file will yield further FileInfos.
|
// Readdir reads the contents of the directory associated with file and returns a slice of up to n FileInfo values, as would be returned by Lstat, in directory order. Subsequent calls on the same file will yield further FileInfos.
|
||||||
//
|
//
|
||||||
// If n > 0, Readdir returns at most n FileInfo structures. In this case, if Readdir returns an empty slice, it will return a non-nil error explaining why. At the end of a directory, the error is io.EOF.
|
// If n > 0, Readdir returns at most n FileInfo structures. In this case, if Readdir returns an empty slice, it will return a non-nil error explaining why. At the end of a directory, the error is io.EOF.
|
||||||
|
|
47
file_test.go
47
file_test.go
|
@ -1,7 +1,9 @@
|
||||||
package pkger
|
package pkger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -31,3 +33,48 @@ func Test_File_Open_Dir(t *testing.T) {
|
||||||
|
|
||||||
r.NoError(f.Close())
|
r.NoError(f.Close())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_File_Read_Memory(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
f, err := Open("/file_test.go")
|
||||||
|
r.NoError(err)
|
||||||
|
f.data = []byte("hi!")
|
||||||
|
|
||||||
|
r.Equal("file_test.go", f.Name())
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(f)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(string(b), "hi!")
|
||||||
|
r.NoError(f.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_File_Write(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
i := newIndex()
|
||||||
|
|
||||||
|
f, err := i.Create(Path{
|
||||||
|
Name: "/hello.txt",
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(f)
|
||||||
|
|
||||||
|
fi, err := f.Stat()
|
||||||
|
r.NoError(err)
|
||||||
|
r.Zero(fi.Size())
|
||||||
|
|
||||||
|
r.Equal("/hello.txt", fi.Name())
|
||||||
|
|
||||||
|
mt := fi.ModTime()
|
||||||
|
r.NotZero(mt)
|
||||||
|
|
||||||
|
sz, err := io.Copy(f, strings.NewReader(radio))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(int64(1381), sz)
|
||||||
|
|
||||||
|
r.NoError(f.Close())
|
||||||
|
r.Equal(int64(1381), fi.Size())
|
||||||
|
r.NotZero(fi.ModTime())
|
||||||
|
r.NotEqual(mt, fi.ModTime())
|
||||||
|
}
|
||||||
|
|
22
http_test.go
22
http_test.go
|
@ -48,3 +48,25 @@ func Test_HTTP_Dir(t *testing.T) {
|
||||||
|
|
||||||
r.NoError(f.Close())
|
r.NoError(f.Close())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_HTTP_File_Memory(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
i := newIndex()
|
||||||
|
f, err := createFile(i, "/cmd/pkger/main.go")
|
||||||
|
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.FileServer(f))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
res, err := http.Get(ts.URL + "/cmd/pkger/main.go")
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(200, res.StatusCode)
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(res.Body)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Contains(string(b), "I wanna bite the hand that feeds me")
|
||||||
|
|
||||||
|
r.NoError(f.Close())
|
||||||
|
}
|
||||||
|
|
66
index.go
66
index.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gobuffalo/here"
|
"github.com/gobuffalo/here"
|
||||||
"github.com/markbates/pkger/pkgs"
|
"github.com/markbates/pkger/pkgs"
|
||||||
|
@ -16,6 +17,26 @@ type index struct {
|
||||||
Files map[Path]*File
|
Files map[Path]*File
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i index) Create(pt Path) (*File, error) {
|
||||||
|
her, err := pkgs.Pkg(pt.Pkg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := &File{
|
||||||
|
path: pt,
|
||||||
|
index: newIndex(),
|
||||||
|
her: her,
|
||||||
|
info: &FileInfo{
|
||||||
|
name: pt.Name,
|
||||||
|
mode: 0666,
|
||||||
|
modTime: time.Now(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Files[pt] = f
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i index) MarshalJSON() ([]byte, error) {
|
func (i index) MarshalJSON() ([]byte, error) {
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
"pkg": i.Pkg,
|
"pkg": i.Pkg,
|
||||||
|
@ -90,13 +111,11 @@ func (i index) Open(pt Path) (*File, error) {
|
||||||
return i.openDisk(pt)
|
return i.openDisk(pt)
|
||||||
}
|
}
|
||||||
return &File{
|
return &File{
|
||||||
info: f.info,
|
info: f.info,
|
||||||
path: f.path,
|
path: f.path,
|
||||||
data: f.data,
|
data: f.data,
|
||||||
her: f.her,
|
her: f.her,
|
||||||
index: &index{
|
index: newIndex(),
|
||||||
Files: map[Path]*File{},
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +147,35 @@ func (i index) openDisk(pt Path) (*File, error) {
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootIndex = &index{
|
func (i index) Parse(p string) (Path, error) {
|
||||||
Files: map[Path]*File{},
|
var pt Path
|
||||||
|
res := strings.Split(p, ":")
|
||||||
|
|
||||||
|
if len(res) < 1 {
|
||||||
|
return pt, fmt.Errorf("could not parse %q (%d)", res, len(res))
|
||||||
|
}
|
||||||
|
if len(res) == 1 {
|
||||||
|
if strings.HasPrefix(res[0], "/") {
|
||||||
|
pt.Name = res[0]
|
||||||
|
} else {
|
||||||
|
pt.Pkg = res[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pt.Pkg = res[0]
|
||||||
|
pt.Name = res[1]
|
||||||
|
}
|
||||||
|
pt.Name = strings.TrimPrefix(pt.Name, "/")
|
||||||
|
pt.Pkg = strings.TrimPrefix(pt.Pkg, "/")
|
||||||
|
if len(pt.Pkg) == 0 {
|
||||||
|
pt.Pkg = i.Pkg
|
||||||
|
}
|
||||||
|
return pt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newIndex() *index {
|
||||||
|
return &index{
|
||||||
|
Files: map[Path]*File{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootIndex = newIndex()
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package pkger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_index_Create(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
i := newIndex()
|
||||||
|
|
||||||
|
f, err := i.Create(Path{
|
||||||
|
Name: "/hello.txt",
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(f)
|
||||||
|
|
||||||
|
fi, err := f.Stat()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.Equal("/hello.txt", fi.Name())
|
||||||
|
r.Equal(os.FileMode(0666), fi.Mode())
|
||||||
|
r.NotZero(fi.ModTime())
|
||||||
|
|
||||||
|
her := f.her
|
||||||
|
r.NotZero(her)
|
||||||
|
r.Equal("github.com/markbates/pkger", her.ImportPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_index_Create_Write(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
i := newIndex()
|
||||||
|
|
||||||
|
f, err := i.Create(Path{
|
||||||
|
Name: "/hello.txt",
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(f)
|
||||||
|
|
||||||
|
fi, err := f.Stat()
|
||||||
|
r.NoError(err)
|
||||||
|
r.Zero(fi.Size())
|
||||||
|
|
||||||
|
r.Equal("/hello.txt", fi.Name())
|
||||||
|
|
||||||
|
mt := fi.ModTime()
|
||||||
|
r.NotZero(mt)
|
||||||
|
|
||||||
|
sz, err := io.Copy(f, strings.NewReader(radio))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(int64(1381), sz)
|
||||||
|
|
||||||
|
r.NoError(f.Close())
|
||||||
|
r.Equal(int64(1381), fi.Size())
|
||||||
|
r.NotZero(fi.ModTime())
|
||||||
|
r.NotEqual(mt, fi.ModTime())
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
f, err := pkger.Open("github.com/gobuffalo/buffalo:/server.go")
|
f, err := pkger.Open("github.com/gobuffalo/buffalo:/go.mod")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("1", err)
|
log.Fatal("1", err)
|
||||||
}
|
}
|
||||||
|
|
21
path.go
21
path.go
|
@ -2,7 +2,6 @@ package pkger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Path struct {
|
type Path struct {
|
||||||
|
@ -21,23 +20,5 @@ func (p Path) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Parse(p string) (Path, error) {
|
func Parse(p string) (Path, error) {
|
||||||
var pt Path
|
return rootIndex.Parse(p)
|
||||||
res := strings.Split(p, ":")
|
|
||||||
|
|
||||||
if len(res) < 1 {
|
|
||||||
return pt, fmt.Errorf("could not parse %q (%d)", res, len(res))
|
|
||||||
}
|
|
||||||
if len(res) == 1 {
|
|
||||||
if strings.HasPrefix(res[0], "/") {
|
|
||||||
pt.Name = res[0]
|
|
||||||
} else {
|
|
||||||
pt.Pkg = res[0]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pt.Pkg = res[0]
|
|
||||||
pt.Name = res[1]
|
|
||||||
}
|
|
||||||
pt.Name = strings.TrimPrefix(pt.Name, "/")
|
|
||||||
pt.Pkg = strings.TrimPrefix(pt.Pkg, "/")
|
|
||||||
return pt, nil
|
|
||||||
}
|
}
|
||||||
|
|
36
pkger.go
36
pkger.go
|
@ -1,31 +1,6 @@
|
||||||
package pkger
|
package pkger
|
||||||
|
|
||||||
import (
|
// Open opens the named file for reading.
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func modRoot() (string, error) {
|
|
||||||
c := exec.Command("go", "env", "GOMOD")
|
|
||||||
b, err := c.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
b = bytes.TrimSpace(b)
|
|
||||||
if len(b) == 0 {
|
|
||||||
return "", fmt.Errorf("the `go env GOMOD` was empty/modules are required")
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Dir(string(b)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Getwd() (string, error) {
|
|
||||||
return modRoot()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Open(p string) (*File, error) {
|
func Open(p string) (*File, error) {
|
||||||
pt, err := Parse(p)
|
pt, err := Parse(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -33,3 +8,12 @@ func Open(p string) (*File, error) {
|
||||||
}
|
}
|
||||||
return rootIndex.Open(pt)
|
return rootIndex.Open(pt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create creates the named file with mode 0666 (before umask), truncating it if it already exists. If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR. If there is an error, it will be of type *PathError.
|
||||||
|
func Create(p string) (*File, error) {
|
||||||
|
pt, err := Parse(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rootIndex.Create(pt)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package pkger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createFile(i *index, p string, body ...string) (*File, error) {
|
||||||
|
pt, err := i.Parse(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) == 0 {
|
||||||
|
body = append(body, radio)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := i.Create(pt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(f, strings.NewReader(strings.Join(body, "\n\n")))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const radio = `I was tuning in the shine on the late night dial
|
||||||
|
Doing anything my radio advised
|
||||||
|
With every one of those late night stations
|
||||||
|
Playing songs bringing tears to my eyes
|
||||||
|
I was seriously thinking about hiding the receiver
|
||||||
|
When the switch broke 'cause it's old
|
||||||
|
They're saying things that I can hardly believe
|
||||||
|
They really think we're getting out of control
|
||||||
|
Radio is a sound salvation
|
||||||
|
Radio is cleaning up the nation
|
||||||
|
They say you better listen to the voice of reason
|
||||||
|
But they don't give you any choice 'cause they think that it's treason
|
||||||
|
So you had better do as you are told
|
||||||
|
You better listen to the radio
|
||||||
|
I wanna bite the hand that feeds me
|
||||||
|
I wanna bite that hand so badly
|
||||||
|
I want to make them wish they'd never seen me
|
||||||
|
Some of my friends sit around every evening
|
||||||
|
And they worry about the times ahead
|
||||||
|
But everybody else is overwhelmed by indifference
|
||||||
|
And the promise of an early bed
|
||||||
|
You either shut up or get cut up; they don't wanna hear about it
|
||||||
|
It's only inches on the reel-to-reel
|
||||||
|
And the radio is in the hands of such a lot of fools
|
||||||
|
Tryin' to anesthetize the way that you feel
|
||||||
|
Radio is a sound salvation
|
||||||
|
Radio is cleaning up the nation
|
||||||
|
They say you better listen to the voice of reason
|
||||||
|
But they don't give you any choice 'cause they think that it's treason
|
||||||
|
So you had better do as you are told
|
||||||
|
You better listen to the radio
|
||||||
|
Wonderful radio
|
||||||
|
Marvelous radio
|
||||||
|
Wonderful radio
|
||||||
|
Radio, radio`
|
Loading…
Reference in New Issue